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,496 @@
|
|
|
1
|
+
"""Tests for YAML manifest parser."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from julee.docs.sphinx_hcd.domain.models.app import AppType
|
|
8
|
+
from julee.docs.sphinx_hcd.domain.models.integration import Direction
|
|
9
|
+
from julee.docs.sphinx_hcd.parsers.yaml import (
|
|
10
|
+
parse_app_manifest,
|
|
11
|
+
parse_integration_manifest,
|
|
12
|
+
parse_manifest_content,
|
|
13
|
+
scan_app_manifests,
|
|
14
|
+
scan_integration_manifests,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestParseManifestContent:
|
|
19
|
+
"""Test parse_manifest_content function."""
|
|
20
|
+
|
|
21
|
+
def test_parse_valid_yaml(self) -> None:
|
|
22
|
+
"""Test parsing valid YAML content."""
|
|
23
|
+
content = """
|
|
24
|
+
name: Staff Portal
|
|
25
|
+
type: staff
|
|
26
|
+
status: live
|
|
27
|
+
description: Portal for staff members
|
|
28
|
+
accelerators:
|
|
29
|
+
- user-auth
|
|
30
|
+
- doc-upload
|
|
31
|
+
"""
|
|
32
|
+
result = parse_manifest_content(content)
|
|
33
|
+
assert result is not None
|
|
34
|
+
assert result["name"] == "Staff Portal"
|
|
35
|
+
assert result["type"] == "staff"
|
|
36
|
+
assert result["status"] == "live"
|
|
37
|
+
assert result["accelerators"] == ["user-auth", "doc-upload"]
|
|
38
|
+
|
|
39
|
+
def test_parse_empty_content(self) -> None:
|
|
40
|
+
"""Test parsing empty content."""
|
|
41
|
+
result = parse_manifest_content("")
|
|
42
|
+
assert result is None
|
|
43
|
+
|
|
44
|
+
def test_parse_invalid_yaml(self) -> None:
|
|
45
|
+
"""Test parsing invalid YAML."""
|
|
46
|
+
content = """
|
|
47
|
+
name: Test
|
|
48
|
+
invalid yaml: [unclosed bracket
|
|
49
|
+
"""
|
|
50
|
+
result = parse_manifest_content(content)
|
|
51
|
+
assert result is None
|
|
52
|
+
|
|
53
|
+
def test_parse_minimal_yaml(self) -> None:
|
|
54
|
+
"""Test parsing minimal YAML."""
|
|
55
|
+
content = "name: Test App"
|
|
56
|
+
result = parse_manifest_content(content)
|
|
57
|
+
assert result is not None
|
|
58
|
+
assert result["name"] == "Test App"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TestParseAppManifest:
|
|
62
|
+
"""Test parse_app_manifest function."""
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def temp_project(self, tmp_path: Path) -> Path:
|
|
66
|
+
"""Create a temporary project structure."""
|
|
67
|
+
apps_dir = tmp_path / "apps"
|
|
68
|
+
apps_dir.mkdir()
|
|
69
|
+
return tmp_path
|
|
70
|
+
|
|
71
|
+
def test_parse_complete_manifest(self, temp_project: Path) -> None:
|
|
72
|
+
"""Test parsing a complete app manifest."""
|
|
73
|
+
app_dir = temp_project / "apps" / "staff-portal"
|
|
74
|
+
app_dir.mkdir(parents=True)
|
|
75
|
+
manifest = app_dir / "app.yaml"
|
|
76
|
+
manifest.write_text(
|
|
77
|
+
"""
|
|
78
|
+
name: Staff Portal
|
|
79
|
+
type: staff
|
|
80
|
+
status: live
|
|
81
|
+
description: Portal for staff members
|
|
82
|
+
accelerators:
|
|
83
|
+
- user-auth
|
|
84
|
+
"""
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
app = parse_app_manifest(manifest)
|
|
88
|
+
|
|
89
|
+
assert app is not None
|
|
90
|
+
assert app.slug == "staff-portal"
|
|
91
|
+
assert app.name == "Staff Portal"
|
|
92
|
+
assert app.app_type == AppType.STAFF
|
|
93
|
+
assert app.status == "live"
|
|
94
|
+
assert app.accelerators == ["user-auth"]
|
|
95
|
+
|
|
96
|
+
def test_parse_manifest_with_explicit_slug(self, temp_project: Path) -> None:
|
|
97
|
+
"""Test parsing with explicit app slug override."""
|
|
98
|
+
app_dir = temp_project / "apps" / "original-slug"
|
|
99
|
+
app_dir.mkdir(parents=True)
|
|
100
|
+
manifest = app_dir / "app.yaml"
|
|
101
|
+
manifest.write_text("name: Test App")
|
|
102
|
+
|
|
103
|
+
app = parse_app_manifest(manifest, app_slug="override-slug")
|
|
104
|
+
|
|
105
|
+
assert app is not None
|
|
106
|
+
assert app.slug == "override-slug"
|
|
107
|
+
|
|
108
|
+
def test_parse_manifest_default_name(self, temp_project: Path) -> None:
|
|
109
|
+
"""Test default name generated from slug."""
|
|
110
|
+
app_dir = temp_project / "apps" / "my-cool-app"
|
|
111
|
+
app_dir.mkdir(parents=True)
|
|
112
|
+
manifest = app_dir / "app.yaml"
|
|
113
|
+
manifest.write_text("type: staff")
|
|
114
|
+
|
|
115
|
+
app = parse_app_manifest(manifest)
|
|
116
|
+
|
|
117
|
+
assert app is not None
|
|
118
|
+
assert app.name == "My Cool App"
|
|
119
|
+
|
|
120
|
+
def test_parse_manifest_nonexistent(self, temp_project: Path) -> None:
|
|
121
|
+
"""Test parsing a nonexistent file returns None."""
|
|
122
|
+
nonexistent = temp_project / "apps" / "nonexistent" / "app.yaml"
|
|
123
|
+
app = parse_app_manifest(nonexistent)
|
|
124
|
+
assert app is None
|
|
125
|
+
|
|
126
|
+
def test_parse_manifest_empty_file(self, temp_project: Path) -> None:
|
|
127
|
+
"""Test parsing an empty manifest file."""
|
|
128
|
+
app_dir = temp_project / "apps" / "empty-app"
|
|
129
|
+
app_dir.mkdir(parents=True)
|
|
130
|
+
manifest = app_dir / "app.yaml"
|
|
131
|
+
manifest.write_text("")
|
|
132
|
+
|
|
133
|
+
app = parse_app_manifest(manifest)
|
|
134
|
+
assert app is None
|
|
135
|
+
|
|
136
|
+
def test_parse_manifest_invalid_yaml(self, temp_project: Path) -> None:
|
|
137
|
+
"""Test parsing invalid YAML returns None."""
|
|
138
|
+
app_dir = temp_project / "apps" / "bad-app"
|
|
139
|
+
app_dir.mkdir(parents=True)
|
|
140
|
+
manifest = app_dir / "app.yaml"
|
|
141
|
+
manifest.write_text("invalid: [unclosed")
|
|
142
|
+
|
|
143
|
+
app = parse_app_manifest(manifest)
|
|
144
|
+
assert app is None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class TestScanAppManifests:
|
|
148
|
+
"""Test scan_app_manifests function."""
|
|
149
|
+
|
|
150
|
+
@pytest.fixture
|
|
151
|
+
def temp_project(self, tmp_path: Path) -> Path:
|
|
152
|
+
"""Create a temporary project with multiple apps."""
|
|
153
|
+
apps_dir = tmp_path / "apps"
|
|
154
|
+
apps_dir.mkdir()
|
|
155
|
+
|
|
156
|
+
# Create app1
|
|
157
|
+
app1_dir = apps_dir / "staff-portal"
|
|
158
|
+
app1_dir.mkdir()
|
|
159
|
+
(app1_dir / "app.yaml").write_text(
|
|
160
|
+
"""
|
|
161
|
+
name: Staff Portal
|
|
162
|
+
type: staff
|
|
163
|
+
"""
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Create app2
|
|
167
|
+
app2_dir = apps_dir / "customer-portal"
|
|
168
|
+
app2_dir.mkdir()
|
|
169
|
+
(app2_dir / "app.yaml").write_text(
|
|
170
|
+
"""
|
|
171
|
+
name: Customer Portal
|
|
172
|
+
type: external
|
|
173
|
+
"""
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Create app3 (member tool)
|
|
177
|
+
app3_dir = apps_dir / "member-tool"
|
|
178
|
+
app3_dir.mkdir()
|
|
179
|
+
(app3_dir / "app.yaml").write_text(
|
|
180
|
+
"""
|
|
181
|
+
name: Member Tool
|
|
182
|
+
type: member-tool
|
|
183
|
+
"""
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return tmp_path
|
|
187
|
+
|
|
188
|
+
def test_scan_finds_all_apps(self, temp_project: Path) -> None:
|
|
189
|
+
"""Test scanning finds all app manifests."""
|
|
190
|
+
apps_dir = temp_project / "apps"
|
|
191
|
+
apps = scan_app_manifests(apps_dir)
|
|
192
|
+
|
|
193
|
+
assert len(apps) == 3
|
|
194
|
+
slugs = {a.slug for a in apps}
|
|
195
|
+
assert slugs == {"staff-portal", "customer-portal", "member-tool"}
|
|
196
|
+
|
|
197
|
+
def test_scan_extracts_types(self, temp_project: Path) -> None:
|
|
198
|
+
"""Test scanning correctly extracts app types."""
|
|
199
|
+
apps_dir = temp_project / "apps"
|
|
200
|
+
apps = scan_app_manifests(apps_dir)
|
|
201
|
+
|
|
202
|
+
types_by_slug = {a.slug: a.app_type for a in apps}
|
|
203
|
+
assert types_by_slug["staff-portal"] == AppType.STAFF
|
|
204
|
+
assert types_by_slug["customer-portal"] == AppType.EXTERNAL
|
|
205
|
+
assert types_by_slug["member-tool"] == AppType.MEMBER_TOOL
|
|
206
|
+
|
|
207
|
+
def test_scan_nonexistent_directory(self, tmp_path: Path) -> None:
|
|
208
|
+
"""Test scanning nonexistent directory returns empty list."""
|
|
209
|
+
apps = scan_app_manifests(tmp_path / "nonexistent")
|
|
210
|
+
assert apps == []
|
|
211
|
+
|
|
212
|
+
def test_scan_empty_directory(self, tmp_path: Path) -> None:
|
|
213
|
+
"""Test scanning empty directory returns empty list."""
|
|
214
|
+
empty_dir = tmp_path / "empty"
|
|
215
|
+
empty_dir.mkdir()
|
|
216
|
+
apps = scan_app_manifests(empty_dir)
|
|
217
|
+
assert apps == []
|
|
218
|
+
|
|
219
|
+
def test_scan_ignores_files_in_root(self, tmp_path: Path) -> None:
|
|
220
|
+
"""Test scanning ignores non-directory items."""
|
|
221
|
+
apps_dir = tmp_path / "apps"
|
|
222
|
+
apps_dir.mkdir()
|
|
223
|
+
|
|
224
|
+
# Create a file in the apps dir (should be ignored)
|
|
225
|
+
(apps_dir / "README.md").write_text("readme")
|
|
226
|
+
|
|
227
|
+
# Create a valid app
|
|
228
|
+
app_dir = apps_dir / "test-app"
|
|
229
|
+
app_dir.mkdir()
|
|
230
|
+
(app_dir / "app.yaml").write_text("name: Test App")
|
|
231
|
+
|
|
232
|
+
apps = scan_app_manifests(apps_dir)
|
|
233
|
+
assert len(apps) == 1
|
|
234
|
+
assert apps[0].slug == "test-app"
|
|
235
|
+
|
|
236
|
+
def test_scan_skips_directories_without_manifest(self, tmp_path: Path) -> None:
|
|
237
|
+
"""Test scanning skips directories without app.yaml."""
|
|
238
|
+
apps_dir = tmp_path / "apps"
|
|
239
|
+
apps_dir.mkdir()
|
|
240
|
+
|
|
241
|
+
# Create directory without manifest
|
|
242
|
+
(apps_dir / "no-manifest").mkdir()
|
|
243
|
+
|
|
244
|
+
# Create valid app
|
|
245
|
+
app_dir = apps_dir / "valid-app"
|
|
246
|
+
app_dir.mkdir()
|
|
247
|
+
(app_dir / "app.yaml").write_text("name: Valid App")
|
|
248
|
+
|
|
249
|
+
apps = scan_app_manifests(apps_dir)
|
|
250
|
+
assert len(apps) == 1
|
|
251
|
+
assert apps[0].slug == "valid-app"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# Integration manifest parsing tests
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class TestParseIntegrationManifest:
|
|
258
|
+
"""Test parse_integration_manifest function."""
|
|
259
|
+
|
|
260
|
+
@pytest.fixture
|
|
261
|
+
def temp_project(self, tmp_path: Path) -> Path:
|
|
262
|
+
"""Create a temporary project structure."""
|
|
263
|
+
integrations_dir = tmp_path / "integrations"
|
|
264
|
+
integrations_dir.mkdir()
|
|
265
|
+
return tmp_path
|
|
266
|
+
|
|
267
|
+
def test_parse_complete_manifest(self, temp_project: Path) -> None:
|
|
268
|
+
"""Test parsing a complete integration manifest."""
|
|
269
|
+
int_dir = temp_project / "integrations" / "pilot_data_collection"
|
|
270
|
+
int_dir.mkdir(parents=True)
|
|
271
|
+
manifest = int_dir / "integration.yaml"
|
|
272
|
+
manifest.write_text(
|
|
273
|
+
"""
|
|
274
|
+
slug: pilot-data
|
|
275
|
+
name: Pilot Data Collection
|
|
276
|
+
description: Collects pilot data from external systems
|
|
277
|
+
direction: inbound
|
|
278
|
+
depends_on:
|
|
279
|
+
- name: Pilot API
|
|
280
|
+
url: https://pilot.example.com
|
|
281
|
+
- name: Data Lake
|
|
282
|
+
"""
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
integration = parse_integration_manifest(manifest)
|
|
286
|
+
|
|
287
|
+
assert integration is not None
|
|
288
|
+
assert integration.slug == "pilot-data"
|
|
289
|
+
assert integration.module == "pilot_data_collection"
|
|
290
|
+
assert integration.name == "Pilot Data Collection"
|
|
291
|
+
assert integration.direction == Direction.INBOUND
|
|
292
|
+
assert len(integration.depends_on) == 2
|
|
293
|
+
assert integration.depends_on[0].name == "Pilot API"
|
|
294
|
+
assert integration.depends_on[0].url == "https://pilot.example.com"
|
|
295
|
+
|
|
296
|
+
def test_parse_manifest_with_explicit_module(self, temp_project: Path) -> None:
|
|
297
|
+
"""Test parsing with explicit module name override."""
|
|
298
|
+
int_dir = temp_project / "integrations" / "original_module"
|
|
299
|
+
int_dir.mkdir(parents=True)
|
|
300
|
+
manifest = int_dir / "integration.yaml"
|
|
301
|
+
manifest.write_text("name: Test Integration")
|
|
302
|
+
|
|
303
|
+
integration = parse_integration_manifest(
|
|
304
|
+
manifest, module_name="override_module"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
assert integration is not None
|
|
308
|
+
assert integration.module == "override_module"
|
|
309
|
+
|
|
310
|
+
def test_parse_manifest_default_slug(self, temp_project: Path) -> None:
|
|
311
|
+
"""Test default slug from module name."""
|
|
312
|
+
int_dir = temp_project / "integrations" / "my_integration"
|
|
313
|
+
int_dir.mkdir(parents=True)
|
|
314
|
+
manifest = int_dir / "integration.yaml"
|
|
315
|
+
manifest.write_text("name: My Integration")
|
|
316
|
+
|
|
317
|
+
integration = parse_integration_manifest(manifest)
|
|
318
|
+
|
|
319
|
+
assert integration is not None
|
|
320
|
+
assert integration.slug == "my-integration"
|
|
321
|
+
|
|
322
|
+
def test_parse_manifest_default_name(self, temp_project: Path) -> None:
|
|
323
|
+
"""Test default name from slug."""
|
|
324
|
+
int_dir = temp_project / "integrations" / "data_sync"
|
|
325
|
+
int_dir.mkdir(parents=True)
|
|
326
|
+
manifest = int_dir / "integration.yaml"
|
|
327
|
+
manifest.write_text("direction: outbound")
|
|
328
|
+
|
|
329
|
+
integration = parse_integration_manifest(manifest)
|
|
330
|
+
|
|
331
|
+
assert integration is not None
|
|
332
|
+
assert integration.name == "Data Sync"
|
|
333
|
+
|
|
334
|
+
def test_parse_manifest_default_direction(self, temp_project: Path) -> None:
|
|
335
|
+
"""Test default direction is bidirectional."""
|
|
336
|
+
int_dir = temp_project / "integrations" / "test"
|
|
337
|
+
int_dir.mkdir(parents=True)
|
|
338
|
+
manifest = int_dir / "integration.yaml"
|
|
339
|
+
manifest.write_text("name: Test")
|
|
340
|
+
|
|
341
|
+
integration = parse_integration_manifest(manifest)
|
|
342
|
+
|
|
343
|
+
assert integration is not None
|
|
344
|
+
assert integration.direction == Direction.BIDIRECTIONAL
|
|
345
|
+
|
|
346
|
+
def test_parse_manifest_nonexistent(self, temp_project: Path) -> None:
|
|
347
|
+
"""Test parsing a nonexistent file returns None."""
|
|
348
|
+
nonexistent = temp_project / "integrations" / "nonexistent" / "integration.yaml"
|
|
349
|
+
integration = parse_integration_manifest(nonexistent)
|
|
350
|
+
assert integration is None
|
|
351
|
+
|
|
352
|
+
def test_parse_manifest_empty_file(self, temp_project: Path) -> None:
|
|
353
|
+
"""Test parsing an empty manifest file."""
|
|
354
|
+
int_dir = temp_project / "integrations" / "empty"
|
|
355
|
+
int_dir.mkdir(parents=True)
|
|
356
|
+
manifest = int_dir / "integration.yaml"
|
|
357
|
+
manifest.write_text("")
|
|
358
|
+
|
|
359
|
+
integration = parse_integration_manifest(manifest)
|
|
360
|
+
assert integration is None
|
|
361
|
+
|
|
362
|
+
def test_parse_manifest_invalid_yaml(self, temp_project: Path) -> None:
|
|
363
|
+
"""Test parsing invalid YAML returns None."""
|
|
364
|
+
int_dir = temp_project / "integrations" / "bad"
|
|
365
|
+
int_dir.mkdir(parents=True)
|
|
366
|
+
manifest = int_dir / "integration.yaml"
|
|
367
|
+
manifest.write_text("invalid: [unclosed")
|
|
368
|
+
|
|
369
|
+
integration = parse_integration_manifest(manifest)
|
|
370
|
+
assert integration is None
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class TestScanIntegrationManifests:
|
|
374
|
+
"""Test scan_integration_manifests function."""
|
|
375
|
+
|
|
376
|
+
@pytest.fixture
|
|
377
|
+
def temp_project(self, tmp_path: Path) -> Path:
|
|
378
|
+
"""Create a temporary project with multiple integrations."""
|
|
379
|
+
integrations_dir = tmp_path / "integrations"
|
|
380
|
+
integrations_dir.mkdir()
|
|
381
|
+
|
|
382
|
+
# Create inbound integration
|
|
383
|
+
int1_dir = integrations_dir / "pilot_data"
|
|
384
|
+
int1_dir.mkdir()
|
|
385
|
+
(int1_dir / "integration.yaml").write_text(
|
|
386
|
+
"""
|
|
387
|
+
name: Pilot Data
|
|
388
|
+
direction: inbound
|
|
389
|
+
"""
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
# Create outbound integration
|
|
393
|
+
int2_dir = integrations_dir / "analytics_export"
|
|
394
|
+
int2_dir.mkdir()
|
|
395
|
+
(int2_dir / "integration.yaml").write_text(
|
|
396
|
+
"""
|
|
397
|
+
name: Analytics Export
|
|
398
|
+
direction: outbound
|
|
399
|
+
"""
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Create bidirectional integration
|
|
403
|
+
int3_dir = integrations_dir / "data_sync"
|
|
404
|
+
int3_dir.mkdir()
|
|
405
|
+
(int3_dir / "integration.yaml").write_text(
|
|
406
|
+
"""
|
|
407
|
+
name: Data Sync
|
|
408
|
+
direction: bidirectional
|
|
409
|
+
"""
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
return tmp_path
|
|
413
|
+
|
|
414
|
+
def test_scan_finds_all_integrations(self, temp_project: Path) -> None:
|
|
415
|
+
"""Test scanning finds all integration manifests."""
|
|
416
|
+
integrations_dir = temp_project / "integrations"
|
|
417
|
+
integrations = scan_integration_manifests(integrations_dir)
|
|
418
|
+
|
|
419
|
+
assert len(integrations) == 3
|
|
420
|
+
slugs = {i.slug for i in integrations}
|
|
421
|
+
assert slugs == {"pilot-data", "analytics-export", "data-sync"}
|
|
422
|
+
|
|
423
|
+
def test_scan_extracts_directions(self, temp_project: Path) -> None:
|
|
424
|
+
"""Test scanning correctly extracts directions."""
|
|
425
|
+
integrations_dir = temp_project / "integrations"
|
|
426
|
+
integrations = scan_integration_manifests(integrations_dir)
|
|
427
|
+
|
|
428
|
+
directions_by_slug = {i.slug: i.direction for i in integrations}
|
|
429
|
+
assert directions_by_slug["pilot-data"] == Direction.INBOUND
|
|
430
|
+
assert directions_by_slug["analytics-export"] == Direction.OUTBOUND
|
|
431
|
+
assert directions_by_slug["data-sync"] == Direction.BIDIRECTIONAL
|
|
432
|
+
|
|
433
|
+
def test_scan_nonexistent_directory(self, tmp_path: Path) -> None:
|
|
434
|
+
"""Test scanning nonexistent directory returns empty list."""
|
|
435
|
+
integrations = scan_integration_manifests(tmp_path / "nonexistent")
|
|
436
|
+
assert integrations == []
|
|
437
|
+
|
|
438
|
+
def test_scan_empty_directory(self, tmp_path: Path) -> None:
|
|
439
|
+
"""Test scanning empty directory returns empty list."""
|
|
440
|
+
empty_dir = tmp_path / "empty"
|
|
441
|
+
empty_dir.mkdir()
|
|
442
|
+
integrations = scan_integration_manifests(empty_dir)
|
|
443
|
+
assert integrations == []
|
|
444
|
+
|
|
445
|
+
def test_scan_ignores_underscore_directories(self, tmp_path: Path) -> None:
|
|
446
|
+
"""Test scanning ignores directories starting with underscore."""
|
|
447
|
+
integrations_dir = tmp_path / "integrations"
|
|
448
|
+
integrations_dir.mkdir()
|
|
449
|
+
|
|
450
|
+
# Create ignored directory
|
|
451
|
+
ignored_dir = integrations_dir / "_base"
|
|
452
|
+
ignored_dir.mkdir()
|
|
453
|
+
(ignored_dir / "integration.yaml").write_text("name: Base")
|
|
454
|
+
|
|
455
|
+
# Create valid integration
|
|
456
|
+
int_dir = integrations_dir / "valid"
|
|
457
|
+
int_dir.mkdir()
|
|
458
|
+
(int_dir / "integration.yaml").write_text("name: Valid")
|
|
459
|
+
|
|
460
|
+
integrations = scan_integration_manifests(integrations_dir)
|
|
461
|
+
assert len(integrations) == 1
|
|
462
|
+
assert integrations[0].slug == "valid"
|
|
463
|
+
|
|
464
|
+
def test_scan_ignores_files_in_root(self, tmp_path: Path) -> None:
|
|
465
|
+
"""Test scanning ignores non-directory items."""
|
|
466
|
+
integrations_dir = tmp_path / "integrations"
|
|
467
|
+
integrations_dir.mkdir()
|
|
468
|
+
|
|
469
|
+
# Create a file in the integrations dir (should be ignored)
|
|
470
|
+
(integrations_dir / "README.md").write_text("readme")
|
|
471
|
+
|
|
472
|
+
# Create valid integration
|
|
473
|
+
int_dir = integrations_dir / "test"
|
|
474
|
+
int_dir.mkdir()
|
|
475
|
+
(int_dir / "integration.yaml").write_text("name: Test")
|
|
476
|
+
|
|
477
|
+
integrations = scan_integration_manifests(integrations_dir)
|
|
478
|
+
assert len(integrations) == 1
|
|
479
|
+
assert integrations[0].slug == "test"
|
|
480
|
+
|
|
481
|
+
def test_scan_skips_directories_without_manifest(self, tmp_path: Path) -> None:
|
|
482
|
+
"""Test scanning skips directories without integration.yaml."""
|
|
483
|
+
integrations_dir = tmp_path / "integrations"
|
|
484
|
+
integrations_dir.mkdir()
|
|
485
|
+
|
|
486
|
+
# Create directory without manifest
|
|
487
|
+
(integrations_dir / "no_manifest").mkdir()
|
|
488
|
+
|
|
489
|
+
# Create valid integration
|
|
490
|
+
int_dir = integrations_dir / "valid"
|
|
491
|
+
int_dir.mkdir()
|
|
492
|
+
(int_dir / "integration.yaml").write_text("name: Valid")
|
|
493
|
+
|
|
494
|
+
integrations = scan_integration_manifests(integrations_dir)
|
|
495
|
+
assert len(integrations) == 1
|
|
496
|
+
assert integrations[0].slug == "valid"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Repository implementation tests."""
|