Skip to content

Commit 3eb673d

Browse files
feat: integrate mock logo for TECH_FLORIPA in certificate generation tests and enhance QR code generation tests
1 parent cf3edc6 commit 3eb673d

3 files changed

Lines changed: 278 additions & 13 deletions

File tree

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class MockConfig:
2222
SERVICE_API_KEY_REGISTRATION_API_SOLANA = "test-api-key"
2323
# comentário: URL de validação mockada para o Tech Floripa usada nos testes
2424
TECH_FLORIPA_CERTIFICATE_VALIDATE_URL = "https://example.test/certificate-validate/"
25-
25+
TECH_FLORIPA_LOGO_URL = "https://example.test/logo.png"
26+
2627
# comentário: expõe tanto a classe quanto a instância, como o módulo real faria
2728
mock_module.Config = MockConfig
2829
mock_module.config = MockConfig()

tests/test_certified_builder.py

Lines changed: 267 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,17 @@ def mock_certificate_template():
4949
def mock_logo():
5050
return Image.new("RGBA", (150, 150), (255, 255, 255, 0))
5151

52+
@pytest.fixture
53+
def mock_logo_tech_floripa():
54+
return Image.new("RGBA", (100, 100), (255, 255, 255, 255))
55+
5256
@pytest.fixture
5357
def certified_builder():
5458
return CertifiedBuilder()
5559

