@flisk-sh/flisk 1.4.0 → 1.4.1
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.
- package/cli/dist/commands/doctor.js +62 -0
- package/cli/dist/commands/runtime.js +6 -3
- package/cli/dist/generator/template-engine.js +7 -4
- package/cli/dist/utils/runtime-layout.js +7 -0
- package/cli/dist-bundle/chunk-7HR5PVOD.js +54 -0
- package/cli/dist-bundle/{chunk-PVBAGX5X.js → chunk-7IXBBU27.js} +1 -1
- package/cli/dist-bundle/{chunk-VHFFSBVJ.js → chunk-BB5BZ37Y.js} +1 -1
- package/cli/dist-bundle/{chunk-D7N2GGJH.js → chunk-DUNCELUK.js} +1 -1
- package/cli/dist-bundle/{chunk-36SOQVAL.js → chunk-KJOCVAMQ.js} +1 -1
- package/cli/dist-bundle/{chunk-VMB5673O.js → chunk-L6U555CJ.js} +30 -26
- package/cli/dist-bundle/{chunk-HEMPQKD6.js → chunk-M2R464RM.js} +1 -1
- package/cli/dist-bundle/{chunk-QQEV6D74.js → chunk-RX6O3PG4.js} +1 -1
- package/cli/dist-bundle/{chunk-ICXF6PXA.js → chunk-SRN64OPO.js} +2 -2
- package/cli/dist-bundle/{chunk-5EZ4EX6U.js → chunk-TZNXL3IO.js} +1 -1
- package/cli/dist-bundle/{chunk-B6Q5VKL7.js → chunk-UJXR6MZ2.js} +1 -1
- package/cli/dist-bundle/{chunk-BWK6MPCB.js → chunk-VRESY4NR.js} +1 -1
- package/cli/dist-bundle/cli.js +153 -153
- package/cli/dist-bundle/{cold-preflight-ABU26DYW.js → cold-preflight-BBDDHWRV.js} +1 -1
- package/cli/dist-bundle/{demo-stage-EBIUCJBX.js → demo-stage-HIHO4VV3.js} +1 -1
- package/cli/dist-bundle/{open-2KSAF6M2.js → open-W3QX35R6.js} +1 -1
- package/cli/dist-bundle/project-FW5LL2KM.js +2 -0
- package/cli/dist-bundle/{services-V6Q55JR5.js → services-RGTQ2LXC.js} +1 -1
- package/cli/dist-bundle/{tilt-port-owner-MWJ7P6NB.js → tilt-port-owner-6DO7OJRR.js} +1 -1
- package/cli/package.json +1 -1
- package/package.json +11 -2
- package/runtime/platform/services/platform/proxy/service.yaml +14 -0
- package/cli/dist/commands/__tests__/project-e2e-helpers.js +0 -49
- package/cli/dist/commands/__tests__/ui-loading-screen.test.js +0 -16
- package/cli/dist/generator/__tests__/golden-helpers.js +0 -43
- package/cli/dist-bundle/chunk-763VK575.js +0 -54
- package/cli/dist-bundle/project-YD3C4ZV3.js +0 -2
- package/runtime/discovery/test_resource_snapshot.py +0 -253
- package/runtime/engine/assets/docker/wake-gateway/decide.test.ts +0 -177
- package/runtime/engine/topologies/tilt/manifest/tests/test_integration.star +0 -278
- package/runtime/engine/topologies/tilt/manifest/tests/test_loader_parser.star +0 -475
- package/runtime/engine/topologies/tilt/manifest/tests/test_manifest.star +0 -319
- package/runtime/engine/topologies/tilt/manifest/tests/test_validator.star +0 -472
- package/runtime/ext/ide-components/shared/test_config_service.py +0 -328
- /package/{shared-platform-engineering → runtime/shared-platform-engineering}/docker-templates/bun-hoisted-symlink-fix.sh +0 -0
- /package/{shared-platform-engineering → runtime/shared-platform-engineering}/docker-templates/infisical-entrypoint.sh +0 -0
- /package/{shared-platform-engineering → runtime/shared-platform-engineering}/docker-templates/install-deps.sh +0 -0
- /package/{shared-platform-engineering → runtime/shared-platform-engineering}/docker-templates/prisma-bun-client-link-fix.sh +0 -0
- /package/{shared-platform-engineering → runtime/shared-platform-engineering}/docker-templates/prisma-normalize-client.sh +0 -0
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2026 Flisk Landscape contributors
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
# =============================================================================
|
|
4
|
-
# 📋 MANIFEST MODULE - INTEGRATION TESTS (Phase 2)
|
|
5
|
-
# =============================================================================
|
|
6
|
-
# Path: .tilt/topologies/tilt/manifest/tests/test_loader_parser.star
|
|
7
|
-
# Purpose: Integration tests for loader and parser modules
|
|
8
|
-
# Status: Phase 2 of manifest system refactoring
|
|
9
|
-
# =============================================================================
|
|
10
|
-
|
|
11
|
-
load("../__init__.star", "Manifest")
|
|
12
|
-
load("../loader.star", "ManifestLoader")
|
|
13
|
-
load("../parser.star", "ManifestParser")
|
|
14
|
-
|
|
15
|
-
# =============================================================================
|
|
16
|
-
# TEST FRAMEWORK
|
|
17
|
-
# =============================================================================
|
|
18
|
-
|
|
19
|
-
test_results = {
|
|
20
|
-
'passed': 0,
|
|
21
|
-
'failed': 0,
|
|
22
|
-
'errors': [],
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
def assert_true(condition, message="Assertion failed"):
|
|
26
|
-
"""Assert that condition is true."""
|
|
27
|
-
if not condition:
|
|
28
|
-
test_results['failed'] += 1
|
|
29
|
-
test_results['errors'].append("FAIL: " + message)
|
|
30
|
-
else:
|
|
31
|
-
test_results['passed'] += 1
|
|
32
|
-
|
|
33
|
-
def assert_equal(expected, actual, message="Values not equal"):
|
|
34
|
-
"""Assert that expected equals actual."""
|
|
35
|
-
if expected != actual:
|
|
36
|
-
test_results['failed'] += 1
|
|
37
|
-
test_results['errors'].append(
|
|
38
|
-
"FAIL: {} - expected '{}', got '{}'".format(message, expected, actual)
|
|
39
|
-
)
|
|
40
|
-
else:
|
|
41
|
-
test_results['passed'] += 1
|
|
42
|
-
|
|
43
|
-
def assert_not_none(value, message="Value is None"):
|
|
44
|
-
"""Assert that value is not None."""
|
|
45
|
-
if value == None:
|
|
46
|
-
test_results['failed'] += 1
|
|
47
|
-
test_results['errors'].append("FAIL: " + message)
|
|
48
|
-
else:
|
|
49
|
-
test_results['passed'] += 1
|
|
50
|
-
|
|
51
|
-
def assert_in(value, collection, message="Value not in collection"):
|
|
52
|
-
"""Assert that value is in collection."""
|
|
53
|
-
found = False
|
|
54
|
-
for item in collection:
|
|
55
|
-
if item == value:
|
|
56
|
-
found = True
|
|
57
|
-
break
|
|
58
|
-
|
|
59
|
-
if not found:
|
|
60
|
-
test_results['failed'] += 1
|
|
61
|
-
test_results['errors'].append(
|
|
62
|
-
"FAIL: {} - '{}' not in collection".format(message, value)
|
|
63
|
-
)
|
|
64
|
-
else:
|
|
65
|
-
test_results['passed'] += 1
|
|
66
|
-
|
|
67
|
-
# =============================================================================
|
|
68
|
-
# TEST SUITE: Loader Module
|
|
69
|
-
# =============================================================================
|
|
70
|
-
|
|
71
|
-
def _test_loader_functions():
|
|
72
|
-
"""Test loader functions."""
|
|
73
|
-
print("🔍 Testing loader module...")
|
|
74
|
-
|
|
75
|
-
# Test that loader functions are accessible via facade
|
|
76
|
-
assert_not_none(Manifest.load_from_file, "load_from_file should be accessible")
|
|
77
|
-
assert_not_none(Manifest.load_from_path, "load_from_path should be accessible")
|
|
78
|
-
assert_not_none(Manifest.load_all, "load_all should be accessible")
|
|
79
|
-
assert_not_none(Manifest.clear_cache, "clear_cache should be accessible")
|
|
80
|
-
assert_not_none(Manifest.get_cache_stats, "get_cache_stats should be accessible")
|
|
81
|
-
assert_not_none(Manifest.is_cached, "is_cached should be accessible")
|
|
82
|
-
assert_not_none(Manifest.get_manifest_path, "get_manifest_path should be accessible")
|
|
83
|
-
|
|
84
|
-
# Test get_manifest_path
|
|
85
|
-
path = Manifest.get_manifest_path("services/product/user/user-management-backend")
|
|
86
|
-
assert_true(
|
|
87
|
-
path.endswith("service.json"),
|
|
88
|
-
"get_manifest_path should include manifest filename"
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# Test cache functions
|
|
92
|
-
Manifest.clear_cache()
|
|
93
|
-
stats = Manifest.get_cache_stats()
|
|
94
|
-
assert_equal(0, stats['entries'], "Cache should be empty after clear")
|
|
95
|
-
|
|
96
|
-
print("✅ Loader module tests complete\n")
|
|
97
|
-
|
|
98
|
-
# =============================================================================
|
|
99
|
-
# TEST SUITE: Parser Module
|
|
100
|
-
# =============================================================================
|
|
101
|
-
|
|
102
|
-
def _test_parser_functions():
|
|
103
|
-
"""Test parser functions."""
|
|
104
|
-
print("🔍 Testing parser module...")
|
|
105
|
-
|
|
106
|
-
# Test that parser functions are accessible via facade
|
|
107
|
-
assert_not_none(Manifest.parse, "parse should be accessible")
|
|
108
|
-
assert_not_none(Manifest.normalize, "normalize should be accessible")
|
|
109
|
-
assert_not_none(Manifest.get_normalized, "get_normalized should be accessible")
|
|
110
|
-
assert_not_none(Manifest.extract_resource_path, "extract_resource_path should be accessible")
|
|
111
|
-
assert_not_none(Manifest.extract_stack, "extract_stack should be accessible")
|
|
112
|
-
assert_not_none(Manifest.parse_traefik, "parse_traefik should be accessible")
|
|
113
|
-
assert_not_none(Manifest.merge_manifests, "merge_manifests should be accessible")
|
|
114
|
-
assert_not_none(Manifest.get_description, "get_description should be accessible")
|
|
115
|
-
|
|
116
|
-
print("✅ Parser module tests complete\n")
|
|
117
|
-
|
|
118
|
-
# =============================================================================
|
|
119
|
-
# TEST SUITE: Parse and Normalize
|
|
120
|
-
# =============================================================================
|
|
121
|
-
|
|
122
|
-
def _test_parse_and_normalize():
|
|
123
|
-
"""Test parsing and normalization together."""
|
|
124
|
-
print("🔍 Testing parse and normalize integration...")
|
|
125
|
-
|
|
126
|
-
# Test valid JSON parsing
|
|
127
|
-
valid_json = '''
|
|
128
|
-
{
|
|
129
|
-
"appName": "test-service",
|
|
130
|
-
"appType": "backend",
|
|
131
|
-
"stack": "user",
|
|
132
|
-
"port": 4001
|
|
133
|
-
}
|
|
134
|
-
'''
|
|
135
|
-
|
|
136
|
-
result = Manifest.parse(valid_json)
|
|
137
|
-
assert_not_none(result.manifest, "Should parse valid JSON")
|
|
138
|
-
assert_equal(None, result.error, "Should have no error for valid JSON")
|
|
139
|
-
|
|
140
|
-
# Test normalization
|
|
141
|
-
normalized = Manifest.normalize(result.manifest, "services/product/user/test-service")
|
|
142
|
-
assert_not_none(normalized, "Should normalize manifest")
|
|
143
|
-
assert_equal("test-service", normalized['appName'], "Should preserve appName")
|
|
144
|
-
assert_equal("backend", normalized['appType'], "Should preserve appType")
|
|
145
|
-
assert_equal("user", normalized['stack'], "Should preserve stack")
|
|
146
|
-
assert_equal(4001, normalized['port'], "Should preserve port")
|
|
147
|
-
|
|
148
|
-
# Test defaults are applied
|
|
149
|
-
assert_equal(1, normalized['replicas'], "Should apply default replicas")
|
|
150
|
-
assert_equal(3000, normalized['internalPort'], "Should apply default internalPort")
|
|
151
|
-
assert_equal("bun", normalized['runtime'], "Should apply default runtime")
|
|
152
|
-
assert_not_none(normalized['syncs'], "Should compute default syncs")
|
|
153
|
-
assert_not_none(normalized['traefik'], "Should create traefik config")
|
|
154
|
-
|
|
155
|
-
# Test _normalized flag is set
|
|
156
|
-
assert_equal(True, normalized.get('_normalized'), "Should set _normalized flag")
|
|
157
|
-
|
|
158
|
-
print("✅ Parse and normalize tests complete\n")
|
|
159
|
-
|
|
160
|
-
# =============================================================================
|
|
161
|
-
# TEST SUITE: Domain Extraction
|
|
162
|
-
# =============================================================================
|
|
163
|
-
|
|
164
|
-
def _test_stack_extraction():
|
|
165
|
-
"""Test stack extraction from resource paths."""
|
|
166
|
-
print("🔍 Testing stack extraction...")
|
|
167
|
-
|
|
168
|
-
# Standard paths
|
|
169
|
-
stack = Manifest.extract_stack("services/product/user/user-management-backend")
|
|
170
|
-
assert_equal("user", stack, "Should extract user stack")
|
|
171
|
-
|
|
172
|
-
stack = Manifest.extract_stack("services/product/order/order-management-backend")
|
|
173
|
-
assert_equal("order", stack, "Should extract order stack")
|
|
174
|
-
|
|
175
|
-
stack = Manifest.extract_stack("services/product/identity/identity-management-backend")
|
|
176
|
-
assert_equal("identity", stack, "Should extract identity stack")
|
|
177
|
-
|
|
178
|
-
# Special case: order-planner
|
|
179
|
-
stack = Manifest.extract_stack("services/product/order-planner/order-planner-backend")
|
|
180
|
-
assert_equal("order-planner", stack, "Should extract order-planner stack")
|
|
181
|
-
|
|
182
|
-
# Special case: profile -> identity
|
|
183
|
-
stack = Manifest.extract_stack("services/product/profile/profile-management-backend")
|
|
184
|
-
assert_equal("identity", stack, "Should map profile to identity stack")
|
|
185
|
-
|
|
186
|
-
print("✅ Stack extraction tests complete\n")
|
|
187
|
-
|
|
188
|
-
# =============================================================================
|
|
189
|
-
# TEST SUITE: Frontend Manifest Normalization
|
|
190
|
-
# =============================================================================
|
|
191
|
-
|
|
192
|
-
def _test_frontend_normalization():
|
|
193
|
-
"""Test normalization for frontend manifests."""
|
|
194
|
-
print("🔍 Testing frontend normalization...")
|
|
195
|
-
|
|
196
|
-
frontend_json = '''
|
|
197
|
-
{
|
|
198
|
-
"appName": "user-management-frontend",
|
|
199
|
-
"appType": "frontend",
|
|
200
|
-
"stack": "user",
|
|
201
|
-
"port": 3000,
|
|
202
|
-
"backendName": "user-management-backend"
|
|
203
|
-
}
|
|
204
|
-
'''
|
|
205
|
-
|
|
206
|
-
result = Manifest.parse(frontend_json)
|
|
207
|
-
normalized = Manifest.normalize(result.manifest, "services/product/user/user-management-frontend")
|
|
208
|
-
|
|
209
|
-
assert_equal("frontend", normalized['appType'], "Should preserve frontend appType")
|
|
210
|
-
assert_equal("user-management-backend", normalized['backendName'], "Should preserve backendName")
|
|
211
|
-
|
|
212
|
-
# Test backendName auto-computation
|
|
213
|
-
frontend_json_no_backend = '''
|
|
214
|
-
{
|
|
215
|
-
"appName": "test-frontend",
|
|
216
|
-
"appType": "frontend",
|
|
217
|
-
"stack": "user",
|
|
218
|
-
"port": 3001
|
|
219
|
-
}
|
|
220
|
-
'''
|
|
221
|
-
|
|
222
|
-
result2 = Manifest.parse(frontend_json_no_backend)
|
|
223
|
-
normalized2 = Manifest.normalize(result2.manifest, "services/product/user/test-frontend")
|
|
224
|
-
assert_equal("test-backend", normalized2['backendName'], "Should auto-compute backendName")
|
|
225
|
-
|
|
226
|
-
print("✅ Frontend normalization tests complete\n")
|
|
227
|
-
|
|
228
|
-
# =============================================================================
|
|
229
|
-
# TEST SUITE: Traefik Configuration
|
|
230
|
-
# =============================================================================
|
|
231
|
-
|
|
232
|
-
def _test_traefik_config():
|
|
233
|
-
"""Test Traefik configuration parsing."""
|
|
234
|
-
print("🔍 Testing Traefik configuration...")
|
|
235
|
-
|
|
236
|
-
# Backend with explicit traefik config
|
|
237
|
-
backend_json = '''
|
|
238
|
-
{
|
|
239
|
-
"appName": "user-management-backend",
|
|
240
|
-
"appType": "backend",
|
|
241
|
-
"stack": "user",
|
|
242
|
-
"port": 4000,
|
|
243
|
-
"traefik": {
|
|
244
|
-
"pathPrefix": "/api/v1/users",
|
|
245
|
-
"priority": 200
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
'''
|
|
249
|
-
|
|
250
|
-
result = Manifest.parse(backend_json)
|
|
251
|
-
normalized = Manifest.normalize(result.manifest, "services/product/user/user-management-backend")
|
|
252
|
-
|
|
253
|
-
assert_equal("/api/v1/users", normalized['traefik']['pathPrefix'], "Should preserve pathPrefix")
|
|
254
|
-
assert_equal(200, normalized['traefik']['priority'], "Should preserve priority")
|
|
255
|
-
|
|
256
|
-
# Test auto-generated pathPrefix
|
|
257
|
-
backend_json_no_traefik = '''
|
|
258
|
-
{
|
|
259
|
-
"appName": "test-management-backend",
|
|
260
|
-
"appType": "backend",
|
|
261
|
-
"stack": "user",
|
|
262
|
-
"port": 4001
|
|
263
|
-
}
|
|
264
|
-
'''
|
|
265
|
-
|
|
266
|
-
result2 = Manifest.parse(backend_json_no_traefik)
|
|
267
|
-
normalized2 = Manifest.normalize(result2.manifest, "services/product/user/test-management-backend")
|
|
268
|
-
|
|
269
|
-
assert_not_none(normalized2['traefik']['pathPrefix'], "Should auto-generate pathPrefix")
|
|
270
|
-
assert_not_none(normalized2['traefik']['priority'], "Should set default priority")
|
|
271
|
-
|
|
272
|
-
print("✅ Traefik configuration tests complete\n")
|
|
273
|
-
|
|
274
|
-
# =============================================================================
|
|
275
|
-
# TEST SUITE: Manifest Merging
|
|
276
|
-
# =============================================================================
|
|
277
|
-
|
|
278
|
-
def _test_manifest_merging():
|
|
279
|
-
"""Test manifest merging functionality."""
|
|
280
|
-
print("🔍 Testing manifest merging...")
|
|
281
|
-
|
|
282
|
-
base = {
|
|
283
|
-
'appName': 'test-service',
|
|
284
|
-
'appType': 'backend',
|
|
285
|
-
'port': 4000,
|
|
286
|
-
'featuresEnabled': ['nats'],
|
|
287
|
-
'envVars': {
|
|
288
|
-
'SHARED_VAR': 'base_value',
|
|
289
|
-
},
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
overlay = {
|
|
293
|
-
'port': 4001, # Override
|
|
294
|
-
'featuresEnabled': ['prisma'], # Override
|
|
295
|
-
'envVars': {
|
|
296
|
-
'SHARED_VAR': 'overlay_value', # Override
|
|
297
|
-
'NEW_VAR': 'new_value', # Add
|
|
298
|
-
},
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
merged = Manifest.merge_manifests(base, overlay)
|
|
302
|
-
|
|
303
|
-
assert_equal('test-service', merged['appName'], "Should preserve base appName")
|
|
304
|
-
assert_equal(4001, merged['port'], "Should overlay port")
|
|
305
|
-
assert_equal(['prisma'], merged['featuresEnabled'], "Should overlay features")
|
|
306
|
-
assert_equal('overlay_value', merged['envVars']['SHARED_VAR'], "Should overlay nested env var")
|
|
307
|
-
assert_equal('new_value', merged['envVars']['NEW_VAR'], "Should add new env var")
|
|
308
|
-
|
|
309
|
-
print("✅ Manifest merging tests complete\n")
|
|
310
|
-
|
|
311
|
-
# =============================================================================
|
|
312
|
-
# TEST SUITE: Values written into generated config (GHSA-phgf-pww4-7jxc)
|
|
313
|
-
# =============================================================================
|
|
314
|
-
|
|
315
|
-
def _test_generated_string_fields():
|
|
316
|
-
"""A line break or YAML syntax in a field that reaches generated compose output must be rejected."""
|
|
317
|
-
base = {'appName': 'api', 'stack': 'shop', 'traefik': {'pathPrefix': '/api/v1/shop-management'}}
|
|
318
|
-
|
|
319
|
-
assert_equal(None, ManifestParser.generated_string_error(base), "Clean manifest has no generated-field error")
|
|
320
|
-
|
|
321
|
-
safe = {
|
|
322
|
-
'appName': 'api',
|
|
323
|
-
'stack': 'shop-1',
|
|
324
|
-
'healthCheckPath': '/api/v1/health',
|
|
325
|
-
'databaseName': 'Flisk_shop',
|
|
326
|
-
'image': 'ghcr.io/acme/app:1.0',
|
|
327
|
-
'traefik': {'host': 'shop.backend.my-project.local', 'pathPrefix': '/api/v1/shop', 'healthCheck': '/health'},
|
|
328
|
-
'nats': {'queueGroup': 'shop_backend_svc'},
|
|
329
|
-
}
|
|
330
|
-
assert_equal(None, ManifestParser.generated_string_error(safe), "Safe values are accepted")
|
|
331
|
-
|
|
332
|
-
hostile = [
|
|
333
|
-
{'healthCheckPath': '/health\n privileged: true'},
|
|
334
|
-
{'healthCheckPath': '/health"'},
|
|
335
|
-
{'traefik': {'host': 'a.local\n privileged: true', 'pathPrefix': '/api'}},
|
|
336
|
-
{'traefik': {'pathPrefix': '/api/v1/x\n network_mode: host'}},
|
|
337
|
-
{'traefik': {'pathPrefix': '/api', 'healthCheck': '/h\n volumes: []'}},
|
|
338
|
-
{'traefik': {'pathPrefix': '/api'}, 'nats': {'queueGroup': 'g: true'}},
|
|
339
|
-
{'traefik': {'pathPrefix': '/api'}, 'databaseName': 'Flisk_x\n privileged: true'},
|
|
340
|
-
{'traefik': {'pathPrefix': '/api'}, 'stack': 'shop # comment'},
|
|
341
|
-
{'traefik': {'pathPrefix': '/api'}, 'image': 'app\n privileged: true'},
|
|
342
|
-
{'traefik': {'pathPrefix': '/api'}, 'healthCheckPath': 'health'},
|
|
343
|
-
{'traefik': {'pathPrefix': '/api'}, 'healthCheckPath': 42},
|
|
344
|
-
]
|
|
345
|
-
for manifest in hostile:
|
|
346
|
-
assert_not_none(ManifestParser.generated_string_error(manifest), "Rejects unsafe value: " + str(manifest))
|
|
347
|
-
|
|
348
|
-
# The message names the field and never echoes the value
|
|
349
|
-
message = ManifestParser.generated_string_error(hostile[0], 'services/shop/api')
|
|
350
|
-
assert_true("healthCheckPath" in message, "Error names the field")
|
|
351
|
-
assert_true("\n" not in message, "Error does not echo the value")
|
|
352
|
-
|
|
353
|
-
# Fields outside the allowlist also reach generated config, so a line break anywhere is refused
|
|
354
|
-
clean = {'appName': 'api', 'stack': 'shop', 'traefik': {'pathPrefix': '/api'}}
|
|
355
|
-
line_breaks = [
|
|
356
|
-
{'port': '4000\n privileged: true'},
|
|
357
|
-
{'appName': 'api\n privileged: true'},
|
|
358
|
-
{'dockerfile': 'Dockerfile\r privileged: true'},
|
|
359
|
-
{'buildContext': 'src\u2028privileged: true'},
|
|
360
|
-
{'envVars': [{'name': 'A', 'value': 'v\n privileged: true'}]},
|
|
361
|
-
{'params': {'a\n privileged': 'x'}},
|
|
362
|
-
]
|
|
363
|
-
for extra in line_breaks:
|
|
364
|
-
manifest = dict(clean)
|
|
365
|
-
manifest.update(extra)
|
|
366
|
-
assert_not_none(ManifestParser.generated_string_error(manifest), "Rejects a line break: " + repr(extra))
|
|
367
|
-
|
|
368
|
-
smoke = dict(clean)
|
|
369
|
-
smoke['smoke'] = {'via': '/api', 'create': {'body': '{\n}'}}
|
|
370
|
-
assert_equal(None, ManifestParser.generated_string_error(smoke), "smoke is never written into generated config")
|
|
371
|
-
|
|
372
|
-
# =============================================================================
|
|
373
|
-
# TEST SUITE: Get Normalized Convenience Function
|
|
374
|
-
# =============================================================================
|
|
375
|
-
|
|
376
|
-
def _test_get_normalized():
|
|
377
|
-
"""Test the get_normalized convenience function."""
|
|
378
|
-
print("🔍 Testing get_normalized...")
|
|
379
|
-
|
|
380
|
-
json_content = '''
|
|
381
|
-
{
|
|
382
|
-
"appName": "test-service",
|
|
383
|
-
"appType": "backend",
|
|
384
|
-
"stack": "user",
|
|
385
|
-
"port": 4001
|
|
386
|
-
}
|
|
387
|
-
'''
|
|
388
|
-
|
|
389
|
-
result = Manifest.get_normalized(json_content, "services/product/user/test-service")
|
|
390
|
-
|
|
391
|
-
assert_not_none(result.manifest, "Should return normalized manifest")
|
|
392
|
-
assert_equal(None, result.error, "Should have no error")
|
|
393
|
-
assert_equal("test-service", result.manifest['appName'], "Should have correct appName")
|
|
394
|
-
assert_equal("backend", result.manifest['appType'], "Should have correct appType")
|
|
395
|
-
assert_equal(True, result.manifest.get('_normalized'), "Should be marked as normalized")
|
|
396
|
-
|
|
397
|
-
print("✅ Get normalized tests complete\n")
|
|
398
|
-
|
|
399
|
-
# =============================================================================
|
|
400
|
-
# TEST SUITE: Service Description
|
|
401
|
-
# =============================================================================
|
|
402
|
-
|
|
403
|
-
def _test_resource_description():
|
|
404
|
-
"""Test service description generation."""
|
|
405
|
-
print("🔍 Testing service description...")
|
|
406
|
-
|
|
407
|
-
manifest = {
|
|
408
|
-
'appName': 'user-management-backend',
|
|
409
|
-
'appType': 'backend',
|
|
410
|
-
'stack': 'user',
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
desc = Manifest.get_description(manifest)
|
|
414
|
-
assert_equal("user/backend/user-management-backend", desc, "Should generate correct description")
|
|
415
|
-
|
|
416
|
-
print("✅ Service description tests complete\n")
|
|
417
|
-
|
|
418
|
-
# =============================================================================
|
|
419
|
-
# MAIN
|
|
420
|
-
# =============================================================================
|
|
421
|
-
|
|
422
|
-
def _test_byo_health_path_is_optional():
|
|
423
|
-
"""A bring-your-own service without healthCheckPath gets none; /health is only the default for HTTP types."""
|
|
424
|
-
print("\n🧪 Testing bring-your-own health path is optional...")
|
|
425
|
-
|
|
426
|
-
byo = ManifestParser.normalize({'appType': 'bring-your-own', 'appName': 'postgres', 'stack': 'shop', 'port': 4500}, 'services/shop/postgres')
|
|
427
|
-
assert_true('healthCheckPath' not in byo, "BYO without healthCheckPath gets no default /health")
|
|
428
|
-
|
|
429
|
-
byo_http = ManifestParser.normalize({'appType': 'bring-your-own', 'appName': 'web', 'stack': 'shop', 'port': 4501, 'healthCheckPath': '/status'}, 'services/shop/web')
|
|
430
|
-
assert_equal('/status', byo_http.get('healthCheckPath'), "BYO keeps an explicit healthCheckPath")
|
|
431
|
-
|
|
432
|
-
backend = ManifestParser.normalize({'appType': 'backend', 'appName': 'api', 'stack': 'shop', 'port': 4000}, 'services/shop/api')
|
|
433
|
-
assert_equal('/health', backend.get('healthCheckPath'), "Backends still default to /health")
|
|
434
|
-
|
|
435
|
-
def run_tests():
|
|
436
|
-
"""Run all Phase 2 integration tests."""
|
|
437
|
-
print("\n" + "="*70)
|
|
438
|
-
print("📋 MANIFEST MODULE - PHASE 2 INTEGRATION TESTS")
|
|
439
|
-
print("="*70 + "\n")
|
|
440
|
-
|
|
441
|
-
# Run all test suites
|
|
442
|
-
_test_loader_functions()
|
|
443
|
-
_test_parser_functions()
|
|
444
|
-
_test_parse_and_normalize()
|
|
445
|
-
_test_stack_extraction()
|
|
446
|
-
_test_frontend_normalization()
|
|
447
|
-
_test_traefik_config()
|
|
448
|
-
_test_manifest_merging()
|
|
449
|
-
_test_get_normalized()
|
|
450
|
-
_test_resource_description()
|
|
451
|
-
_test_generated_string_fields()
|
|
452
|
-
_test_byo_health_path_is_optional()
|
|
453
|
-
|
|
454
|
-
# Print results
|
|
455
|
-
print("\n" + "="*70)
|
|
456
|
-
print("📊 TEST RESULTS")
|
|
457
|
-
print("="*70)
|
|
458
|
-
print("✅ Passed: {}".format(test_results['passed']))
|
|
459
|
-
print("❌ Failed: {}".format(test_results['failed']))
|
|
460
|
-
|
|
461
|
-
if test_results['errors']:
|
|
462
|
-
print("\n📝 ERRORS:")
|
|
463
|
-
for error in test_results['errors']:
|
|
464
|
-
print(" " + error)
|
|
465
|
-
else:
|
|
466
|
-
print("\n🎉 All Phase 2 integration tests passed!")
|
|
467
|
-
|
|
468
|
-
print("="*70 + "\n")
|
|
469
|
-
|
|
470
|
-
return test_results['failed'] == 0
|
|
471
|
-
|
|
472
|
-
# Export test runner
|
|
473
|
-
TestLoaderParser = struct(
|
|
474
|
-
run=run_tests,
|
|
475
|
-
)
|