File size: 3,690 Bytes
569bceb
 
 
 
 
 
 
 
 
 
 
 
b76f199
569bceb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""HTTP tests for canonical paragraph rollout (scan + apply guards)."""

import pytest

from app.db.models import Document, IngestStatus
from app.models.schemas import SearchResult


class _FakeVS:
    def __init__(self, rows: list[SearchResult]) -> None:
        self._rows = rows

    def search(self, query: str, tenant_id: str, k: int = 10, **kwargs: object) -> list[SearchResult]:
        return self._rows

    def delete_document(self, doc_id: str) -> None:
        return None

    def count_for_doc(self, doc_id: str) -> int:
        return 0


@pytest.mark.asyncio
async def test_canonical_scan_returns_matches(
    async_client: object,
    test_db: object,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    doc_id = "cccccccc-cccc-cccc-cccc-cccccccccccc"
    tenant = "tenant_test"
    doc = Document(
        id=doc_id,
        tenant_id=tenant,
        filename="guidance.docx",
        file_path="/tmp/guidance.docx",
        status=IngestStatus.complete,
    )
    test_db.add(doc)
    await test_db.commit()

    hits = [
        SearchResult(
            chunk_id=f"{doc_id}_00003",
            doc_id=doc_id,
            tenant_id=tenant,
            text=(
                "The damp proof course should be at least 150 mm above external ground level "
                "in accordance with approved guidance for moisture protection."
            ),
            score=0.41,
            section_type="paragraph",
        ),
    ]
    monkeypatch.setattr(
        "app.services.canonical_rollout.get_vectorstore",
        lambda: _FakeVS(hits),
    )

    res = await async_client.post(
        "/content/canonical-scan",
        json={
            "canonical_text": (
                "Damp proof courses must provide adequate clearance above external ground "
                "to limit moisture ingress per current building standards."
            ),
            "limit": 10,
            "min_relevance_percent": 20.0,
            "min_jaccard_vs_canonical": 0.05,
        },
        headers={"X-Tenant-ID": tenant},
    )
    assert res.status_code == 200
    data = res.json()
    assert len(data["matches"]) == 1
    assert data["matches"][0]["document_id"] == doc_id
    assert data["matches"][0]["chunk_id"] == f"{doc_id}_00003"
    assert data["matches"][0]["compatibility"] in ("high", "review", "low")
    assert "damp" in data["matches"][0]["proposed_replacement"].lower()


@pytest.mark.asyncio
async def test_canonical_apply_requires_confirm(
    async_client: object,
) -> None:
    res = await async_client.post(
        "/content/canonical-apply",
        json={
            "document_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
            "chunk_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa_00000",
            "replacement_text": "Updated text.",
            "confirm_destructive_docx": False,
        },
        headers={"X-Tenant-ID": "tenant_test"},
    )
    assert res.status_code == 400


@pytest.mark.asyncio
async def test_canonical_apply_rejects_pdf(
    async_client: object,
    test_db: object,
) -> None:
    doc_id = "dddddddd-dddd-dddd-dddd-dddddddddddd"
    tenant = "tenant_test"
    doc = Document(
        id=doc_id,
        tenant_id=tenant,
        filename="ref.pdf",
        file_path="/tmp/ref.pdf",
        status=IngestStatus.complete,
    )
    test_db.add(doc)
    await test_db.commit()

    res = await async_client.post(
        "/content/canonical-apply",
        json={
            "document_id": doc_id,
            "chunk_id": f"{doc_id}_00000",
            "replacement_text": "x",
            "confirm_destructive_docx": True,
        },
        headers={"X-Tenant-ID": tenant},
    )
    assert res.status_code == 422