1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env python3
# cs/devtools/test_expand_system.gpt.py
"""
Test that expand_system.gpt.py generates valid and correct docker configs.
"""
import yaml
import sys
from pathlib import Path
def test_generated_files():
"""Verify generated files exist and have correct structure."""
# When run via Bazel, use cwd. When run directly, use relative path.
if len(sys.argv) > 1:
root = Path(sys.argv[1])
else:
root = Path(__file__).parent.parent.parent
# Check files exist
dockerfile = root / "Dockerfile"
compose_prod = root / "docker-compose.yml"
compose_dev = root / "docker-compose.dev.yml"
assert dockerfile.exists(), f"{dockerfile} not found"
assert compose_prod.exists(), f"{compose_prod} not found"
assert compose_dev.exists(), f"{compose_dev} not found"
print("✓ All generated files exist")
# Validate YAML
with open(compose_prod) as f:
prod = yaml.safe_load(f)
with open(compose_dev) as f:
dev = yaml.safe_load(f)
print("✓ Generated YAML is valid")
# Check structure
assert "services" in prod, "Missing services in prod"
assert "volumes" in prod, "Missing volumes in prod"
assert "services" in dev, "Missing services in dev"
assert "volumes" in dev, "Missing volumes in dev"
print("✓ YAML structure is correct")
# Verify all expected services
expected_services = [
"www-trycopilot-ai",
"www-cite-pub",
"code-viewer",
"database-service",
"load-balancer",
"service-registry",
"ngrok",
]
for svc in expected_services:
assert svc in prod["services"], f"Missing service {svc} in prod"
assert svc in dev["services"], f"Missing service {svc} in dev"
print(f"✓ All {len(expected_services)} services present")
# Verify environment differences
prod_replicas = (
prod["services"]["www-trycopilot-ai"].get("deploy", {}).get("replicas", 1)
)
dev_replicas = (
dev["services"]["www-trycopilot-ai"].get("deploy", {}).get("replicas", 1)
)
assert prod_replicas == 5, f"Expected 5 prod replicas, got {prod_replicas}"
assert dev_replicas == 3, f"Expected 3 dev replicas, got {dev_replicas}"
print(f"✓ Environment-specific scaling: prod={prod_replicas}, dev={dev_replicas}")
# Verify load-balancer ports
prod_lb = prod["services"]["load-balancer"]
dev_lb = dev["services"]["load-balancer"]
assert "expose" in prod_lb, "Load balancer should be internal in prod"
assert "ports" in dev_lb, "Load balancer should be exposed in dev"
print("✓ Load balancer correctly exposed in dev, internal in prod")
# Verify volumes are external where needed
assert (
prod["volumes"]["database-volume"].get("external") == True
), "database-volume should be external"
print("✓ External volumes correctly configured")
# Verify security isolation
assert (
prod["services"]["database-service"]["user"] == "0:0"
), "database-service should run as root"
assert (
prod["services"]["www-trycopilot-ai"]["user"] == "8877:8877"
), "www-trycopilot-ai should be unprivileged"
print("✓ Security isolation levels correct")
# Verify Dockerfile has all build stages
dockerfile_content = dockerfile.read_text()
for svc in expected_services:
if svc == "ngrok":
continue
stage_name = svc.replace("-", "_")
assert (
f"AS build_{stage_name}" in dockerfile_content
), f"Missing build stage for {svc}"
assert (
f"AS {stage_name}" in dockerfile_content
), f"Missing runtime stage for {svc}"
print("✓ Dockerfile has all build and runtime stages")
print("\n✅ All tests passed!")
return 0
if __name__ == "__main__":
try:
sys.exit(test_generated_files())
except AssertionError as e:
print(f"\n❌ Test failed: {e}")
sys.exit(1)
except Exception as e:
print(f"\n❌ Error: {e}")
sys.exit(1)