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,258 @@
|
|
|
1
|
+
"""Tests for App domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.app import App, AppType
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestAppType:
|
|
10
|
+
"""Test AppType enum."""
|
|
11
|
+
|
|
12
|
+
def test_app_type_values(self) -> None:
|
|
13
|
+
"""Test AppType enum values."""
|
|
14
|
+
assert AppType.STAFF.value == "staff"
|
|
15
|
+
assert AppType.EXTERNAL.value == "external"
|
|
16
|
+
assert AppType.MEMBER_TOOL.value == "member-tool"
|
|
17
|
+
assert AppType.UNKNOWN.value == "unknown"
|
|
18
|
+
|
|
19
|
+
def test_from_string_valid(self) -> None:
|
|
20
|
+
"""Test from_string with valid values."""
|
|
21
|
+
assert AppType.from_string("staff") == AppType.STAFF
|
|
22
|
+
assert AppType.from_string("external") == AppType.EXTERNAL
|
|
23
|
+
assert AppType.from_string("member-tool") == AppType.MEMBER_TOOL
|
|
24
|
+
|
|
25
|
+
def test_from_string_case_insensitive(self) -> None:
|
|
26
|
+
"""Test from_string is case-insensitive."""
|
|
27
|
+
assert AppType.from_string("STAFF") == AppType.STAFF
|
|
28
|
+
assert AppType.from_string("Staff") == AppType.STAFF
|
|
29
|
+
|
|
30
|
+
def test_from_string_unknown(self) -> None:
|
|
31
|
+
"""Test from_string returns UNKNOWN for invalid values."""
|
|
32
|
+
assert AppType.from_string("invalid") == AppType.UNKNOWN
|
|
33
|
+
assert AppType.from_string("") == AppType.UNKNOWN
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestAppCreation:
|
|
37
|
+
"""Test App model creation and validation."""
|
|
38
|
+
|
|
39
|
+
def test_create_app_with_required_fields(self) -> None:
|
|
40
|
+
"""Test creating an app with minimum required fields."""
|
|
41
|
+
app = App(
|
|
42
|
+
slug="staff-portal",
|
|
43
|
+
name="Staff Portal",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assert app.slug == "staff-portal"
|
|
47
|
+
assert app.name == "Staff Portal"
|
|
48
|
+
assert app.app_type == AppType.UNKNOWN
|
|
49
|
+
assert app.status is None
|
|
50
|
+
assert app.description == ""
|
|
51
|
+
assert app.accelerators == []
|
|
52
|
+
|
|
53
|
+
def test_create_app_with_all_fields(self) -> None:
|
|
54
|
+
"""Test creating an app with all fields."""
|
|
55
|
+
app = App(
|
|
56
|
+
slug="staff-portal",
|
|
57
|
+
name="Staff Portal",
|
|
58
|
+
app_type=AppType.STAFF,
|
|
59
|
+
status="live",
|
|
60
|
+
description="Portal for staff members",
|
|
61
|
+
accelerators=["user-auth", "doc-upload"],
|
|
62
|
+
manifest_path="/path/to/app.yaml",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
assert app.slug == "staff-portal"
|
|
66
|
+
assert app.name == "Staff Portal"
|
|
67
|
+
assert app.app_type == AppType.STAFF
|
|
68
|
+
assert app.status == "live"
|
|
69
|
+
assert app.description == "Portal for staff members"
|
|
70
|
+
assert app.accelerators == ["user-auth", "doc-upload"]
|
|
71
|
+
assert app.manifest_path == "/path/to/app.yaml"
|
|
72
|
+
|
|
73
|
+
def test_name_normalized_computed_automatically(self) -> None:
|
|
74
|
+
"""Test that name_normalized is computed from name."""
|
|
75
|
+
app = App(
|
|
76
|
+
slug="staff-portal",
|
|
77
|
+
name="Staff Portal",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
assert app.name_normalized == "staff portal"
|
|
81
|
+
|
|
82
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
83
|
+
"""Test that empty slug raises validation error."""
|
|
84
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
85
|
+
App(
|
|
86
|
+
slug="",
|
|
87
|
+
name="Test App",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def test_empty_name_raises_error(self) -> None:
|
|
91
|
+
"""Test that empty name raises validation error."""
|
|
92
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
93
|
+
App(
|
|
94
|
+
slug="test-app",
|
|
95
|
+
name="",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def test_whitespace_only_slug_raises_error(self) -> None:
|
|
99
|
+
"""Test that whitespace-only slug raises validation error."""
|
|
100
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
101
|
+
App(
|
|
102
|
+
slug=" ",
|
|
103
|
+
name="Test App",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestAppFromManifest:
|
|
108
|
+
"""Test App.from_manifest factory method."""
|
|
109
|
+
|
|
110
|
+
def test_from_manifest_creates_app(self) -> None:
|
|
111
|
+
"""Test creating an app from manifest data."""
|
|
112
|
+
manifest = {
|
|
113
|
+
"name": "Staff Portal",
|
|
114
|
+
"type": "staff",
|
|
115
|
+
"status": "live",
|
|
116
|
+
"description": "Portal for staff members",
|
|
117
|
+
"accelerators": ["user-auth"],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
app = App.from_manifest(
|
|
121
|
+
slug="staff-portal",
|
|
122
|
+
manifest=manifest,
|
|
123
|
+
manifest_path="/apps/staff-portal/app.yaml",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
assert app.slug == "staff-portal"
|
|
127
|
+
assert app.name == "Staff Portal"
|
|
128
|
+
assert app.app_type == AppType.STAFF
|
|
129
|
+
assert app.status == "live"
|
|
130
|
+
assert app.description == "Portal for staff members"
|
|
131
|
+
assert app.accelerators == ["user-auth"]
|
|
132
|
+
assert app.manifest_path == "/apps/staff-portal/app.yaml"
|
|
133
|
+
|
|
134
|
+
def test_from_manifest_default_name(self) -> None:
|
|
135
|
+
"""Test default name from slug when not in manifest."""
|
|
136
|
+
manifest = {}
|
|
137
|
+
|
|
138
|
+
app = App.from_manifest(
|
|
139
|
+
slug="staff-portal",
|
|
140
|
+
manifest=manifest,
|
|
141
|
+
manifest_path="/apps/staff-portal/app.yaml",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
assert app.name == "Staff Portal"
|
|
145
|
+
|
|
146
|
+
def test_from_manifest_default_type(self) -> None:
|
|
147
|
+
"""Test default type when not in manifest."""
|
|
148
|
+
manifest = {"name": "Test App"}
|
|
149
|
+
|
|
150
|
+
app = App.from_manifest(
|
|
151
|
+
slug="test-app",
|
|
152
|
+
manifest=manifest,
|
|
153
|
+
manifest_path="/apps/test-app/app.yaml",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
assert app.app_type == AppType.UNKNOWN
|
|
157
|
+
|
|
158
|
+
def test_from_manifest_strips_description(self) -> None:
|
|
159
|
+
"""Test that description whitespace is stripped."""
|
|
160
|
+
manifest = {
|
|
161
|
+
"name": "Test App",
|
|
162
|
+
"description": " Some description \n",
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
app = App.from_manifest(
|
|
166
|
+
slug="test-app",
|
|
167
|
+
manifest=manifest,
|
|
168
|
+
manifest_path="/apps/test-app/app.yaml",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
assert app.description == "Some description"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestAppMatching:
|
|
175
|
+
"""Test App matching methods."""
|
|
176
|
+
|
|
177
|
+
@pytest.fixture
|
|
178
|
+
def sample_app(self) -> App:
|
|
179
|
+
"""Create a sample app for testing."""
|
|
180
|
+
return App(
|
|
181
|
+
slug="staff-portal",
|
|
182
|
+
name="Staff Portal",
|
|
183
|
+
app_type=AppType.STAFF,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def test_matches_type_with_enum(self, sample_app: App) -> None:
|
|
187
|
+
"""Test type matching with AppType enum."""
|
|
188
|
+
assert sample_app.matches_type(AppType.STAFF) is True
|
|
189
|
+
assert sample_app.matches_type(AppType.EXTERNAL) is False
|
|
190
|
+
|
|
191
|
+
def test_matches_type_with_string(self, sample_app: App) -> None:
|
|
192
|
+
"""Test type matching with string."""
|
|
193
|
+
assert sample_app.matches_type("staff") is True
|
|
194
|
+
assert sample_app.matches_type("external") is False
|
|
195
|
+
|
|
196
|
+
def test_matches_name_exact(self, sample_app: App) -> None:
|
|
197
|
+
"""Test name matching with exact name."""
|
|
198
|
+
assert sample_app.matches_name("Staff Portal") is True
|
|
199
|
+
|
|
200
|
+
def test_matches_name_case_insensitive(self, sample_app: App) -> None:
|
|
201
|
+
"""Test name matching is case-insensitive."""
|
|
202
|
+
assert sample_app.matches_name("staff portal") is True
|
|
203
|
+
assert sample_app.matches_name("STAFF PORTAL") is True
|
|
204
|
+
|
|
205
|
+
def test_matches_name_no_match(self, sample_app: App) -> None:
|
|
206
|
+
"""Test name matching returns False for non-match."""
|
|
207
|
+
assert sample_app.matches_name("External App") is False
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class TestAppTypeLabel:
|
|
211
|
+
"""Test App type_label property."""
|
|
212
|
+
|
|
213
|
+
def test_type_label_staff(self) -> None:
|
|
214
|
+
"""Test type label for staff app."""
|
|
215
|
+
app = App(slug="test", name="Test", app_type=AppType.STAFF)
|
|
216
|
+
assert app.type_label == "Staff Application"
|
|
217
|
+
|
|
218
|
+
def test_type_label_external(self) -> None:
|
|
219
|
+
"""Test type label for external app."""
|
|
220
|
+
app = App(slug="test", name="Test", app_type=AppType.EXTERNAL)
|
|
221
|
+
assert app.type_label == "External Application"
|
|
222
|
+
|
|
223
|
+
def test_type_label_member_tool(self) -> None:
|
|
224
|
+
"""Test type label for member tool."""
|
|
225
|
+
app = App(slug="test", name="Test", app_type=AppType.MEMBER_TOOL)
|
|
226
|
+
assert app.type_label == "Member Tool"
|
|
227
|
+
|
|
228
|
+
def test_type_label_unknown(self) -> None:
|
|
229
|
+
"""Test type label for unknown type."""
|
|
230
|
+
app = App(slug="test", name="Test", app_type=AppType.UNKNOWN)
|
|
231
|
+
assert app.type_label == "Unknown"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class TestAppSerialization:
|
|
235
|
+
"""Test App serialization."""
|
|
236
|
+
|
|
237
|
+
def test_app_to_dict(self) -> None:
|
|
238
|
+
"""Test app can be serialized to dict."""
|
|
239
|
+
app = App(
|
|
240
|
+
slug="test-app",
|
|
241
|
+
name="Test App",
|
|
242
|
+
app_type=AppType.STAFF,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
data = app.model_dump()
|
|
246
|
+
assert data["slug"] == "test-app"
|
|
247
|
+
assert data["name"] == "Test App"
|
|
248
|
+
assert data["app_type"] == AppType.STAFF
|
|
249
|
+
|
|
250
|
+
def test_app_to_json(self) -> None:
|
|
251
|
+
"""Test app can be serialized to JSON."""
|
|
252
|
+
app = App(
|
|
253
|
+
slug="test-app",
|
|
254
|
+
name="Test App",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
json_str = app.model_dump_json()
|
|
258
|
+
assert '"slug":"test-app"' in json_str
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Tests for CodeInfo domain models."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.code_info import (
|
|
7
|
+
BoundedContextInfo,
|
|
8
|
+
ClassInfo,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestClassInfo:
|
|
13
|
+
"""Test ClassInfo model."""
|
|
14
|
+
|
|
15
|
+
def test_create_with_name_only(self) -> None:
|
|
16
|
+
"""Test creating with just name."""
|
|
17
|
+
info = ClassInfo(name="Document")
|
|
18
|
+
assert info.name == "Document"
|
|
19
|
+
assert info.docstring == ""
|
|
20
|
+
assert info.file == ""
|
|
21
|
+
|
|
22
|
+
def test_create_with_all_fields(self) -> None:
|
|
23
|
+
"""Test creating with all fields."""
|
|
24
|
+
info = ClassInfo(
|
|
25
|
+
name="Document",
|
|
26
|
+
docstring="A document entity.",
|
|
27
|
+
file="document.py",
|
|
28
|
+
)
|
|
29
|
+
assert info.name == "Document"
|
|
30
|
+
assert info.docstring == "A document entity."
|
|
31
|
+
assert info.file == "document.py"
|
|
32
|
+
|
|
33
|
+
def test_empty_name_raises_error(self) -> None:
|
|
34
|
+
"""Test that empty name raises validation error."""
|
|
35
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
36
|
+
ClassInfo(name="")
|
|
37
|
+
|
|
38
|
+
def test_whitespace_name_raises_error(self) -> None:
|
|
39
|
+
"""Test that whitespace-only name raises validation error."""
|
|
40
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
41
|
+
ClassInfo(name=" ")
|
|
42
|
+
|
|
43
|
+
def test_name_stripped(self) -> None:
|
|
44
|
+
"""Test that name is stripped of whitespace."""
|
|
45
|
+
info = ClassInfo(name=" Document ")
|
|
46
|
+
assert info.name == "Document"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestBoundedContextInfoCreation:
|
|
50
|
+
"""Test BoundedContextInfo model creation and validation."""
|
|
51
|
+
|
|
52
|
+
def test_create_minimal(self) -> None:
|
|
53
|
+
"""Test creating with minimum fields."""
|
|
54
|
+
info = BoundedContextInfo(slug="vocabulary")
|
|
55
|
+
assert info.slug == "vocabulary"
|
|
56
|
+
assert info.entities == []
|
|
57
|
+
assert info.use_cases == []
|
|
58
|
+
assert info.repository_protocols == []
|
|
59
|
+
assert info.service_protocols == []
|
|
60
|
+
assert info.has_infrastructure is False
|
|
61
|
+
assert info.code_dir == ""
|
|
62
|
+
assert info.objective is None
|
|
63
|
+
assert info.docstring is None
|
|
64
|
+
|
|
65
|
+
def test_create_complete(self) -> None:
|
|
66
|
+
"""Test creating with all fields."""
|
|
67
|
+
entities = [
|
|
68
|
+
ClassInfo(
|
|
69
|
+
name="Vocabulary", docstring="A vocabulary entity", file="vocabulary.py"
|
|
70
|
+
),
|
|
71
|
+
ClassInfo(name="Term", docstring="A term in a vocabulary", file="term.py"),
|
|
72
|
+
]
|
|
73
|
+
use_cases = [
|
|
74
|
+
ClassInfo(
|
|
75
|
+
name="CreateVocabulary",
|
|
76
|
+
docstring="Create a vocabulary",
|
|
77
|
+
file="create.py",
|
|
78
|
+
),
|
|
79
|
+
]
|
|
80
|
+
repo_protocols = [
|
|
81
|
+
ClassInfo(
|
|
82
|
+
name="VocabularyRepository",
|
|
83
|
+
docstring="Repository protocol",
|
|
84
|
+
file="vocabulary.py",
|
|
85
|
+
),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
info = BoundedContextInfo(
|
|
89
|
+
slug="vocabulary",
|
|
90
|
+
entities=entities,
|
|
91
|
+
use_cases=use_cases,
|
|
92
|
+
repository_protocols=repo_protocols,
|
|
93
|
+
service_protocols=[],
|
|
94
|
+
has_infrastructure=True,
|
|
95
|
+
code_dir="vocabulary",
|
|
96
|
+
objective="Manage vocabulary catalogs",
|
|
97
|
+
docstring="Full module documentation here.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert info.slug == "vocabulary"
|
|
101
|
+
assert len(info.entities) == 2
|
|
102
|
+
assert len(info.use_cases) == 1
|
|
103
|
+
assert len(info.repository_protocols) == 1
|
|
104
|
+
assert info.has_infrastructure is True
|
|
105
|
+
assert info.objective == "Manage vocabulary catalogs"
|
|
106
|
+
|
|
107
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
108
|
+
"""Test that empty slug raises validation error."""
|
|
109
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
110
|
+
BoundedContextInfo(slug="")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestBoundedContextInfoProperties:
|
|
114
|
+
"""Test BoundedContextInfo properties."""
|
|
115
|
+
|
|
116
|
+
@pytest.fixture
|
|
117
|
+
def sample_context(self) -> BoundedContextInfo:
|
|
118
|
+
"""Create a sample bounded context for testing."""
|
|
119
|
+
return BoundedContextInfo(
|
|
120
|
+
slug="vocabulary",
|
|
121
|
+
entities=[
|
|
122
|
+
ClassInfo(name="Vocabulary", file="vocabulary.py"),
|
|
123
|
+
ClassInfo(name="Term", file="term.py"),
|
|
124
|
+
],
|
|
125
|
+
use_cases=[
|
|
126
|
+
ClassInfo(name="CreateVocabulary", file="create.py"),
|
|
127
|
+
],
|
|
128
|
+
repository_protocols=[
|
|
129
|
+
ClassInfo(name="VocabularyRepository", file="vocabulary.py"),
|
|
130
|
+
],
|
|
131
|
+
service_protocols=[
|
|
132
|
+
ClassInfo(name="NotificationService", file="notification.py"),
|
|
133
|
+
],
|
|
134
|
+
has_infrastructure=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def test_entity_count(self, sample_context: BoundedContextInfo) -> None:
|
|
138
|
+
"""Test entity_count property."""
|
|
139
|
+
assert sample_context.entity_count == 2
|
|
140
|
+
|
|
141
|
+
def test_use_case_count(self, sample_context: BoundedContextInfo) -> None:
|
|
142
|
+
"""Test use_case_count property."""
|
|
143
|
+
assert sample_context.use_case_count == 1
|
|
144
|
+
|
|
145
|
+
def test_protocol_count(self, sample_context: BoundedContextInfo) -> None:
|
|
146
|
+
"""Test protocol_count property."""
|
|
147
|
+
assert sample_context.protocol_count == 2 # 1 repo + 1 service
|
|
148
|
+
|
|
149
|
+
def test_has_entities(self, sample_context: BoundedContextInfo) -> None:
|
|
150
|
+
"""Test has_entities property."""
|
|
151
|
+
assert sample_context.has_entities is True
|
|
152
|
+
|
|
153
|
+
def test_has_entities_empty(self) -> None:
|
|
154
|
+
"""Test has_entities with empty context."""
|
|
155
|
+
info = BoundedContextInfo(slug="test")
|
|
156
|
+
assert info.has_entities is False
|
|
157
|
+
|
|
158
|
+
def test_has_use_cases(self, sample_context: BoundedContextInfo) -> None:
|
|
159
|
+
"""Test has_use_cases property."""
|
|
160
|
+
assert sample_context.has_use_cases is True
|
|
161
|
+
|
|
162
|
+
def test_has_use_cases_empty(self) -> None:
|
|
163
|
+
"""Test has_use_cases with empty context."""
|
|
164
|
+
info = BoundedContextInfo(slug="test")
|
|
165
|
+
assert info.has_use_cases is False
|
|
166
|
+
|
|
167
|
+
def test_has_protocols(self, sample_context: BoundedContextInfo) -> None:
|
|
168
|
+
"""Test has_protocols property."""
|
|
169
|
+
assert sample_context.has_protocols is True
|
|
170
|
+
|
|
171
|
+
def test_has_protocols_empty(self) -> None:
|
|
172
|
+
"""Test has_protocols with empty context."""
|
|
173
|
+
info = BoundedContextInfo(slug="test")
|
|
174
|
+
assert info.has_protocols is False
|
|
175
|
+
|
|
176
|
+
def test_get_entity_names(self, sample_context: BoundedContextInfo) -> None:
|
|
177
|
+
"""Test get_entity_names method."""
|
|
178
|
+
names = sample_context.get_entity_names()
|
|
179
|
+
assert names == ["Vocabulary", "Term"]
|
|
180
|
+
|
|
181
|
+
def test_get_use_case_names(self, sample_context: BoundedContextInfo) -> None:
|
|
182
|
+
"""Test get_use_case_names method."""
|
|
183
|
+
names = sample_context.get_use_case_names()
|
|
184
|
+
assert names == ["CreateVocabulary"]
|
|
185
|
+
|
|
186
|
+
def test_summary(self, sample_context: BoundedContextInfo) -> None:
|
|
187
|
+
"""Test summary method."""
|
|
188
|
+
summary = sample_context.summary()
|
|
189
|
+
assert "2 entities" in summary
|
|
190
|
+
assert "1 use cases" in summary
|
|
191
|
+
assert "1 repository protocols" in summary
|
|
192
|
+
assert "1 service protocols" in summary
|
|
193
|
+
|
|
194
|
+
def test_summary_empty(self) -> None:
|
|
195
|
+
"""Test summary with empty context."""
|
|
196
|
+
info = BoundedContextInfo(slug="test")
|
|
197
|
+
assert info.summary() == "empty"
|
|
198
|
+
|
|
199
|
+
def test_summary_partial(self) -> None:
|
|
200
|
+
"""Test summary with partial data."""
|
|
201
|
+
info = BoundedContextInfo(
|
|
202
|
+
slug="test",
|
|
203
|
+
entities=[ClassInfo(name="Entity", file="e.py")],
|
|
204
|
+
)
|
|
205
|
+
summary = info.summary()
|
|
206
|
+
assert summary == "1 entities"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestBoundedContextInfoSerialization:
|
|
210
|
+
"""Test BoundedContextInfo serialization."""
|
|
211
|
+
|
|
212
|
+
def test_to_dict(self) -> None:
|
|
213
|
+
"""Test serialization to dict."""
|
|
214
|
+
info = BoundedContextInfo(
|
|
215
|
+
slug="test",
|
|
216
|
+
entities=[ClassInfo(name="Entity", file="e.py")],
|
|
217
|
+
objective="Test objective",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
data = info.model_dump()
|
|
221
|
+
assert data["slug"] == "test"
|
|
222
|
+
assert len(data["entities"]) == 1
|
|
223
|
+
assert data["entities"][0]["name"] == "Entity"
|
|
224
|
+
assert data["objective"] == "Test objective"
|
|
225
|
+
|
|
226
|
+
def test_to_json(self) -> None:
|
|
227
|
+
"""Test serialization to JSON."""
|
|
228
|
+
info = BoundedContextInfo(slug="test", objective="Test")
|
|
229
|
+
json_str = info.model_dump_json()
|
|
230
|
+
assert '"slug":"test"' in json_str
|
|
231
|
+
assert '"objective":"Test"' in json_str
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Tests for Epic domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.epic import Epic
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestEpicCreation:
|
|
10
|
+
"""Test Epic model creation and validation."""
|
|
11
|
+
|
|
12
|
+
def test_create_epic_minimal(self) -> None:
|
|
13
|
+
"""Test creating an epic with minimum fields."""
|
|
14
|
+
epic = Epic(slug="vocabulary-management")
|
|
15
|
+
assert epic.slug == "vocabulary-management"
|
|
16
|
+
assert epic.description == ""
|
|
17
|
+
assert epic.story_refs == []
|
|
18
|
+
assert epic.docname == ""
|
|
19
|
+
|
|
20
|
+
def test_create_epic_complete(self) -> None:
|
|
21
|
+
"""Test creating an epic with all fields."""
|
|
22
|
+
epic = Epic(
|
|
23
|
+
slug="vocabulary-management",
|
|
24
|
+
description="Manage terminology and vocabulary catalogs",
|
|
25
|
+
story_refs=["Upload Document", "Review Vocabulary", "Publish Catalog"],
|
|
26
|
+
docname="epics/vocabulary-management",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
assert epic.slug == "vocabulary-management"
|
|
30
|
+
assert epic.description == "Manage terminology and vocabulary catalogs"
|
|
31
|
+
assert len(epic.story_refs) == 3
|
|
32
|
+
assert epic.docname == "epics/vocabulary-management"
|
|
33
|
+
|
|
34
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
35
|
+
"""Test that empty slug raises validation error."""
|
|
36
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
37
|
+
Epic(slug="")
|
|
38
|
+
|
|
39
|
+
def test_whitespace_slug_raises_error(self) -> None:
|
|
40
|
+
"""Test that whitespace-only slug raises validation error."""
|
|
41
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
42
|
+
Epic(slug=" ")
|
|
43
|
+
|
|
44
|
+
def test_slug_stripped(self) -> None:
|
|
45
|
+
"""Test that slug is stripped of whitespace."""
|
|
46
|
+
epic = Epic(slug=" vocabulary-management ")
|
|
47
|
+
assert epic.slug == "vocabulary-management"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestEpicStoryOperations:
|
|
51
|
+
"""Test Epic story reference operations."""
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def sample_epic(self) -> Epic:
|
|
55
|
+
"""Create a sample epic for testing."""
|
|
56
|
+
return Epic(
|
|
57
|
+
slug="vocabulary-management",
|
|
58
|
+
description="Manage terminology",
|
|
59
|
+
story_refs=["Upload Document", "Review Vocabulary"],
|
|
60
|
+
docname="epics/vocabulary-management",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def test_add_story(self) -> None:
|
|
64
|
+
"""Test adding a story to an epic."""
|
|
65
|
+
epic = Epic(slug="test-epic")
|
|
66
|
+
assert epic.story_count == 0
|
|
67
|
+
|
|
68
|
+
epic.add_story("New Story")
|
|
69
|
+
assert epic.story_count == 1
|
|
70
|
+
assert "New Story" in epic.story_refs
|
|
71
|
+
|
|
72
|
+
def test_has_story_exact_match(self, sample_epic: Epic) -> None:
|
|
73
|
+
"""Test has_story with exact match."""
|
|
74
|
+
assert sample_epic.has_story("Upload Document") is True
|
|
75
|
+
assert sample_epic.has_story("Review Vocabulary") is True
|
|
76
|
+
|
|
77
|
+
def test_has_story_case_insensitive(self, sample_epic: Epic) -> None:
|
|
78
|
+
"""Test has_story is case-insensitive."""
|
|
79
|
+
assert sample_epic.has_story("upload document") is True
|
|
80
|
+
assert sample_epic.has_story("UPLOAD DOCUMENT") is True
|
|
81
|
+
assert sample_epic.has_story("Upload document") is True
|
|
82
|
+
|
|
83
|
+
def test_has_story_no_match(self, sample_epic: Epic) -> None:
|
|
84
|
+
"""Test has_story returns False for non-match."""
|
|
85
|
+
assert sample_epic.has_story("Unknown Story") is False
|
|
86
|
+
|
|
87
|
+
def test_get_story_refs_normalized(self, sample_epic: Epic) -> None:
|
|
88
|
+
"""Test getting normalized story references."""
|
|
89
|
+
normalized = sample_epic.get_story_refs_normalized()
|
|
90
|
+
assert normalized == ["upload document", "review vocabulary"]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TestEpicProperties:
|
|
94
|
+
"""Test Epic properties."""
|
|
95
|
+
|
|
96
|
+
def test_display_title(self) -> None:
|
|
97
|
+
"""Test display_title property."""
|
|
98
|
+
epic = Epic(slug="vocabulary-management")
|
|
99
|
+
assert epic.display_title == "Vocabulary Management"
|
|
100
|
+
|
|
101
|
+
def test_display_title_multiple_words(self) -> None:
|
|
102
|
+
"""Test display_title with multiple hyphens."""
|
|
103
|
+
epic = Epic(slug="credential-creation-workflow")
|
|
104
|
+
assert epic.display_title == "Credential Creation Workflow"
|
|
105
|
+
|
|
106
|
+
def test_story_count_empty(self) -> None:
|
|
107
|
+
"""Test story_count with no stories."""
|
|
108
|
+
epic = Epic(slug="test")
|
|
109
|
+
assert epic.story_count == 0
|
|
110
|
+
|
|
111
|
+
def test_story_count_with_stories(self) -> None:
|
|
112
|
+
"""Test story_count with stories."""
|
|
113
|
+
epic = Epic(slug="test", story_refs=["Story 1", "Story 2", "Story 3"])
|
|
114
|
+
assert epic.story_count == 3
|
|
115
|
+
|
|
116
|
+
def test_has_stories_empty(self) -> None:
|
|
117
|
+
"""Test has_stories with no stories."""
|
|
118
|
+
epic = Epic(slug="test")
|
|
119
|
+
assert epic.has_stories is False
|
|
120
|
+
|
|
121
|
+
def test_has_stories_with_stories(self) -> None:
|
|
122
|
+
"""Test has_stories with stories."""
|
|
123
|
+
epic = Epic(slug="test", story_refs=["Story 1"])
|
|
124
|
+
assert epic.has_stories is True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestEpicSerialization:
|
|
128
|
+
"""Test Epic serialization."""
|
|
129
|
+
|
|
130
|
+
def test_epic_to_dict(self) -> None:
|
|
131
|
+
"""Test epic can be serialized to dict."""
|
|
132
|
+
epic = Epic(
|
|
133
|
+
slug="test",
|
|
134
|
+
description="Test description",
|
|
135
|
+
story_refs=["Story 1"],
|
|
136
|
+
docname="test/doc",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
data = epic.model_dump()
|
|
140
|
+
assert data["slug"] == "test"
|
|
141
|
+
assert data["description"] == "Test description"
|
|
142
|
+
assert data["story_refs"] == ["Story 1"]
|
|
143
|
+
assert data["docname"] == "test/doc"
|
|
144
|
+
|
|
145
|
+
def test_epic_to_json(self) -> None:
|
|
146
|
+
"""Test epic can be serialized to JSON."""
|
|
147
|
+
epic = Epic(slug="test", description="Test")
|
|
148
|
+
json_str = epic.model_dump_json()
|
|
149
|
+
assert '"slug":"test"' in json_str
|
|
150
|
+
assert '"description":"Test"' in json_str
|
|
151
|
+
|
|
152
|
+
def test_epic_from_dict(self) -> None:
|
|
153
|
+
"""Test epic can be deserialized from dict."""
|
|
154
|
+
data = {
|
|
155
|
+
"slug": "test",
|
|
156
|
+
"description": "Test description",
|
|
157
|
+
"story_refs": ["Story 1", "Story 2"],
|
|
158
|
+
"docname": "test/doc",
|
|
159
|
+
}
|
|
160
|
+
epic = Epic.model_validate(data)
|
|
161
|
+
assert epic.slug == "test"
|
|
162
|
+
assert epic.description == "Test description"
|
|
163
|
+
assert len(epic.story_refs) == 2
|