56-
def test_generate_certificate(certified_builder, mock_participant, mock_certificate_template, mock_logo):
60+
def test_generate_certificate(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
5761
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
58-
certificate = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo)
62+
certificate = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
5963
assert isinstance(certificate, Image.Image)
6064
assert certificate.size == mock_certificate_template.size
6165
assert certificate.mode == "RGBA"
@@ -78,11 +82,21 @@ def test_create_validation_code_image(certified_builder, mock_participant, mock_
7882
assert validation_code_image.size == mock_certificate_template.size
7983
assert validation_code_image.mode == "RGBA"
8084

81-
def test_build_certificates(certified_builder, mock_participant, mock_certificate_template, mock_logo):
85+
def test_build_certificates(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
8286
participants = [mock_participant]
8387

8488
# comentário: mock do download de imagens e da resposta do serviço Solana para evitar chamada externa
85-
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]), \
89+
# Função que retorna o mock baseado na URL chamada
90+
def mock_download_image(url):
91+
if url == mock_participant.certificate.background:
92+
return mock_certificate_template
93+
elif url == mock_participant.certificate.logo:
94+
return mock_logo
95+
elif url == "https://example.test/logo.png": # TECH_FLORIPA_LOGO_URL do config mock
96+
return mock_logo_tech_floripa
97+
raise ValueError(f"URL não mockada: {url}")
98+
99+
with patch.object(certified_builder, '_download_image', side_effect=mock_download_image), \
86100
patch('certified_builder.certified_builder.CertificatesOnSolana.register_certificate_on_solana', return_value={
87101
# comentário: mock alinhado ao contrato atual do serviço
88102
"status": "sucesso",
@@ -128,12 +142,12 @@ def _count_non_transparent_pixels(img: Image.Image) -> int:
128142
return sum(1 for p in alpha.getdata() if p != 0)
129143

130144

131-
def test_scan_to_validate_is_centered_and_below_qr(certified_builder, mock_participant, mock_certificate_template, mock_logo):
145+
def test_scan_to_validate_is_centered_and_below_qr(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
132146
# Garante url para QR
133147
mock_participant.authenticity_verification_url = "https://example.com/verify"
134148

135149
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
136-
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo)
150+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
137151

138152
# Recalcula posição esperada do texto seguindo a mesma lógica do código
139153
qrcode_size = (150, 150)
@@ -163,11 +177,11 @@ def test_scan_to_validate_is_centered_and_below_qr(certified_builder, mock_parti
163177
assert _count_non_transparent_pixels(cropped) > 0, "Texto 'Scan to Validate' não encontrado na área esperada"
164178

165179

166-
def test_qr_is_placed_at_expected_region(certified_builder, mock_participant, mock_certificate_template, mock_logo):
180+
def test_qr_is_placed_at_expected_region(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
167181
mock_participant.authenticity_verification_url = "https://example.com/verify"
168182

169183
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
170-
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo)
184+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
171185

172186
# QR é esperado em (50,200) com 150x150
173187
qr_left, qr_top = 50, 200
@@ -180,3 +194,248 @@ def test_qr_is_placed_at_expected_region(certified_builder, mock_participant, mo
180194
below_region = result.crop((qr_left, qr_bottom, qr_right, min(qr_bottom + 30, result.height)))
181195
assert _count_non_transparent_pixels(below_region) > 0, "Nenhum conteúdo encontrado abaixo do QR onde o texto deveria estar"
182196

197+
198+
def test_name_is_centered_on_certificate(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
199+
"""Verifica que o nome do participante está centralizado no certificado."""
200+
mock_participant.authenticity_verification_url = "https://example.com/verify"
201+
202+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
203+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
204+
205+
# Verifica que há conteúdo na região central onde o nome deveria estar
206+
center_x = result.width // 2
207+
center_y = result.height // 2
208+
name_region = result.crop((center_x - 200, center_y - 50, center_x + 200, center_y + 50))
209+
210+
assert _count_non_transparent_pixels(name_region) > 0, "Nome não encontrado na região central do certificado"
211+
212+
213+
def test_validation_code_is_at_bottom_right(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
214+
"""Verifica que o código de validação está posicionado no canto inferior direito."""
215+
mock_participant.authenticity_verification_url = "https://example.com/verify"
216+
217+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
218+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
219+
220+
# Verifica região inferior direita onde o código de validação deveria estar
221+
validation_region = result.crop((result.width - 250, result.height - 80, result.width, result.height))
222+
223+
assert _count_non_transparent_pixels(validation_region) > 0, "Código de validação não encontrado no canto inferior direito"
224+
225+
226+
def test_logo_is_positioned_at_top_left(certified_builder, mock_participant, mock_certificate_template, mock_logo_tech_floripa):
227+
"""Verifica que o logo está posicionado no canto superior esquerdo."""
228+
mock_participant.authenticity_verification_url = "https://example.com/verify"
229+
# Usa um logo visível (não transparente) para o teste
230+
visible_logo = Image.new("RGBA", (150, 150), (255, 255, 255, 255))
231+
232+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, visible_logo]):
233+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, visible_logo, mock_logo_tech_floripa)
234+
235+
# Logo é esperado em (50, 50) com tamanho máximo de 150x150
236+
logo_region = result.crop((50, 50, 200, 200))
237+
238+
assert _count_non_transparent_pixels(logo_region) > 0, "Logo não encontrado na região esperada"
239+
240+
241+
def test_large_logo_is_resized(certified_builder, mock_participant, mock_certificate_template, mock_logo_tech_floripa):
242+
"""Verifica que logos grandes são redimensionados para o tamanho máximo."""
243+
mock_participant.authenticity_verification_url = "https://example.com/verify"
244+
large_logo = Image.new("RGBA", (300, 300), (255, 255, 255, 255))
245+
246+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, large_logo]):
247+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, large_logo, mock_logo_tech_floripa)
248+
249+
# Verifica que o logo foi redimensionado (não deve ocupar toda a região de 300x300)
250+
logo_region = result.crop((50, 50, 200, 200))
251+
252+
assert _count_non_transparent_pixels(logo_region) > 0, "Logo redimensionado não encontrado"
253+
254+
255+
def test_details_are_split_into_three_lines(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
256+
"""Verifica que os detalhes do certificado são divididos em 3 linhas."""
257+
mock_participant.authenticity_verification_url = "https://example.com/verify"
258+
259+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
260+
result = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
261+
262+
# Verifica região onde os detalhes deveriam estar (abaixo do centro)
263+
center_y = result.height // 2
264+
details_region = result.crop((0, center_y + 50, result.width, center_y + 150))
265+
266+
assert _count_non_transparent_pixels(details_region) > 0, "Detalhes não encontrados na região esperada"
267+
268+
269+
def test_build_certificates_with_multiple_participants_same_resources(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
270+
"""Testa geração de certificados para múltiplos participantes com mesmo background e logo."""
271+
second_participant = Participant(
272+
first_name="Maria",
273+
last_name="Silva",
274+
email="maria@example.com",
275+
phone="(48) 99999-9999",
276+
cpf="111.111.111-11",
277+
certificate=mock_participant.certificate,
278+
event=mock_participant.event
279+
)
280+
281+
participants = [mock_participant, second_participant]
282+
283+
def mock_download_image(url):
284+
if url == mock_participant.certificate.background:
285+
return mock_certificate_template
286+
elif url == mock_participant.certificate.logo:
287+
return mock_logo
288+
elif url == "https://example.test/logo.png":
289+
return mock_logo_tech_floripa
290+
raise ValueError(f"URL não mockada: {url}")
291+
292+
with patch.object(certified_builder, '_download_image', side_effect=mock_download_image), \
293+
patch('certified_builder.certified_builder.CertificatesOnSolana.register_certificate_on_solana', return_value={
294+
"blockchain": {
295+
"explorer_url": "https://explorer.solana.com/tx/test123?cluster=devnet"
296+
}
297+
}), \
298+
patch.object(certified_builder, 'save_certificate') as mock_save:
299+
300+
results = certified_builder.build_certificates(participants)
301+
302+
assert len(results) == 2
303+
assert all(r["success"] for r in results)
304+
assert mock_save.call_count == 2
305+
306+
307+
def test_build_certificates_with_different_backgrounds(certified_builder, mock_participant, mock_logo, mock_logo_tech_floripa):
308+
"""Testa geração de certificados para participantes com backgrounds diferentes."""
309+
different_background = "https://example.com/different-background.png"
310+
different_template = Image.new("RGBA", (1920, 1080), (200, 200, 200, 255))
311+
312+
second_certificate = Certificate(
313+
details=mock_participant.certificate.details,
314+
logo=mock_participant.certificate.logo,
315+
background=different_background
316+
)
317+
318+
second_participant = Participant(
319+
first_name="João",
320+
last_name="Santos",
321+
email="joao@example.com",
322+
phone="(48) 88888-8888",
323+
cpf="222.222.222-22",
324+
certificate=second_certificate,
325+
event=mock_participant.event
326+
)
327+
328+
participants = [mock_participant, second_participant]
329+
330+
def mock_download_image(url):
331+
if url == mock_participant.certificate.background:
332+
return Image.new("RGBA", (1920, 1080), (255, 255, 255, 0))
333+
elif url == different_background:
334+
return different_template
335+
elif url == mock_participant.certificate.logo:
336+
return mock_logo
337+
elif url == "https://example.test/logo.png":
338+
return mock_logo_tech_floripa
339+
raise ValueError(f"URL não mockada: {url}")
340+
341+
with patch.object(certified_builder, '_download_image', side_effect=mock_download_image), \
342+
patch('certified_builder.certified_builder.CertificatesOnSolana.register_certificate_on_solana', return_value={
343+
"blockchain": {
344+
"explorer_url": "https://explorer.solana.com/tx/test123?cluster=devnet"
345+
}
346+
}), \
347+
patch.object(certified_builder, 'save_certificate') as mock_save:
348+
349+
results = certified_builder.build_certificates(participants)
350+
351+
assert len(results) == 2
352+
assert all(r["success"] for r in results)
353+
354+
355+
def test_build_certificates_handles_solana_registration_error(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
356+
"""Testa tratamento de erro quando o registro na Solana falha."""
357+
participants = [mock_participant]
358+
359+
def mock_download_image(url):
360+
if url == mock_participant.certificate.background:
361+
return mock_certificate_template
362+
elif url == mock_participant.certificate.logo:
363+
return mock_logo
364+
elif url == "https://example.test/logo.png":
365+
return mock_logo_tech_floripa
366+
raise ValueError(f"URL não mockada: {url}")
367+
368+
with patch.object(certified_builder, '_download_image', side_effect=mock_download_image), \
369+
patch('certified_builder.certified_builder.CertificatesOnSolana.register_certificate_on_solana', return_value={
370+
"blockchain": {} # Sem explorer_url - simula erro
371+
}):
372+
373+
results = certified_builder.build_certificates(participants)
374+
375+
assert len(results) == 1
376+
assert results[0]["success"] == False
377+
assert "Failed to get authenticity verification URL" in results[0]["error"]
378+
379+
380+
def test_save_certificate_saves_to_temp_directory(certified_builder, mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
381+
"""Testa que o certificado é salvo no diretório temporário."""
382+
mock_participant.authenticity_verification_url = "https://example.com/verify"
383+
384+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
385+
certificate = certified_builder.generate_certificate(mock_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
386+
file_path = certified_builder.save_certificate(certificate, mock_participant)
387+
388+
assert file_path.startswith("/tmp/certificates/")
389+
assert file_path.endswith(".png")
390+
391+
import os
392+
assert os.path.exists(file_path), "Arquivo do certificado não foi criado"
393+
394+
395+
def test_generate_certificate_with_long_name(certified_builder, mock_certificate, mock_certificate_template, mock_logo, mock_logo_tech_floripa):
396+
"""Testa geração de certificado com nome muito longo."""
397+
mock_event = Event(
398+
order_id=1,
399+
product_id=1,
400+
product_name="Evento Teste",
401+
date=datetime.now(),
402+
time_checkin=datetime.now(),
403+
checkin_latitude=0.0,
404+
checkin_longitude=0.0
405+
)
406+
407+
long_name_participant = Participant(
408+
first_name="João Pedro",
409+
last_name="da Silva Santos Oliveira",
410+
email="joao@example.com",
411+
phone="(48) 99999-9999",
412+
cpf="123.456.789-00",
413+
certificate=mock_certificate,
414+
event=mock_event
415+
)
416+
long_name_participant.authenticity_verification_url = "https://example.com/verify"
417+
418+
with patch('certified_builder.utils.fetch_file_certificate.fetch_file_certificate', side_effect=[mock_certificate_template, mock_logo]):
419+
result = certified_builder.generate_certificate(long_name_participant, mock_certificate_template, mock_logo, mock_logo_tech_floripa)
420+
421+
assert isinstance(result, Image.Image)
422+
assert result.size == mock_certificate_template.size
423+
424+
425+
def test_ensure_valid_rgba_converts_rgb_image(certified_builder):
426+
"""Testa que _ensure_valid_rgba converte imagens RGB para RGBA."""
427+
rgb_image = Image.new("RGB", (100, 100), (255, 0, 0))
428+
rgba_image = certified_builder._ensure_valid_rgba(rgb_image)
429+
430+
assert rgba_image.mode == "RGBA"
431+
assert rgba_image.size == rgb_image.size
432+
433+
434+
def test_ensure_valid_rgba_preserves_rgba_image(certified_builder):
435+
"""Testa que _ensure_valid_rgba preserva imagens já em RGBA."""
436+
rgba_image = Image.new("RGBA", (100, 100), (255, 0, 0, 128))
437+
result = certified_builder._ensure_valid_rgba(rgba_image)
438+
439+
assert result.mode == "RGBA"
440+
assert result.size == rgba_image.size
441+

tests/test_make_qrcode.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44
from certified_builder.make_qrcode import MakeQRCode
55

66

7+
@pytest.fixture
8+
def mock_logo_tech_floripa():
9+
return Image.new("RGBA", (100, 100), (255, 255, 255, 255))
10+
11+
712
def _count_alpha(img: Image.Image):
813
a = img.split()[-1]
914
zeros = sum(1 for p in a.getdata() if p == 0)
1015
nonzeros = sum(1 for p in a.getdata() if p != 0)
1116
return zeros, nonzeros
1217

1318

14-
def test_generate_qr_code_returns_image_png_rgba():
19+
def test_generate_qr_code_returns_image_png_rgba(mock_logo_tech_floripa):
1520
data = "https://example.com/verify/ABC123"
16-
byte_io = MakeQRCode.generate_qr_code(data)
21+
byte_io = MakeQRCode.generate_qr_code(data, mock_logo_tech_floripa)
1722

1823
assert isinstance(byte_io, BytesIO)
1924

@@ -22,9 +27,9 @@ def test_generate_qr_code_returns_image_png_rgba():
2227
assert img.size[0] > 0 and img.size[1] > 0
2328

2429

25-
def test_qr_has_transparent_background_and_visible_foreground():
30+
def test_qr_has_transparent_background_and_visible_foreground(mock_logo_tech_floripa):
2631
data = "hello world"
27-
byte_io = MakeQRCode.generate_qr_code(data)
32+
byte_io = MakeQRCode.generate_qr_code(data, mock_logo_tech_floripa)
2833
img = Image.open(byte_io).convert("RGBA")
2934

3035
zeros, nonzeros = _count_alpha(img)

0 commit comments

Comments
 (0)