7dfdf80e9e
Grok is a first-class worker CLI. Launcher argv includes --always-approve so unattended tool calls are not blocked; sandbox stays required.
356 lines
14 KiB
Python
356 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import copy
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPTS_DIR = REPO_ROOT / "skills" / "ack" / "scripts"
|
|
VALIDATOR = SCRIPTS_DIR / "validate_delivery.py"
|
|
EXAMPLE = REPO_ROOT / "skills" / "ack" / "examples" / "delivery.example.yaml"
|
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
|
|
import validate_delivery # noqa: E402
|
|
|
|
|
|
def valid_contract() -> dict:
|
|
entrypoints = {
|
|
name: {
|
|
"kind": "make",
|
|
"target": name,
|
|
"args": [],
|
|
"requiredSecrets": [],
|
|
"workingDirectory": ".",
|
|
"timeoutSeconds": 300,
|
|
}
|
|
for name in ("verify", "build", "upload", "deploy", "health", "rollback")
|
|
}
|
|
return {
|
|
"version": 1,
|
|
"updatedAt": "2026-08-01T10:00:00+08:00",
|
|
"project": {"name": "demo"},
|
|
"enabled": True,
|
|
"defaultProfile": "review",
|
|
"entrypoints": entrypoints,
|
|
"artifacts": {
|
|
"service-deb": {
|
|
"type": "deb",
|
|
"build": "build",
|
|
"outputs": ["dist/*.deb"],
|
|
}
|
|
},
|
|
"destinations": {
|
|
"preview-apt": {
|
|
"type": "apt-repository",
|
|
"channel": "preview",
|
|
"endpoint": "https://packages.example.com",
|
|
"repository": "testing",
|
|
"upload": "upload",
|
|
}
|
|
},
|
|
"environments": {
|
|
"test-server": {
|
|
"type": "ssh-host",
|
|
"classification": "development",
|
|
"target": "test-server",
|
|
"deploy": "deploy",
|
|
"healthCheck": "health",
|
|
"rollback": "rollback",
|
|
"mutex": "test-server-deploy",
|
|
}
|
|
},
|
|
"profiles": {
|
|
"review": {
|
|
"stopAt": "review_ready",
|
|
"steps": [
|
|
{"id": "verify", "action": "verify", "entrypoint": "verify"},
|
|
{
|
|
"id": "open-pr",
|
|
"action": "pull-request",
|
|
"draft": True,
|
|
"remote": "origin",
|
|
"baseBranch": "main",
|
|
},
|
|
{"id": "build", "action": "build", "artifact": "service-deb"},
|
|
{
|
|
"id": "publish",
|
|
"action": "publish",
|
|
"artifact": "service-deb",
|
|
"destination": "preview-apt",
|
|
},
|
|
{
|
|
"id": "deploy",
|
|
"action": "deploy",
|
|
"artifact": "service-deb",
|
|
"environment": "test-server",
|
|
},
|
|
{
|
|
"id": "health",
|
|
"action": "health-check",
|
|
"environment": "test-server",
|
|
},
|
|
{"id": "ready", "action": "mark-ready"},
|
|
],
|
|
}
|
|
},
|
|
}
|
|
|
|
|
|
class AckDeliveryValidationTests(unittest.TestCase):
|
|
def test_example_is_valid_with_and_without_site_packages(self) -> None:
|
|
for no_site_packages in (False, True):
|
|
command = [sys.executable]
|
|
if no_site_packages:
|
|
command.append("-S")
|
|
result = subprocess.run(
|
|
[*command, str(VALIDATOR), str(EXAMPLE)],
|
|
cwd=REPO_ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
with self.subTest(no_site_packages=no_site_packages):
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertIn("交付契约校验通过", result.stdout)
|
|
|
|
def test_default_profile_rejects_stable_and_production_targets(self) -> None:
|
|
contract = valid_contract()
|
|
contract["destinations"]["preview-apt"]["channel"] = "stable"
|
|
contract["environments"]["test-server"]["classification"] = "production"
|
|
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
|
|
self.assertTrue(any("stable 发布前必须有 release approval" in item for item in errors))
|
|
self.assertTrue(any("production 部署前必须有 production approval" in item for item in errors))
|
|
self.assertTrue(any("defaultProfile 不能发布 stable" in item for item in errors))
|
|
self.assertTrue(any("defaultProfile 不能部署 production" in item for item in errors))
|
|
|
|
def test_nondefault_release_profile_supports_stable_and_production_with_gates(self) -> None:
|
|
contract = valid_contract()
|
|
contract["destinations"]["stable-apt"] = {
|
|
"type": "apt-repository",
|
|
"channel": "stable",
|
|
"endpoint": "https://packages.example.com",
|
|
"repository": "stable",
|
|
"upload": "upload",
|
|
}
|
|
contract["environments"]["prod-server"] = {
|
|
"type": "ssh-host",
|
|
"classification": "production",
|
|
"target": "prod-server",
|
|
"deploy": "deploy",
|
|
"healthCheck": "health",
|
|
"rollback": "rollback",
|
|
"mutex": "prod-server-deploy",
|
|
}
|
|
contract["profiles"]["release"] = {
|
|
"stopAt": "released",
|
|
"steps": [
|
|
{"id": "verify-release", "action": "verify", "entrypoint": "verify"},
|
|
{
|
|
"id": "open-release-pr",
|
|
"action": "pull-request",
|
|
"draft": True,
|
|
"remote": "origin",
|
|
"baseBranch": "main",
|
|
},
|
|
{"id": "build-release", "action": "build", "artifact": "service-deb"},
|
|
{"id": "approve-release", "action": "approval", "gate": "release"},
|
|
{
|
|
"id": "publish-release",
|
|
"action": "publish",
|
|
"artifact": "service-deb",
|
|
"destination": "stable-apt",
|
|
},
|
|
{"id": "approve-production", "action": "approval", "gate": "production"},
|
|
{
|
|
"id": "deploy-production",
|
|
"action": "deploy",
|
|
"artifact": "service-deb",
|
|
"environment": "prod-server",
|
|
},
|
|
{
|
|
"id": "health-production",
|
|
"action": "health-check",
|
|
"environment": "prod-server",
|
|
},
|
|
{"id": "ready-release", "action": "mark-ready"},
|
|
],
|
|
}
|
|
|
|
self.assertEqual(validate_delivery.validate_builtin(contract), [])
|
|
|
|
def test_default_validation_profile_requires_deploy_and_health_check(self) -> None:
|
|
contract = valid_contract()
|
|
contract["defaultProfile"] = "local-validation"
|
|
contract["profiles"]["local-validation"] = {
|
|
"stopAt": "validation_ready",
|
|
"steps": [
|
|
{"id": "build-local", "action": "build", "artifact": "service-deb"},
|
|
{
|
|
"id": "deploy-local",
|
|
"action": "deploy",
|
|
"artifact": "service-deb",
|
|
"environment": "test-server",
|
|
},
|
|
{
|
|
"id": "health-local",
|
|
"action": "health-check",
|
|
"environment": "test-server",
|
|
},
|
|
],
|
|
}
|
|
|
|
self.assertEqual(validate_delivery.validate_builtin(contract), [])
|
|
|
|
contract["profiles"]["local-validation"]["steps"].pop()
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
self.assertTrue(any("必须全部完成 health-check" in item for item in errors))
|
|
|
|
def test_publish_and_health_check_require_prior_steps(self) -> None:
|
|
contract = valid_contract()
|
|
steps = contract["profiles"]["review"]["steps"]
|
|
steps[2], steps[3] = steps[3], steps[2]
|
|
steps[4], steps[5] = steps[5], steps[4]
|
|
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
|
|
self.assertTrue(any("publish 前必须先 build" in item for item in errors))
|
|
self.assertTrue(any("health-check 前必须先 deploy" in item for item in errors))
|
|
|
|
def test_pull_request_requires_explicit_safe_remote_and_base(self) -> None:
|
|
contract = valid_contract()
|
|
step = contract["profiles"]["review"]["steps"][1]
|
|
del step["remote"]
|
|
step["baseBranch"] = "../main"
|
|
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
|
|
self.assertTrue(any(".remote: action='pull-request' 时必填" in item for item in errors))
|
|
self.assertTrue(any(".remote: 必须是安全的 Git remote 名称" in item for item in errors))
|
|
self.assertTrue(any(".baseBranch: 必须是安全的 Git 分支名" in item for item in errors))
|
|
|
|
def test_unknown_shell_and_inline_secret_are_rejected(self) -> None:
|
|
contract = valid_contract()
|
|
contract["entrypoints"]["verify"]["shell"] = "make verify"
|
|
contract["entrypoints"]["verify"]["requiredSecrets"] = ["token-value"]
|
|
contract["destinations"]["preview-apt"]["repository"] = (
|
|
"token=abcdefghijklmnop"
|
|
)
|
|
contract["destinations"]["preview-apt"]["registry"] = "unexpected.example"
|
|
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
|
|
self.assertTrue(any("未知字段 'shell'" in item for item in errors))
|
|
self.assertTrue(any("requiredSecrets" in item for item in errors))
|
|
self.assertTrue(any("type='apt-repository' 不允许此字段" in item for item in errors))
|
|
self.assertTrue(any("疑似包含敏感信息" in item for item in errors))
|
|
|
|
def test_project_script_must_be_executable_and_not_a_symlink(self) -> None:
|
|
contract = valid_contract()
|
|
contract["entrypoints"]["verify"] = {
|
|
"kind": "script",
|
|
"path": "scripts/verify.sh",
|
|
"args": [],
|
|
"requiredSecrets": [],
|
|
"workingDirectory": ".",
|
|
"timeoutSeconds": 300,
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
root = Path(temp_dir)
|
|
scripts = root / "scripts"
|
|
scripts.mkdir()
|
|
target = scripts / "target.sh"
|
|
target.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
|
target.chmod(0o755)
|
|
os.symlink("target.sh", scripts / "verify.sh")
|
|
|
|
errors = validate_delivery.validate_builtin(contract, root)
|
|
self.assertTrue(any("路径不能包含 symlink" in item for item in errors))
|
|
|
|
(scripts / "verify.sh").unlink()
|
|
plain = scripts / "verify.sh"
|
|
plain.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
|
plain.chmod(0o644)
|
|
errors = validate_delivery.validate_builtin(contract, root)
|
|
self.assertTrue(any("脚本不可执行" in item for item in errors))
|
|
|
|
def test_tasks_link_requires_fixed_path_runs_and_same_project(self) -> None:
|
|
contract = valid_contract()
|
|
tasks = {
|
|
"project": {"name": "other", "deliveryFile": "delivery.yaml"},
|
|
"tasks": [],
|
|
}
|
|
|
|
errors = validate_delivery.validate_tasks_link(contract, tasks)
|
|
|
|
self.assertIn(
|
|
"tasks.project.deliveryFile 必须固定为 docs/ack/delivery.yaml",
|
|
errors,
|
|
)
|
|
self.assertIn("delivery.project.name 必须与 tasks.project.name 一致", errors)
|
|
self.assertIn("引用 deliveryFile 的任务板必须包含 deliveryRuns 列表", errors)
|
|
|
|
def test_disabled_empty_contract_remains_valid(self) -> None:
|
|
contract = copy.deepcopy(valid_contract())
|
|
contract.update(
|
|
{
|
|
"enabled": False,
|
|
"defaultProfile": None,
|
|
"intents": {"testEnvironment": None, "release": None},
|
|
"entrypoints": {},
|
|
"artifacts": {},
|
|
"destinations": {},
|
|
"environments": {},
|
|
"profiles": {},
|
|
}
|
|
)
|
|
|
|
self.assertEqual(validate_delivery.validate_builtin(contract), [])
|
|
|
|
def test_intents_must_point_at_matching_stop_points(self) -> None:
|
|
contract = valid_contract()
|
|
contract["intents"] = {
|
|
"testEnvironment": "review",
|
|
"release": None,
|
|
}
|
|
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
self.assertTrue(
|
|
any("intents.testEnvironment" in item and "validation_ready" in item for item in errors)
|
|
)
|
|
|
|
contract["intents"]["testEnvironment"] = "local-validation"
|
|
contract["profiles"]["local-validation"] = {
|
|
"stopAt": "validation_ready",
|
|
"steps": [
|
|
{"id": "build-local", "action": "build", "artifact": "service-deb"},
|
|
{
|
|
"id": "deploy-local",
|
|
"action": "deploy",
|
|
"artifact": "service-deb",
|
|
"environment": "test-server",
|
|
},
|
|
{
|
|
"id": "health-local",
|
|
"action": "health-check",
|
|
"environment": "test-server",
|
|
},
|
|
],
|
|
}
|
|
self.assertEqual(validate_delivery.validate_builtin(contract), [])
|
|
|
|
contract["intents"]["release"] = "missing-release"
|
|
errors = validate_delivery.validate_builtin(contract)
|
|
self.assertTrue(any("未定义 profile 'missing-release'" in item for item in errors))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|