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,265 @@
1
+ """Tests for resolve_app_references use case."""
2
+
3
+ from julee.docs.sphinx_hcd.domain.models.app import App, AppType
4
+ from julee.docs.sphinx_hcd.domain.models.epic import Epic
5
+ from julee.docs.sphinx_hcd.domain.models.journey import Journey, JourneyStep
6
+ from julee.docs.sphinx_hcd.domain.models.story import Story
7
+ from julee.docs.sphinx_hcd.domain.use_cases.resolve_app_references import (
8
+ get_app_cross_references,
9
+ get_epics_for_app,
10
+ get_journeys_for_app,
11
+ get_personas_for_app,
12
+ get_stories_for_app,
13
+ )
14
+
15
+
16
+ def create_app(slug: str, name: str = "") -> App:
17
+ """Helper to create test apps."""
18
+ return App(
19
+ slug=slug,
20
+ name=name or slug.replace("-", " ").title(),
21
+ app_type=AppType.STAFF,
22
+ manifest_path=f"apps/{slug}/app.yaml",
23
+ )
24
+
25
+
26
+ def create_story(
27
+ feature_title: str,
28
+ app_slug: str,
29
+ persona: str = "Test User",
30
+ ) -> Story:
31
+ """Helper to create test stories."""
32
+ return Story(
33
+ slug=feature_title.lower().replace(" ", "-"),
34
+ feature_title=feature_title,
35
+ persona=persona,
36
+ i_want="test",
37
+ so_that="verify",
38
+ app_slug=app_slug,
39
+ file_path="test.feature",
40
+ )
41
+
42
+
43
+ def create_epic(slug: str, story_refs: list[str]) -> Epic:
44
+ """Helper to create test epics."""
45
+ return Epic(slug=slug, story_refs=story_refs)
46
+
47
+
48
+ def create_journey(slug: str, story_refs: list[str]) -> Journey:
49
+ """Helper to create test journeys."""
50
+ steps = [JourneyStep.story(ref) for ref in story_refs]
51
+ return Journey(slug=slug, persona="User", steps=steps)
52
+
53
+
54
+ class TestGetStoriesForApp:
55
+ """Test get_stories_for_app function."""
56
+
57
+ def test_find_stories(self) -> None:
58
+ """Test finding stories for an app."""
59
+ app = create_app("vocabulary-tool")
60
+ stories = [
61
+ create_story("Upload Document", "vocabulary-tool"),
62
+ create_story("Review Vocabulary", "vocabulary-tool"),
63
+ create_story("Other Feature", "other-app"),
64
+ ]
65
+
66
+ result = get_stories_for_app(app, stories)
67
+
68
+ assert len(result) == 2
69
+ titles = {s.feature_title for s in result}
70
+ assert titles == {"Upload Document", "Review Vocabulary"}
71
+
72
+ def test_no_stories(self) -> None:
73
+ """Test when app has no stories."""
74
+ app = create_app("empty-app")
75
+ stories = [create_story("Feature", "other-app")]
76
+
77
+ result = get_stories_for_app(app, stories)
78
+
79
+ assert result == []
80
+
81
+ def test_sorted_by_feature_title(self) -> None:
82
+ """Test results are sorted by feature title."""
83
+ app = create_app("test-app")
84
+ stories = [
85
+ create_story("Zebra Feature", "test-app"),
86
+ create_story("Alpha Feature", "test-app"),
87
+ ]
88
+
89
+ result = get_stories_for_app(app, stories)
90
+
91
+ titles = [s.feature_title for s in result]
92
+ assert titles == ["Alpha Feature", "Zebra Feature"]
93
+
94
+
95
+ class TestGetPersonasForApp:
96
+ """Test get_personas_for_app function."""
97
+
98
+ def test_find_personas(self) -> None:
99
+ """Test finding personas that use an app."""
100
+ app = create_app("vocabulary-tool")
101
+ stories = [
102
+ create_story("Upload Document", "vocabulary-tool", "Knowledge Curator"),
103
+ create_story("Review Document", "vocabulary-tool", "Reviewer"),
104
+ create_story("Other Feature", "other-app", "Other User"),
105
+ ]
106
+ epics: list[Epic] = []
107
+
108
+ result = get_personas_for_app(app, stories, epics)
109
+
110
+ assert len(result) == 2
111
+ names = {p.name for p in result}
112
+ assert names == {"Knowledge Curator", "Reviewer"}
113
+
114
+ def test_single_persona_multiple_stories(self) -> None:
115
+ """Test persona appears once even with multiple stories."""
116
+ app = create_app("vocabulary-tool")
117
+ stories = [
118
+ create_story("Upload Document", "vocabulary-tool", "Curator"),
119
+ create_story("Review Document", "vocabulary-tool", "Curator"),
120
+ ]
121
+ epics: list[Epic] = []
122
+
123
+ result = get_personas_for_app(app, stories, epics)
124
+
125
+ assert len(result) == 1
126
+ assert result[0].name == "Curator"
127
+
128
+ def test_sorted_by_name(self) -> None:
129
+ """Test results are sorted by name."""
130
+ app = create_app("test-app")
131
+ stories = [
132
+ create_story("Feature Z", "test-app", "Zebra User"),
133
+ create_story("Feature A", "test-app", "Alpha User"),
134
+ ]
135
+ epics: list[Epic] = []
136
+
137
+ result = get_personas_for_app(app, stories, epics)
138
+
139
+ names = [p.name for p in result]
140
+ assert names == ["Alpha User", "Zebra User"]
141
+
142
+
143
+ class TestGetJourneysForApp:
144
+ """Test get_journeys_for_app function."""
145
+
146
+ def test_find_journeys(self) -> None:
147
+ """Test finding journeys containing app's stories."""
148
+ app = create_app("vocabulary-tool")
149
+ stories = [
150
+ create_story("Upload Document", "vocabulary-tool"),
151
+ create_story("Other Feature", "other-app"),
152
+ ]
153
+ journeys = [
154
+ create_journey("build-vocabulary", ["Upload Document"]),
155
+ create_journey("other-journey", ["Other Feature"]),
156
+ ]
157
+
158
+ result = get_journeys_for_app(app, stories, journeys)
159
+
160
+ assert len(result) == 1
161
+ assert result[0].slug == "build-vocabulary"
162
+
163
+ def test_no_journeys(self) -> None:
164
+ """Test when app's stories are not in any journey."""
165
+ app = create_app("vocabulary-tool")
166
+ stories = [create_story("Lonely Feature", "vocabulary-tool")]
167
+ journeys = [create_journey("other-journey", ["Other Story"])]
168
+
169
+ result = get_journeys_for_app(app, stories, journeys)
170
+
171
+ assert result == []
172
+
173
+ def test_no_stories(self) -> None:
174
+ """Test when app has no stories."""
175
+ app = create_app("empty-app")
176
+ stories = [create_story("Feature", "other-app")]
177
+ journeys = [create_journey("test", ["Feature"])]
178
+
179
+ result = get_journeys_for_app(app, stories, journeys)
180
+
181
+ assert result == []
182
+
183
+
184
+ class TestGetEpicsForApp:
185
+ """Test get_epics_for_app function."""
186
+
187
+ def test_find_epics(self) -> None:
188
+ """Test finding epics containing app's stories."""
189
+ app = create_app("vocabulary-tool")
190
+ stories = [
191
+ create_story("Upload Document", "vocabulary-tool"),
192
+ create_story("Other Feature", "other-app"),
193
+ ]
194
+ epics = [
195
+ create_epic("vocabulary-management", ["Upload Document"]),
196
+ create_epic("other-epic", ["Other Feature"]),
197
+ ]
198
+
199
+ result = get_epics_for_app(app, stories, epics)
200
+
201
+ assert len(result) == 1
202
+ assert result[0].slug == "vocabulary-management"
203
+
204
+ def test_multiple_epics(self) -> None:
205
+ """Test finding multiple epics."""
206
+ app = create_app("vocabulary-tool")
207
+ stories = [
208
+ create_story("Upload Document", "vocabulary-tool"),
209
+ create_story("Review Document", "vocabulary-tool"),
210
+ ]
211
+ epics = [
212
+ create_epic("epic-1", ["Upload Document"]),
213
+ create_epic("epic-2", ["Review Document"]),
214
+ ]
215
+
216
+ result = get_epics_for_app(app, stories, epics)
217
+
218
+ assert len(result) == 2
219
+
220
+ def test_no_stories_for_app(self) -> None:
221
+ """Test when app has no stories at all."""
222
+ app = create_app("empty-app")
223
+ stories = [create_story("Feature", "other-app")]
224
+ epics = [create_epic("some-epic", ["Feature"])]
225
+
226
+ result = get_epics_for_app(app, stories, epics)
227
+
228
+ assert result == []
229
+
230
+ def test_no_epics_contain_app_stories(self) -> None:
231
+ """Test when app has stories but no epics reference them."""
232
+ app = create_app("vocabulary-tool")
233
+ stories = [create_story("Lonely Feature", "vocabulary-tool")]
234
+ epics = [create_epic("other-epic", ["Other Feature"])]
235
+
236
+ result = get_epics_for_app(app, stories, epics)
237
+
238
+ assert result == []
239
+
240
+
241
+ class TestGetAppCrossReferences:
242
+ """Test get_app_cross_references function."""
243
+
244
+ def test_cross_references(self) -> None:
245
+ """Test getting all cross-references for an app."""
246
+ app = create_app("vocabulary-tool")
247
+ stories = [
248
+ create_story("Upload Document", "vocabulary-tool", "Curator"),
249
+ create_story("Review Document", "vocabulary-tool", "Reviewer"),
250
+ ]
251
+ epics = [
252
+ create_epic(
253
+ "vocabulary-management", ["Upload Document", "Review Document"]
254
+ ),
255
+ ]
256
+ journeys = [
257
+ create_journey("build-vocabulary", ["Upload Document"]),
258
+ ]
259
+
260
+ result = get_app_cross_references(app, stories, epics, journeys)
261
+
262
+ assert len(result["stories"]) == 2
263
+ assert len(result["personas"]) == 2
264
+ assert len(result["journeys"]) == 1
265
+ assert len(result["epics"]) == 1
@@ -0,0 +1,229 @@
1
+ """Tests for resolve_story_references use case."""
2
+
3
+ from julee.docs.sphinx_hcd.domain.models.epic import Epic
4
+ from julee.docs.sphinx_hcd.domain.models.journey import Journey, JourneyStep
5
+ from julee.docs.sphinx_hcd.domain.models.story import Story
6
+ from julee.docs.sphinx_hcd.domain.use_cases.resolve_story_references import (
7
+ get_epics_for_story,
8
+ get_journeys_for_story,
9
+ get_related_stories,
10
+ get_story_cross_references,
11
+ )
12
+
13
+
14
+ def create_story(feature_title: str, app_slug: str = "test-app") -> Story:
15
+ """Helper to create test stories."""
16
+ return Story(
17
+ slug=feature_title.lower().replace(" ", "-"),
18
+ feature_title=feature_title,
19
+ persona="Test User",
20
+ i_want="test",
21
+ so_that="verify",
22
+ app_slug=app_slug,
23
+ file_path="test.feature",
24
+ )
25
+
26
+
27
+ def create_epic(slug: str, story_refs: list[str]) -> Epic:
28
+ """Helper to create test epics."""
29
+ return Epic(slug=slug, story_refs=story_refs)
30
+
31
+
32
+ def create_journey(slug: str, story_refs: list[str]) -> Journey:
33
+ """Helper to create test journeys."""
34
+ steps = [JourneyStep.story(ref) for ref in story_refs]
35
+ return Journey(slug=slug, persona="User", steps=steps)
36
+
37
+
38
+ class TestGetEpicsForStory:
39
+ """Test get_epics_for_story function."""
40
+
41
+ def test_find_single_epic(self) -> None:
42
+ """Test finding a story in one epic."""
43
+ story = create_story("Upload Document")
44
+ epics = [
45
+ create_epic(
46
+ "vocabulary-management", ["Upload Document", "Review Vocabulary"]
47
+ ),
48
+ create_epic("other-epic", ["Other Story"]),
49
+ ]
50
+
51
+ result = get_epics_for_story(story, epics)
52
+
53
+ assert len(result) == 1
54
+ assert result[0].slug == "vocabulary-management"
55
+
56
+ def test_find_multiple_epics(self) -> None:
57
+ """Test finding a story in multiple epics."""
58
+ story = create_story("Upload Document")
59
+ epics = [
60
+ create_epic("vocabulary-management", ["Upload Document"]),
61
+ create_epic("document-processing", ["Upload Document", "Process Document"]),
62
+ ]
63
+
64
+ result = get_epics_for_story(story, epics)
65
+
66
+ assert len(result) == 2
67
+ slugs = {e.slug for e in result}
68
+ assert slugs == {"vocabulary-management", "document-processing"}
69
+
70
+ def test_case_insensitive_matching(self) -> None:
71
+ """Test that matching is case-insensitive."""
72
+ story = create_story("Upload Document")
73
+ epics = [create_epic("test-epic", ["upload document"])] # lowercase
74
+
75
+ result = get_epics_for_story(story, epics)
76
+
77
+ assert len(result) == 1
78
+
79
+ def test_no_matching_epics(self) -> None:
80
+ """Test when story is not in any epic."""
81
+ story = create_story("Unknown Story")
82
+ epics = [create_epic("test-epic", ["Other Story"])]
83
+
84
+ result = get_epics_for_story(story, epics)
85
+
86
+ assert result == []
87
+
88
+ def test_sorted_by_slug(self) -> None:
89
+ """Test results are sorted by slug."""
90
+ story = create_story("Shared Story")
91
+ epics = [
92
+ create_epic("zebra-epic", ["Shared Story"]),
93
+ create_epic("alpha-epic", ["Shared Story"]),
94
+ ]
95
+
96
+ result = get_epics_for_story(story, epics)
97
+
98
+ slugs = [e.slug for e in result]
99
+ assert slugs == ["alpha-epic", "zebra-epic"]
100
+
101
+
102
+ class TestGetJourneysForStory:
103
+ """Test get_journeys_for_story function."""
104
+
105
+ def test_find_single_journey(self) -> None:
106
+ """Test finding a story in one journey."""
107
+ story = create_story("Upload Document")
108
+ journeys = [
109
+ create_journey("build-vocabulary", ["Upload Document"]),
110
+ create_journey("other-journey", ["Other Story"]),
111
+ ]
112
+
113
+ result = get_journeys_for_story(story, journeys)
114
+
115
+ assert len(result) == 1
116
+ assert result[0].slug == "build-vocabulary"
117
+
118
+ def test_find_multiple_journeys(self) -> None:
119
+ """Test finding a story in multiple journeys."""
120
+ story = create_story("Upload Document")
121
+ journeys = [
122
+ create_journey("journey-1", ["Upload Document"]),
123
+ create_journey("journey-2", ["Upload Document", "Other Story"]),
124
+ ]
125
+
126
+ result = get_journeys_for_story(story, journeys)
127
+
128
+ assert len(result) == 2
129
+
130
+ def test_no_matching_journeys(self) -> None:
131
+ """Test when story is not in any journey."""
132
+ story = create_story("Unknown Story")
133
+ journeys = [create_journey("test", ["Other Story"])]
134
+
135
+ result = get_journeys_for_story(story, journeys)
136
+
137
+ assert result == []
138
+
139
+
140
+ class TestGetRelatedStories:
141
+ """Test get_related_stories function."""
142
+
143
+ def test_find_related_stories(self) -> None:
144
+ """Test finding stories in same epic."""
145
+ stories = [
146
+ create_story("Upload Document"),
147
+ create_story("Review Vocabulary"),
148
+ create_story("Publish Catalog"),
149
+ create_story("Unrelated Story"),
150
+ ]
151
+ epics = [
152
+ create_epic(
153
+ "vocabulary-management",
154
+ ["Upload Document", "Review Vocabulary", "Publish Catalog"],
155
+ ),
156
+ ]
157
+
158
+ result = get_related_stories(stories[0], stories, epics)
159
+
160
+ assert len(result) == 2
161
+ titles = {s.feature_title for s in result}
162
+ assert titles == {"Review Vocabulary", "Publish Catalog"}
163
+
164
+ def test_excludes_original_story(self) -> None:
165
+ """Test that original story is excluded from results."""
166
+ stories = [create_story("Upload Document")]
167
+ epics = [create_epic("test-epic", ["Upload Document"])]
168
+
169
+ result = get_related_stories(stories[0], stories, epics)
170
+
171
+ assert result == []
172
+
173
+ def test_multiple_epics(self) -> None:
174
+ """Test finding related stories across multiple epics."""
175
+ stories = [
176
+ create_story("Shared Story"),
177
+ create_story("Epic1 Story"),
178
+ create_story("Epic2 Story"),
179
+ ]
180
+ epics = [
181
+ create_epic("epic-1", ["Shared Story", "Epic1 Story"]),
182
+ create_epic("epic-2", ["Shared Story", "Epic2 Story"]),
183
+ ]
184
+
185
+ result = get_related_stories(stories[0], stories, epics)
186
+
187
+ assert len(result) == 2
188
+ titles = {s.feature_title for s in result}
189
+ assert titles == {"Epic1 Story", "Epic2 Story"}
190
+
191
+ def test_sorted_by_feature_title(self) -> None:
192
+ """Test results are sorted by feature title."""
193
+ stories = [
194
+ create_story("Main Story"),
195
+ create_story("Zebra Story"),
196
+ create_story("Alpha Story"),
197
+ ]
198
+ epics = [create_epic("test", ["Main Story", "Zebra Story", "Alpha Story"])]
199
+
200
+ result = get_related_stories(stories[0], stories, epics)
201
+
202
+ titles = [s.feature_title for s in result]
203
+ assert titles == ["Alpha Story", "Zebra Story"]
204
+
205
+
206
+ class TestGetStoryCrossReferences:
207
+ """Test get_story_cross_references function."""
208
+
209
+ def test_cross_references(self) -> None:
210
+ """Test getting all cross-references for a story."""
211
+ stories = [
212
+ create_story("Upload Document"),
213
+ create_story("Review Vocabulary"),
214
+ ]
215
+ epics = [
216
+ create_epic(
217
+ "vocabulary-management", ["Upload Document", "Review Vocabulary"]
218
+ ),
219
+ ]
220
+ journeys = [
221
+ create_journey("build-vocabulary", ["Upload Document"]),
222
+ ]
223
+
224
+ result = get_story_cross_references(stories[0], stories, epics, journeys)
225
+
226
+ assert len(result["epics"]) == 1
227
+ assert len(result["journeys"]) == 1
228
+ assert len(result["related_stories"]) == 1
229
+ assert result["related_stories"][0].feature_title == "Review Vocabulary"
@@ -0,0 +1 @@
1
+ """Integration tests."""
@@ -0,0 +1 @@
1
+ """Parser tests."""