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,268 @@
|
|
|
1
|
+
"""Tests for MemoryIntegrationRepository."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.integration import (
|
|
7
|
+
Direction,
|
|
8
|
+
ExternalDependency,
|
|
9
|
+
Integration,
|
|
10
|
+
)
|
|
11
|
+
from julee.docs.sphinx_hcd.repositories.memory.integration import (
|
|
12
|
+
MemoryIntegrationRepository,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_integration(
|
|
17
|
+
slug: str = "test-integration",
|
|
18
|
+
module: str = "test_integration",
|
|
19
|
+
name: str = "Test Integration",
|
|
20
|
+
direction: Direction = Direction.BIDIRECTIONAL,
|
|
21
|
+
depends_on: list[ExternalDependency] | None = None,
|
|
22
|
+
) -> Integration:
|
|
23
|
+
"""Helper to create test integrations."""
|
|
24
|
+
return Integration(
|
|
25
|
+
slug=slug,
|
|
26
|
+
module=module,
|
|
27
|
+
name=name,
|
|
28
|
+
direction=direction,
|
|
29
|
+
depends_on=depends_on or [],
|
|
30
|
+
manifest_path=f"integrations/{module}/integration.yaml",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestMemoryIntegrationRepositoryBasicOperations:
|
|
35
|
+
"""Test basic CRUD operations."""
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def repo(self) -> MemoryIntegrationRepository:
|
|
39
|
+
"""Create a fresh repository."""
|
|
40
|
+
return MemoryIntegrationRepository()
|
|
41
|
+
|
|
42
|
+
@pytest.mark.asyncio
|
|
43
|
+
async def test_save_and_get(self, repo: MemoryIntegrationRepository) -> None:
|
|
44
|
+
"""Test saving and retrieving an integration."""
|
|
45
|
+
integration = create_integration(slug="data-sync")
|
|
46
|
+
await repo.save(integration)
|
|
47
|
+
|
|
48
|
+
retrieved = await repo.get("data-sync")
|
|
49
|
+
assert retrieved is not None
|
|
50
|
+
assert retrieved.slug == "data-sync"
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_get_nonexistent(self, repo: MemoryIntegrationRepository) -> None:
|
|
54
|
+
"""Test getting a nonexistent integration returns None."""
|
|
55
|
+
result = await repo.get("nonexistent")
|
|
56
|
+
assert result is None
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_list_all(self, repo: MemoryIntegrationRepository) -> None:
|
|
60
|
+
"""Test listing all integrations."""
|
|
61
|
+
await repo.save(create_integration(slug="int-1", module="int_1"))
|
|
62
|
+
await repo.save(create_integration(slug="int-2", module="int_2"))
|
|
63
|
+
await repo.save(create_integration(slug="int-3", module="int_3"))
|
|
64
|
+
|
|
65
|
+
all_integrations = await repo.list_all()
|
|
66
|
+
assert len(all_integrations) == 3
|
|
67
|
+
slugs = {i.slug for i in all_integrations}
|
|
68
|
+
assert slugs == {"int-1", "int-2", "int-3"}
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_delete(self, repo: MemoryIntegrationRepository) -> None:
|
|
72
|
+
"""Test deleting an integration."""
|
|
73
|
+
await repo.save(create_integration(slug="to-delete", module="to_delete"))
|
|
74
|
+
assert await repo.get("to-delete") is not None
|
|
75
|
+
|
|
76
|
+
result = await repo.delete("to-delete")
|
|
77
|
+
assert result is True
|
|
78
|
+
assert await repo.get("to-delete") is None
|
|
79
|
+
|
|
80
|
+
@pytest.mark.asyncio
|
|
81
|
+
async def test_delete_nonexistent(self, repo: MemoryIntegrationRepository) -> None:
|
|
82
|
+
"""Test deleting a nonexistent integration."""
|
|
83
|
+
result = await repo.delete("nonexistent")
|
|
84
|
+
assert result is False
|
|
85
|
+
|
|
86
|
+
@pytest.mark.asyncio
|
|
87
|
+
async def test_clear(self, repo: MemoryIntegrationRepository) -> None:
|
|
88
|
+
"""Test clearing all integrations."""
|
|
89
|
+
await repo.save(create_integration(slug="int-1", module="int_1"))
|
|
90
|
+
await repo.save(create_integration(slug="int-2", module="int_2"))
|
|
91
|
+
assert len(await repo.list_all()) == 2
|
|
92
|
+
|
|
93
|
+
await repo.clear()
|
|
94
|
+
assert len(await repo.list_all()) == 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestMemoryIntegrationRepositoryQueries:
|
|
98
|
+
"""Test integration-specific query methods."""
|
|
99
|
+
|
|
100
|
+
@pytest.fixture
|
|
101
|
+
def repo(self) -> MemoryIntegrationRepository:
|
|
102
|
+
"""Create a repository."""
|
|
103
|
+
return MemoryIntegrationRepository()
|
|
104
|
+
|
|
105
|
+
@pytest_asyncio.fixture
|
|
106
|
+
async def populated_repo(
|
|
107
|
+
self, repo: MemoryIntegrationRepository
|
|
108
|
+
) -> MemoryIntegrationRepository:
|
|
109
|
+
"""Create a repository with sample integrations."""
|
|
110
|
+
integrations = [
|
|
111
|
+
create_integration(
|
|
112
|
+
slug="pilot-data",
|
|
113
|
+
module="pilot_data",
|
|
114
|
+
name="Pilot Data Collection",
|
|
115
|
+
direction=Direction.INBOUND,
|
|
116
|
+
depends_on=[ExternalDependency(name="Pilot API")],
|
|
117
|
+
),
|
|
118
|
+
create_integration(
|
|
119
|
+
slug="analytics-export",
|
|
120
|
+
module="analytics_export",
|
|
121
|
+
name="Analytics Export",
|
|
122
|
+
direction=Direction.OUTBOUND,
|
|
123
|
+
depends_on=[
|
|
124
|
+
ExternalDependency(name="AWS S3"),
|
|
125
|
+
ExternalDependency(name="Analytics Service"),
|
|
126
|
+
],
|
|
127
|
+
),
|
|
128
|
+
create_integration(
|
|
129
|
+
slug="data-sync",
|
|
130
|
+
module="data_sync",
|
|
131
|
+
name="Data Sync",
|
|
132
|
+
direction=Direction.BIDIRECTIONAL,
|
|
133
|
+
depends_on=[ExternalDependency(name="AWS S3")],
|
|
134
|
+
),
|
|
135
|
+
create_integration(
|
|
136
|
+
slug="notifications",
|
|
137
|
+
module="notifications",
|
|
138
|
+
name="Notifications",
|
|
139
|
+
direction=Direction.OUTBOUND,
|
|
140
|
+
),
|
|
141
|
+
create_integration(
|
|
142
|
+
slug="file-import",
|
|
143
|
+
module="file_import",
|
|
144
|
+
name="File Import",
|
|
145
|
+
direction=Direction.INBOUND,
|
|
146
|
+
),
|
|
147
|
+
]
|
|
148
|
+
for integration in integrations:
|
|
149
|
+
await repo.save(integration)
|
|
150
|
+
return repo
|
|
151
|
+
|
|
152
|
+
@pytest.mark.asyncio
|
|
153
|
+
async def test_get_by_direction_inbound(
|
|
154
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Test getting inbound integrations."""
|
|
157
|
+
integrations = await populated_repo.get_by_direction(Direction.INBOUND)
|
|
158
|
+
assert len(integrations) == 2
|
|
159
|
+
assert all(i.direction == Direction.INBOUND for i in integrations)
|
|
160
|
+
|
|
161
|
+
@pytest.mark.asyncio
|
|
162
|
+
async def test_get_by_direction_outbound(
|
|
163
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Test getting outbound integrations."""
|
|
166
|
+
integrations = await populated_repo.get_by_direction(Direction.OUTBOUND)
|
|
167
|
+
assert len(integrations) == 2
|
|
168
|
+
assert all(i.direction == Direction.OUTBOUND for i in integrations)
|
|
169
|
+
|
|
170
|
+
@pytest.mark.asyncio
|
|
171
|
+
async def test_get_by_direction_bidirectional(
|
|
172
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Test getting bidirectional integrations."""
|
|
175
|
+
integrations = await populated_repo.get_by_direction(Direction.BIDIRECTIONAL)
|
|
176
|
+
assert len(integrations) == 1
|
|
177
|
+
assert integrations[0].slug == "data-sync"
|
|
178
|
+
|
|
179
|
+
@pytest.mark.asyncio
|
|
180
|
+
async def test_get_by_module(
|
|
181
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Test getting integration by module name."""
|
|
184
|
+
integration = await populated_repo.get_by_module("pilot_data")
|
|
185
|
+
assert integration is not None
|
|
186
|
+
assert integration.slug == "pilot-data"
|
|
187
|
+
|
|
188
|
+
@pytest.mark.asyncio
|
|
189
|
+
async def test_get_by_module_not_found(
|
|
190
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Test getting integration by nonexistent module."""
|
|
193
|
+
integration = await populated_repo.get_by_module("nonexistent")
|
|
194
|
+
assert integration is None
|
|
195
|
+
|
|
196
|
+
@pytest.mark.asyncio
|
|
197
|
+
async def test_get_by_name(
|
|
198
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
199
|
+
) -> None:
|
|
200
|
+
"""Test getting integration by name."""
|
|
201
|
+
integration = await populated_repo.get_by_name("Pilot Data Collection")
|
|
202
|
+
assert integration is not None
|
|
203
|
+
assert integration.slug == "pilot-data"
|
|
204
|
+
|
|
205
|
+
@pytest.mark.asyncio
|
|
206
|
+
async def test_get_by_name_case_insensitive(
|
|
207
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Test name matching is case-insensitive."""
|
|
210
|
+
integration = await populated_repo.get_by_name("pilot data collection")
|
|
211
|
+
assert integration is not None
|
|
212
|
+
assert integration.slug == "pilot-data"
|
|
213
|
+
|
|
214
|
+
@pytest.mark.asyncio
|
|
215
|
+
async def test_get_by_name_not_found(
|
|
216
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
217
|
+
) -> None:
|
|
218
|
+
"""Test getting integration by nonexistent name."""
|
|
219
|
+
integration = await populated_repo.get_by_name("Nonexistent Integration")
|
|
220
|
+
assert integration is None
|
|
221
|
+
|
|
222
|
+
@pytest.mark.asyncio
|
|
223
|
+
async def test_get_all_directions(
|
|
224
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Test getting all unique directions."""
|
|
227
|
+
directions = await populated_repo.get_all_directions()
|
|
228
|
+
assert directions == {
|
|
229
|
+
Direction.INBOUND,
|
|
230
|
+
Direction.OUTBOUND,
|
|
231
|
+
Direction.BIDIRECTIONAL,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@pytest.mark.asyncio
|
|
235
|
+
async def test_get_with_dependencies(
|
|
236
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Test getting integrations with dependencies."""
|
|
239
|
+
integrations = await populated_repo.get_with_dependencies()
|
|
240
|
+
assert len(integrations) == 3
|
|
241
|
+
slugs = {i.slug for i in integrations}
|
|
242
|
+
assert slugs == {"pilot-data", "analytics-export", "data-sync"}
|
|
243
|
+
|
|
244
|
+
@pytest.mark.asyncio
|
|
245
|
+
async def test_get_by_dependency(
|
|
246
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Test getting integrations by dependency name."""
|
|
249
|
+
integrations = await populated_repo.get_by_dependency("AWS S3")
|
|
250
|
+
assert len(integrations) == 2
|
|
251
|
+
slugs = {i.slug for i in integrations}
|
|
252
|
+
assert slugs == {"analytics-export", "data-sync"}
|
|
253
|
+
|
|
254
|
+
@pytest.mark.asyncio
|
|
255
|
+
async def test_get_by_dependency_case_insensitive(
|
|
256
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Test dependency matching is case-insensitive."""
|
|
259
|
+
integrations = await populated_repo.get_by_dependency("aws s3")
|
|
260
|
+
assert len(integrations) == 2
|
|
261
|
+
|
|
262
|
+
@pytest.mark.asyncio
|
|
263
|
+
async def test_get_by_dependency_not_found(
|
|
264
|
+
self, populated_repo: MemoryIntegrationRepository
|
|
265
|
+
) -> None:
|
|
266
|
+
"""Test getting integrations by nonexistent dependency."""
|
|
267
|
+
integrations = await populated_repo.get_by_dependency("Unknown Service")
|
|
268
|
+
assert len(integrations) == 0
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Tests for MemoryJourneyRepository."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.journey import Journey, JourneyStep
|
|
7
|
+
from julee.docs.sphinx_hcd.repositories.memory.journey import MemoryJourneyRepository
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_journey(
|
|
11
|
+
slug: str = "test-journey",
|
|
12
|
+
persona: str = "User",
|
|
13
|
+
docname: str = "journeys/test",
|
|
14
|
+
depends_on: list[str] | None = None,
|
|
15
|
+
steps: list[JourneyStep] | None = None,
|
|
16
|
+
) -> Journey:
|
|
17
|
+
"""Helper to create test journeys."""
|
|
18
|
+
return Journey(
|
|
19
|
+
slug=slug,
|
|
20
|
+
persona=persona,
|
|
21
|
+
docname=docname,
|
|
22
|
+
depends_on=depends_on or [],
|
|
23
|
+
steps=steps or [],
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestMemoryJourneyRepositoryBasicOperations:
|
|
28
|
+
"""Test basic CRUD operations."""
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def repo(self) -> MemoryJourneyRepository:
|
|
32
|
+
"""Create a fresh repository."""
|
|
33
|
+
return MemoryJourneyRepository()
|
|
34
|
+
|
|
35
|
+
@pytest.mark.asyncio
|
|
36
|
+
async def test_save_and_get(self, repo: MemoryJourneyRepository) -> None:
|
|
37
|
+
"""Test saving and retrieving a journey."""
|
|
38
|
+
journey = create_journey(slug="build-vocabulary")
|
|
39
|
+
await repo.save(journey)
|
|
40
|
+
|
|
41
|
+
retrieved = await repo.get("build-vocabulary")
|
|
42
|
+
assert retrieved is not None
|
|
43
|
+
assert retrieved.slug == "build-vocabulary"
|
|
44
|
+
|
|
45
|
+
@pytest.mark.asyncio
|
|
46
|
+
async def test_get_nonexistent(self, repo: MemoryJourneyRepository) -> None:
|
|
47
|
+
"""Test getting a nonexistent journey returns None."""
|
|
48
|
+
result = await repo.get("nonexistent")
|
|
49
|
+
assert result is None
|
|
50
|
+
|
|
51
|
+
@pytest.mark.asyncio
|
|
52
|
+
async def test_list_all(self, repo: MemoryJourneyRepository) -> None:
|
|
53
|
+
"""Test listing all journeys."""
|
|
54
|
+
await repo.save(create_journey(slug="journey-1"))
|
|
55
|
+
await repo.save(create_journey(slug="journey-2"))
|
|
56
|
+
await repo.save(create_journey(slug="journey-3"))
|
|
57
|
+
|
|
58
|
+
all_journeys = await repo.list_all()
|
|
59
|
+
assert len(all_journeys) == 3
|
|
60
|
+
slugs = {j.slug for j in all_journeys}
|
|
61
|
+
assert slugs == {"journey-1", "journey-2", "journey-3"}
|
|
62
|
+
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_delete(self, repo: MemoryJourneyRepository) -> None:
|
|
65
|
+
"""Test deleting a journey."""
|
|
66
|
+
await repo.save(create_journey(slug="to-delete"))
|
|
67
|
+
assert await repo.get("to-delete") is not None
|
|
68
|
+
|
|
69
|
+
result = await repo.delete("to-delete")
|
|
70
|
+
assert result is True
|
|
71
|
+
assert await repo.get("to-delete") is None
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_delete_nonexistent(self, repo: MemoryJourneyRepository) -> None:
|
|
75
|
+
"""Test deleting a nonexistent journey."""
|
|
76
|
+
result = await repo.delete("nonexistent")
|
|
77
|
+
assert result is False
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_clear(self, repo: MemoryJourneyRepository) -> None:
|
|
81
|
+
"""Test clearing all journeys."""
|
|
82
|
+
await repo.save(create_journey(slug="journey-1"))
|
|
83
|
+
await repo.save(create_journey(slug="journey-2"))
|
|
84
|
+
assert len(await repo.list_all()) == 2
|
|
85
|
+
|
|
86
|
+
await repo.clear()
|
|
87
|
+
assert len(await repo.list_all()) == 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestMemoryJourneyRepositoryQueries:
|
|
91
|
+
"""Test journey-specific query methods."""
|
|
92
|
+
|
|
93
|
+
@pytest.fixture
|
|
94
|
+
def repo(self) -> MemoryJourneyRepository:
|
|
95
|
+
"""Create a repository."""
|
|
96
|
+
return MemoryJourneyRepository()
|
|
97
|
+
|
|
98
|
+
@pytest_asyncio.fixture
|
|
99
|
+
async def populated_repo(
|
|
100
|
+
self, repo: MemoryJourneyRepository
|
|
101
|
+
) -> MemoryJourneyRepository:
|
|
102
|
+
"""Create a repository with sample journeys."""
|
|
103
|
+
journeys = [
|
|
104
|
+
create_journey(
|
|
105
|
+
slug="build-vocabulary",
|
|
106
|
+
persona="Knowledge Curator",
|
|
107
|
+
docname="journeys/build-vocabulary",
|
|
108
|
+
depends_on=["operate-pipelines"],
|
|
109
|
+
steps=[
|
|
110
|
+
JourneyStep.story("Upload Document"),
|
|
111
|
+
JourneyStep.epic("vocabulary-management"),
|
|
112
|
+
],
|
|
113
|
+
),
|
|
114
|
+
create_journey(
|
|
115
|
+
slug="operate-pipelines",
|
|
116
|
+
persona="Knowledge Curator",
|
|
117
|
+
docname="journeys/operate-pipelines",
|
|
118
|
+
steps=[
|
|
119
|
+
JourneyStep.story("Configure Pipeline"),
|
|
120
|
+
],
|
|
121
|
+
),
|
|
122
|
+
create_journey(
|
|
123
|
+
slug="analyze-data",
|
|
124
|
+
persona="Analyst",
|
|
125
|
+
docname="journeys/analyze-data",
|
|
126
|
+
depends_on=["build-vocabulary", "operate-pipelines"],
|
|
127
|
+
steps=[
|
|
128
|
+
JourneyStep.story("Run Analysis"),
|
|
129
|
+
JourneyStep.epic("vocabulary-management"),
|
|
130
|
+
],
|
|
131
|
+
),
|
|
132
|
+
create_journey(
|
|
133
|
+
slug="review-results",
|
|
134
|
+
persona="Analyst",
|
|
135
|
+
docname="journeys/review-results",
|
|
136
|
+
),
|
|
137
|
+
create_journey(
|
|
138
|
+
slug="admin-setup",
|
|
139
|
+
persona="Administrator",
|
|
140
|
+
docname="journeys/admin-setup",
|
|
141
|
+
),
|
|
142
|
+
]
|
|
143
|
+
for journey in journeys:
|
|
144
|
+
await repo.save(journey)
|
|
145
|
+
return repo
|
|
146
|
+
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_get_by_persona(
|
|
149
|
+
self, populated_repo: MemoryJourneyRepository
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Test getting journeys by persona."""
|
|
152
|
+
journeys = await populated_repo.get_by_persona("Knowledge Curator")
|
|
153
|
+
assert len(journeys) == 2
|
|
154
|
+
slugs = {j.slug for j in journeys}
|
|
155
|
+
assert slugs == {"build-vocabulary", "operate-pipelines"}
|
|
156
|
+
|
|
157
|
+
@pytest.mark.asyncio
|
|
158
|
+
async def test_get_by_persona_case_insensitive(
|
|
159
|
+
self, populated_repo: MemoryJourneyRepository
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Test persona matching is case-insensitive."""
|
|
162
|
+
journeys = await populated_repo.get_by_persona("knowledge curator")
|
|
163
|
+
assert len(journeys) == 2
|
|
164
|
+
|
|
165
|
+
@pytest.mark.asyncio
|
|
166
|
+
async def test_get_by_persona_no_results(
|
|
167
|
+
self, populated_repo: MemoryJourneyRepository
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Test getting journeys for persona with none."""
|
|
170
|
+
journeys = await populated_repo.get_by_persona("Unknown Persona")
|
|
171
|
+
assert len(journeys) == 0
|
|
172
|
+
|
|
173
|
+
@pytest.mark.asyncio
|
|
174
|
+
async def test_get_by_docname(
|
|
175
|
+
self, populated_repo: MemoryJourneyRepository
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Test getting journeys by document name."""
|
|
178
|
+
journeys = await populated_repo.get_by_docname("journeys/build-vocabulary")
|
|
179
|
+
assert len(journeys) == 1
|
|
180
|
+
assert journeys[0].slug == "build-vocabulary"
|
|
181
|
+
|
|
182
|
+
@pytest.mark.asyncio
|
|
183
|
+
async def test_get_by_docname_no_results(
|
|
184
|
+
self, populated_repo: MemoryJourneyRepository
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Test getting journeys for unknown document."""
|
|
187
|
+
journeys = await populated_repo.get_by_docname("unknown/document")
|
|
188
|
+
assert len(journeys) == 0
|
|
189
|
+
|
|
190
|
+
@pytest.mark.asyncio
|
|
191
|
+
async def test_clear_by_docname(
|
|
192
|
+
self, populated_repo: MemoryJourneyRepository
|
|
193
|
+
) -> None:
|
|
194
|
+
"""Test clearing journeys by document name."""
|
|
195
|
+
count = await populated_repo.clear_by_docname("journeys/build-vocabulary")
|
|
196
|
+
assert count == 1
|
|
197
|
+
assert await populated_repo.get("build-vocabulary") is None
|
|
198
|
+
# Other journeys should remain
|
|
199
|
+
assert len(await populated_repo.list_all()) == 4
|
|
200
|
+
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_clear_by_docname_none_found(
|
|
203
|
+
self, populated_repo: MemoryJourneyRepository
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Test clearing non-existent document returns 0."""
|
|
206
|
+
count = await populated_repo.clear_by_docname("unknown/document")
|
|
207
|
+
assert count == 0
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_get_dependents(
|
|
211
|
+
self, populated_repo: MemoryJourneyRepository
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Test getting journeys that depend on a journey."""
|
|
214
|
+
dependents = await populated_repo.get_dependents("operate-pipelines")
|
|
215
|
+
assert len(dependents) == 2
|
|
216
|
+
slugs = {j.slug for j in dependents}
|
|
217
|
+
assert slugs == {"build-vocabulary", "analyze-data"}
|
|
218
|
+
|
|
219
|
+
@pytest.mark.asyncio
|
|
220
|
+
async def test_get_dependents_none(
|
|
221
|
+
self, populated_repo: MemoryJourneyRepository
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Test getting dependents for journey with none."""
|
|
224
|
+
dependents = await populated_repo.get_dependents("admin-setup")
|
|
225
|
+
assert len(dependents) == 0
|
|
226
|
+
|
|
227
|
+
@pytest.mark.asyncio
|
|
228
|
+
async def test_get_dependencies(
|
|
229
|
+
self, populated_repo: MemoryJourneyRepository
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Test getting journeys that a journey depends on."""
|
|
232
|
+
deps = await populated_repo.get_dependencies("analyze-data")
|
|
233
|
+
assert len(deps) == 2
|
|
234
|
+
slugs = {j.slug for j in deps}
|
|
235
|
+
assert slugs == {"build-vocabulary", "operate-pipelines"}
|
|
236
|
+
|
|
237
|
+
@pytest.mark.asyncio
|
|
238
|
+
async def test_get_dependencies_nonexistent_journey(
|
|
239
|
+
self, populated_repo: MemoryJourneyRepository
|
|
240
|
+
) -> None:
|
|
241
|
+
"""Test getting dependencies for nonexistent journey."""
|
|
242
|
+
deps = await populated_repo.get_dependencies("nonexistent")
|
|
243
|
+
assert len(deps) == 0
|
|
244
|
+
|
|
245
|
+
@pytest.mark.asyncio
|
|
246
|
+
async def test_get_all_personas(
|
|
247
|
+
self, populated_repo: MemoryJourneyRepository
|
|
248
|
+
) -> None:
|
|
249
|
+
"""Test getting all unique personas."""
|
|
250
|
+
personas = await populated_repo.get_all_personas()
|
|
251
|
+
assert personas == {"knowledge curator", "analyst", "administrator"}
|
|
252
|
+
|
|
253
|
+
@pytest.mark.asyncio
|
|
254
|
+
async def test_get_with_story_ref(
|
|
255
|
+
self, populated_repo: MemoryJourneyRepository
|
|
256
|
+
) -> None:
|
|
257
|
+
"""Test getting journeys with a story reference."""
|
|
258
|
+
journeys = await populated_repo.get_with_story_ref("Upload Document")
|
|
259
|
+
assert len(journeys) == 1
|
|
260
|
+
assert journeys[0].slug == "build-vocabulary"
|
|
261
|
+
|
|
262
|
+
@pytest.mark.asyncio
|
|
263
|
+
async def test_get_with_story_ref_case_insensitive(
|
|
264
|
+
self, populated_repo: MemoryJourneyRepository
|
|
265
|
+
) -> None:
|
|
266
|
+
"""Test story ref matching is case-insensitive."""
|
|
267
|
+
journeys = await populated_repo.get_with_story_ref("upload document")
|
|
268
|
+
assert len(journeys) == 1
|
|
269
|
+
|
|
270
|
+
@pytest.mark.asyncio
|
|
271
|
+
async def test_get_with_story_ref_none(
|
|
272
|
+
self, populated_repo: MemoryJourneyRepository
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Test getting journeys with nonexistent story."""
|
|
275
|
+
journeys = await populated_repo.get_with_story_ref("Unknown Story")
|
|
276
|
+
assert len(journeys) == 0
|
|
277
|
+
|
|
278
|
+
@pytest.mark.asyncio
|
|
279
|
+
async def test_get_with_epic_ref(
|
|
280
|
+
self, populated_repo: MemoryJourneyRepository
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Test getting journeys with an epic reference."""
|
|
283
|
+
journeys = await populated_repo.get_with_epic_ref("vocabulary-management")
|
|
284
|
+
assert len(journeys) == 2
|
|
285
|
+
slugs = {j.slug for j in journeys}
|
|
286
|
+
assert slugs == {"build-vocabulary", "analyze-data"}
|
|
287
|
+
|
|
288
|
+
@pytest.mark.asyncio
|
|
289
|
+
async def test_get_with_epic_ref_none(
|
|
290
|
+
self, populated_repo: MemoryJourneyRepository
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Test getting journeys with nonexistent epic."""
|
|
293
|
+
journeys = await populated_repo.get_with_epic_ref("unknown-epic")
|
|
294
|
+
assert len(journeys) == 0
|