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.
- julee/__init__.py +1 -1
- julee/api/tests/routers/test_assembly_specifications.py +2 -0
- julee/api/tests/routers/test_documents.py +2 -0
- julee/api/tests/routers/test_knowledge_service_configs.py +2 -0
- julee/api/tests/routers/test_knowledge_service_queries.py +2 -0
- julee/api/tests/routers/test_system.py +2 -0
- julee/api/tests/routers/test_workflows.py +2 -0
- julee/api/tests/test_app.py +2 -0
- julee/api/tests/test_dependencies.py +2 -0
- julee/api/tests/test_requests.py +2 -0
- julee/contrib/polling/__init__.py +22 -19
- julee/contrib/polling/apps/__init__.py +17 -0
- julee/contrib/polling/apps/worker/__init__.py +17 -0
- julee/contrib/polling/apps/worker/pipelines.py +288 -0
- julee/contrib/polling/domain/__init__.py +7 -9
- julee/contrib/polling/domain/models/__init__.py +6 -7
- julee/contrib/polling/domain/models/polling_config.py +18 -1
- julee/contrib/polling/domain/services/__init__.py +6 -5
- julee/contrib/polling/domain/services/poller.py +1 -1
- julee/contrib/polling/infrastructure/__init__.py +9 -8
- julee/contrib/polling/infrastructure/services/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +6 -5
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +5 -2
- julee/contrib/polling/infrastructure/temporal/__init__.py +12 -12
- julee/contrib/polling/infrastructure/temporal/activities.py +1 -1
- julee/contrib/polling/infrastructure/temporal/manager.py +291 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +1 -1
- julee/contrib/polling/tests/unit/apps/worker/test_pipelines.py +580 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +40 -2
- julee/contrib/polling/tests/unit/infrastructure/temporal/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/temporal/test_manager.py +475 -0
- 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/domain/models/assembly/tests/test_assembly.py +2 -0
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +2 -0
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +2 -0
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -0
- julee/domain/models/document/tests/test_document.py +2 -0
- julee/domain/models/policy/tests/test_document_policy_validation.py +2 -0
- julee/domain/models/policy/tests/test_policy.py +2 -0
- julee/domain/use_cases/tests/test_extract_assemble_data.py +2 -0
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -0
- julee/domain/use_cases/tests/test_validate_document.py +2 -0
- julee/maintenance/release.py +10 -5
- julee/repositories/memory/tests/test_document.py +2 -0
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -0
- julee/repositories/memory/tests/test_policy.py +2 -0
- julee/repositories/minio/tests/test_assembly.py +2 -0
- julee/repositories/minio/tests/test_assembly_specification.py +2 -0
- julee/repositories/minio/tests/test_client_protocol.py +3 -0
- julee/repositories/minio/tests/test_document.py +2 -0
- julee/repositories/minio/tests/test_document_policy_validation.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_config.py +2 -0
- julee/repositories/minio/tests/test_knowledge_service_query.py +2 -0
- julee/repositories/minio/tests/test_policy.py +2 -0
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/memory/test_knowledge_service.py +2 -0
- julee/services/knowledge_service/test_factory.py +2 -0
- julee/util/tests/test_decorators.py +2 -0
- julee-0.1.6.dist-info/METADATA +104 -0
- julee-0.1.6.dist-info/RECORD +288 -0
- 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.4.dist-info/METADATA +0 -197
- julee-0.1.4.dist-info/RECORD +0 -196
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/WHEEL +0 -0
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.4.dist-info → julee-0.1.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"""Tests for Integration domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.integration import (
|
|
7
|
+
Direction,
|
|
8
|
+
ExternalDependency,
|
|
9
|
+
Integration,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestDirection:
|
|
14
|
+
"""Test Direction enum."""
|
|
15
|
+
|
|
16
|
+
def test_direction_values(self) -> None:
|
|
17
|
+
"""Test Direction enum values."""
|
|
18
|
+
assert Direction.INBOUND.value == "inbound"
|
|
19
|
+
assert Direction.OUTBOUND.value == "outbound"
|
|
20
|
+
assert Direction.BIDIRECTIONAL.value == "bidirectional"
|
|
21
|
+
|
|
22
|
+
def test_from_string_valid(self) -> None:
|
|
23
|
+
"""Test from_string with valid values."""
|
|
24
|
+
assert Direction.from_string("inbound") == Direction.INBOUND
|
|
25
|
+
assert Direction.from_string("outbound") == Direction.OUTBOUND
|
|
26
|
+
assert Direction.from_string("bidirectional") == Direction.BIDIRECTIONAL
|
|
27
|
+
|
|
28
|
+
def test_from_string_case_insensitive(self) -> None:
|
|
29
|
+
"""Test from_string is case-insensitive."""
|
|
30
|
+
assert Direction.from_string("INBOUND") == Direction.INBOUND
|
|
31
|
+
assert Direction.from_string("Outbound") == Direction.OUTBOUND
|
|
32
|
+
|
|
33
|
+
def test_from_string_unknown(self) -> None:
|
|
34
|
+
"""Test from_string defaults to BIDIRECTIONAL for invalid values."""
|
|
35
|
+
assert Direction.from_string("invalid") == Direction.BIDIRECTIONAL
|
|
36
|
+
assert Direction.from_string("") == Direction.BIDIRECTIONAL
|
|
37
|
+
|
|
38
|
+
def test_direction_labels(self) -> None:
|
|
39
|
+
"""Test direction label property."""
|
|
40
|
+
assert Direction.INBOUND.label == "Inbound (data source)"
|
|
41
|
+
assert Direction.OUTBOUND.label == "Outbound (data sink)"
|
|
42
|
+
assert Direction.BIDIRECTIONAL.label == "Bidirectional"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestExternalDependency:
|
|
46
|
+
"""Test ExternalDependency model."""
|
|
47
|
+
|
|
48
|
+
def test_create_with_name_only(self) -> None:
|
|
49
|
+
"""Test creating with just name."""
|
|
50
|
+
dep = ExternalDependency(name="External API")
|
|
51
|
+
assert dep.name == "External API"
|
|
52
|
+
assert dep.url is None
|
|
53
|
+
assert dep.description == ""
|
|
54
|
+
|
|
55
|
+
def test_create_with_all_fields(self) -> None:
|
|
56
|
+
"""Test creating with all fields."""
|
|
57
|
+
dep = ExternalDependency(
|
|
58
|
+
name="External API",
|
|
59
|
+
url="https://api.example.com",
|
|
60
|
+
description="Third party API",
|
|
61
|
+
)
|
|
62
|
+
assert dep.name == "External API"
|
|
63
|
+
assert dep.url == "https://api.example.com"
|
|
64
|
+
assert dep.description == "Third party API"
|
|
65
|
+
|
|
66
|
+
def test_empty_name_raises_error(self) -> None:
|
|
67
|
+
"""Test that empty name raises validation error."""
|
|
68
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
69
|
+
ExternalDependency(name="")
|
|
70
|
+
|
|
71
|
+
def test_from_dict_complete(self) -> None:
|
|
72
|
+
"""Test from_dict with complete data."""
|
|
73
|
+
data = {
|
|
74
|
+
"name": "External API",
|
|
75
|
+
"url": "https://api.example.com",
|
|
76
|
+
"description": "Third party API",
|
|
77
|
+
}
|
|
78
|
+
dep = ExternalDependency.from_dict(data)
|
|
79
|
+
assert dep.name == "External API"
|
|
80
|
+
assert dep.url == "https://api.example.com"
|
|
81
|
+
|
|
82
|
+
def test_from_dict_minimal(self) -> None:
|
|
83
|
+
"""Test from_dict with minimal data."""
|
|
84
|
+
data = {"name": "Simple API"}
|
|
85
|
+
dep = ExternalDependency.from_dict(data)
|
|
86
|
+
assert dep.name == "Simple API"
|
|
87
|
+
assert dep.url is None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestIntegrationCreation:
|
|
91
|
+
"""Test Integration model creation and validation."""
|
|
92
|
+
|
|
93
|
+
def test_create_with_required_fields(self) -> None:
|
|
94
|
+
"""Test creating with minimum required fields."""
|
|
95
|
+
integration = Integration(
|
|
96
|
+
slug="data-sync",
|
|
97
|
+
module="data_sync",
|
|
98
|
+
name="Data Sync",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert integration.slug == "data-sync"
|
|
102
|
+
assert integration.module == "data_sync"
|
|
103
|
+
assert integration.name == "Data Sync"
|
|
104
|
+
assert integration.direction == Direction.BIDIRECTIONAL
|
|
105
|
+
assert integration.depends_on == []
|
|
106
|
+
|
|
107
|
+
def test_create_with_all_fields(self) -> None:
|
|
108
|
+
"""Test creating with all fields."""
|
|
109
|
+
deps = [ExternalDependency(name="AWS S3", url="https://aws.amazon.com/s3")]
|
|
110
|
+
integration = Integration(
|
|
111
|
+
slug="data-sync",
|
|
112
|
+
module="data_sync",
|
|
113
|
+
name="Data Sync",
|
|
114
|
+
description="Synchronizes data with external systems",
|
|
115
|
+
direction=Direction.OUTBOUND,
|
|
116
|
+
depends_on=deps,
|
|
117
|
+
manifest_path="/path/to/integration.yaml",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
assert integration.slug == "data-sync"
|
|
121
|
+
assert integration.direction == Direction.OUTBOUND
|
|
122
|
+
assert len(integration.depends_on) == 1
|
|
123
|
+
assert integration.depends_on[0].name == "AWS S3"
|
|
124
|
+
|
|
125
|
+
def test_name_normalized_computed(self) -> None:
|
|
126
|
+
"""Test that name_normalized is computed."""
|
|
127
|
+
integration = Integration(
|
|
128
|
+
slug="data-sync",
|
|
129
|
+
module="data_sync",
|
|
130
|
+
name="Data Sync Service",
|
|
131
|
+
)
|
|
132
|
+
assert integration.name_normalized == "data sync service"
|
|
133
|
+
|
|
134
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
135
|
+
"""Test that empty slug raises validation error."""
|
|
136
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
137
|
+
Integration(slug="", module="test", name="Test")
|
|
138
|
+
|
|
139
|
+
def test_empty_module_raises_error(self) -> None:
|
|
140
|
+
"""Test that empty module raises validation error."""
|
|
141
|
+
with pytest.raises(ValidationError, match="module cannot be empty"):
|
|
142
|
+
Integration(slug="test", module="", name="Test")
|
|
143
|
+
|
|
144
|
+
def test_empty_name_raises_error(self) -> None:
|
|
145
|
+
"""Test that empty name raises validation error."""
|
|
146
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
147
|
+
Integration(slug="test", module="test", name="")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestIntegrationFromManifest:
|
|
151
|
+
"""Test Integration.from_manifest factory method."""
|
|
152
|
+
|
|
153
|
+
def test_from_manifest_complete(self) -> None:
|
|
154
|
+
"""Test creating from complete manifest."""
|
|
155
|
+
manifest = {
|
|
156
|
+
"slug": "pilot-data",
|
|
157
|
+
"name": "Pilot Data Collection",
|
|
158
|
+
"description": "Collects pilot data",
|
|
159
|
+
"direction": "inbound",
|
|
160
|
+
"depends_on": [
|
|
161
|
+
{"name": "Pilot API", "url": "https://pilot.example.com"},
|
|
162
|
+
{"name": "Data Lake"},
|
|
163
|
+
],
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
integration = Integration.from_manifest(
|
|
167
|
+
module_name="pilot_data_collection",
|
|
168
|
+
manifest=manifest,
|
|
169
|
+
manifest_path="/integrations/pilot_data_collection/integration.yaml",
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
assert integration.slug == "pilot-data"
|
|
173
|
+
assert integration.module == "pilot_data_collection"
|
|
174
|
+
assert integration.name == "Pilot Data Collection"
|
|
175
|
+
assert integration.direction == Direction.INBOUND
|
|
176
|
+
assert len(integration.depends_on) == 2
|
|
177
|
+
assert integration.depends_on[0].name == "Pilot API"
|
|
178
|
+
assert integration.depends_on[0].url == "https://pilot.example.com"
|
|
179
|
+
|
|
180
|
+
def test_from_manifest_default_slug(self) -> None:
|
|
181
|
+
"""Test default slug from module name."""
|
|
182
|
+
manifest = {"name": "Test Integration"}
|
|
183
|
+
|
|
184
|
+
integration = Integration.from_manifest(
|
|
185
|
+
module_name="my_integration",
|
|
186
|
+
manifest=manifest,
|
|
187
|
+
manifest_path="/path/to/integration.yaml",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
assert integration.slug == "my-integration"
|
|
191
|
+
|
|
192
|
+
def test_from_manifest_default_name(self) -> None:
|
|
193
|
+
"""Test default name from slug."""
|
|
194
|
+
manifest = {}
|
|
195
|
+
|
|
196
|
+
integration = Integration.from_manifest(
|
|
197
|
+
module_name="data_sync",
|
|
198
|
+
manifest=manifest,
|
|
199
|
+
manifest_path="/path/to/integration.yaml",
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
assert integration.name == "Data Sync"
|
|
203
|
+
|
|
204
|
+
def test_from_manifest_default_direction(self) -> None:
|
|
205
|
+
"""Test default direction is bidirectional."""
|
|
206
|
+
manifest = {"name": "Test"}
|
|
207
|
+
|
|
208
|
+
integration = Integration.from_manifest(
|
|
209
|
+
module_name="test",
|
|
210
|
+
manifest=manifest,
|
|
211
|
+
manifest_path="/path/to/integration.yaml",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
assert integration.direction == Direction.BIDIRECTIONAL
|
|
215
|
+
|
|
216
|
+
def test_from_manifest_string_dependency(self) -> None:
|
|
217
|
+
"""Test parsing simple string dependencies."""
|
|
218
|
+
manifest = {
|
|
219
|
+
"name": "Test",
|
|
220
|
+
"depends_on": ["Simple Dep"],
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
integration = Integration.from_manifest(
|
|
224
|
+
module_name="test",
|
|
225
|
+
manifest=manifest,
|
|
226
|
+
manifest_path="/path/to/integration.yaml",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
assert len(integration.depends_on) == 1
|
|
230
|
+
assert integration.depends_on[0].name == "Simple Dep"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class TestIntegrationMatching:
|
|
234
|
+
"""Test Integration matching methods."""
|
|
235
|
+
|
|
236
|
+
@pytest.fixture
|
|
237
|
+
def sample_integration(self) -> Integration:
|
|
238
|
+
"""Create a sample integration for testing."""
|
|
239
|
+
return Integration(
|
|
240
|
+
slug="data-sync",
|
|
241
|
+
module="data_sync",
|
|
242
|
+
name="Data Sync Service",
|
|
243
|
+
direction=Direction.OUTBOUND,
|
|
244
|
+
depends_on=[
|
|
245
|
+
ExternalDependency(name="AWS S3"),
|
|
246
|
+
ExternalDependency(name="External API"),
|
|
247
|
+
],
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def test_matches_direction_with_enum(self, sample_integration: Integration) -> None:
|
|
251
|
+
"""Test direction matching with enum."""
|
|
252
|
+
assert sample_integration.matches_direction(Direction.OUTBOUND) is True
|
|
253
|
+
assert sample_integration.matches_direction(Direction.INBOUND) is False
|
|
254
|
+
|
|
255
|
+
def test_matches_direction_with_string(
|
|
256
|
+
self, sample_integration: Integration
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Test direction matching with string."""
|
|
259
|
+
assert sample_integration.matches_direction("outbound") is True
|
|
260
|
+
assert sample_integration.matches_direction("inbound") is False
|
|
261
|
+
|
|
262
|
+
def test_matches_name_exact(self, sample_integration: Integration) -> None:
|
|
263
|
+
"""Test name matching with exact name."""
|
|
264
|
+
assert sample_integration.matches_name("Data Sync Service") is True
|
|
265
|
+
|
|
266
|
+
def test_matches_name_case_insensitive(
|
|
267
|
+
self, sample_integration: Integration
|
|
268
|
+
) -> None:
|
|
269
|
+
"""Test name matching is case-insensitive."""
|
|
270
|
+
assert sample_integration.matches_name("data sync service") is True
|
|
271
|
+
|
|
272
|
+
def test_has_dependency(self, sample_integration: Integration) -> None:
|
|
273
|
+
"""Test checking for dependency."""
|
|
274
|
+
assert sample_integration.has_dependency("AWS S3") is True
|
|
275
|
+
assert sample_integration.has_dependency("aws s3") is True
|
|
276
|
+
assert sample_integration.has_dependency("Unknown") is False
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class TestIntegrationProperties:
|
|
280
|
+
"""Test Integration properties."""
|
|
281
|
+
|
|
282
|
+
def test_direction_label(self) -> None:
|
|
283
|
+
"""Test direction_label property."""
|
|
284
|
+
integration = Integration(
|
|
285
|
+
slug="test",
|
|
286
|
+
module="test",
|
|
287
|
+
name="Test",
|
|
288
|
+
direction=Direction.INBOUND,
|
|
289
|
+
)
|
|
290
|
+
assert integration.direction_label == "Inbound (data source)"
|
|
291
|
+
|
|
292
|
+
def test_module_path(self) -> None:
|
|
293
|
+
"""Test module_path property."""
|
|
294
|
+
integration = Integration(
|
|
295
|
+
slug="test",
|
|
296
|
+
module="my_module",
|
|
297
|
+
name="Test",
|
|
298
|
+
)
|
|
299
|
+
assert integration.module_path == "integrations.my_module"
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class TestIntegrationSerialization:
|
|
303
|
+
"""Test Integration serialization."""
|
|
304
|
+
|
|
305
|
+
def test_integration_to_dict(self) -> None:
|
|
306
|
+
"""Test integration can be serialized to dict."""
|
|
307
|
+
integration = Integration(
|
|
308
|
+
slug="test",
|
|
309
|
+
module="test",
|
|
310
|
+
name="Test",
|
|
311
|
+
direction=Direction.INBOUND,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
data = integration.model_dump()
|
|
315
|
+
assert data["slug"] == "test"
|
|
316
|
+
assert data["direction"] == Direction.INBOUND
|
|
317
|
+
|
|
318
|
+
def test_integration_to_json(self) -> None:
|
|
319
|
+
"""Test integration can be serialized to JSON."""
|
|
320
|
+
integration = Integration(
|
|
321
|
+
slug="test",
|
|
322
|
+
module="test",
|
|
323
|
+
name="Test",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
json_str = integration.model_dump_json()
|
|
327
|
+
assert '"slug":"test"' in json_str
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Tests for Journey domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.journey import (
|
|
7
|
+
Journey,
|
|
8
|
+
JourneyStep,
|
|
9
|
+
StepType,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestStepType:
|
|
14
|
+
"""Test StepType enum."""
|
|
15
|
+
|
|
16
|
+
def test_step_type_values(self) -> None:
|
|
17
|
+
"""Test StepType enum values."""
|
|
18
|
+
assert StepType.STORY.value == "story"
|
|
19
|
+
assert StepType.EPIC.value == "epic"
|
|
20
|
+
assert StepType.PHASE.value == "phase"
|
|
21
|
+
|
|
22
|
+
def test_from_string_valid(self) -> None:
|
|
23
|
+
"""Test from_string with valid values."""
|
|
24
|
+
assert StepType.from_string("story") == StepType.STORY
|
|
25
|
+
assert StepType.from_string("epic") == StepType.EPIC
|
|
26
|
+
assert StepType.from_string("phase") == StepType.PHASE
|
|
27
|
+
|
|
28
|
+
def test_from_string_case_insensitive(self) -> None:
|
|
29
|
+
"""Test from_string is case-insensitive."""
|
|
30
|
+
assert StepType.from_string("STORY") == StepType.STORY
|
|
31
|
+
assert StepType.from_string("Epic") == StepType.EPIC
|
|
32
|
+
|
|
33
|
+
def test_from_string_invalid(self) -> None:
|
|
34
|
+
"""Test from_string raises for invalid values."""
|
|
35
|
+
with pytest.raises(ValueError, match="Invalid step type"):
|
|
36
|
+
StepType.from_string("invalid")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TestJourneyStep:
|
|
40
|
+
"""Test JourneyStep model."""
|
|
41
|
+
|
|
42
|
+
def test_create_story_step(self) -> None:
|
|
43
|
+
"""Test creating a story step."""
|
|
44
|
+
step = JourneyStep(step_type=StepType.STORY, ref="Upload Document")
|
|
45
|
+
assert step.step_type == StepType.STORY
|
|
46
|
+
assert step.ref == "Upload Document"
|
|
47
|
+
assert step.is_story is True
|
|
48
|
+
assert step.is_epic is False
|
|
49
|
+
assert step.is_phase is False
|
|
50
|
+
|
|
51
|
+
def test_create_epic_step(self) -> None:
|
|
52
|
+
"""Test creating an epic step."""
|
|
53
|
+
step = JourneyStep(step_type=StepType.EPIC, ref="vocabulary-management")
|
|
54
|
+
assert step.step_type == StepType.EPIC
|
|
55
|
+
assert step.ref == "vocabulary-management"
|
|
56
|
+
assert step.is_epic is True
|
|
57
|
+
|
|
58
|
+
def test_create_phase_step(self) -> None:
|
|
59
|
+
"""Test creating a phase step with description."""
|
|
60
|
+
step = JourneyStep(
|
|
61
|
+
step_type=StepType.PHASE,
|
|
62
|
+
ref="Upload Sources",
|
|
63
|
+
description="Add reference materials to the knowledge base.",
|
|
64
|
+
)
|
|
65
|
+
assert step.step_type == StepType.PHASE
|
|
66
|
+
assert step.ref == "Upload Sources"
|
|
67
|
+
assert step.description == "Add reference materials to the knowledge base."
|
|
68
|
+
assert step.is_phase is True
|
|
69
|
+
|
|
70
|
+
def test_empty_ref_raises_error(self) -> None:
|
|
71
|
+
"""Test that empty ref raises validation error."""
|
|
72
|
+
with pytest.raises(ValidationError, match="ref cannot be empty"):
|
|
73
|
+
JourneyStep(step_type=StepType.STORY, ref="")
|
|
74
|
+
|
|
75
|
+
def test_story_factory(self) -> None:
|
|
76
|
+
"""Test story factory method."""
|
|
77
|
+
step = JourneyStep.story("Upload Document")
|
|
78
|
+
assert step.step_type == StepType.STORY
|
|
79
|
+
assert step.ref == "Upload Document"
|
|
80
|
+
|
|
81
|
+
def test_epic_factory(self) -> None:
|
|
82
|
+
"""Test epic factory method."""
|
|
83
|
+
step = JourneyStep.epic("vocabulary-management")
|
|
84
|
+
assert step.step_type == StepType.EPIC
|
|
85
|
+
assert step.ref == "vocabulary-management"
|
|
86
|
+
|
|
87
|
+
def test_phase_factory(self) -> None:
|
|
88
|
+
"""Test phase factory method."""
|
|
89
|
+
step = JourneyStep.phase("Upload Sources", "Add materials.")
|
|
90
|
+
assert step.step_type == StepType.PHASE
|
|
91
|
+
assert step.ref == "Upload Sources"
|
|
92
|
+
assert step.description == "Add materials."
|
|
93
|
+
|
|
94
|
+
def test_phase_factory_without_description(self) -> None:
|
|
95
|
+
"""Test phase factory without description."""
|
|
96
|
+
step = JourneyStep.phase("Upload Sources")
|
|
97
|
+
assert step.description == ""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class TestJourneyCreation:
|
|
101
|
+
"""Test Journey model creation and validation."""
|
|
102
|
+
|
|
103
|
+
def test_create_journey_minimal(self) -> None:
|
|
104
|
+
"""Test creating a journey with minimum fields."""
|
|
105
|
+
journey = Journey(slug="build-vocabulary")
|
|
106
|
+
assert journey.slug == "build-vocabulary"
|
|
107
|
+
assert journey.persona == ""
|
|
108
|
+
assert journey.steps == []
|
|
109
|
+
assert journey.depends_on == []
|
|
110
|
+
|
|
111
|
+
def test_create_journey_complete(self) -> None:
|
|
112
|
+
"""Test creating a journey with all fields."""
|
|
113
|
+
steps = [
|
|
114
|
+
JourneyStep.story("Upload Document"),
|
|
115
|
+
JourneyStep.epic("vocabulary-management"),
|
|
116
|
+
]
|
|
117
|
+
journey = Journey(
|
|
118
|
+
slug="build-vocabulary",
|
|
119
|
+
persona="Knowledge Curator",
|
|
120
|
+
intent="Ensure consistent terminology across programs",
|
|
121
|
+
outcome="Semantic interoperability enabling compliance mapping",
|
|
122
|
+
goal="Create a Sustainable Vocabulary Catalog",
|
|
123
|
+
depends_on=["operate-pipelines", "setup-system"],
|
|
124
|
+
steps=steps,
|
|
125
|
+
preconditions=["Source materials available", "SME accessible"],
|
|
126
|
+
postconditions=["SVC published and versioned"],
|
|
127
|
+
docname="journeys/build-vocabulary",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
assert journey.slug == "build-vocabulary"
|
|
131
|
+
assert journey.persona == "Knowledge Curator"
|
|
132
|
+
assert journey.persona_normalized == "knowledge curator"
|
|
133
|
+
assert journey.intent == "Ensure consistent terminology across programs"
|
|
134
|
+
assert len(journey.steps) == 2
|
|
135
|
+
assert len(journey.depends_on) == 2
|
|
136
|
+
assert journey.docname == "journeys/build-vocabulary"
|
|
137
|
+
|
|
138
|
+
def test_persona_normalized_computed(self) -> None:
|
|
139
|
+
"""Test that persona_normalized is computed."""
|
|
140
|
+
journey = Journey(slug="test", persona="Knowledge Curator")
|
|
141
|
+
assert journey.persona_normalized == "knowledge curator"
|
|
142
|
+
|
|
143
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
144
|
+
"""Test that empty slug raises validation error."""
|
|
145
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
146
|
+
Journey(slug="")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestJourneyMatching:
|
|
150
|
+
"""Test Journey matching methods."""
|
|
151
|
+
|
|
152
|
+
@pytest.fixture
|
|
153
|
+
def sample_journey(self) -> Journey:
|
|
154
|
+
"""Create a sample journey for testing."""
|
|
155
|
+
return Journey(
|
|
156
|
+
slug="build-vocabulary",
|
|
157
|
+
persona="Knowledge Curator",
|
|
158
|
+
depends_on=["operate-pipelines", "setup-system"],
|
|
159
|
+
steps=[
|
|
160
|
+
JourneyStep.story("Upload Document"),
|
|
161
|
+
JourneyStep.epic("vocabulary-management"),
|
|
162
|
+
JourneyStep.story("Review Vocabulary"),
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def test_matches_persona_exact(self, sample_journey: Journey) -> None:
|
|
167
|
+
"""Test persona matching with exact name."""
|
|
168
|
+
assert sample_journey.matches_persona("Knowledge Curator") is True
|
|
169
|
+
|
|
170
|
+
def test_matches_persona_case_insensitive(self, sample_journey: Journey) -> None:
|
|
171
|
+
"""Test persona matching is case-insensitive."""
|
|
172
|
+
assert sample_journey.matches_persona("knowledge curator") is True
|
|
173
|
+
assert sample_journey.matches_persona("KNOWLEDGE CURATOR") is True
|
|
174
|
+
|
|
175
|
+
def test_matches_persona_no_match(self, sample_journey: Journey) -> None:
|
|
176
|
+
"""Test persona matching returns False for non-match."""
|
|
177
|
+
assert sample_journey.matches_persona("Analyst") is False
|
|
178
|
+
|
|
179
|
+
def test_has_dependency(self, sample_journey: Journey) -> None:
|
|
180
|
+
"""Test dependency checking."""
|
|
181
|
+
assert sample_journey.has_dependency("operate-pipelines") is True
|
|
182
|
+
assert sample_journey.has_dependency("setup-system") is True
|
|
183
|
+
assert sample_journey.has_dependency("unknown") is False
|
|
184
|
+
|
|
185
|
+
def test_get_story_refs(self, sample_journey: Journey) -> None:
|
|
186
|
+
"""Test getting story references."""
|
|
187
|
+
refs = sample_journey.get_story_refs()
|
|
188
|
+
assert refs == ["Upload Document", "Review Vocabulary"]
|
|
189
|
+
|
|
190
|
+
def test_get_epic_refs(self, sample_journey: Journey) -> None:
|
|
191
|
+
"""Test getting epic references."""
|
|
192
|
+
refs = sample_journey.get_epic_refs()
|
|
193
|
+
assert refs == ["vocabulary-management"]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TestJourneySteps:
|
|
197
|
+
"""Test Journey step operations."""
|
|
198
|
+
|
|
199
|
+
def test_add_step(self) -> None:
|
|
200
|
+
"""Test adding a step."""
|
|
201
|
+
journey = Journey(slug="test")
|
|
202
|
+
assert journey.step_count == 0
|
|
203
|
+
|
|
204
|
+
journey.add_step(JourneyStep.story("Test Story"))
|
|
205
|
+
assert journey.step_count == 1
|
|
206
|
+
assert journey.has_steps is True
|
|
207
|
+
|
|
208
|
+
def test_has_steps_empty(self) -> None:
|
|
209
|
+
"""Test has_steps with empty journey."""
|
|
210
|
+
journey = Journey(slug="test")
|
|
211
|
+
assert journey.has_steps is False
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class TestJourneyProperties:
|
|
215
|
+
"""Test Journey properties."""
|
|
216
|
+
|
|
217
|
+
def test_display_title(self) -> None:
|
|
218
|
+
"""Test display_title property."""
|
|
219
|
+
journey = Journey(slug="build-vocabulary")
|
|
220
|
+
assert journey.display_title == "Build Vocabulary"
|
|
221
|
+
|
|
222
|
+
def test_display_title_multiple_words(self) -> None:
|
|
223
|
+
"""Test display_title with multiple hyphens."""
|
|
224
|
+
journey = Journey(slug="operate-data-pipelines")
|
|
225
|
+
assert journey.display_title == "Operate Data Pipelines"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TestJourneySerialization:
|
|
229
|
+
"""Test Journey serialization."""
|
|
230
|
+
|
|
231
|
+
def test_journey_to_dict(self) -> None:
|
|
232
|
+
"""Test journey can be serialized to dict."""
|
|
233
|
+
journey = Journey(
|
|
234
|
+
slug="test",
|
|
235
|
+
persona="User",
|
|
236
|
+
steps=[JourneyStep.story("Test Story")],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
data = journey.model_dump()
|
|
240
|
+
assert data["slug"] == "test"
|
|
241
|
+
assert data["persona"] == "User"
|
|
242
|
+
assert len(data["steps"]) == 1
|
|
243
|
+
assert data["steps"][0]["step_type"] == StepType.STORY
|
|
244
|
+
|
|
245
|
+
def test_journey_to_json(self) -> None:
|
|
246
|
+
"""Test journey can be serialized to JSON."""
|
|
247
|
+
journey = Journey(slug="test", persona="User")
|
|
248
|
+
json_str = journey.model_dump_json()
|
|
249
|
+
assert '"slug":"test"' in json_str
|