Commit inicial - upload de todos os arquivos da pasta

This commit is contained in:
2026-07-24 15:51:10 -03:00
commit efa06def71
44 changed files with 3604 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Tests for Lernotafiscal."""
+141
View File
@@ -0,0 +1,141 @@
"""Testes do app: fallback de data, autenticação e CRUD de documentos."""
import os
import tempfile
import unittest
from datetime import date
from pathlib import Path
class DateFallbackTests(unittest.TestCase):
def test_iso_date_is_kept(self):
from app.dates import resolve_purchase_date
self.assertEqual(resolve_purchase_date("2026-06-09"), "2026-06-09")
def test_br_date_is_normalized(self):
from app.dates import resolve_purchase_date
self.assertEqual(resolve_purchase_date("09/06/2026"), "2026-06-09")
def test_illegible_date_falls_back_to_first_of_month(self):
from app.dates import resolve_purchase_date
ref = date(2026, 7, 24)
self.assertEqual(resolve_purchase_date("ilegível", reference=ref), "2026-07-01")
self.assertEqual(resolve_purchase_date(None, reference=ref), "2026-07-01")
self.assertEqual(resolve_purchase_date("", reference=ref), "2026-07-01")
def test_invalid_calendar_date_falls_back(self):
from app.dates import resolve_purchase_date
ref = date(2026, 7, 24)
self.assertEqual(resolve_purchase_date("2026-13-40", reference=ref), "2026-07-01")
class PasswordTests(unittest.TestCase):
def test_hash_and_verify(self):
from app import auth
h = auth.hash_password("segredo-forte")
self.assertTrue(auth.verify_password("segredo-forte", h))
self.assertFalse(auth.verify_password("errada", h))
class CrudTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
os.environ["DB_PATH"] = str(Path(self._tmp.name) / "crud.sqlite3")
# zera o cache de settings para pegar o DB_PATH novo
from app.config import get_settings
get_settings.cache_clear()
def tearDown(self):
os.environ.pop("DB_PATH", None)
from app.config import get_settings
get_settings.cache_clear()
self._tmp.cleanup()
def test_create_update_delete_list(self):
from app import database as db
with db.session() as conn:
doc_id = db.create_fiscal(
conn, purchase_date="2026-07-01", supplier_name="Padaria X", total_paid=10.0
)
self.assertEqual(db.overall_totals(conn)["count"], 1)
db.update_fiscal(
conn, doc_id, purchase_date="2026-07-02", supplier_name="Padaria Y", total_paid=12.5
)
row = db.get_fiscal(conn, doc_id)
self.assertEqual(row["supplier_name"], "Padaria Y")
self.assertEqual(row["total_paid"], 12.5)
rows = db.list_fiscal(conn, supplier="Padaria")
self.assertEqual(len(rows), 1)
db.delete_fiscal(conn, doc_id)
self.assertEqual(db.overall_totals(conn)["count"], 0)
class StagingGateTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
os.environ["DB_PATH"] = str(Path(self._tmp.name) / "stage.sqlite3")
from app.config import get_settings
get_settings.cache_clear()
def tearDown(self):
os.environ.pop("DB_PATH", None)
from app.config import get_settings
get_settings.cache_clear()
self._tmp.cleanup()
def _stage(self, conn, batch_id, upload_id, **over):
base = dict(
upload_id=upload_id, batch_id=batch_id, source_file_name="f.pdf",
source_page=None, source_location="f.pdf", raw_text="",
purchase_date="2026-07-01", supplier_name="Loja", total_paid=10.0,
confidence="high", field_confidence={}, legible=True,
uncertain_fields=[], extractor="local",
)
base.update(over)
from app import database as db
return db.insert_detected(conn, **base)
def test_incomplete_row_counts_as_pending_and_is_not_imported(self):
from app import database as db
with db.session() as conn:
batch = db.create_batch(conn)
up = db.insert_upload(conn, batch, "f.pdf", Path("f.pdf"), "application/pdf", 1)
self._stage(conn, batch, up) # completo
# legível mas SEM fornecedor -> incompleto, deve contar como pendente
self._stage(conn, batch, up, supplier_name=None, legible=True)
summary = db.batch_summary(conn, batch)
self.assertEqual(summary["count"], 2)
self.assertEqual(summary["incompletos"], 1)
self.assertEqual(summary["pendentes"], 1)
def test_confirm_only_promotes_complete_rows(self):
from app import database as db
with db.session() as conn:
batch = db.create_batch(conn)
up = db.insert_upload(conn, batch, "f.pdf", Path("f.pdf"), "application/pdf", 1)
self._stage(conn, batch, up, supplier_name="Loja A", total_paid=10.0)
self._stage(conn, batch, up, supplier_name="Loja B", total_paid=20.0)
inserted = db.confirm_batch(conn, batch)
self.assertEqual(inserted, 2)
self.assertEqual(db.overall_totals(conn)["total"], 30.0)
if __name__ == "__main__":
unittest.main()
+114
View File
@@ -0,0 +1,114 @@
import tempfile
import unittest
from pathlib import Path
from lernotafiscal import db
from lernotafiscal.extraction import detect_documents, extract_pdf_text_naive, segment_blocks
from lernotafiscal.extraction import PageSource
SINGLE_DOC = """
MERCADO CENTRAL LTDA
CNPJ 12.345.678/0001-90
DATA 13/07/2026
ITEM ARROZ 10,00
VALOR TOTAL R$ 42,50
"""
MULTI_DOC = """
MERCADO CENTRAL LTDA
CNPJ 12.345.678/0001-90
DATA 13/07/2026
VALOR TOTAL R$ 42,50
FARMACIA SAUDE
CNPJ 98.765.432/0001-10
DATA 14/07/2026
TOTAL A PAGAR R$ 18,90
"""
class ExtractionTests(unittest.TestCase):
def test_single_image_text_creates_one_candidate(self) -> None:
pages = [PageSource(1, SINGLE_DOC, None, "image")]
docs = detect_documents(pages, "cupom.png")
self.assertEqual(len(docs), 1)
self.assertEqual(docs[0].purchase_date, "2026-07-13")
self.assertEqual(docs[0].supplier_name, "MERCADO CENTRAL LTDA")
self.assertEqual(docs[0].total_paid, 42.50)
def test_pdf_page_with_multiple_blocks_creates_multiple_candidates(self) -> None:
blocks = segment_blocks(MULTI_DOC)
self.assertEqual(len(blocks), 2)
docs = detect_documents([PageSource(1, MULTI_DOC, None, "pdf")], "lote.pdf")
self.assertEqual(len(docs), 2)
self.assertEqual(docs[0].supplier_name, "MERCADO CENTRAL LTDA")
self.assertEqual(docs[1].supplier_name, "FARMACIA SAUDE")
def test_pdf_with_one_document_per_page_creates_review_item_per_page(self) -> None:
pages = [
PageSource(1, SINGLE_DOC, None, "pdf"),
PageSource(2, SINGLE_DOC.replace("MERCADO CENTRAL LTDA", "POSTO AVENIDA"), None, "pdf"),
]
docs = detect_documents(pages, "duas-paginas.pdf")
self.assertEqual(len(docs), 2)
self.assertEqual(docs[0].source_location, "pagina 1, bloco 1")
self.assertEqual(docs[1].source_location, "pagina 2, bloco 1")
self.assertEqual(docs[1].supplier_name, "POSTO AVENIDA")
def test_naive_pdf_extraction_rejects_binary_streams_without_fake_pages(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
pdf_path = Path(tmp) / "scan.pdf"
pdf_path.write_bytes(
b"%PDF-1.4\n"
b"1 0 obj << /Type /Page >> endobj\n"
b"2 0 obj << /Length 28 >> stream\n"
b"\x01\x02binary\x0cnoise\x0cnot text\xff\n"
b"endstream\n%%EOF"
)
pages = extract_pdf_text_naive(pdf_path)
self.assertEqual(pages, [""])
def test_low_confidence_candidate_can_be_corrected_and_confirmed(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
conn = db.connect(Path(tmp) / "test.sqlite3")
try:
db.init_db(conn)
upload_id = db.insert_upload(conn, "scan.png", Path(tmp) / "scan.png", "image/png", 12)
candidate = detect_documents([PageSource(1, "", None, "image")], "scan.png")[0]
detected_id = db.insert_detected_document(conn, upload_id, candidate)
db.confirm_document(conn, detected_id, "2026-07-15", "PADARIA BOA", 9.75)
docs = db.dashboard_documents(conn)
self.assertEqual(len(docs), 1)
self.assertEqual(docs[0]["supplier_name"], "PADARIA BOA")
self.assertEqual(docs[0]["total_paid"], 9.75)
finally:
conn.close()
def test_ignored_documents_do_not_reach_dashboard(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
conn = db.connect(Path(tmp) / "test.sqlite3")
try:
db.init_db(conn)
upload_id = db.insert_upload(conn, "cupom.png", Path(tmp) / "cupom.png", "image/png", 12)
candidate = detect_documents([PageSource(1, SINGLE_DOC, None, "image")], "cupom.png")[0]
detected_id = db.insert_detected_document(conn, upload_id, candidate)
db.ignore_document(conn, detected_id)
docs = db.dashboard_documents(conn)
self.assertEqual(docs, [])
finally:
conn.close()
if __name__ == "__main__":
unittest.main()