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,172 @@
|
|
|
1
|
+
"""Tests for Persona domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.persona import Persona
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestPersonaCreation:
|
|
10
|
+
"""Test Persona model creation and validation."""
|
|
11
|
+
|
|
12
|
+
def test_create_persona_minimal(self) -> None:
|
|
13
|
+
"""Test creating a persona with minimum fields."""
|
|
14
|
+
persona = Persona(name="Knowledge Curator")
|
|
15
|
+
assert persona.name == "Knowledge Curator"
|
|
16
|
+
assert persona.app_slugs == []
|
|
17
|
+
assert persona.epic_slugs == []
|
|
18
|
+
|
|
19
|
+
def test_create_persona_complete(self) -> None:
|
|
20
|
+
"""Test creating a persona with all fields."""
|
|
21
|
+
persona = Persona(
|
|
22
|
+
name="Knowledge Curator",
|
|
23
|
+
app_slugs=["vocabulary-tool", "admin-portal"],
|
|
24
|
+
epic_slugs=["vocabulary-management", "credential-creation"],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assert persona.name == "Knowledge Curator"
|
|
28
|
+
assert len(persona.app_slugs) == 2
|
|
29
|
+
assert len(persona.epic_slugs) == 2
|
|
30
|
+
|
|
31
|
+
def test_empty_name_raises_error(self) -> None:
|
|
32
|
+
"""Test that empty name raises validation error."""
|
|
33
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
34
|
+
Persona(name="")
|
|
35
|
+
|
|
36
|
+
def test_whitespace_name_raises_error(self) -> None:
|
|
37
|
+
"""Test that whitespace-only name raises validation error."""
|
|
38
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
39
|
+
Persona(name=" ")
|
|
40
|
+
|
|
41
|
+
def test_name_stripped(self) -> None:
|
|
42
|
+
"""Test that name is stripped of whitespace."""
|
|
43
|
+
persona = Persona(name=" Knowledge Curator ")
|
|
44
|
+
assert persona.name == "Knowledge Curator"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestPersonaProperties:
|
|
48
|
+
"""Test Persona properties."""
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def sample_persona(self) -> Persona:
|
|
52
|
+
"""Create a sample persona for testing."""
|
|
53
|
+
return Persona(
|
|
54
|
+
name="Knowledge Curator",
|
|
55
|
+
app_slugs=["vocabulary-tool", "admin-portal"],
|
|
56
|
+
epic_slugs=["vocabulary-management", "credential-creation"],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def test_normalized_name(self, sample_persona: Persona) -> None:
|
|
60
|
+
"""Test normalized_name computed field."""
|
|
61
|
+
assert sample_persona.normalized_name == "knowledge curator"
|
|
62
|
+
|
|
63
|
+
def test_display_name(self, sample_persona: Persona) -> None:
|
|
64
|
+
"""Test display_name property."""
|
|
65
|
+
assert sample_persona.display_name == "Knowledge Curator"
|
|
66
|
+
|
|
67
|
+
def test_app_count(self, sample_persona: Persona) -> None:
|
|
68
|
+
"""Test app_count property."""
|
|
69
|
+
assert sample_persona.app_count == 2
|
|
70
|
+
|
|
71
|
+
def test_epic_count(self, sample_persona: Persona) -> None:
|
|
72
|
+
"""Test epic_count property."""
|
|
73
|
+
assert sample_persona.epic_count == 2
|
|
74
|
+
|
|
75
|
+
def test_has_apps_true(self, sample_persona: Persona) -> None:
|
|
76
|
+
"""Test has_apps property when true."""
|
|
77
|
+
assert sample_persona.has_apps is True
|
|
78
|
+
|
|
79
|
+
def test_has_apps_false(self) -> None:
|
|
80
|
+
"""Test has_apps property when false."""
|
|
81
|
+
persona = Persona(name="Test")
|
|
82
|
+
assert persona.has_apps is False
|
|
83
|
+
|
|
84
|
+
def test_has_epics_true(self, sample_persona: Persona) -> None:
|
|
85
|
+
"""Test has_epics property when true."""
|
|
86
|
+
assert sample_persona.has_epics is True
|
|
87
|
+
|
|
88
|
+
def test_has_epics_false(self) -> None:
|
|
89
|
+
"""Test has_epics property when false."""
|
|
90
|
+
persona = Persona(name="Test")
|
|
91
|
+
assert persona.has_epics is False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestPersonaMethods:
|
|
95
|
+
"""Test Persona methods."""
|
|
96
|
+
|
|
97
|
+
@pytest.fixture
|
|
98
|
+
def sample_persona(self) -> Persona:
|
|
99
|
+
"""Create a sample persona for testing."""
|
|
100
|
+
return Persona(
|
|
101
|
+
name="Knowledge Curator",
|
|
102
|
+
app_slugs=["vocabulary-tool", "admin-portal"],
|
|
103
|
+
epic_slugs=["vocabulary-management"],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def test_uses_app_true(self, sample_persona: Persona) -> None:
|
|
107
|
+
"""Test uses_app returns True for used app."""
|
|
108
|
+
assert sample_persona.uses_app("vocabulary-tool") is True
|
|
109
|
+
assert sample_persona.uses_app("admin-portal") is True
|
|
110
|
+
|
|
111
|
+
def test_uses_app_false(self, sample_persona: Persona) -> None:
|
|
112
|
+
"""Test uses_app returns False for unused app."""
|
|
113
|
+
assert sample_persona.uses_app("unknown-app") is False
|
|
114
|
+
|
|
115
|
+
def test_participates_in_epic_true(self, sample_persona: Persona) -> None:
|
|
116
|
+
"""Test participates_in_epic returns True."""
|
|
117
|
+
assert sample_persona.participates_in_epic("vocabulary-management") is True
|
|
118
|
+
|
|
119
|
+
def test_participates_in_epic_false(self, sample_persona: Persona) -> None:
|
|
120
|
+
"""Test participates_in_epic returns False."""
|
|
121
|
+
assert sample_persona.participates_in_epic("unknown-epic") is False
|
|
122
|
+
|
|
123
|
+
def test_add_app_new(self) -> None:
|
|
124
|
+
"""Test adding a new app."""
|
|
125
|
+
persona = Persona(name="Test")
|
|
126
|
+
persona.add_app("new-app")
|
|
127
|
+
assert "new-app" in persona.app_slugs
|
|
128
|
+
assert persona.app_count == 1
|
|
129
|
+
|
|
130
|
+
def test_add_app_duplicate(self, sample_persona: Persona) -> None:
|
|
131
|
+
"""Test adding a duplicate app is ignored."""
|
|
132
|
+
initial_count = sample_persona.app_count
|
|
133
|
+
sample_persona.add_app("vocabulary-tool")
|
|
134
|
+
assert sample_persona.app_count == initial_count
|
|
135
|
+
|
|
136
|
+
def test_add_epic_new(self) -> None:
|
|
137
|
+
"""Test adding a new epic."""
|
|
138
|
+
persona = Persona(name="Test")
|
|
139
|
+
persona.add_epic("new-epic")
|
|
140
|
+
assert "new-epic" in persona.epic_slugs
|
|
141
|
+
assert persona.epic_count == 1
|
|
142
|
+
|
|
143
|
+
def test_add_epic_duplicate(self, sample_persona: Persona) -> None:
|
|
144
|
+
"""Test adding a duplicate epic is ignored."""
|
|
145
|
+
initial_count = sample_persona.epic_count
|
|
146
|
+
sample_persona.add_epic("vocabulary-management")
|
|
147
|
+
assert sample_persona.epic_count == initial_count
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestPersonaSerialization:
|
|
151
|
+
"""Test Persona serialization."""
|
|
152
|
+
|
|
153
|
+
def test_persona_to_dict(self) -> None:
|
|
154
|
+
"""Test persona can be serialized to dict."""
|
|
155
|
+
persona = Persona(
|
|
156
|
+
name="Test Persona",
|
|
157
|
+
app_slugs=["app-1"],
|
|
158
|
+
epic_slugs=["epic-1"],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
data = persona.model_dump()
|
|
162
|
+
assert data["name"] == "Test Persona"
|
|
163
|
+
assert data["app_slugs"] == ["app-1"]
|
|
164
|
+
assert data["epic_slugs"] == ["epic-1"]
|
|
165
|
+
assert data["normalized_name"] == "test persona"
|
|
166
|
+
|
|
167
|
+
def test_persona_to_json(self) -> None:
|
|
168
|
+
"""Test persona can be serialized to JSON."""
|
|
169
|
+
persona = Persona(name="Test Persona")
|
|
170
|
+
json_str = persona.model_dump_json()
|
|
171
|
+
assert '"name":"Test Persona"' in json_str
|
|
172
|
+
assert '"normalized_name":"test persona"' in json_str
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Tests for Story domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.story import Story
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestStoryCreation:
|
|
10
|
+
"""Test Story model creation and validation."""
|
|
11
|
+
|
|
12
|
+
def test_create_story_with_required_fields(self) -> None:
|
|
13
|
+
"""Test creating a story with minimum required fields."""
|
|
14
|
+
story = Story(
|
|
15
|
+
slug="submit-order",
|
|
16
|
+
feature_title="Submit Order",
|
|
17
|
+
persona="Customer",
|
|
18
|
+
app_slug="checkout-app",
|
|
19
|
+
file_path="tests/e2e/checkout-app/features/submit_order.feature",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
assert story.slug == "submit-order"
|
|
23
|
+
assert story.feature_title == "Submit Order"
|
|
24
|
+
assert story.persona == "Customer"
|
|
25
|
+
assert story.app_slug == "checkout-app"
|
|
26
|
+
assert story.file_path == "tests/e2e/checkout-app/features/submit_order.feature"
|
|
27
|
+
|
|
28
|
+
def test_create_story_with_all_fields(self) -> None:
|
|
29
|
+
"""Test creating a story with all fields."""
|
|
30
|
+
story = Story(
|
|
31
|
+
slug="submit-order",
|
|
32
|
+
feature_title="Submit Order",
|
|
33
|
+
persona="Customer",
|
|
34
|
+
persona_normalized="customer",
|
|
35
|
+
i_want="submit my order",
|
|
36
|
+
so_that="I can purchase products",
|
|
37
|
+
app_slug="checkout-app",
|
|
38
|
+
app_normalized="checkout app",
|
|
39
|
+
file_path="tests/e2e/checkout-app/features/submit.feature",
|
|
40
|
+
abs_path="/abs/path/to/submit.feature",
|
|
41
|
+
gherkin_snippet="Feature: Submit Order\n As a Customer",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
assert story.slug == "submit-order"
|
|
45
|
+
assert story.persona_normalized == "customer"
|
|
46
|
+
assert story.i_want == "submit my order"
|
|
47
|
+
assert story.so_that == "I can purchase products"
|
|
48
|
+
assert story.gherkin_snippet == "Feature: Submit Order\n As a Customer"
|
|
49
|
+
|
|
50
|
+
def test_normalized_fields_computed_automatically(self) -> None:
|
|
51
|
+
"""Test that normalized fields are computed from raw values."""
|
|
52
|
+
story = Story(
|
|
53
|
+
slug="test",
|
|
54
|
+
feature_title="Test Feature",
|
|
55
|
+
persona="Staff Member",
|
|
56
|
+
app_slug="Staff-Portal",
|
|
57
|
+
file_path="test.feature",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
assert story.persona_normalized == "staff member"
|
|
61
|
+
assert story.app_normalized == "staff portal"
|
|
62
|
+
|
|
63
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
64
|
+
"""Test that empty slug raises validation error."""
|
|
65
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
66
|
+
Story(
|
|
67
|
+
slug="",
|
|
68
|
+
feature_title="Test",
|
|
69
|
+
persona="User",
|
|
70
|
+
app_slug="app",
|
|
71
|
+
file_path="test.feature",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def test_empty_feature_title_raises_error(self) -> None:
|
|
75
|
+
"""Test that empty feature title raises validation error."""
|
|
76
|
+
with pytest.raises(ValidationError, match="Feature title cannot be empty"):
|
|
77
|
+
Story(
|
|
78
|
+
slug="test",
|
|
79
|
+
feature_title="",
|
|
80
|
+
persona="User",
|
|
81
|
+
app_slug="app",
|
|
82
|
+
file_path="test.feature",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_empty_persona_defaults_to_unknown(self) -> None:
|
|
86
|
+
"""Test that empty persona defaults to 'unknown'."""
|
|
87
|
+
story = Story(
|
|
88
|
+
slug="test",
|
|
89
|
+
feature_title="Test",
|
|
90
|
+
persona="",
|
|
91
|
+
app_slug="app",
|
|
92
|
+
file_path="test.feature",
|
|
93
|
+
)
|
|
94
|
+
assert story.persona == "unknown"
|
|
95
|
+
|
|
96
|
+
def test_empty_app_slug_defaults_to_unknown(self) -> None:
|
|
97
|
+
"""Test that empty app slug defaults to 'unknown'."""
|
|
98
|
+
story = Story(
|
|
99
|
+
slug="test",
|
|
100
|
+
feature_title="Test",
|
|
101
|
+
persona="User",
|
|
102
|
+
app_slug="",
|
|
103
|
+
file_path="test.feature",
|
|
104
|
+
)
|
|
105
|
+
assert story.app_slug == "unknown"
|
|
106
|
+
|
|
107
|
+
def test_whitespace_only_slug_raises_error(self) -> None:
|
|
108
|
+
"""Test that whitespace-only slug raises validation error."""
|
|
109
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
110
|
+
Story(
|
|
111
|
+
slug=" ",
|
|
112
|
+
feature_title="Test",
|
|
113
|
+
persona="User",
|
|
114
|
+
app_slug="app",
|
|
115
|
+
file_path="test.feature",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class TestStoryFromFeatureFile:
|
|
120
|
+
"""Test Story.from_feature_file factory method."""
|
|
121
|
+
|
|
122
|
+
def test_from_feature_file_creates_story(self) -> None:
|
|
123
|
+
"""Test creating a story from feature file data."""
|
|
124
|
+
story = Story.from_feature_file(
|
|
125
|
+
feature_title="Upload Document",
|
|
126
|
+
persona="Staff Member",
|
|
127
|
+
i_want="upload a document",
|
|
128
|
+
so_that="it can be analyzed",
|
|
129
|
+
app_slug="staff-portal",
|
|
130
|
+
file_path="tests/e2e/staff-portal/features/upload.feature",
|
|
131
|
+
abs_path="/home/project/tests/e2e/staff-portal/features/upload.feature",
|
|
132
|
+
gherkin_snippet="Feature: Upload Document",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
assert (
|
|
136
|
+
story.slug == "staff-portal--upload-document"
|
|
137
|
+
) # App prefix prevents collisions
|
|
138
|
+
assert story.feature_title == "Upload Document"
|
|
139
|
+
assert story.persona == "Staff Member"
|
|
140
|
+
assert story.persona_normalized == "staff member"
|
|
141
|
+
assert story.i_want == "upload a document"
|
|
142
|
+
assert story.so_that == "it can be analyzed"
|
|
143
|
+
assert story.app_slug == "staff-portal"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TestStoryMatching:
|
|
147
|
+
"""Test Story matching methods."""
|
|
148
|
+
|
|
149
|
+
@pytest.fixture
|
|
150
|
+
def sample_story(self) -> Story:
|
|
151
|
+
"""Create a sample story for testing."""
|
|
152
|
+
return Story(
|
|
153
|
+
slug="test-story",
|
|
154
|
+
feature_title="Test Story",
|
|
155
|
+
persona="Staff Member",
|
|
156
|
+
app_slug="staff-portal",
|
|
157
|
+
file_path="test.feature",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def test_matches_persona_exact(self, sample_story: Story) -> None:
|
|
161
|
+
"""Test persona matching with exact name."""
|
|
162
|
+
assert sample_story.matches_persona("Staff Member") is True
|
|
163
|
+
|
|
164
|
+
def test_matches_persona_case_insensitive(self, sample_story: Story) -> None:
|
|
165
|
+
"""Test persona matching is case-insensitive."""
|
|
166
|
+
assert sample_story.matches_persona("staff member") is True
|
|
167
|
+
assert sample_story.matches_persona("STAFF MEMBER") is True
|
|
168
|
+
|
|
169
|
+
def test_matches_persona_no_match(self, sample_story: Story) -> None:
|
|
170
|
+
"""Test persona matching returns False for non-match."""
|
|
171
|
+
assert sample_story.matches_persona("Customer") is False
|
|
172
|
+
|
|
173
|
+
def test_matches_app_exact(self, sample_story: Story) -> None:
|
|
174
|
+
"""Test app matching with exact name."""
|
|
175
|
+
assert sample_story.matches_app("staff-portal") is True
|
|
176
|
+
|
|
177
|
+
def test_matches_app_with_different_separators(self, sample_story: Story) -> None:
|
|
178
|
+
"""Test app matching handles different separators."""
|
|
179
|
+
assert sample_story.matches_app("staff portal") is True
|
|
180
|
+
assert sample_story.matches_app("Staff Portal") is True
|
|
181
|
+
|
|
182
|
+
def test_matches_app_no_match(self, sample_story: Story) -> None:
|
|
183
|
+
"""Test app matching returns False for non-match."""
|
|
184
|
+
assert sample_story.matches_app("checkout-app") is False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TestStorySerialization:
|
|
188
|
+
"""Test Story serialization."""
|
|
189
|
+
|
|
190
|
+
def test_story_to_dict(self) -> None:
|
|
191
|
+
"""Test story can be serialized to dict."""
|
|
192
|
+
story = Story(
|
|
193
|
+
slug="test",
|
|
194
|
+
feature_title="Test",
|
|
195
|
+
persona="User",
|
|
196
|
+
app_slug="app",
|
|
197
|
+
file_path="test.feature",
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
data = story.model_dump()
|
|
201
|
+
assert data["slug"] == "test"
|
|
202
|
+
assert data["feature_title"] == "Test"
|
|
203
|
+
assert data["persona"] == "User"
|
|
204
|
+
|
|
205
|
+
def test_story_to_json(self) -> None:
|
|
206
|
+
"""Test story can be serialized to JSON."""
|
|
207
|
+
story = Story(
|
|
208
|
+
slug="test",
|
|
209
|
+
feature_title="Test",
|
|
210
|
+
persona="User",
|
|
211
|
+
app_slug="app",
|
|
212
|
+
file_path="test.feature",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
json_str = story.model_dump_json()
|
|
216
|
+
assert '"slug":"test"' in json_str
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests for domain use cases."""
|