julee 0.1.4__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 (165) hide show
  1. julee/__init__.py +1 -1
  2. julee/api/tests/routers/test_assembly_specifications.py +2 -0
  3. julee/api/tests/routers/test_documents.py +2 -0
  4. julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
  5. julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
  6. julee/api/tests/routers/test_system.py +2 -0
  7. julee/api/tests/routers/test_workflows.py +2 -0
  8. julee/api/tests/test_app.py +2 -0
  9. julee/api/tests/test_dependencies.py +2 -0
  10. julee/api/tests/test_requests.py +2 -0
  11. julee/contrib/polling/__init__.py +22 -19
  12. julee/contrib/polling/apps/__init__.py +17 -0
  13. julee/contrib/polling/apps/worker/__init__.py +17 -0
  14. julee/contrib/polling/apps/worker/pipelines.py +288 -0
  15. julee/contrib/polling/domain/__init__.py +7 -9
  16. julee/contrib/polling/domain/models/__init__.py +6 -7
  17. julee/contrib/polling/domain/models/polling_config.py +18 -1
  18. julee/contrib/polling/domain/services/__init__.py +6 -5
  19. julee/contrib/polling/domain/services/poller.py +1 -1
  20. julee/contrib/polling/infrastructure/__init__.py +9 -8
  21. julee/contrib/polling/infrastructure/services/__init__.py +6 -5
  22. julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
  23. julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
  24. julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
  25. julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
  26. julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
  27. julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
  28. julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
  29. julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
  30. julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
  31. julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
  32. julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
  33. julee/docs/sphinx_hcd/__init__.py +146 -13
  34. julee/docs/sphinx_hcd/domain/__init__.py +5 -0
  35. julee/docs/sphinx_hcd/domain/models/__init__.py +32 -0
  36. julee/docs/sphinx_hcd/domain/models/accelerator.py +152 -0
  37. julee/docs/sphinx_hcd/domain/models/app.py +151 -0
  38. julee/docs/sphinx_hcd/domain/models/code_info.py +121 -0
  39. julee/docs/sphinx_hcd/domain/models/epic.py +79 -0
  40. julee/docs/sphinx_hcd/domain/models/integration.py +230 -0
  41. julee/docs/sphinx_hcd/domain/models/journey.py +222 -0
  42. julee/docs/sphinx_hcd/domain/models/persona.py +106 -0
  43. julee/docs/sphinx_hcd/domain/models/story.py +128 -0
  44. julee/docs/sphinx_hcd/domain/repositories/__init__.py +25 -0
  45. julee/docs/sphinx_hcd/domain/repositories/accelerator.py +98 -0
  46. julee/docs/sphinx_hcd/domain/repositories/app.py +57 -0
  47. julee/docs/sphinx_hcd/domain/repositories/base.py +89 -0
  48. julee/docs/sphinx_hcd/domain/repositories/code_info.py +69 -0
  49. julee/docs/sphinx_hcd/domain/repositories/epic.py +62 -0
  50. julee/docs/sphinx_hcd/domain/repositories/integration.py +79 -0
  51. julee/docs/sphinx_hcd/domain/repositories/journey.py +106 -0
  52. julee/docs/sphinx_hcd/domain/repositories/story.py +68 -0
  53. julee/docs/sphinx_hcd/domain/use_cases/__init__.py +64 -0
  54. julee/docs/sphinx_hcd/domain/use_cases/derive_personas.py +166 -0
  55. julee/docs/sphinx_hcd/domain/use_cases/resolve_accelerator_references.py +236 -0
  56. julee/docs/sphinx_hcd/domain/use_cases/resolve_app_references.py +144 -0
  57. julee/docs/sphinx_hcd/domain/use_cases/resolve_story_references.py +121 -0
  58. julee/docs/sphinx_hcd/parsers/__init__.py +48 -0
  59. julee/docs/sphinx_hcd/parsers/ast.py +150 -0
  60. julee/docs/sphinx_hcd/parsers/gherkin.py +155 -0
  61. julee/docs/sphinx_hcd/parsers/yaml.py +184 -0
  62. julee/docs/sphinx_hcd/repositories/__init__.py +4 -0
  63. julee/docs/sphinx_hcd/repositories/memory/__init__.py +25 -0
  64. julee/docs/sphinx_hcd/repositories/memory/accelerator.py +86 -0
  65. julee/docs/sphinx_hcd/repositories/memory/app.py +45 -0
  66. julee/docs/sphinx_hcd/repositories/memory/base.py +106 -0
  67. julee/docs/sphinx_hcd/repositories/memory/code_info.py +59 -0
  68. julee/docs/sphinx_hcd/repositories/memory/epic.py +54 -0
  69. julee/docs/sphinx_hcd/repositories/memory/integration.py +70 -0
  70. julee/docs/sphinx_hcd/repositories/memory/journey.py +96 -0
  71. julee/docs/sphinx_hcd/repositories/memory/story.py +63 -0
  72. julee/docs/sphinx_hcd/sphinx/__init__.py +28 -0
  73. julee/docs/sphinx_hcd/sphinx/adapters.py +116 -0
  74. julee/docs/sphinx_hcd/sphinx/context.py +163 -0
  75. julee/docs/sphinx_hcd/sphinx/directives/__init__.py +160 -0
  76. julee/docs/sphinx_hcd/sphinx/directives/accelerator.py +576 -0
  77. julee/docs/sphinx_hcd/sphinx/directives/app.py +349 -0
  78. julee/docs/sphinx_hcd/sphinx/directives/base.py +211 -0
  79. julee/docs/sphinx_hcd/sphinx/directives/epic.py +434 -0
  80. julee/docs/sphinx_hcd/sphinx/directives/integration.py +220 -0
  81. julee/docs/sphinx_hcd/sphinx/directives/journey.py +642 -0
  82. julee/docs/sphinx_hcd/sphinx/directives/persona.py +345 -0
  83. julee/docs/sphinx_hcd/sphinx/directives/story.py +575 -0
  84. julee/docs/sphinx_hcd/sphinx/event_handlers/__init__.py +16 -0
  85. julee/docs/sphinx_hcd/sphinx/event_handlers/builder_inited.py +31 -0
  86. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_read.py +27 -0
  87. julee/docs/sphinx_hcd/sphinx/event_handlers/doctree_resolved.py +43 -0
  88. julee/docs/sphinx_hcd/sphinx/event_handlers/env_purge_doc.py +42 -0
  89. julee/docs/sphinx_hcd/sphinx/initialization.py +139 -0
  90. julee/docs/sphinx_hcd/tests/__init__.py +9 -0
  91. julee/docs/sphinx_hcd/tests/conftest.py +6 -0
  92. julee/docs/sphinx_hcd/tests/domain/__init__.py +1 -0
  93. julee/docs/sphinx_hcd/tests/domain/models/__init__.py +1 -0
  94. julee/docs/sphinx_hcd/tests/domain/models/test_accelerator.py +266 -0
  95. julee/docs/sphinx_hcd/tests/domain/models/test_app.py +258 -0
  96. julee/docs/sphinx_hcd/tests/domain/models/test_code_info.py +231 -0
  97. julee/docs/sphinx_hcd/tests/domain/models/test_epic.py +163 -0
  98. julee/docs/sphinx_hcd/tests/domain/models/test_integration.py +327 -0
  99. julee/docs/sphinx_hcd/tests/domain/models/test_journey.py +249 -0
  100. julee/docs/sphinx_hcd/tests/domain/models/test_persona.py +172 -0
  101. julee/docs/sphinx_hcd/tests/domain/models/test_story.py +216 -0
  102. julee/docs/sphinx_hcd/tests/domain/use_cases/__init__.py +1 -0
  103. julee/docs/sphinx_hcd/tests/domain/use_cases/test_derive_personas.py +314 -0
  104. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_accelerator_references.py +476 -0
  105. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_app_references.py +265 -0
  106. julee/docs/sphinx_hcd/tests/domain/use_cases/test_resolve_story_references.py +229 -0
  107. julee/docs/sphinx_hcd/tests/integration/__init__.py +1 -0
  108. julee/docs/sphinx_hcd/tests/parsers/__init__.py +1 -0
  109. julee/docs/sphinx_hcd/tests/parsers/test_ast.py +298 -0
  110. julee/docs/sphinx_hcd/tests/parsers/test_gherkin.py +282 -0
  111. julee/docs/sphinx_hcd/tests/parsers/test_yaml.py +496 -0
  112. julee/docs/sphinx_hcd/tests/repositories/__init__.py +1 -0
  113. julee/docs/sphinx_hcd/tests/repositories/test_accelerator.py +298 -0
  114. julee/docs/sphinx_hcd/tests/repositories/test_app.py +218 -0
  115. julee/docs/sphinx_hcd/tests/repositories/test_base.py +151 -0
  116. julee/docs/sphinx_hcd/tests/repositories/test_code_info.py +253 -0
  117. julee/docs/sphinx_hcd/tests/repositories/test_epic.py +237 -0
  118. julee/docs/sphinx_hcd/tests/repositories/test_integration.py +268 -0
  119. julee/docs/sphinx_hcd/tests/repositories/test_journey.py +294 -0
  120. julee/docs/sphinx_hcd/tests/repositories/test_story.py +236 -0
  121. julee/docs/sphinx_hcd/tests/sphinx/__init__.py +1 -0
  122. julee/docs/sphinx_hcd/tests/sphinx/directives/__init__.py +1 -0
  123. julee/docs/sphinx_hcd/tests/sphinx/directives/test_base.py +160 -0
  124. julee/docs/sphinx_hcd/tests/sphinx/test_adapters.py +176 -0
  125. julee/docs/sphinx_hcd/tests/sphinx/test_context.py +257 -0
  126. julee/domain/models/assembly/tests/test_assembly.py +2 -0
  127. julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
  128. julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
  129. julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
  130. julee/domain/models/document/tests/test_document.py +2 -0
  131. julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
  132. julee/domain/models/policy/tests/test_policy.py +2 -0
  133. julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
  134. julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
  135. julee/domain/use_cases/tests/test_validate_document.py +2 -0
  136. julee/maintenance/release.py +10 -5
  137. julee/repositories/memory/tests/test_document.py +2 -0
  138. julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
  139. julee/repositories/memory/tests/test_policy.py +2 -0
  140. julee/repositories/minio/tests/test_assembly.py +2 -0
  141. julee/repositories/minio/tests/test_assembly_specification.py +2 -0
  142. julee/repositories/minio/tests/test_client_protocol.py +3 -0
  143. julee/repositories/minio/tests/test_document.py +2 -0
  144. julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
  145. julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
  146. julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
  147. julee/repositories/minio/tests/test_policy.py +2 -0
  148. julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
  149. julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
  150. julee/services/knowledge_service/test_factory.py +2 -0
  151. julee/util/tests/test_decorators.py +2 -0
  152. julee-0.1.6.dist-info/METADATA +104 -0
  153. julee-0.1.6.dist-info/RECORD +288 -0
  154. julee/docs/sphinx_hcd/accelerators.py +0 -1175
  155. julee/docs/sphinx_hcd/apps.py +0 -518
  156. julee/docs/sphinx_hcd/epics.py +0 -453
  157. julee/docs/sphinx_hcd/integrations.py +0 -310
  158. julee/docs/sphinx_hcd/journeys.py +0 -797
  159. julee/docs/sphinx_hcd/personas.py +0 -457
  160. julee/docs/sphinx_hcd/stories.py +0 -960
  161. julee-0.1.4.dist-info/METADATA +0 -197
  162. julee-0.1.4.dist-info/RECORD +0 -196
  163. {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
  164. {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
  165. {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,314 @@
1
+ """Tests for derive_personas use case."""
2
+
3
+ import pytest
4
+
5
+ from julee.docs.sphinx_hcd.domain.models.app import App, AppType
6
+ from julee.docs.sphinx_hcd.domain.models.epic import Epic
7
+ from julee.docs.sphinx_hcd.domain.models.story import Story
8
+ from julee.docs.sphinx_hcd.domain.use_cases.derive_personas import (
9
+ derive_personas,
10
+ derive_personas_by_app_type,
11
+ get_apps_for_persona,
12
+ get_epics_for_persona,
13
+ )
14
+
15
+
16
+ def create_story(
17
+ feature_title: str,
18
+ persona: str,
19
+ app_slug: str,
20
+ ) -> Story:
21
+ """Helper to create test stories."""
22
+ return Story(
23
+ slug=feature_title.lower().replace(" ", "-"),
24
+ feature_title=feature_title,
25
+ persona=persona,
26
+ i_want="test want",
27
+ so_that="test outcome",
28
+ app_slug=app_slug,
29
+ file_path=f"features/{app_slug}.feature",
30
+ )
31
+
32
+
33
+ def create_epic(
34
+ slug: str,
35
+ story_refs: list[str],
36
+ ) -> Epic:
37
+ """Helper to create test epics."""
38
+ return Epic(
39
+ slug=slug,
40
+ description=f"Epic for {slug}",
41
+ story_refs=story_refs,
42
+ )
43
+
44
+
45
+ def create_app(
46
+ slug: str,
47
+ name: str,
48
+ app_type: AppType = AppType.STAFF,
49
+ ) -> App:
50
+ """Helper to create test apps."""
51
+ return App(
52
+ slug=slug,
53
+ name=name,
54
+ app_type=app_type,
55
+ manifest_path=f"apps/{slug}/app.yaml",
56
+ )
57
+
58
+
59
+ class TestDerivePersonas:
60
+ """Test derive_personas function."""
61
+
62
+ def test_derive_single_persona(self) -> None:
63
+ """Test deriving a single persona from stories."""
64
+ stories = [
65
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
66
+ create_story("Review Vocabulary", "Knowledge Curator", "vocabulary-tool"),
67
+ ]
68
+ epics: list[Epic] = []
69
+
70
+ personas = derive_personas(stories, epics)
71
+
72
+ assert len(personas) == 1
73
+ assert personas[0].name == "Knowledge Curator"
74
+ assert personas[0].app_slugs == ["vocabulary-tool"]
75
+
76
+ def test_derive_multiple_personas(self) -> None:
77
+ """Test deriving multiple personas from stories."""
78
+ stories = [
79
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
80
+ create_story("Run Analysis", "Analyst", "analytics-app"),
81
+ create_story("Configure System", "Administrator", "admin-portal"),
82
+ ]
83
+ epics: list[Epic] = []
84
+
85
+ personas = derive_personas(stories, epics)
86
+
87
+ assert len(personas) == 3
88
+ names = [p.name for p in personas]
89
+ assert "Administrator" in names
90
+ assert "Analyst" in names
91
+ assert "Knowledge Curator" in names
92
+
93
+ def test_derive_persona_with_multiple_apps(self) -> None:
94
+ """Test persona using multiple apps."""
95
+ stories = [
96
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
97
+ create_story("Manage Users", "Knowledge Curator", "admin-portal"),
98
+ create_story("Review Data", "Knowledge Curator", "analytics-app"),
99
+ ]
100
+ epics: list[Epic] = []
101
+
102
+ personas = derive_personas(stories, epics)
103
+
104
+ assert len(personas) == 1
105
+ persona = personas[0]
106
+ assert set(persona.app_slugs) == {
107
+ "vocabulary-tool",
108
+ "admin-portal",
109
+ "analytics-app",
110
+ }
111
+
112
+ def test_derive_persona_with_epics(self) -> None:
113
+ """Test persona epic association."""
114
+ stories = [
115
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
116
+ create_story("Review Vocabulary", "Knowledge Curator", "vocabulary-tool"),
117
+ ]
118
+ epics = [
119
+ create_epic(
120
+ "vocabulary-management", ["Upload Document", "Review Vocabulary"]
121
+ ),
122
+ create_epic(
123
+ "credential-creation", ["Create Credential"]
124
+ ), # Different persona
125
+ ]
126
+
127
+ personas = derive_personas(stories, epics)
128
+
129
+ assert len(personas) == 1
130
+ persona = personas[0]
131
+ assert persona.epic_slugs == ["vocabulary-management"]
132
+
133
+ def test_derive_skips_unknown_persona(self) -> None:
134
+ """Test that 'unknown' persona is skipped."""
135
+ stories = [
136
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
137
+ create_story("Unknown Feature", "unknown", "some-app"),
138
+ ]
139
+ epics: list[Epic] = []
140
+
141
+ personas = derive_personas(stories, epics)
142
+
143
+ assert len(personas) == 1
144
+ assert personas[0].name == "Knowledge Curator"
145
+
146
+ def test_derive_empty_lists(self) -> None:
147
+ """Test with empty input lists."""
148
+ personas = derive_personas([], [])
149
+ assert personas == []
150
+
151
+ def test_derive_sorted_by_name(self) -> None:
152
+ """Test personas are sorted by name."""
153
+ stories = [
154
+ create_story("Feature Z", "Zebra User", "app-z"),
155
+ create_story("Feature A", "Alpha User", "app-a"),
156
+ create_story("Feature M", "Middle User", "app-m"),
157
+ ]
158
+ epics: list[Epic] = []
159
+
160
+ personas = derive_personas(stories, epics)
161
+
162
+ names = [p.name for p in personas]
163
+ assert names == ["Alpha User", "Middle User", "Zebra User"]
164
+
165
+
166
+ class TestDerivePersonasByAppType:
167
+ """Test derive_personas_by_app_type function."""
168
+
169
+ def test_group_by_app_type(self) -> None:
170
+ """Test grouping personas by app type."""
171
+ stories = [
172
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
173
+ create_story("View Portal", "Customer", "customer-portal"),
174
+ ]
175
+ epics: list[Epic] = []
176
+ apps = [
177
+ create_app("vocabulary-tool", "Vocabulary Tool", AppType.STAFF),
178
+ create_app("customer-portal", "Customer Portal", AppType.EXTERNAL),
179
+ ]
180
+
181
+ personas_by_type = derive_personas_by_app_type(stories, epics, apps)
182
+
183
+ assert "staff" in personas_by_type
184
+ assert "external" in personas_by_type
185
+ assert len(personas_by_type["staff"]) == 1
186
+ assert personas_by_type["staff"][0].name == "Knowledge Curator"
187
+ assert len(personas_by_type["external"]) == 1
188
+ assert personas_by_type["external"][0].name == "Customer"
189
+
190
+ def test_persona_in_multiple_types(self) -> None:
191
+ """Test persona using apps of different types."""
192
+ stories = [
193
+ create_story("Upload Document", "Power User", "staff-tool"),
194
+ create_story("View Portal", "Power User", "external-portal"),
195
+ ]
196
+ epics: list[Epic] = []
197
+ apps = [
198
+ create_app("staff-tool", "Staff Tool", AppType.STAFF),
199
+ create_app("external-portal", "External Portal", AppType.EXTERNAL),
200
+ ]
201
+
202
+ personas_by_type = derive_personas_by_app_type(stories, epics, apps)
203
+
204
+ # Power User appears in both groups
205
+ assert any(p.name == "Power User" for p in personas_by_type.get("staff", []))
206
+ assert any(p.name == "Power User" for p in personas_by_type.get("external", []))
207
+
208
+ def test_unknown_app_type(self) -> None:
209
+ """Test handling of unknown app type."""
210
+ stories = [
211
+ create_story("Upload Document", "User", "unknown-app"),
212
+ ]
213
+ epics: list[Epic] = []
214
+ apps: list[App] = [] # No app definitions
215
+
216
+ personas_by_type = derive_personas_by_app_type(stories, epics, apps)
217
+
218
+ assert "unknown" in personas_by_type
219
+ assert len(personas_by_type["unknown"]) == 1
220
+
221
+
222
+ class TestGetEpicsForPersona:
223
+ """Test get_epics_for_persona function."""
224
+
225
+ @pytest.fixture
226
+ def sample_data(self) -> tuple[list[Story], list[Epic]]:
227
+ """Create sample test data."""
228
+ stories = [
229
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
230
+ create_story("Review Vocabulary", "Knowledge Curator", "vocabulary-tool"),
231
+ create_story("Run Analysis", "Analyst", "analytics-app"),
232
+ ]
233
+ epics = [
234
+ create_epic(
235
+ "vocabulary-management", ["Upload Document", "Review Vocabulary"]
236
+ ),
237
+ create_epic("analytics", ["Run Analysis"]),
238
+ create_epic("mixed-epic", ["Upload Document", "Run Analysis"]),
239
+ ]
240
+ return stories, epics
241
+
242
+ def test_get_epics_for_persona(
243
+ self, sample_data: tuple[list[Story], list[Epic]]
244
+ ) -> None:
245
+ """Test getting epics for a persona."""
246
+ stories, epics = sample_data
247
+ all_personas = derive_personas(stories, epics)
248
+ curator = next(p for p in all_personas if p.name == "Knowledge Curator")
249
+
250
+ persona_epics = get_epics_for_persona(curator, epics, stories)
251
+
252
+ assert len(persona_epics) == 2
253
+ slugs = {e.slug for e in persona_epics}
254
+ assert slugs == {"vocabulary-management", "mixed-epic"}
255
+
256
+ def test_get_epics_sorted_by_slug(
257
+ self, sample_data: tuple[list[Story], list[Epic]]
258
+ ) -> None:
259
+ """Test epics are sorted by slug."""
260
+ stories, epics = sample_data
261
+ all_personas = derive_personas(stories, epics)
262
+ curator = next(p for p in all_personas if p.name == "Knowledge Curator")
263
+
264
+ persona_epics = get_epics_for_persona(curator, epics, stories)
265
+
266
+ slugs = [e.slug for e in persona_epics]
267
+ assert slugs == sorted(slugs)
268
+
269
+
270
+ class TestGetAppsForPersona:
271
+ """Test get_apps_for_persona function."""
272
+
273
+ def test_get_apps_for_persona(self) -> None:
274
+ """Test getting apps for a persona."""
275
+ stories = [
276
+ create_story("Upload Document", "Knowledge Curator", "vocabulary-tool"),
277
+ create_story("Admin Task", "Knowledge Curator", "admin-portal"),
278
+ ]
279
+ epics: list[Epic] = []
280
+ apps = [
281
+ create_app("vocabulary-tool", "Vocabulary Tool"),
282
+ create_app("admin-portal", "Admin Portal"),
283
+ create_app("other-app", "Other App"), # Not used by this persona
284
+ ]
285
+
286
+ all_personas = derive_personas(stories, epics)
287
+ curator = all_personas[0]
288
+
289
+ persona_apps = get_apps_for_persona(curator, apps)
290
+
291
+ assert len(persona_apps) == 2
292
+ slugs = {a.slug for a in persona_apps}
293
+ assert slugs == {"vocabulary-tool", "admin-portal"}
294
+
295
+ def test_get_apps_missing_app_definition(self) -> None:
296
+ """Test handling when app definition is missing."""
297
+ stories = [
298
+ create_story("Upload Document", "User", "defined-app"),
299
+ create_story("Other Task", "User", "undefined-app"),
300
+ ]
301
+ epics: list[Epic] = []
302
+ apps = [
303
+ create_app("defined-app", "Defined App"),
304
+ # undefined-app is not in the list
305
+ ]
306
+
307
+ all_personas = derive_personas(stories, epics)
308
+ user = all_personas[0]
309
+
310
+ persona_apps = get_apps_for_persona(user, apps)
311
+
312
+ # Only the defined app is returned
313
+ assert len(persona_apps) == 1
314
+ assert persona_apps[0].slug == "defined-app"