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.
Files changed (105) hide show
  1. julee/docs/sphinx_hcd/__init__.py +146 -13
  2. julee/docs/sphinx_hcd/domain/__init__.py +5 -0
  3. julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
  4. julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
  5. julee/docs/sphinx_hcd/domain/models/app.py +151 -0
  6. julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
  7. julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
  8. julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
  9. julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
  10. julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
  11. julee/docs/sphinx_hcd/domain/models/story.py +128 -0
  12. julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
  13. julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
  14. julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
  15. julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
  16. julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
  17. julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
  18. julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
  19. julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
  20. julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
  21. julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
  22. julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
  23. julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
  24. julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
  25. julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
  26. julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
  27. julee/docs/sphinx_hcd/parsers/ast.py +150 -0
  28. julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
  29. julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
  30. julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
  31. julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
  32. julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
  33. julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
  34. julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
  35. julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
  36. julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
  37. julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
  38. julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
  39. julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
  40. julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
  41. julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
  42. julee/docs/sphinx_hcd/sphinx/context.py +163 -0
  43. julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
  44. julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
  45. julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
  46. julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
  47. julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
  48. julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
  49. julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
  50. julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
  51. julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
  52. julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
  53. julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
  54. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
  55. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
  56. julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
  57. julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
  58. julee/docs/sphinx_hcd/tests/__init__.py +9 -0
  59. julee/docs/sphinx_hcd/tests/conftest.py +6 -0
  60. julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
  61. julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
  62. julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
  63. julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
  64. julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
  65. julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
  66. julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
  67. julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
  68. julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
  69. julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
  70. julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
  71. julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
  72. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
  73. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
  74. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
  75. julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
  76. julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
  77. julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
  78. julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
  79. julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
  80. julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
  81. julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
  82. julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
  83. julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
  84. julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
  85. julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
  86. julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
  87. julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
  88. julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
  89. julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
  90. julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
  91. julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
  92. julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
  93. julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
  94. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/METADATA +2 -1
  95. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/RECORD +98 -13
  96. julee/docs/sphinx_hcd/accelerators.py +0 -1175
  97. julee/docs/sphinx_hcd/apps.py +0 -518
  98. julee/docs/sphinx_hcd/epics.py +0 -453
  99. julee/docs/sphinx_hcd/integrations.py +0 -310
  100. julee/docs/sphinx_hcd/journeys.py +0 -797
  101. julee/docs/sphinx_hcd/personas.py +0 -457
  102. julee/docs/sphinx_hcd/stories.py +0 -960
  103. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
  104. {julee-0.1.5.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
  105. {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