-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_resource_manager.py
More file actions
172 lines (131 loc) · 5.48 KB
/
test_resource_manager.py
File metadata and controls
172 lines (131 loc) · 5.48 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""Tests for ResourceManager with mock embedder."""
from __future__ import annotations
import hashlib
from typing import TYPE_CHECKING
import pytest
from qp_vault.core.chunker import ChunkerConfig
from qp_vault.core.resource_manager import ResourceManager, _detect_resource_type
from qp_vault.enums import EventType, ResourceStatus, ResourceType
from qp_vault.storage.sqlite import SQLiteBackend
if TYPE_CHECKING:
from qp_vault.models import VaultEvent
class MockEmbedder:
"""Deterministic embedder for testing."""
@property
def dimensions(self) -> int:
return 4
async def embed(self, texts: list[str]) -> list[list[float]]:
return [
[float(b) / 255 for b in hashlib.sha256(t.encode()).digest()[:4]]
for t in texts
]
class MockAuditor:
"""Captures audit events for assertions."""
def __init__(self) -> None:
self.events: list[VaultEvent] = []
async def record(self, event: VaultEvent) -> str:
self.events.append(event)
return f"audit-{len(self.events)}"
@pytest.fixture
async def backend(tmp_path):
b = SQLiteBackend(tmp_path / "rm-test.db")
await b.initialize()
return b
@pytest.fixture
def auditor():
return MockAuditor()
@pytest.fixture
def manager(backend, auditor):
return ResourceManager(
storage=backend,
embedder=MockEmbedder(),
auditor=auditor,
chunker_config=ChunkerConfig(target_tokens=50, min_tokens=5, max_tokens=100),
)
class TestResourceManagerAdd:
@pytest.mark.asyncio
async def test_add_returns_resource(self, manager):
r = await manager.add("Test content", name="test.md")
assert r.name == "test.md"
assert r.status == ResourceStatus.INDEXED
assert r.chunk_count >= 1
@pytest.mark.asyncio
async def test_add_with_embeddings(self, manager):
r = await manager.add("Content for embedding", name="embed.md")
assert r.chunk_count >= 1
@pytest.mark.asyncio
async def test_add_emits_create_event(self, manager, auditor):
await manager.add("Test", name="test.md")
assert len(auditor.events) == 1
assert auditor.events[0].event_type == EventType.CREATE
@pytest.mark.asyncio
async def test_add_computes_cid(self, manager):
r = await manager.add("Test", name="test.md")
assert r.cid.startswith("vault://sha3-256/")
@pytest.mark.asyncio
async def test_add_same_content_same_hash(self, manager):
r1 = await manager.add("Identical", name="a.md")
r2 = await manager.add("Identical", name="b.md")
assert r1.content_hash == r2.content_hash
class TestResourceManagerCRUD:
@pytest.mark.asyncio
async def test_get(self, manager):
r = await manager.add("Test", name="test.md")
fetched = await manager.get(r.id)
assert fetched.id == r.id
@pytest.mark.asyncio
async def test_get_nonexistent(self, manager):
from qp_vault.exceptions import VaultError
with pytest.raises(VaultError):
await manager.get("nonexistent")
@pytest.mark.asyncio
async def test_list(self, manager):
await manager.add("A", name="a.md")
await manager.add("B", name="b.md")
resources = await manager.list()
assert len(resources) == 2
@pytest.mark.asyncio
async def test_update_trust(self, manager, auditor):
r = await manager.add("Test", name="test.md")
updated = await manager.update(r.id, trust_tier="canonical")
assert updated.trust_tier == "canonical"
# Should emit trust_change event
trust_events = [e for e in auditor.events if e.event_type == EventType.TRUST_CHANGE]
assert len(trust_events) == 1
@pytest.mark.asyncio
async def test_delete_soft(self, manager, auditor):
r = await manager.add("Test", name="test.md")
await manager.delete(r.id)
delete_events = [e for e in auditor.events if e.event_type == EventType.DELETE]
assert len(delete_events) == 1
assert delete_events[0].details["hard"] is False
@pytest.mark.asyncio
async def test_delete_hard(self, manager):
r = await manager.add("Test", name="test.md")
await manager.delete(r.id, hard=True)
from qp_vault.exceptions import VaultError
with pytest.raises(VaultError):
await manager.get(r.id)
@pytest.mark.asyncio
async def test_restore(self, manager, auditor):
r = await manager.add("Test", name="test.md")
await manager.delete(r.id)
restored = await manager.restore(r.id)
assert restored.status == ResourceStatus.INDEXED
restore_events = [e for e in auditor.events if e.event_type == EventType.RESTORE]
assert len(restore_events) == 1
class TestResourceTypeDetection:
def test_pdf(self):
assert _detect_resource_type("report.pdf") == ResourceType.DOCUMENT
def test_python(self):
assert _detect_resource_type("script.py") == ResourceType.CODE
def test_markdown(self):
assert _detect_resource_type("readme.md") == ResourceType.NOTE
def test_image(self):
assert _detect_resource_type("photo.jpg") == ResourceType.IMAGE
def test_transcript(self):
assert _detect_resource_type("meeting.vtt") == ResourceType.TRANSCRIPT
def test_spreadsheet(self):
assert _detect_resource_type("data.csv") == ResourceType.SPREADSHEET
def test_unknown_defaults_to_document(self):
assert _detect_resource_type("file.xyz") == ResourceType.DOCUMENT