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,298 @@
|
|
|
1
|
+
"""Tests for MemoryAcceleratorRepository."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.accelerator import (
|
|
7
|
+
Accelerator,
|
|
8
|
+
IntegrationReference,
|
|
9
|
+
)
|
|
10
|
+
from julee.docs.sphinx_hcd.repositories.memory.accelerator import (
|
|
11
|
+
MemoryAcceleratorRepository,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_accelerator(
|
|
16
|
+
slug: str = "test-accelerator",
|
|
17
|
+
status: str = "alpha",
|
|
18
|
+
docname: str = "accelerators/test",
|
|
19
|
+
sources_from: list[IntegrationReference] | None = None,
|
|
20
|
+
publishes_to: list[IntegrationReference] | None = None,
|
|
21
|
+
feeds_into: list[str] | None = None,
|
|
22
|
+
depends_on: list[str] | None = None,
|
|
23
|
+
) -> Accelerator:
|
|
24
|
+
"""Helper to create test accelerators."""
|
|
25
|
+
return Accelerator(
|
|
26
|
+
slug=slug,
|
|
27
|
+
status=status,
|
|
28
|
+
docname=docname,
|
|
29
|
+
sources_from=sources_from or [],
|
|
30
|
+
publishes_to=publishes_to or [],
|
|
31
|
+
feeds_into=feeds_into or [],
|
|
32
|
+
depends_on=depends_on or [],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestMemoryAcceleratorRepositoryBasicOperations:
|
|
37
|
+
"""Test basic CRUD operations."""
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def repo(self) -> MemoryAcceleratorRepository:
|
|
41
|
+
"""Create a fresh repository."""
|
|
42
|
+
return MemoryAcceleratorRepository()
|
|
43
|
+
|
|
44
|
+
@pytest.mark.asyncio
|
|
45
|
+
async def test_save_and_get(self, repo: MemoryAcceleratorRepository) -> None:
|
|
46
|
+
"""Test saving and retrieving an accelerator."""
|
|
47
|
+
accel = create_accelerator(slug="vocabulary")
|
|
48
|
+
await repo.save(accel)
|
|
49
|
+
|
|
50
|
+
retrieved = await repo.get("vocabulary")
|
|
51
|
+
assert retrieved is not None
|
|
52
|
+
assert retrieved.slug == "vocabulary"
|
|
53
|
+
|
|
54
|
+
@pytest.mark.asyncio
|
|
55
|
+
async def test_get_nonexistent(self, repo: MemoryAcceleratorRepository) -> None:
|
|
56
|
+
"""Test getting a nonexistent accelerator returns None."""
|
|
57
|
+
result = await repo.get("nonexistent")
|
|
58
|
+
assert result is None
|
|
59
|
+
|
|
60
|
+
@pytest.mark.asyncio
|
|
61
|
+
async def test_list_all(self, repo: MemoryAcceleratorRepository) -> None:
|
|
62
|
+
"""Test listing all accelerators."""
|
|
63
|
+
await repo.save(create_accelerator(slug="accel-1"))
|
|
64
|
+
await repo.save(create_accelerator(slug="accel-2"))
|
|
65
|
+
await repo.save(create_accelerator(slug="accel-3"))
|
|
66
|
+
|
|
67
|
+
all_accels = await repo.list_all()
|
|
68
|
+
assert len(all_accels) == 3
|
|
69
|
+
slugs = {a.slug for a in all_accels}
|
|
70
|
+
assert slugs == {"accel-1", "accel-2", "accel-3"}
|
|
71
|
+
|
|
72
|
+
@pytest.mark.asyncio
|
|
73
|
+
async def test_delete(self, repo: MemoryAcceleratorRepository) -> None:
|
|
74
|
+
"""Test deleting an accelerator."""
|
|
75
|
+
await repo.save(create_accelerator(slug="to-delete"))
|
|
76
|
+
assert await repo.get("to-delete") is not None
|
|
77
|
+
|
|
78
|
+
result = await repo.delete("to-delete")
|
|
79
|
+
assert result is True
|
|
80
|
+
assert await repo.get("to-delete") is None
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def test_delete_nonexistent(self, repo: MemoryAcceleratorRepository) -> None:
|
|
84
|
+
"""Test deleting a nonexistent accelerator."""
|
|
85
|
+
result = await repo.delete("nonexistent")
|
|
86
|
+
assert result is False
|
|
87
|
+
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_clear(self, repo: MemoryAcceleratorRepository) -> None:
|
|
90
|
+
"""Test clearing all accelerators."""
|
|
91
|
+
await repo.save(create_accelerator(slug="accel-1"))
|
|
92
|
+
await repo.save(create_accelerator(slug="accel-2"))
|
|
93
|
+
assert len(await repo.list_all()) == 2
|
|
94
|
+
|
|
95
|
+
await repo.clear()
|
|
96
|
+
assert len(await repo.list_all()) == 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TestMemoryAcceleratorRepositoryQueries:
|
|
100
|
+
"""Test accelerator-specific query methods."""
|
|
101
|
+
|
|
102
|
+
@pytest.fixture
|
|
103
|
+
def repo(self) -> MemoryAcceleratorRepository:
|
|
104
|
+
"""Create a repository."""
|
|
105
|
+
return MemoryAcceleratorRepository()
|
|
106
|
+
|
|
107
|
+
@pytest_asyncio.fixture
|
|
108
|
+
async def populated_repo(
|
|
109
|
+
self, repo: MemoryAcceleratorRepository
|
|
110
|
+
) -> MemoryAcceleratorRepository:
|
|
111
|
+
"""Create a repository with sample accelerators."""
|
|
112
|
+
accelerators = [
|
|
113
|
+
create_accelerator(
|
|
114
|
+
slug="vocabulary",
|
|
115
|
+
status="alpha",
|
|
116
|
+
docname="accelerators/vocabulary",
|
|
117
|
+
sources_from=[
|
|
118
|
+
IntegrationReference(slug="pilot-data", description="Pilot data"),
|
|
119
|
+
],
|
|
120
|
+
publishes_to=[
|
|
121
|
+
IntegrationReference(slug="reference-impl", description="SVC"),
|
|
122
|
+
],
|
|
123
|
+
feeds_into=["traceability"],
|
|
124
|
+
depends_on=["core-infrastructure"],
|
|
125
|
+
),
|
|
126
|
+
create_accelerator(
|
|
127
|
+
slug="traceability",
|
|
128
|
+
status="alpha",
|
|
129
|
+
docname="accelerators/traceability",
|
|
130
|
+
sources_from=[
|
|
131
|
+
IntegrationReference(slug="pilot-data", description="Trace data"),
|
|
132
|
+
],
|
|
133
|
+
depends_on=["vocabulary"],
|
|
134
|
+
),
|
|
135
|
+
create_accelerator(
|
|
136
|
+
slug="conformity",
|
|
137
|
+
status="future",
|
|
138
|
+
docname="accelerators/conformity",
|
|
139
|
+
depends_on=["vocabulary", "traceability"],
|
|
140
|
+
),
|
|
141
|
+
create_accelerator(
|
|
142
|
+
slug="core-infrastructure",
|
|
143
|
+
status="production",
|
|
144
|
+
docname="accelerators/core",
|
|
145
|
+
),
|
|
146
|
+
create_accelerator(
|
|
147
|
+
slug="analytics",
|
|
148
|
+
status="alpha",
|
|
149
|
+
docname="accelerators/vocabulary", # Same docname as vocabulary
|
|
150
|
+
),
|
|
151
|
+
]
|
|
152
|
+
for accel in accelerators:
|
|
153
|
+
await repo.save(accel)
|
|
154
|
+
return repo
|
|
155
|
+
|
|
156
|
+
@pytest.mark.asyncio
|
|
157
|
+
async def test_get_by_status(
|
|
158
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Test getting accelerators by status."""
|
|
161
|
+
accels = await populated_repo.get_by_status("alpha")
|
|
162
|
+
assert len(accels) == 3
|
|
163
|
+
slugs = {a.slug for a in accels}
|
|
164
|
+
assert slugs == {"vocabulary", "traceability", "analytics"}
|
|
165
|
+
|
|
166
|
+
@pytest.mark.asyncio
|
|
167
|
+
async def test_get_by_status_case_insensitive(
|
|
168
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Test status matching is case-insensitive."""
|
|
171
|
+
accels = await populated_repo.get_by_status("ALPHA")
|
|
172
|
+
assert len(accels) == 3
|
|
173
|
+
|
|
174
|
+
@pytest.mark.asyncio
|
|
175
|
+
async def test_get_by_status_no_results(
|
|
176
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Test getting accelerators with unknown status."""
|
|
179
|
+
accels = await populated_repo.get_by_status("unknown")
|
|
180
|
+
assert len(accels) == 0
|
|
181
|
+
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_get_by_docname(
|
|
184
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Test getting accelerators by document name."""
|
|
187
|
+
accels = await populated_repo.get_by_docname("accelerators/vocabulary")
|
|
188
|
+
assert len(accels) == 2
|
|
189
|
+
slugs = {a.slug for a in accels}
|
|
190
|
+
assert slugs == {"vocabulary", "analytics"}
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_get_by_docname_no_results(
|
|
194
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Test getting accelerators for unknown document."""
|
|
197
|
+
accels = await populated_repo.get_by_docname("unknown/document")
|
|
198
|
+
assert len(accels) == 0
|
|
199
|
+
|
|
200
|
+
@pytest.mark.asyncio
|
|
201
|
+
async def test_clear_by_docname(
|
|
202
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
203
|
+
) -> None:
|
|
204
|
+
"""Test clearing accelerators by document name."""
|
|
205
|
+
count = await populated_repo.clear_by_docname("accelerators/vocabulary")
|
|
206
|
+
assert count == 2
|
|
207
|
+
assert await populated_repo.get("vocabulary") is None
|
|
208
|
+
assert await populated_repo.get("analytics") is None
|
|
209
|
+
# Other accelerators should remain
|
|
210
|
+
assert len(await populated_repo.list_all()) == 3
|
|
211
|
+
|
|
212
|
+
@pytest.mark.asyncio
|
|
213
|
+
async def test_clear_by_docname_none_found(
|
|
214
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Test clearing non-existent document returns 0."""
|
|
217
|
+
count = await populated_repo.clear_by_docname("unknown/document")
|
|
218
|
+
assert count == 0
|
|
219
|
+
|
|
220
|
+
@pytest.mark.asyncio
|
|
221
|
+
async def test_get_by_integration_sources_from(
|
|
222
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Test getting accelerators that source from an integration."""
|
|
225
|
+
accels = await populated_repo.get_by_integration("pilot-data", "sources_from")
|
|
226
|
+
assert len(accels) == 2
|
|
227
|
+
slugs = {a.slug for a in accels}
|
|
228
|
+
assert slugs == {"vocabulary", "traceability"}
|
|
229
|
+
|
|
230
|
+
@pytest.mark.asyncio
|
|
231
|
+
async def test_get_by_integration_publishes_to(
|
|
232
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
233
|
+
) -> None:
|
|
234
|
+
"""Test getting accelerators that publish to an integration."""
|
|
235
|
+
accels = await populated_repo.get_by_integration(
|
|
236
|
+
"reference-impl", "publishes_to"
|
|
237
|
+
)
|
|
238
|
+
assert len(accels) == 1
|
|
239
|
+
assert accels[0].slug == "vocabulary"
|
|
240
|
+
|
|
241
|
+
@pytest.mark.asyncio
|
|
242
|
+
async def test_get_by_integration_no_results(
|
|
243
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
244
|
+
) -> None:
|
|
245
|
+
"""Test getting accelerators with unknown integration."""
|
|
246
|
+
accels = await populated_repo.get_by_integration("unknown", "sources_from")
|
|
247
|
+
assert len(accels) == 0
|
|
248
|
+
|
|
249
|
+
@pytest.mark.asyncio
|
|
250
|
+
async def test_get_dependents(
|
|
251
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Test getting accelerators that depend on another."""
|
|
254
|
+
dependents = await populated_repo.get_dependents("vocabulary")
|
|
255
|
+
assert len(dependents) == 2
|
|
256
|
+
slugs = {a.slug for a in dependents}
|
|
257
|
+
assert slugs == {"traceability", "conformity"}
|
|
258
|
+
|
|
259
|
+
@pytest.mark.asyncio
|
|
260
|
+
async def test_get_dependents_none(
|
|
261
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Test getting dependents for accelerator with none."""
|
|
264
|
+
dependents = await populated_repo.get_dependents("conformity")
|
|
265
|
+
assert len(dependents) == 0
|
|
266
|
+
|
|
267
|
+
@pytest.mark.asyncio
|
|
268
|
+
async def test_get_fed_by(
|
|
269
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
270
|
+
) -> None:
|
|
271
|
+
"""Test getting accelerators that feed into another."""
|
|
272
|
+
fed_by = await populated_repo.get_fed_by("traceability")
|
|
273
|
+
assert len(fed_by) == 1
|
|
274
|
+
assert fed_by[0].slug == "vocabulary"
|
|
275
|
+
|
|
276
|
+
@pytest.mark.asyncio
|
|
277
|
+
async def test_get_fed_by_none(
|
|
278
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Test getting fed_by for accelerator with none."""
|
|
281
|
+
fed_by = await populated_repo.get_fed_by("vocabulary")
|
|
282
|
+
assert len(fed_by) == 0
|
|
283
|
+
|
|
284
|
+
@pytest.mark.asyncio
|
|
285
|
+
async def test_get_all_statuses(
|
|
286
|
+
self, populated_repo: MemoryAcceleratorRepository
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Test getting all unique statuses."""
|
|
289
|
+
statuses = await populated_repo.get_all_statuses()
|
|
290
|
+
assert statuses == {"alpha", "future", "production"}
|
|
291
|
+
|
|
292
|
+
@pytest.mark.asyncio
|
|
293
|
+
async def test_get_all_statuses_empty_repo(
|
|
294
|
+
self, repo: MemoryAcceleratorRepository
|
|
295
|
+
) -> None:
|
|
296
|
+
"""Test getting statuses from empty repository."""
|
|
297
|
+
statuses = await repo.get_all_statuses()
|
|
298
|
+
assert statuses == set()
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Tests for MemoryAppRepository."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.app import App, AppType
|
|
7
|
+
from julee.docs.sphinx_hcd.repositories.memory.app import MemoryAppRepository
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_app(
|
|
11
|
+
slug: str = "test-app",
|
|
12
|
+
name: str = "Test App",
|
|
13
|
+
app_type: AppType = AppType.STAFF,
|
|
14
|
+
status: str | None = None,
|
|
15
|
+
accelerators: list[str] | None = None,
|
|
16
|
+
) -> App:
|
|
17
|
+
"""Helper to create test apps."""
|
|
18
|
+
return App(
|
|
19
|
+
slug=slug,
|
|
20
|
+
name=name,
|
|
21
|
+
app_type=app_type,
|
|
22
|
+
status=status,
|
|
23
|
+
accelerators=accelerators or [],
|
|
24
|
+
manifest_path=f"apps/{slug}/app.yaml",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestMemoryAppRepositoryBasicOperations:
|
|
29
|
+
"""Test basic CRUD operations."""
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def repo(self) -> MemoryAppRepository:
|
|
33
|
+
"""Create a fresh repository."""
|
|
34
|
+
return MemoryAppRepository()
|
|
35
|
+
|
|
36
|
+
@pytest.mark.asyncio
|
|
37
|
+
async def test_save_and_get(self, repo: MemoryAppRepository) -> None:
|
|
38
|
+
"""Test saving and retrieving an app."""
|
|
39
|
+
app = create_app(slug="staff-portal")
|
|
40
|
+
await repo.save(app)
|
|
41
|
+
|
|
42
|
+
retrieved = await repo.get("staff-portal")
|
|
43
|
+
assert retrieved is not None
|
|
44
|
+
assert retrieved.slug == "staff-portal"
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_get_nonexistent(self, repo: MemoryAppRepository) -> None:
|
|
48
|
+
"""Test getting a nonexistent app returns None."""
|
|
49
|
+
result = await repo.get("nonexistent")
|
|
50
|
+
assert result is None
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_list_all(self, repo: MemoryAppRepository) -> None:
|
|
54
|
+
"""Test listing all apps."""
|
|
55
|
+
await repo.save(create_app(slug="app-1"))
|
|
56
|
+
await repo.save(create_app(slug="app-2"))
|
|
57
|
+
await repo.save(create_app(slug="app-3"))
|
|
58
|
+
|
|
59
|
+
all_apps = await repo.list_all()
|
|
60
|
+
assert len(all_apps) == 3
|
|
61
|
+
slugs = {a.slug for a in all_apps}
|
|
62
|
+
assert slugs == {"app-1", "app-2", "app-3"}
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_delete(self, repo: MemoryAppRepository) -> None:
|
|
66
|
+
"""Test deleting an app."""
|
|
67
|
+
await repo.save(create_app(slug="to-delete"))
|
|
68
|
+
assert await repo.get("to-delete") is not None
|
|
69
|
+
|
|
70
|
+
result = await repo.delete("to-delete")
|
|
71
|
+
assert result is True
|
|
72
|
+
assert await repo.get("to-delete") is None
|
|
73
|
+
|
|
74
|
+
@pytest.mark.asyncio
|
|
75
|
+
async def test_delete_nonexistent(self, repo: MemoryAppRepository) -> None:
|
|
76
|
+
"""Test deleting a nonexistent app."""
|
|
77
|
+
result = await repo.delete("nonexistent")
|
|
78
|
+
assert result is False
|
|
79
|
+
|
|
80
|
+
@pytest.mark.asyncio
|
|
81
|
+
async def test_clear(self, repo: MemoryAppRepository) -> None:
|
|
82
|
+
"""Test clearing all apps."""
|
|
83
|
+
await repo.save(create_app(slug="app-1"))
|
|
84
|
+
await repo.save(create_app(slug="app-2"))
|
|
85
|
+
assert len(await repo.list_all()) == 2
|
|
86
|
+
|
|
87
|
+
await repo.clear()
|
|
88
|
+
assert len(await repo.list_all()) == 0
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TestMemoryAppRepositoryQueries:
|
|
92
|
+
"""Test app-specific query methods."""
|
|
93
|
+
|
|
94
|
+
@pytest.fixture
|
|
95
|
+
def repo(self) -> MemoryAppRepository:
|
|
96
|
+
"""Create a repository with sample data."""
|
|
97
|
+
return MemoryAppRepository()
|
|
98
|
+
|
|
99
|
+
@pytest_asyncio.fixture
|
|
100
|
+
async def populated_repo(self, repo: MemoryAppRepository) -> MemoryAppRepository:
|
|
101
|
+
"""Create a repository with sample apps."""
|
|
102
|
+
apps = [
|
|
103
|
+
create_app(
|
|
104
|
+
slug="staff-portal",
|
|
105
|
+
name="Staff Portal",
|
|
106
|
+
app_type=AppType.STAFF,
|
|
107
|
+
accelerators=["user-auth", "doc-upload"],
|
|
108
|
+
),
|
|
109
|
+
create_app(
|
|
110
|
+
slug="admin-tool",
|
|
111
|
+
name="Admin Tool",
|
|
112
|
+
app_type=AppType.STAFF,
|
|
113
|
+
accelerators=["admin-config"],
|
|
114
|
+
),
|
|
115
|
+
create_app(
|
|
116
|
+
slug="customer-portal",
|
|
117
|
+
name="Customer Portal",
|
|
118
|
+
app_type=AppType.EXTERNAL,
|
|
119
|
+
),
|
|
120
|
+
create_app(
|
|
121
|
+
slug="checkout-app",
|
|
122
|
+
name="Checkout App",
|
|
123
|
+
app_type=AppType.EXTERNAL,
|
|
124
|
+
accelerators=["payment-processor"],
|
|
125
|
+
),
|
|
126
|
+
create_app(
|
|
127
|
+
slug="member-tool",
|
|
128
|
+
name="Member Tool",
|
|
129
|
+
app_type=AppType.MEMBER_TOOL,
|
|
130
|
+
),
|
|
131
|
+
]
|
|
132
|
+
for app in apps:
|
|
133
|
+
await repo.save(app)
|
|
134
|
+
return repo
|
|
135
|
+
|
|
136
|
+
@pytest.mark.asyncio
|
|
137
|
+
async def test_get_by_type_staff(self, populated_repo: MemoryAppRepository) -> None:
|
|
138
|
+
"""Test getting apps by staff type."""
|
|
139
|
+
apps = await populated_repo.get_by_type(AppType.STAFF)
|
|
140
|
+
assert len(apps) == 2
|
|
141
|
+
assert all(a.app_type == AppType.STAFF for a in apps)
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
async def test_get_by_type_external(
|
|
145
|
+
self, populated_repo: MemoryAppRepository
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Test getting apps by external type."""
|
|
148
|
+
apps = await populated_repo.get_by_type(AppType.EXTERNAL)
|
|
149
|
+
assert len(apps) == 2
|
|
150
|
+
assert all(a.app_type == AppType.EXTERNAL for a in apps)
|
|
151
|
+
|
|
152
|
+
@pytest.mark.asyncio
|
|
153
|
+
async def test_get_by_type_member_tool(
|
|
154
|
+
self, populated_repo: MemoryAppRepository
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Test getting apps by member-tool type."""
|
|
157
|
+
apps = await populated_repo.get_by_type(AppType.MEMBER_TOOL)
|
|
158
|
+
assert len(apps) == 1
|
|
159
|
+
assert apps[0].slug == "member-tool"
|
|
160
|
+
|
|
161
|
+
@pytest.mark.asyncio
|
|
162
|
+
async def test_get_by_type_no_results(
|
|
163
|
+
self, populated_repo: MemoryAppRepository
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Test getting apps by type with no matches."""
|
|
166
|
+
apps = await populated_repo.get_by_type(AppType.UNKNOWN)
|
|
167
|
+
assert len(apps) == 0
|
|
168
|
+
|
|
169
|
+
@pytest.mark.asyncio
|
|
170
|
+
async def test_get_by_name(self, populated_repo: MemoryAppRepository) -> None:
|
|
171
|
+
"""Test getting an app by name."""
|
|
172
|
+
app = await populated_repo.get_by_name("Staff Portal")
|
|
173
|
+
assert app is not None
|
|
174
|
+
assert app.slug == "staff-portal"
|
|
175
|
+
|
|
176
|
+
@pytest.mark.asyncio
|
|
177
|
+
async def test_get_by_name_case_insensitive(
|
|
178
|
+
self, populated_repo: MemoryAppRepository
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Test name matching is case-insensitive."""
|
|
181
|
+
app = await populated_repo.get_by_name("staff portal")
|
|
182
|
+
assert app is not None
|
|
183
|
+
assert app.slug == "staff-portal"
|
|
184
|
+
|
|
185
|
+
@pytest.mark.asyncio
|
|
186
|
+
async def test_get_by_name_not_found(
|
|
187
|
+
self, populated_repo: MemoryAppRepository
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Test getting app by nonexistent name."""
|
|
190
|
+
app = await populated_repo.get_by_name("Nonexistent App")
|
|
191
|
+
assert app is None
|
|
192
|
+
|
|
193
|
+
@pytest.mark.asyncio
|
|
194
|
+
async def test_get_all_types(self, populated_repo: MemoryAppRepository) -> None:
|
|
195
|
+
"""Test getting all unique app types."""
|
|
196
|
+
types = await populated_repo.get_all_types()
|
|
197
|
+
assert types == {AppType.STAFF, AppType.EXTERNAL, AppType.MEMBER_TOOL}
|
|
198
|
+
|
|
199
|
+
@pytest.mark.asyncio
|
|
200
|
+
async def test_get_apps_with_accelerators(
|
|
201
|
+
self, populated_repo: MemoryAppRepository
|
|
202
|
+
) -> None:
|
|
203
|
+
"""Test getting apps that have accelerators."""
|
|
204
|
+
apps = await populated_repo.get_apps_with_accelerators()
|
|
205
|
+
assert len(apps) == 3
|
|
206
|
+
slugs = {a.slug for a in apps}
|
|
207
|
+
assert slugs == {"staff-portal", "admin-tool", "checkout-app"}
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_get_apps_with_accelerators_empty(
|
|
211
|
+
self, repo: MemoryAppRepository
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Test getting apps with accelerators when none have any."""
|
|
214
|
+
await repo.save(create_app(slug="no-accel-1"))
|
|
215
|
+
await repo.save(create_app(slug="no-accel-2"))
|
|
216
|
+
|
|
217
|
+
apps = await repo.get_apps_with_accelerators()
|
|
218
|
+
assert len(apps) == 0
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Tests for MemoryRepositoryMixin base utility methods."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from julee.docs.sphinx_hcd.domain.models.story import Story
|
|
6
|
+
from julee.docs.sphinx_hcd.repositories.memory.story import MemoryStoryRepository
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_story(
|
|
10
|
+
slug: str = "test",
|
|
11
|
+
feature_title: str = "Test Feature",
|
|
12
|
+
persona: str = "User",
|
|
13
|
+
app_slug: str = "app",
|
|
14
|
+
) -> Story:
|
|
15
|
+
"""Helper to create test stories."""
|
|
16
|
+
return Story(
|
|
17
|
+
slug=slug,
|
|
18
|
+
feature_title=feature_title,
|
|
19
|
+
persona=persona,
|
|
20
|
+
app_slug=app_slug,
|
|
21
|
+
file_path=f"tests/e2e/{app_slug}/features/{slug}.feature",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestFindByField:
|
|
26
|
+
"""Test the find_by_field utility method from MemoryRepositoryMixin."""
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def repo(self) -> MemoryStoryRepository:
|
|
30
|
+
"""Create a fresh repository."""
|
|
31
|
+
return MemoryStoryRepository()
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_find_by_field_single_match(
|
|
35
|
+
self, repo: MemoryStoryRepository
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Test finding entities by a field with single match."""
|
|
38
|
+
await repo.save(create_story(slug="story-1", persona="Admin"))
|
|
39
|
+
await repo.save(create_story(slug="story-2", persona="User"))
|
|
40
|
+
await repo.save(create_story(slug="story-3", persona="User"))
|
|
41
|
+
|
|
42
|
+
result = await repo.find_by_field("persona", "Admin")
|
|
43
|
+
|
|
44
|
+
assert len(result) == 1
|
|
45
|
+
assert result[0].slug == "story-1"
|
|
46
|
+
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_find_by_field_multiple_matches(
|
|
49
|
+
self, repo: MemoryStoryRepository
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Test finding entities by a field with multiple matches."""
|
|
52
|
+
await repo.save(create_story(slug="story-1", app_slug="portal"))
|
|
53
|
+
await repo.save(create_story(slug="story-2", app_slug="portal"))
|
|
54
|
+
await repo.save(create_story(slug="story-3", app_slug="other"))
|
|
55
|
+
|
|
56
|
+
result = await repo.find_by_field("app_slug", "portal")
|
|
57
|
+
|
|
58
|
+
assert len(result) == 2
|
|
59
|
+
slugs = {s.slug for s in result}
|
|
60
|
+
assert slugs == {"story-1", "story-2"}
|
|
61
|
+
|
|
62
|
+
@pytest.mark.asyncio
|
|
63
|
+
async def test_find_by_field_no_matches(self, repo: MemoryStoryRepository) -> None:
|
|
64
|
+
"""Test finding entities by a field with no matches."""
|
|
65
|
+
await repo.save(create_story(slug="story-1", persona="User"))
|
|
66
|
+
|
|
67
|
+
result = await repo.find_by_field("persona", "Admin")
|
|
68
|
+
|
|
69
|
+
assert result == []
|
|
70
|
+
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_find_by_field_nonexistent_field(
|
|
73
|
+
self, repo: MemoryStoryRepository
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Test finding by a field that doesn't exist returns empty list."""
|
|
76
|
+
await repo.save(create_story(slug="story-1"))
|
|
77
|
+
|
|
78
|
+
result = await repo.find_by_field("nonexistent_field", "value")
|
|
79
|
+
|
|
80
|
+
assert result == []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TestFindByFieldIn:
|
|
84
|
+
"""Test the find_by_field_in utility method from MemoryRepositoryMixin."""
|
|
85
|
+
|
|
86
|
+
@pytest.fixture
|
|
87
|
+
def repo(self) -> MemoryStoryRepository:
|
|
88
|
+
"""Create a fresh repository."""
|
|
89
|
+
return MemoryStoryRepository()
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_find_by_field_in_multiple_values(
|
|
93
|
+
self, repo: MemoryStoryRepository
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Test finding entities where field is in a list of values."""
|
|
96
|
+
await repo.save(create_story(slug="story-1", persona="Admin"))
|
|
97
|
+
await repo.save(create_story(slug="story-2", persona="User"))
|
|
98
|
+
await repo.save(create_story(slug="story-3", persona="Guest"))
|
|
99
|
+
|
|
100
|
+
result = await repo.find_by_field_in("persona", ["Admin", "User"])
|
|
101
|
+
|
|
102
|
+
assert len(result) == 2
|
|
103
|
+
personas = {s.persona for s in result}
|
|
104
|
+
assert personas == {"Admin", "User"}
|
|
105
|
+
|
|
106
|
+
@pytest.mark.asyncio
|
|
107
|
+
async def test_find_by_field_in_single_value(
|
|
108
|
+
self, repo: MemoryStoryRepository
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Test finding entities with single value in list."""
|
|
111
|
+
await repo.save(create_story(slug="story-1", app_slug="portal"))
|
|
112
|
+
await repo.save(create_story(slug="story-2", app_slug="other"))
|
|
113
|
+
|
|
114
|
+
result = await repo.find_by_field_in("app_slug", ["portal"])
|
|
115
|
+
|
|
116
|
+
assert len(result) == 1
|
|
117
|
+
assert result[0].slug == "story-1"
|
|
118
|
+
|
|
119
|
+
@pytest.mark.asyncio
|
|
120
|
+
async def test_find_by_field_in_no_matches(
|
|
121
|
+
self, repo: MemoryStoryRepository
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Test finding entities with no matching values."""
|
|
124
|
+
await repo.save(create_story(slug="story-1", persona="User"))
|
|
125
|
+
|
|
126
|
+
result = await repo.find_by_field_in("persona", ["Admin", "Guest"])
|
|
127
|
+
|
|
128
|
+
assert result == []
|
|
129
|
+
|
|
130
|
+
@pytest.mark.asyncio
|
|
131
|
+
async def test_find_by_field_in_empty_list(
|
|
132
|
+
self, repo: MemoryStoryRepository
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Test finding with empty values list returns empty result."""
|
|
135
|
+
await repo.save(create_story(slug="story-1"))
|
|
136
|
+
|
|
137
|
+
result = await repo.find_by_field_in("persona", [])
|
|
138
|
+
|
|
139
|
+
assert result == []
|
|
140
|
+
|
|
141
|
+
@pytest.mark.asyncio
|
|
142
|
+
async def test_find_by_field_in_all_match(
|
|
143
|
+
self, repo: MemoryStoryRepository
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Test when all entities match."""
|
|
146
|
+
await repo.save(create_story(slug="story-1", persona="Admin"))
|
|
147
|
+
await repo.save(create_story(slug="story-2", persona="User"))
|
|
148
|
+
|
|
149
|
+
result = await repo.find_by_field_in("persona", ["Admin", "User", "Guest"])
|
|
150
|
+
|
|
151
|
+
assert len(result) == 2
|