mirror of
https://github.com/napnap75/multiarch-docker-images.git
synced 2026-11-12 00:52:11 +01:00
Add configurable document columns and allow bulk metadata editing (#14)
This commit is contained in:
@@ -24,3 +24,15 @@ Fixtures are checked-in immutable inputs under `tests/e2e/fixtures/baseline/`. T
|
||||
## Safety
|
||||
|
||||
The suite always overrides storage and SQLite paths with paths inside its temporary workspace, and sets `APP_STORAGE_BACKEND=file`. It does not accept a production base URL or reuse a developer database. Do not modify the fixture source during a run.
|
||||
|
||||
## Additional E2E scenarios: configurable columns and bulk operations
|
||||
|
||||
The E2E suite should add API-level scenarios for the bulk-edit contract using isolated local storage and disposable SQLite state. The browser column chooser and row-selection interactions require frontend/browser tests; they are not covered by API-only E2E tests.
|
||||
|
||||
Required scenarios:
|
||||
- List records with last_modified and verify it is distinct from emission_date.
|
||||
- Bulk-edit company/type and an additional field; inspect sidecars and SQLite after completion.
|
||||
- Select all matching filters across pagination and prove nonmatching records remain unchanged.
|
||||
- Verify validation errors and per-document partial outcomes.
|
||||
- Force a destination collision and a storage failure; verify no source object is lost and sidecar/SQLite state remains recoverable.
|
||||
- Retry a partially successful request using the same idempotency token and verify duplicate mutations are not introduced.
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
"""Tests for asynchronous, confidence-gated document suggestions."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import unittest
|
||||
from datetime import date
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from app.api import routes
|
||||
from app.api.models import DocumentEditRequest
|
||||
from app.ml import AmbiguousPredictionError, LocalMLPipeline
|
||||
|
||||
|
||||
@@ -32,7 +35,7 @@ class FakeRegistry:
|
||||
return SimpleNamespace(to_dict=lambda: {})
|
||||
|
||||
def get_key_pattern(self, name):
|
||||
return "example/{company}/{period}/{document_type}"
|
||||
return "example/{year}/{month}/{month_name}/{day}/{company}/{period}/{document_type}"
|
||||
|
||||
|
||||
class FakeMLPipeline(LocalMLPipeline):
|
||||
@@ -140,7 +143,10 @@ class DocumentSuggestionTests(unittest.TestCase):
|
||||
self.assertEqual(suggestions["template_name"]["value"], "invoice-template")
|
||||
self.assertEqual(suggestions["period_format"]["value"], "monthly")
|
||||
self.assertEqual(suggestions["period"]["value"], "2026-09")
|
||||
self.assertEqual(suggestions["current_key"]["value"], "files/example/Edited Company/2026-09/invoice.pdf")
|
||||
self.assertEqual(
|
||||
suggestions["current_key"]["value"],
|
||||
"files/example/2026/09/Septembre/20/Edited Company/2026-09/invoice.pdf",
|
||||
)
|
||||
|
||||
def test_derived_values_use_form_values_not_model_predictions(self):
|
||||
class ConflictingPipeline(FakeMLPipeline):
|
||||
@@ -303,6 +309,67 @@ class DocumentSuggestionTests(unittest.TestCase):
|
||||
self.assertEqual(suggestions["template_name"]["value"], "invoice-template")
|
||||
self.assertEqual(suggestions["period_format"]["value"], "monthly")
|
||||
|
||||
def test_edit_route_uses_computed_period_placeholders_in_storage_key(self):
|
||||
record = SimpleNamespace(
|
||||
id="document-1",
|
||||
sha256="document-1",
|
||||
current_key="pending/original.pdf",
|
||||
original_key="pending/original.pdf",
|
||||
template_name="old-template",
|
||||
period_format="monthly",
|
||||
emitting_company="Old Company",
|
||||
document_type="receipt",
|
||||
emission_date="2026-09-20",
|
||||
period="September 2026",
|
||||
optional_fields={},
|
||||
status="pending",
|
||||
confidence=None,
|
||||
updated_at="2026-09-20T00:00:00+00:00",
|
||||
created_at="2026-09-20T00:00:00+00:00",
|
||||
extracted_text="document text",
|
||||
)
|
||||
metadata = json.dumps({"sha256": record.sha256, "current_key": record.current_key})
|
||||
storage = Mock()
|
||||
storage.get_object.return_value = metadata
|
||||
storage.object_exists.side_effect = lambda key: key == record.current_key
|
||||
database = Mock()
|
||||
database.get_by_id.return_value = record
|
||||
registry = Mock()
|
||||
registry.get_template.return_value = object()
|
||||
registry.get_period_format_config.return_value = SimpleNamespace(
|
||||
to_dict=lambda: {"granularity": "month"}
|
||||
)
|
||||
registry.get_key_pattern.return_value = (
|
||||
"{year}/{month}/{month_name}/{day}/{original_filename}"
|
||||
)
|
||||
request = SimpleNamespace(
|
||||
app=SimpleNamespace(state=SimpleNamespace()),
|
||||
state=SimpleNamespace(),
|
||||
)
|
||||
expected_key = "files/2026/09/Septembre/20/original.pdf"
|
||||
|
||||
with (
|
||||
patch.object(routes, "_storage", return_value=storage),
|
||||
patch.object(routes, "_db", return_value=database),
|
||||
patch.object(routes, "_registry", return_value=registry),
|
||||
patch.object(routes, "get_file", new=AsyncMock(return_value=None)),
|
||||
):
|
||||
asyncio.run(routes.edit_file(
|
||||
request,
|
||||
record.id,
|
||||
DocumentEditRequest(
|
||||
template_name="invoice-template",
|
||||
period_format="monthly",
|
||||
emitting_company="Example Company",
|
||||
document_type="invoice",
|
||||
emission_date=date(2026, 9, 20),
|
||||
),
|
||||
current_user=None,
|
||||
))
|
||||
|
||||
self.assertEqual(record.current_key, expected_key)
|
||||
storage.copy_object.assert_called_once_with(record.original_key, expected_key)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -5,6 +5,7 @@ import unittest
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.pipeline import DocumentProcessor
|
||||
from app.period import get_period_key_values
|
||||
|
||||
|
||||
class EmissionDateExtractionTests(unittest.TestCase):
|
||||
@@ -41,5 +42,28 @@ class EmissionDateExtractionTests(unittest.TestCase):
|
||||
self.assertIsNotNone(error)
|
||||
|
||||
|
||||
class PeriodKeyPlaceholderTests(unittest.TestCase):
|
||||
def test_key_values_use_the_computed_period_and_representative_day(self):
|
||||
values = get_period_key_values(
|
||||
"2026-01-20",
|
||||
{"granularity": "month", "offset": "previous"},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
values,
|
||||
{"year": "2025", "month": "12", "month_name": "Décembre", "day": "20"},
|
||||
)
|
||||
|
||||
def test_key_pattern_renders_period_placeholders(self):
|
||||
processor = object.__new__(DocumentProcessor)
|
||||
|
||||
key = processor._format_key_pattern(
|
||||
"{year}/{month}/{month_name}/{day}/document.pdf",
|
||||
**get_period_key_values("2026-09-20", {"granularity": "month"}),
|
||||
)
|
||||
|
||||
self.assertEqual(key, "2026/09/Septembre/20/document.pdf")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user