fix: complete in-progress OpenSpec changes

- git-repo-working-clones: Complete remaining test task
- opencode-web-terminal: Add port validation tests, fix model validator
- session-management-fixes: Mark frontend tasks as complete (already implemented)

All in-progress changes now complete.
This commit is contained in:
Fusion
2026-05-22 21:39:43 +02:00
parent 649496b762
commit b7d17cea78
5 changed files with 93 additions and 65 deletions
+29 -38
View File
@@ -97,46 +97,9 @@ class ToolTypeCreate(BaseModel):
@field_validator("default_port")
@classmethod
def validate_default_port(cls, v: int, info) -> int:
def validate_default_port(cls, v: int) -> int:
if v <= 0 or v > 65535:
raise ValueError("Port must be between 1 and 65535")
# Get compose_template from the model data
data = info.data
if data.get("definition_type") != "compose":
return v
template = data.get("compose_template")
if not template:
return v
try:
parsed = yaml.safe_load(template)
except yaml.YAMLError:
return v
# Check if the port is exposed in any service
port_str = str(v)
port_exposed = False
if isinstance(parsed, dict) and "services" in parsed:
for service_name, service_config in parsed["services"].items():
if isinstance(service_config, dict) and "ports" in service_config:
for port_mapping in service_config["ports"]:
if isinstance(port_mapping, str):
# Format: "8443:8443" or "8443"
if port_str in port_mapping:
port_exposed = True
break
elif isinstance(port_mapping, int) and port_mapping == v:
port_exposed = True
break
if port_exposed:
break
if not port_exposed:
raise ValueError(f"Port {v} is not exposed in the compose template. Add it to the 'ports' section.")
return v
@field_validator("required_variables")
@@ -166,6 +129,34 @@ class ToolTypeCreate(BaseModel):
raise ValueError("dockerfile_template is required when definition_type is 'dockerfile'")
if self.definition_type == "compose" and self.compose_template is None:
raise ValueError("compose_template is required when definition_type is 'compose'")
# Validate that default_port is exposed in compose template
if self.definition_type == "compose" and self.compose_template:
try:
parsed = yaml.safe_load(self.compose_template)
except yaml.YAMLError:
return self
port_str = str(self.default_port)
port_exposed = False
if isinstance(parsed, dict) and "services" in parsed:
for service_name, service_config in parsed["services"].items():
if isinstance(service_config, dict) and "ports" in service_config:
for port_mapping in service_config["ports"]:
if isinstance(port_mapping, str):
if port_str in port_mapping:
port_exposed = True
break
elif isinstance(port_mapping, int) and port_mapping == self.default_port:
port_exposed = True
break
if port_exposed:
break
if not port_exposed:
raise ValueError(f"Port {self.default_port} is not exposed in the compose template. Add it to the 'ports' section.")
return self
@@ -39,7 +39,7 @@ class TestToolTypesAPIExtended:
"interfaces": ["web"],
"default_port": 8080,
"definition_type": "compose",
"compose_template": "version: '3.8'\nservices:\n app:\n image: nginx",
"compose_template": "version: '3.8'\nservices:\n app:\n image: nginx\n ports:\n - '8080:8080'",
"readiness_probe": {
"command": "curl -f http://localhost:8080",
"timeout": 30,
@@ -92,7 +92,7 @@ class TestToolTypesAPIExtended:
"display_name": "Update Test Tool",
"default_port": 8080,
"definition_type": "compose",
"compose_template": "version: '3.8'\nservices:\n app:\n image: nginx",
"compose_template": "version: '3.8'\nservices:\n app:\n image: nginx\n ports:\n - '8080:8080'",
"required_variables": [],
},
)
@@ -167,7 +167,7 @@ class TestToolTypesAPIExtended:
"interfaces": ["web", "terminal"],
"default_port": 8443,
"definition_type": "compose",
"compose_template": "version: '3.8'\nservices:\n app:\n image: code-server\n volumes:\n - \"{{REPO_PATH}}:/workspace\"",
"compose_template": "version: '3.8'\nservices:\n app:\n image: code-server\n ports:\n - '8443:8443'\n volumes:\n - \"{{REPO_PATH}}:/workspace\"",
"readiness_probe": {
"command": "curl -f http://localhost:8443",
"timeout": 30,
@@ -186,3 +186,40 @@ class TestToolTypesAPIExtended:
assert data["category"] == "editor"
assert data["interfaces"] == ["web", "terminal"]
assert "readiness_probe" in data
def test_create_tool_type_without_port_fails(self, authenticated_client: TestClient) -> None:
"""Test that creating a tool type without default_port fails validation."""
response = authenticated_client.post(
"/tool-types",
json={
"name": "no-port-tool",
"display_name": "No Port Tool",
"category": "utility",
"interfaces": ["web"],
"definition_type": "compose",
"compose_template": "version: '3.8'\nservices:\n app:\n image: nginx\n ports:\n - '8080:8080'",
"required_variables": [],
},
)
assert response.status_code == 422
data = response.json()
assert "default_port" in str(data)
def test_create_tool_type_with_port_mismatch_fails(self, authenticated_client: TestClient) -> None:
"""Test that port mismatch between default_port and compose template fails."""
response = authenticated_client.post(
"/tool-types",
json={
"name": "port-mismatch-tool",
"display_name": "Port Mismatch Tool",
"category": "utility",
"interfaces": ["web"],
"default_port": 9999,
"definition_type": "compose",
"compose_template": "version: '3.8'\nservices:\n app:\n image: nginx\n ports:\n - '8080:8080'",
"required_variables": [],
},
)
assert response.status_code == 422
data = response.json()
assert "Port 9999 is not exposed" in str(data)