julee 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- julee/docs/sphinx_hcd/__init__.py +146 -13
- julee/docs/sphinx_hcd/domain/__init__.py +5 -0
- julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
- julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
- julee/docs/sphinx_hcd/domain/models/app.py +151 -0
- julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
- julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
- julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
- julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
- julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
- julee/docs/sphinx_hcd/domain/models/story.py +128 -0
- julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
- julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
- julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
- julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
- julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
- julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
- julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
- julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
- julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
- julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
- julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
- julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
- julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
- julee/docs/sphinx_hcd/parsers/ast.py +150 -0
- julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
- julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
- julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
- julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
- julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
- julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
- julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
- julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
- julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
- julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
- julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
- julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
- julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
- julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
- julee/docs/sphinx_hcd/sphinx/context.py +163 -0
- julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
- julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
- julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
- julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
- julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
- julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
- julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
- julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
- julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
- julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
- julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
- julee/docs/sphinx_hcd/tests/__init__.py +9 -0
- julee/docs/sphinx_hcd/tests/conftest.py +6 -0
- julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
- julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
- julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
- julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
- julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
- julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
- julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
- julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
- julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
- julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
- julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
- julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
- julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
- julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
- julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
- julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
- julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
- julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/METADATA +2 -1
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/RECORD +98 -13
- julee/docs/sphinx_hcd/accelerators.py +0 -1175
- julee/docs/sphinx_hcd/apps.py +0 -518
- julee/docs/sphinx_hcd/epics.py +0 -453
- julee/docs/sphinx_hcd/integrations.py +0 -310
- julee/docs/sphinx_hcd/journeys.py +0 -797
- julee/docs/sphinx_hcd/personas.py +0 -457
- julee/docs/sphinx_hcd/stories.py +0 -960
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Tests for MemoryCodeInfoRepository."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.code_info import (
|
|
7
|
+
BoundedContextInfo,
|
|
8
|
+
ClassInfo,
|
|
9
|
+
)
|
|
10
|
+
from julee.docs.sphinx_hcd.repositories.memory.code_info import (
|
|
11
|
+
MemoryCodeInfoRepository,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_class_info(name: str, file: str = "test.py") -> ClassInfo:
|
|
16
|
+
"""Helper to create ClassInfo."""
|
|
17
|
+
return ClassInfo(name=name, docstring=f"{name} class", file=file)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_context_info(
|
|
21
|
+
slug: str = "test-context",
|
|
22
|
+
entities: list[ClassInfo] | None = None,
|
|
23
|
+
use_cases: list[ClassInfo] | None = None,
|
|
24
|
+
repository_protocols: list[ClassInfo] | None = None,
|
|
25
|
+
service_protocols: list[ClassInfo] | None = None,
|
|
26
|
+
has_infrastructure: bool = False,
|
|
27
|
+
code_dir: str = "",
|
|
28
|
+
) -> BoundedContextInfo:
|
|
29
|
+
"""Helper to create test context info."""
|
|
30
|
+
return BoundedContextInfo(
|
|
31
|
+
slug=slug,
|
|
32
|
+
entities=entities or [],
|
|
33
|
+
use_cases=use_cases or [],
|
|
34
|
+
repository_protocols=repository_protocols or [],
|
|
35
|
+
service_protocols=service_protocols or [],
|
|
36
|
+
has_infrastructure=has_infrastructure,
|
|
37
|
+
code_dir=code_dir or slug,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestMemoryCodeInfoRepositoryBasicOperations:
|
|
42
|
+
"""Test basic CRUD operations."""
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def repo(self) -> MemoryCodeInfoRepository:
|
|
46
|
+
"""Create a fresh repository."""
|
|
47
|
+
return MemoryCodeInfoRepository()
|
|
48
|
+
|
|
49
|
+
@pytest.mark.asyncio
|
|
50
|
+
async def test_save_and_get(self, repo: MemoryCodeInfoRepository) -> None:
|
|
51
|
+
"""Test saving and retrieving context info."""
|
|
52
|
+
info = create_context_info(slug="vocabulary")
|
|
53
|
+
await repo.save(info)
|
|
54
|
+
|
|
55
|
+
retrieved = await repo.get("vocabulary")
|
|
56
|
+
assert retrieved is not None
|
|
57
|
+
assert retrieved.slug == "vocabulary"
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_get_nonexistent(self, repo: MemoryCodeInfoRepository) -> None:
|
|
61
|
+
"""Test getting nonexistent context info returns None."""
|
|
62
|
+
result = await repo.get("nonexistent")
|
|
63
|
+
assert result is None
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_list_all(self, repo: MemoryCodeInfoRepository) -> None:
|
|
67
|
+
"""Test listing all context infos."""
|
|
68
|
+
await repo.save(create_context_info(slug="context-1"))
|
|
69
|
+
await repo.save(create_context_info(slug="context-2"))
|
|
70
|
+
await repo.save(create_context_info(slug="context-3"))
|
|
71
|
+
|
|
72
|
+
all_infos = await repo.list_all()
|
|
73
|
+
assert len(all_infos) == 3
|
|
74
|
+
slugs = {i.slug for i in all_infos}
|
|
75
|
+
assert slugs == {"context-1", "context-2", "context-3"}
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_delete(self, repo: MemoryCodeInfoRepository) -> None:
|
|
79
|
+
"""Test deleting context info."""
|
|
80
|
+
await repo.save(create_context_info(slug="to-delete"))
|
|
81
|
+
assert await repo.get("to-delete") is not None
|
|
82
|
+
|
|
83
|
+
result = await repo.delete("to-delete")
|
|
84
|
+
assert result is True
|
|
85
|
+
assert await repo.get("to-delete") is None
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_delete_nonexistent(self, repo: MemoryCodeInfoRepository) -> None:
|
|
89
|
+
"""Test deleting nonexistent context info."""
|
|
90
|
+
result = await repo.delete("nonexistent")
|
|
91
|
+
assert result is False
|
|
92
|
+
|
|
93
|
+
@pytest.mark.asyncio
|
|
94
|
+
async def test_clear(self, repo: MemoryCodeInfoRepository) -> None:
|
|
95
|
+
"""Test clearing all context infos."""
|
|
96
|
+
await repo.save(create_context_info(slug="context-1"))
|
|
97
|
+
await repo.save(create_context_info(slug="context-2"))
|
|
98
|
+
assert len(await repo.list_all()) == 2
|
|
99
|
+
|
|
100
|
+
await repo.clear()
|
|
101
|
+
assert len(await repo.list_all()) == 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestMemoryCodeInfoRepositoryQueries:
|
|
105
|
+
"""Test code info-specific query methods."""
|
|
106
|
+
|
|
107
|
+
@pytest.fixture
|
|
108
|
+
def repo(self) -> MemoryCodeInfoRepository:
|
|
109
|
+
"""Create a repository."""
|
|
110
|
+
return MemoryCodeInfoRepository()
|
|
111
|
+
|
|
112
|
+
@pytest_asyncio.fixture
|
|
113
|
+
async def populated_repo(
|
|
114
|
+
self, repo: MemoryCodeInfoRepository
|
|
115
|
+
) -> MemoryCodeInfoRepository:
|
|
116
|
+
"""Create a repository with sample context infos."""
|
|
117
|
+
contexts = [
|
|
118
|
+
create_context_info(
|
|
119
|
+
slug="vocabulary",
|
|
120
|
+
entities=[
|
|
121
|
+
create_class_info("Vocabulary", "vocabulary.py"),
|
|
122
|
+
create_class_info("Term", "term.py"),
|
|
123
|
+
],
|
|
124
|
+
use_cases=[
|
|
125
|
+
create_class_info("CreateVocabulary", "create.py"),
|
|
126
|
+
create_class_info("PublishVocabulary", "publish.py"),
|
|
127
|
+
],
|
|
128
|
+
repository_protocols=[
|
|
129
|
+
create_class_info("VocabularyRepository", "vocabulary.py"),
|
|
130
|
+
],
|
|
131
|
+
has_infrastructure=True,
|
|
132
|
+
code_dir="vocabulary",
|
|
133
|
+
),
|
|
134
|
+
create_context_info(
|
|
135
|
+
slug="traceability",
|
|
136
|
+
entities=[
|
|
137
|
+
create_class_info("TraceLink", "trace_link.py"),
|
|
138
|
+
],
|
|
139
|
+
use_cases=[
|
|
140
|
+
create_class_info("CreateTraceLink", "create.py"),
|
|
141
|
+
],
|
|
142
|
+
has_infrastructure=True,
|
|
143
|
+
code_dir="traceability",
|
|
144
|
+
),
|
|
145
|
+
create_context_info(
|
|
146
|
+
slug="conformity",
|
|
147
|
+
entities=[
|
|
148
|
+
create_class_info("Assessment", "assessment.py"),
|
|
149
|
+
],
|
|
150
|
+
# No use cases
|
|
151
|
+
has_infrastructure=False,
|
|
152
|
+
code_dir="conformity",
|
|
153
|
+
),
|
|
154
|
+
create_context_info(
|
|
155
|
+
slug="empty-context",
|
|
156
|
+
# No entities, no use cases
|
|
157
|
+
has_infrastructure=False,
|
|
158
|
+
code_dir="empty_context",
|
|
159
|
+
),
|
|
160
|
+
]
|
|
161
|
+
for ctx in contexts:
|
|
162
|
+
await repo.save(ctx)
|
|
163
|
+
return repo
|
|
164
|
+
|
|
165
|
+
@pytest.mark.asyncio
|
|
166
|
+
async def test_get_by_code_dir(
|
|
167
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Test getting context info by code directory."""
|
|
170
|
+
info = await populated_repo.get_by_code_dir("vocabulary")
|
|
171
|
+
assert info is not None
|
|
172
|
+
assert info.slug == "vocabulary"
|
|
173
|
+
|
|
174
|
+
@pytest.mark.asyncio
|
|
175
|
+
async def test_get_by_code_dir_different_name(
|
|
176
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Test getting context info where code_dir differs from slug."""
|
|
179
|
+
info = await populated_repo.get_by_code_dir("empty_context")
|
|
180
|
+
assert info is not None
|
|
181
|
+
assert info.slug == "empty-context"
|
|
182
|
+
|
|
183
|
+
@pytest.mark.asyncio
|
|
184
|
+
async def test_get_by_code_dir_not_found(
|
|
185
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
186
|
+
) -> None:
|
|
187
|
+
"""Test getting context info for unknown code directory."""
|
|
188
|
+
info = await populated_repo.get_by_code_dir("unknown")
|
|
189
|
+
assert info is None
|
|
190
|
+
|
|
191
|
+
@pytest.mark.asyncio
|
|
192
|
+
async def test_get_with_entities(
|
|
193
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Test getting contexts with entities."""
|
|
196
|
+
contexts = await populated_repo.get_with_entities()
|
|
197
|
+
assert len(contexts) == 3
|
|
198
|
+
slugs = {c.slug for c in contexts}
|
|
199
|
+
assert slugs == {"vocabulary", "traceability", "conformity"}
|
|
200
|
+
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_get_with_use_cases(
|
|
203
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Test getting contexts with use cases."""
|
|
206
|
+
contexts = await populated_repo.get_with_use_cases()
|
|
207
|
+
assert len(contexts) == 2
|
|
208
|
+
slugs = {c.slug for c in contexts}
|
|
209
|
+
assert slugs == {"vocabulary", "traceability"}
|
|
210
|
+
|
|
211
|
+
@pytest.mark.asyncio
|
|
212
|
+
async def test_get_with_infrastructure(
|
|
213
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
214
|
+
) -> None:
|
|
215
|
+
"""Test getting contexts with infrastructure."""
|
|
216
|
+
contexts = await populated_repo.get_with_infrastructure()
|
|
217
|
+
assert len(contexts) == 2
|
|
218
|
+
slugs = {c.slug for c in contexts}
|
|
219
|
+
assert slugs == {"vocabulary", "traceability"}
|
|
220
|
+
|
|
221
|
+
@pytest.mark.asyncio
|
|
222
|
+
async def test_get_all_entity_names(
|
|
223
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
224
|
+
) -> None:
|
|
225
|
+
"""Test getting all unique entity names."""
|
|
226
|
+
names = await populated_repo.get_all_entity_names()
|
|
227
|
+
expected = {"Vocabulary", "Term", "TraceLink", "Assessment"}
|
|
228
|
+
assert names == expected
|
|
229
|
+
|
|
230
|
+
@pytest.mark.asyncio
|
|
231
|
+
async def test_get_all_entity_names_empty_repo(
|
|
232
|
+
self, repo: MemoryCodeInfoRepository
|
|
233
|
+
) -> None:
|
|
234
|
+
"""Test getting entity names from empty repository."""
|
|
235
|
+
names = await repo.get_all_entity_names()
|
|
236
|
+
assert names == set()
|
|
237
|
+
|
|
238
|
+
@pytest.mark.asyncio
|
|
239
|
+
async def test_get_all_use_case_names(
|
|
240
|
+
self, populated_repo: MemoryCodeInfoRepository
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Test getting all unique use case names."""
|
|
243
|
+
names = await populated_repo.get_all_use_case_names()
|
|
244
|
+
expected = {"CreateVocabulary", "PublishVocabulary", "CreateTraceLink"}
|
|
245
|
+
assert names == expected
|
|
246
|
+
|
|
247
|
+
@pytest.mark.asyncio
|
|
248
|
+
async def test_get_all_use_case_names_empty_repo(
|
|
249
|
+
self, repo: MemoryCodeInfoRepository
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Test getting use case names from empty repository."""
|
|
252
|
+
names = await repo.get_all_use_case_names()
|
|
253
|
+
assert names == set()
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Tests for MemoryEpicRepository."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.epic import Epic
|
|
7
|
+
from julee.docs.sphinx_hcd.repositories.memory.epic import MemoryEpicRepository
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_epic(
|
|
11
|
+
slug: str = "test-epic",
|
|
12
|
+
description: str = "Test description",
|
|
13
|
+
docname: str = "epics/test",
|
|
14
|
+
story_refs: list[str] | None = None,
|
|
15
|
+
) -> Epic:
|
|
16
|
+
"""Helper to create test epics."""
|
|
17
|
+
return Epic(
|
|
18
|
+
slug=slug,
|
|
19
|
+
description=description,
|
|
20
|
+
docname=docname,
|
|
21
|
+
story_refs=story_refs or [],
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestMemoryEpicRepositoryBasicOperations:
|
|
26
|
+
"""Test basic CRUD operations."""
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def repo(self) -> MemoryEpicRepository:
|
|
30
|
+
"""Create a fresh repository."""
|
|
31
|
+
return MemoryEpicRepository()
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_save_and_get(self, repo: MemoryEpicRepository) -> None:
|
|
35
|
+
"""Test saving and retrieving an epic."""
|
|
36
|
+
epic = create_epic(slug="vocabulary-management")
|
|
37
|
+
await repo.save(epic)
|
|
38
|
+
|
|
39
|
+
retrieved = await repo.get("vocabulary-management")
|
|
40
|
+
assert retrieved is not None
|
|
41
|
+
assert retrieved.slug == "vocabulary-management"
|
|
42
|
+
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_get_nonexistent(self, repo: MemoryEpicRepository) -> None:
|
|
45
|
+
"""Test getting a nonexistent epic returns None."""
|
|
46
|
+
result = await repo.get("nonexistent")
|
|
47
|
+
assert result is None
|
|
48
|
+
|
|
49
|
+
@pytest.mark.asyncio
|
|
50
|
+
async def test_list_all(self, repo: MemoryEpicRepository) -> None:
|
|
51
|
+
"""Test listing all epics."""
|
|
52
|
+
await repo.save(create_epic(slug="epic-1"))
|
|
53
|
+
await repo.save(create_epic(slug="epic-2"))
|
|
54
|
+
await repo.save(create_epic(slug="epic-3"))
|
|
55
|
+
|
|
56
|
+
all_epics = await repo.list_all()
|
|
57
|
+
assert len(all_epics) == 3
|
|
58
|
+
slugs = {e.slug for e in all_epics}
|
|
59
|
+
assert slugs == {"epic-1", "epic-2", "epic-3"}
|
|
60
|
+
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_delete(self, repo: MemoryEpicRepository) -> None:
|
|
63
|
+
"""Test deleting an epic."""
|
|
64
|
+
await repo.save(create_epic(slug="to-delete"))
|
|
65
|
+
assert await repo.get("to-delete") is not None
|
|
66
|
+
|
|
67
|
+
result = await repo.delete("to-delete")
|
|
68
|
+
assert result is True
|
|
69
|
+
assert await repo.get("to-delete") is None
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_delete_nonexistent(self, repo: MemoryEpicRepository) -> None:
|
|
73
|
+
"""Test deleting a nonexistent epic."""
|
|
74
|
+
result = await repo.delete("nonexistent")
|
|
75
|
+
assert result is False
|
|
76
|
+
|
|
77
|
+
@pytest.mark.asyncio
|
|
78
|
+
async def test_clear(self, repo: MemoryEpicRepository) -> None:
|
|
79
|
+
"""Test clearing all epics."""
|
|
80
|
+
await repo.save(create_epic(slug="epic-1"))
|
|
81
|
+
await repo.save(create_epic(slug="epic-2"))
|
|
82
|
+
assert len(await repo.list_all()) == 2
|
|
83
|
+
|
|
84
|
+
await repo.clear()
|
|
85
|
+
assert len(await repo.list_all()) == 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestMemoryEpicRepositoryQueries:
|
|
89
|
+
"""Test epic-specific query methods."""
|
|
90
|
+
|
|
91
|
+
@pytest.fixture
|
|
92
|
+
def repo(self) -> MemoryEpicRepository:
|
|
93
|
+
"""Create a repository."""
|
|
94
|
+
return MemoryEpicRepository()
|
|
95
|
+
|
|
96
|
+
@pytest_asyncio.fixture
|
|
97
|
+
async def populated_repo(self, repo: MemoryEpicRepository) -> MemoryEpicRepository:
|
|
98
|
+
"""Create a repository with sample epics."""
|
|
99
|
+
epics = [
|
|
100
|
+
create_epic(
|
|
101
|
+
slug="vocabulary-management",
|
|
102
|
+
description="Manage vocabulary catalogs",
|
|
103
|
+
docname="epics/vocabulary",
|
|
104
|
+
story_refs=["Upload Document", "Review Vocabulary", "Publish Catalog"],
|
|
105
|
+
),
|
|
106
|
+
create_epic(
|
|
107
|
+
slug="credential-creation",
|
|
108
|
+
description="Create credentials",
|
|
109
|
+
docname="epics/credentials",
|
|
110
|
+
story_refs=["Create Credential", "Assign Credential"],
|
|
111
|
+
),
|
|
112
|
+
create_epic(
|
|
113
|
+
slug="pipeline-operations",
|
|
114
|
+
description="Operate data pipelines",
|
|
115
|
+
docname="epics/pipelines",
|
|
116
|
+
story_refs=["Configure Pipeline", "Run Pipeline"],
|
|
117
|
+
),
|
|
118
|
+
create_epic(
|
|
119
|
+
slug="analytics",
|
|
120
|
+
description="Analytics features",
|
|
121
|
+
docname="epics/vocabulary", # Same docname as vocabulary-management
|
|
122
|
+
story_refs=["Review Vocabulary", "Generate Report"],
|
|
123
|
+
),
|
|
124
|
+
create_epic(
|
|
125
|
+
slug="empty-epic",
|
|
126
|
+
description="Epic with no stories",
|
|
127
|
+
docname="epics/empty",
|
|
128
|
+
),
|
|
129
|
+
]
|
|
130
|
+
for epic in epics:
|
|
131
|
+
await repo.save(epic)
|
|
132
|
+
return repo
|
|
133
|
+
|
|
134
|
+
@pytest.mark.asyncio
|
|
135
|
+
async def test_get_by_docname(self, populated_repo: MemoryEpicRepository) -> None:
|
|
136
|
+
"""Test getting epics by document name."""
|
|
137
|
+
epics = await populated_repo.get_by_docname("epics/vocabulary")
|
|
138
|
+
assert len(epics) == 2
|
|
139
|
+
slugs = {e.slug for e in epics}
|
|
140
|
+
assert slugs == {"vocabulary-management", "analytics"}
|
|
141
|
+
|
|
142
|
+
@pytest.mark.asyncio
|
|
143
|
+
async def test_get_by_docname_single(
|
|
144
|
+
self, populated_repo: MemoryEpicRepository
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Test getting epics for document with one epic."""
|
|
147
|
+
epics = await populated_repo.get_by_docname("epics/credentials")
|
|
148
|
+
assert len(epics) == 1
|
|
149
|
+
assert epics[0].slug == "credential-creation"
|
|
150
|
+
|
|
151
|
+
@pytest.mark.asyncio
|
|
152
|
+
async def test_get_by_docname_no_results(
|
|
153
|
+
self, populated_repo: MemoryEpicRepository
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Test getting epics for unknown document."""
|
|
156
|
+
epics = await populated_repo.get_by_docname("unknown/document")
|
|
157
|
+
assert len(epics) == 0
|
|
158
|
+
|
|
159
|
+
@pytest.mark.asyncio
|
|
160
|
+
async def test_clear_by_docname(self, populated_repo: MemoryEpicRepository) -> None:
|
|
161
|
+
"""Test clearing epics by document name."""
|
|
162
|
+
count = await populated_repo.clear_by_docname("epics/vocabulary")
|
|
163
|
+
assert count == 2
|
|
164
|
+
assert await populated_repo.get("vocabulary-management") is None
|
|
165
|
+
assert await populated_repo.get("analytics") is None
|
|
166
|
+
# Other epics should remain
|
|
167
|
+
assert len(await populated_repo.list_all()) == 3
|
|
168
|
+
|
|
169
|
+
@pytest.mark.asyncio
|
|
170
|
+
async def test_clear_by_docname_none_found(
|
|
171
|
+
self, populated_repo: MemoryEpicRepository
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Test clearing non-existent document returns 0."""
|
|
174
|
+
count = await populated_repo.clear_by_docname("unknown/document")
|
|
175
|
+
assert count == 0
|
|
176
|
+
|
|
177
|
+
@pytest.mark.asyncio
|
|
178
|
+
async def test_get_with_story_ref(
|
|
179
|
+
self, populated_repo: MemoryEpicRepository
|
|
180
|
+
) -> None:
|
|
181
|
+
"""Test getting epics with a story reference."""
|
|
182
|
+
epics = await populated_repo.get_with_story_ref("Upload Document")
|
|
183
|
+
assert len(epics) == 1
|
|
184
|
+
assert epics[0].slug == "vocabulary-management"
|
|
185
|
+
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_get_with_story_ref_multiple(
|
|
188
|
+
self, populated_repo: MemoryEpicRepository
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Test getting epics with a story in multiple epics."""
|
|
191
|
+
epics = await populated_repo.get_with_story_ref("Review Vocabulary")
|
|
192
|
+
assert len(epics) == 2
|
|
193
|
+
slugs = {e.slug for e in epics}
|
|
194
|
+
assert slugs == {"vocabulary-management", "analytics"}
|
|
195
|
+
|
|
196
|
+
@pytest.mark.asyncio
|
|
197
|
+
async def test_get_with_story_ref_case_insensitive(
|
|
198
|
+
self, populated_repo: MemoryEpicRepository
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Test story ref matching is case-insensitive."""
|
|
201
|
+
epics = await populated_repo.get_with_story_ref("upload document")
|
|
202
|
+
assert len(epics) == 1
|
|
203
|
+
assert epics[0].slug == "vocabulary-management"
|
|
204
|
+
|
|
205
|
+
@pytest.mark.asyncio
|
|
206
|
+
async def test_get_with_story_ref_no_results(
|
|
207
|
+
self, populated_repo: MemoryEpicRepository
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Test getting epics with nonexistent story."""
|
|
210
|
+
epics = await populated_repo.get_with_story_ref("Unknown Story")
|
|
211
|
+
assert len(epics) == 0
|
|
212
|
+
|
|
213
|
+
@pytest.mark.asyncio
|
|
214
|
+
async def test_get_all_story_refs(
|
|
215
|
+
self, populated_repo: MemoryEpicRepository
|
|
216
|
+
) -> None:
|
|
217
|
+
"""Test getting all unique story references."""
|
|
218
|
+
refs = await populated_repo.get_all_story_refs()
|
|
219
|
+
expected = {
|
|
220
|
+
"upload document",
|
|
221
|
+
"review vocabulary",
|
|
222
|
+
"publish catalog",
|
|
223
|
+
"create credential",
|
|
224
|
+
"assign credential",
|
|
225
|
+
"configure pipeline",
|
|
226
|
+
"run pipeline",
|
|
227
|
+
"generate report",
|
|
228
|
+
}
|
|
229
|
+
assert refs == expected
|
|
230
|
+
|
|
231
|
+
@pytest.mark.asyncio
|
|
232
|
+
async def test_get_all_story_refs_empty_repo(
|
|
233
|
+
self, repo: MemoryEpicRepository
|
|
234
|
+
) -> None:
|
|
235
|
+
"""Test getting story refs from empty repository."""
|
|
236
|
+
refs = await repo.get_all_story_refs()
|
|
237
|
+
assert refs == set()
|