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,42 @@
|
|
|
1
|
+
"""Env-purge-doc event handler for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Clears document-specific state when a document is re-read.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ..directives import (
|
|
7
|
+
clear_accelerator_state,
|
|
8
|
+
clear_epic_state,
|
|
9
|
+
clear_journey_state,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def on_env_purge_doc(app, env, docname):
|
|
14
|
+
"""Clear state when a document is re-read.
|
|
15
|
+
|
|
16
|
+
This handler runs when a document needs to be re-read (incremental build).
|
|
17
|
+
It clears any state associated with that document.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
app: Sphinx application instance
|
|
21
|
+
env: Sphinx build environment
|
|
22
|
+
docname: The document name being purged
|
|
23
|
+
"""
|
|
24
|
+
# Clear epic state for this document
|
|
25
|
+
clear_epic_state(app, env, docname)
|
|
26
|
+
|
|
27
|
+
# Clear journey state for this document
|
|
28
|
+
clear_journey_state(app, env, docname)
|
|
29
|
+
|
|
30
|
+
# Clear accelerator state for this document
|
|
31
|
+
clear_accelerator_state(app, env, docname)
|
|
32
|
+
|
|
33
|
+
# Clear documented apps tracker
|
|
34
|
+
if hasattr(env, "documented_apps") and docname in env.documented_apps:
|
|
35
|
+
env.documented_apps.discard(docname)
|
|
36
|
+
|
|
37
|
+
# Clear documented integrations tracker
|
|
38
|
+
if (
|
|
39
|
+
hasattr(env, "documented_integrations")
|
|
40
|
+
and docname in env.documented_integrations
|
|
41
|
+
):
|
|
42
|
+
env.documented_integrations.discard(docname)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Sphinx initialization handlers for HCD context.
|
|
2
|
+
|
|
3
|
+
Handles builder-inited event to set up the HCDContext and populate
|
|
4
|
+
repositories with data that doesn't change during the build.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from ..config import get_config
|
|
10
|
+
from ..parsers import (
|
|
11
|
+
scan_app_manifests,
|
|
12
|
+
scan_bounded_contexts,
|
|
13
|
+
scan_feature_directory,
|
|
14
|
+
scan_integration_manifests,
|
|
15
|
+
)
|
|
16
|
+
from .context import HCDContext, set_hcd_context
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def initialize_hcd_context(app) -> None:
|
|
22
|
+
"""Initialize HCDContext at builder-inited.
|
|
23
|
+
|
|
24
|
+
Creates the context and populates repositories with data that is
|
|
25
|
+
static during the build:
|
|
26
|
+
- Stories (from .feature files)
|
|
27
|
+
- Apps (from app.yaml manifests)
|
|
28
|
+
- Integrations (from integration.yaml manifests)
|
|
29
|
+
- Code info (from src/ introspection)
|
|
30
|
+
|
|
31
|
+
Journeys, epics, and accelerators are populated during doctree
|
|
32
|
+
processing as they're defined in RST files.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
app: Sphinx application object
|
|
36
|
+
"""
|
|
37
|
+
context = HCDContext()
|
|
38
|
+
set_hcd_context(app, context)
|
|
39
|
+
|
|
40
|
+
config = get_config()
|
|
41
|
+
|
|
42
|
+
# Load stories from feature files
|
|
43
|
+
_load_stories(context, config)
|
|
44
|
+
|
|
45
|
+
# Load apps from manifests
|
|
46
|
+
_load_apps(context, config)
|
|
47
|
+
|
|
48
|
+
# Load integrations from manifests
|
|
49
|
+
_load_integrations(context, config)
|
|
50
|
+
|
|
51
|
+
# Load code info from src/ introspection
|
|
52
|
+
_load_code_info(context, config)
|
|
53
|
+
|
|
54
|
+
logger.info("HCDContext initialized")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _load_stories(context: HCDContext, config) -> None:
|
|
58
|
+
"""Load stories from feature files into the repository."""
|
|
59
|
+
features_dir = config.get_path("feature_files")
|
|
60
|
+
if not features_dir.exists():
|
|
61
|
+
logger.info(f"Features directory not found: {features_dir}")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
stories = scan_feature_directory(features_dir, config.project_root)
|
|
65
|
+
for story in stories:
|
|
66
|
+
context.story_repo.save(story)
|
|
67
|
+
|
|
68
|
+
logger.info(f"Loaded {len(stories)} stories from feature files")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _load_apps(context: HCDContext, config) -> None:
|
|
72
|
+
"""Load apps from manifest files into the repository."""
|
|
73
|
+
apps_dir = config.get_path("app_manifests")
|
|
74
|
+
if not apps_dir.exists():
|
|
75
|
+
logger.info(f"Applications directory not found: {apps_dir}")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
apps = scan_app_manifests(apps_dir)
|
|
79
|
+
for app in apps:
|
|
80
|
+
context.app_repo.save(app)
|
|
81
|
+
|
|
82
|
+
logger.info(f"Loaded {len(apps)} apps from manifests")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _load_integrations(context: HCDContext, config) -> None:
|
|
86
|
+
"""Load integrations from manifest files into the repository."""
|
|
87
|
+
integrations_dir = config.get_path("integration_manifests")
|
|
88
|
+
if not integrations_dir.exists():
|
|
89
|
+
logger.info(f"Integrations directory not found: {integrations_dir}")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
integrations = scan_integration_manifests(integrations_dir)
|
|
93
|
+
for integration in integrations:
|
|
94
|
+
context.integration_repo.save(integration)
|
|
95
|
+
|
|
96
|
+
logger.info(f"Loaded {len(integrations)} integrations from manifests")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _load_code_info(context: HCDContext, config) -> None:
|
|
100
|
+
"""Load code info from src/ introspection into the repository."""
|
|
101
|
+
src_dir = config.get_path("bounded_contexts")
|
|
102
|
+
if not src_dir.exists():
|
|
103
|
+
logger.info(f"Source directory not found: {src_dir}")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
contexts = scan_bounded_contexts(src_dir)
|
|
107
|
+
for code_info in contexts:
|
|
108
|
+
context.code_info_repo.save(code_info)
|
|
109
|
+
|
|
110
|
+
logger.info(f"Loaded {len(contexts)} bounded contexts from source")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def purge_doc_from_context(app, env, docname: str) -> None:
|
|
114
|
+
"""Purge entities from a document when it's being re-read.
|
|
115
|
+
|
|
116
|
+
Called during env-purge-doc event for incremental builds.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
app: Sphinx application object
|
|
120
|
+
env: Sphinx environment
|
|
121
|
+
docname: Document being purged
|
|
122
|
+
"""
|
|
123
|
+
from .context import get_hcd_context
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
context = get_hcd_context(app)
|
|
127
|
+
results = context.clear_by_docname(docname)
|
|
128
|
+
|
|
129
|
+
total = sum(results.values())
|
|
130
|
+
if total > 0:
|
|
131
|
+
logger.debug(
|
|
132
|
+
f"Purged from {docname}: "
|
|
133
|
+
f"{results.get('journeys', 0)} journeys, "
|
|
134
|
+
f"{results.get('epics', 0)} epics, "
|
|
135
|
+
f"{results.get('accelerators', 0)} accelerators"
|
|
136
|
+
)
|
|
137
|
+
except AttributeError:
|
|
138
|
+
# Context not initialized yet - this is fine during startup
|
|
139
|
+
pass
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Tests for sphinx_hcd.
|
|
2
|
+
|
|
3
|
+
Organized by layer:
|
|
4
|
+
- domain/: Domain model and use case tests
|
|
5
|
+
- repositories/: Repository implementation tests
|
|
6
|
+
- parsers/: Parser tests
|
|
7
|
+
- sphinx/: Sphinx adapter and directive tests
|
|
8
|
+
- integration/: Full Sphinx build tests
|
|
9
|
+
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Domain layer tests."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Domain model tests."""
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Tests for Accelerator domain model."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from julee.docs.sphinx_hcd.domain.models.accelerator import (
|
|
7
|
+
Accelerator,
|
|
8
|
+
IntegrationReference,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestIntegrationReference:
|
|
13
|
+
"""Test IntegrationReference model."""
|
|
14
|
+
|
|
15
|
+
def test_create_with_slug_only(self) -> None:
|
|
16
|
+
"""Test creating with just slug."""
|
|
17
|
+
ref = IntegrationReference(slug="pilot-data")
|
|
18
|
+
assert ref.slug == "pilot-data"
|
|
19
|
+
assert ref.description == ""
|
|
20
|
+
|
|
21
|
+
def test_create_with_description(self) -> None:
|
|
22
|
+
"""Test creating with description."""
|
|
23
|
+
ref = IntegrationReference(
|
|
24
|
+
slug="pilot-data",
|
|
25
|
+
description="Scheme documentation, standards materials",
|
|
26
|
+
)
|
|
27
|
+
assert ref.slug == "pilot-data"
|
|
28
|
+
assert ref.description == "Scheme documentation, standards materials"
|
|
29
|
+
|
|
30
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
31
|
+
"""Test that empty slug raises validation error."""
|
|
32
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
33
|
+
IntegrationReference(slug="")
|
|
34
|
+
|
|
35
|
+
def test_from_dict_complete(self) -> None:
|
|
36
|
+
"""Test from_dict with full dict."""
|
|
37
|
+
ref = IntegrationReference.from_dict(
|
|
38
|
+
{
|
|
39
|
+
"slug": "pilot-data",
|
|
40
|
+
"description": "Test description",
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
assert ref.slug == "pilot-data"
|
|
44
|
+
assert ref.description == "Test description"
|
|
45
|
+
|
|
46
|
+
def test_from_dict_string(self) -> None:
|
|
47
|
+
"""Test from_dict with plain string."""
|
|
48
|
+
ref = IntegrationReference.from_dict("pilot-data")
|
|
49
|
+
assert ref.slug == "pilot-data"
|
|
50
|
+
assert ref.description == ""
|
|
51
|
+
|
|
52
|
+
def test_from_dict_minimal(self) -> None:
|
|
53
|
+
"""Test from_dict with minimal dict."""
|
|
54
|
+
ref = IntegrationReference.from_dict({"slug": "pilot-data"})
|
|
55
|
+
assert ref.slug == "pilot-data"
|
|
56
|
+
assert ref.description == ""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestAcceleratorCreation:
|
|
60
|
+
"""Test Accelerator model creation and validation."""
|
|
61
|
+
|
|
62
|
+
def test_create_accelerator_minimal(self) -> None:
|
|
63
|
+
"""Test creating an accelerator with minimum fields."""
|
|
64
|
+
accel = Accelerator(slug="vocabulary")
|
|
65
|
+
assert accel.slug == "vocabulary"
|
|
66
|
+
assert accel.status == ""
|
|
67
|
+
assert accel.milestone is None
|
|
68
|
+
assert accel.acceptance is None
|
|
69
|
+
assert accel.objective == ""
|
|
70
|
+
assert accel.sources_from == []
|
|
71
|
+
assert accel.feeds_into == []
|
|
72
|
+
assert accel.publishes_to == []
|
|
73
|
+
assert accel.depends_on == []
|
|
74
|
+
assert accel.docname == ""
|
|
75
|
+
|
|
76
|
+
def test_create_accelerator_complete(self) -> None:
|
|
77
|
+
"""Test creating an accelerator with all fields."""
|
|
78
|
+
accel = Accelerator(
|
|
79
|
+
slug="vocabulary",
|
|
80
|
+
status="alpha",
|
|
81
|
+
milestone="2 (Nov 2025)",
|
|
82
|
+
acceptance="Reference environment deployed and accepted.",
|
|
83
|
+
objective="Accelerate the creation of Sustainable Vocabulary Catalogs.",
|
|
84
|
+
sources_from=[
|
|
85
|
+
IntegrationReference(
|
|
86
|
+
slug="pilot-data-collection",
|
|
87
|
+
description="Scheme documentation, standards materials",
|
|
88
|
+
),
|
|
89
|
+
],
|
|
90
|
+
feeds_into=["traceability", "conformity"],
|
|
91
|
+
publishes_to=[
|
|
92
|
+
IntegrationReference(
|
|
93
|
+
slug="reference-implementation",
|
|
94
|
+
description="SVC artefacts",
|
|
95
|
+
),
|
|
96
|
+
],
|
|
97
|
+
depends_on=["core-infrastructure"],
|
|
98
|
+
docname="accelerators/vocabulary",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert accel.slug == "vocabulary"
|
|
102
|
+
assert accel.status == "alpha"
|
|
103
|
+
assert accel.milestone == "2 (Nov 2025)"
|
|
104
|
+
assert len(accel.sources_from) == 1
|
|
105
|
+
assert accel.sources_from[0].slug == "pilot-data-collection"
|
|
106
|
+
assert len(accel.feeds_into) == 2
|
|
107
|
+
assert len(accel.publishes_to) == 1
|
|
108
|
+
|
|
109
|
+
def test_empty_slug_raises_error(self) -> None:
|
|
110
|
+
"""Test that empty slug raises validation error."""
|
|
111
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
112
|
+
Accelerator(slug="")
|
|
113
|
+
|
|
114
|
+
def test_whitespace_slug_raises_error(self) -> None:
|
|
115
|
+
"""Test that whitespace-only slug raises validation error."""
|
|
116
|
+
with pytest.raises(ValidationError, match="slug cannot be empty"):
|
|
117
|
+
Accelerator(slug=" ")
|
|
118
|
+
|
|
119
|
+
def test_slug_stripped(self) -> None:
|
|
120
|
+
"""Test that slug is stripped of whitespace."""
|
|
121
|
+
accel = Accelerator(slug=" vocabulary ")
|
|
122
|
+
assert accel.slug == "vocabulary"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class TestAcceleratorProperties:
|
|
126
|
+
"""Test Accelerator properties."""
|
|
127
|
+
|
|
128
|
+
def test_display_title(self) -> None:
|
|
129
|
+
"""Test display_title property."""
|
|
130
|
+
accel = Accelerator(slug="vocabulary")
|
|
131
|
+
assert accel.display_title == "Vocabulary"
|
|
132
|
+
|
|
133
|
+
def test_display_title_multiple_words(self) -> None:
|
|
134
|
+
"""Test display_title with hyphens."""
|
|
135
|
+
accel = Accelerator(slug="core-infrastructure")
|
|
136
|
+
assert accel.display_title == "Core Infrastructure"
|
|
137
|
+
|
|
138
|
+
def test_status_normalized(self) -> None:
|
|
139
|
+
"""Test status_normalized property."""
|
|
140
|
+
accel = Accelerator(slug="test", status="Alpha")
|
|
141
|
+
assert accel.status_normalized == "alpha"
|
|
142
|
+
|
|
143
|
+
def test_status_normalized_empty(self) -> None:
|
|
144
|
+
"""Test status_normalized with empty status."""
|
|
145
|
+
accel = Accelerator(slug="test")
|
|
146
|
+
assert accel.status_normalized == ""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TestAcceleratorDependencies:
|
|
150
|
+
"""Test Accelerator dependency methods."""
|
|
151
|
+
|
|
152
|
+
@pytest.fixture
|
|
153
|
+
def sample_accelerator(self) -> Accelerator:
|
|
154
|
+
"""Create a sample accelerator for testing."""
|
|
155
|
+
return Accelerator(
|
|
156
|
+
slug="vocabulary",
|
|
157
|
+
sources_from=[
|
|
158
|
+
IntegrationReference(slug="pilot-data", description="Pilot data"),
|
|
159
|
+
IntegrationReference(slug="standards", description="Standards"),
|
|
160
|
+
],
|
|
161
|
+
publishes_to=[
|
|
162
|
+
IntegrationReference(slug="reference-impl", description="SVC"),
|
|
163
|
+
],
|
|
164
|
+
feeds_into=["traceability", "conformity"],
|
|
165
|
+
depends_on=["core-infrastructure"],
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def test_has_integration_dependency_sources(
|
|
169
|
+
self, sample_accelerator: Accelerator
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Test checking sources_from dependency."""
|
|
172
|
+
assert sample_accelerator.has_integration_dependency("pilot-data") is True
|
|
173
|
+
assert sample_accelerator.has_integration_dependency("standards") is True
|
|
174
|
+
|
|
175
|
+
def test_has_integration_dependency_publishes(
|
|
176
|
+
self, sample_accelerator: Accelerator
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Test checking publishes_to dependency."""
|
|
179
|
+
assert sample_accelerator.has_integration_dependency("reference-impl") is True
|
|
180
|
+
|
|
181
|
+
def test_has_integration_dependency_no_match(
|
|
182
|
+
self, sample_accelerator: Accelerator
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Test checking nonexistent dependency."""
|
|
185
|
+
assert sample_accelerator.has_integration_dependency("unknown") is False
|
|
186
|
+
|
|
187
|
+
def test_has_accelerator_dependency_depends(
|
|
188
|
+
self, sample_accelerator: Accelerator
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Test checking depends_on dependency."""
|
|
191
|
+
assert (
|
|
192
|
+
sample_accelerator.has_accelerator_dependency("core-infrastructure") is True
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def test_has_accelerator_dependency_feeds(
|
|
196
|
+
self, sample_accelerator: Accelerator
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Test checking feeds_into dependency."""
|
|
199
|
+
assert sample_accelerator.has_accelerator_dependency("traceability") is True
|
|
200
|
+
assert sample_accelerator.has_accelerator_dependency("conformity") is True
|
|
201
|
+
|
|
202
|
+
def test_has_accelerator_dependency_no_match(
|
|
203
|
+
self, sample_accelerator: Accelerator
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Test checking nonexistent accelerator dependency."""
|
|
206
|
+
assert sample_accelerator.has_accelerator_dependency("unknown") is False
|
|
207
|
+
|
|
208
|
+
def test_get_sources_from_slugs(self, sample_accelerator: Accelerator) -> None:
|
|
209
|
+
"""Test getting source integration slugs."""
|
|
210
|
+
slugs = sample_accelerator.get_sources_from_slugs()
|
|
211
|
+
assert slugs == ["pilot-data", "standards"]
|
|
212
|
+
|
|
213
|
+
def test_get_publishes_to_slugs(self, sample_accelerator: Accelerator) -> None:
|
|
214
|
+
"""Test getting publish integration slugs."""
|
|
215
|
+
slugs = sample_accelerator.get_publishes_to_slugs()
|
|
216
|
+
assert slugs == ["reference-impl"]
|
|
217
|
+
|
|
218
|
+
def test_get_integration_description_sources(
|
|
219
|
+
self, sample_accelerator: Accelerator
|
|
220
|
+
) -> None:
|
|
221
|
+
"""Test getting description from sources_from."""
|
|
222
|
+
desc = sample_accelerator.get_integration_description(
|
|
223
|
+
"pilot-data", "sources_from"
|
|
224
|
+
)
|
|
225
|
+
assert desc == "Pilot data"
|
|
226
|
+
|
|
227
|
+
def test_get_integration_description_publishes(
|
|
228
|
+
self, sample_accelerator: Accelerator
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Test getting description from publishes_to."""
|
|
231
|
+
desc = sample_accelerator.get_integration_description(
|
|
232
|
+
"reference-impl", "publishes_to"
|
|
233
|
+
)
|
|
234
|
+
assert desc == "SVC"
|
|
235
|
+
|
|
236
|
+
def test_get_integration_description_not_found(
|
|
237
|
+
self, sample_accelerator: Accelerator
|
|
238
|
+
) -> None:
|
|
239
|
+
"""Test getting description for nonexistent integration."""
|
|
240
|
+
desc = sample_accelerator.get_integration_description("unknown", "sources_from")
|
|
241
|
+
assert desc is None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class TestAcceleratorSerialization:
|
|
245
|
+
"""Test Accelerator serialization."""
|
|
246
|
+
|
|
247
|
+
def test_accelerator_to_dict(self) -> None:
|
|
248
|
+
"""Test accelerator can be serialized to dict."""
|
|
249
|
+
accel = Accelerator(
|
|
250
|
+
slug="test",
|
|
251
|
+
status="alpha",
|
|
252
|
+
sources_from=[IntegrationReference(slug="pilot", description="Data")],
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
data = accel.model_dump()
|
|
256
|
+
assert data["slug"] == "test"
|
|
257
|
+
assert data["status"] == "alpha"
|
|
258
|
+
assert len(data["sources_from"]) == 1
|
|
259
|
+
assert data["sources_from"][0]["slug"] == "pilot"
|
|
260
|
+
|
|
261
|
+
def test_accelerator_to_json(self) -> None:
|
|
262
|
+
"""Test accelerator can be serialized to JSON."""
|
|
263
|
+
accel = Accelerator(slug="test", status="alpha")
|
|
264
|
+
json_str = accel.model_dump_json()
|
|
265
|
+
assert '"slug":"test"' in json_str
|
|
266
|
+
assert '"status":"alpha"' in json_str
|