Files
story/internal/contractgovernance/device_contract_test.go
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

101 lines
3.3 KiB
Go

package contractgovernance
import (
"io/fs"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
)
func TestDeviceContractCompatibilityGuardrails(t *testing.T) {
root := repositoryRoot(t)
contract := readFile(t, filepath.Join(root, "openapi", "openapi.yaml"))
versions := regexp.MustCompile(`(?m)^ /device/v([0-9]+)(?:/[^:]+)?:$`).FindAllStringSubmatch(contract, -1)
if len(versions) < 10 {
t.Fatalf("found only %d versioned device paths; contract scan likely stopped matching", len(versions))
}
v2Count, v3Count := 0, 0
for _, match := range versions {
switch match[1] {
case "2":
v2Count++
case "3":
v3Count++
default:
t.Fatalf("device path %q uses an unsupported compatibility generation", match[0])
}
}
if v2Count < 10 || v3Count < 2 {
t.Fatalf("device contract must preserve v2 and publish hardware-agent v3; v2=%d v3=%d", v2Count, v3Count)
}
for _, required := range []string{
"rtc_credential_renewal:",
"省略时保持旧版v2终端行为",
"设备不得据此改变采集、编码、发布、订阅或播放逻辑",
} {
if !strings.Contains(contract, required) {
t.Fatalf("OpenAPI is missing device compatibility rule %q", required)
}
}
contractVersion := regexp.MustCompile(`(?m)^ version: ([0-9]+\.[0-9]+\.[0-9]+)$`).FindStringSubmatch(contract)
if len(contractVersion) != 2 {
t.Fatal("OpenAPI info.version must be a semantic version")
}
notes := readFile(t, filepath.Join(root, "docs", "device-api-compatibility.md"))
if !strings.Contains(notes, "当前接口契约:OpenAPI "+contractVersion[1]) || !strings.Contains(notes, "/device/v2") || !strings.Contains(notes, "/device/v3") {
t.Fatal("device compatibility notes are not synchronized with the current contract and next breaking version")
}
}
func TestDeviceBehaviorDoesNotDependOnUserAgentOrEngineMode(t *testing.T) {
root := repositoryRoot(t)
for _, relative := range []string{"internal/deviceauth", "internal/httpapi", "internal/orchestration", "cmd/sim-device"} {
directory := filepath.Join(root, filepath.FromSlash(relative))
err := filepath.WalkDir(directory, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
return nil
}
contents := readFile(t, path)
for _, forbidden := range []string{`Header.Get("User-Agent")`, ".UserAgent()"} {
if strings.Contains(contents, forbidden) {
t.Errorf("%s uses firmware User-Agent for behavior selection", filepath.ToSlash(path))
}
}
if relative == "cmd/sim-device" && strings.Contains(contents, "engine_mode") {
t.Errorf("%s makes the terminal aware of diagnostic-only engine_mode", filepath.ToSlash(path))
}
return nil
})
if err != nil {
t.Fatalf("scan %s: %v", relative, err)
}
}
}
func repositoryRoot(t *testing.T) string {
t.Helper()
_, current, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("resolve contract test location")
}
return filepath.Clean(filepath.Join(filepath.Dir(current), "..", ".."))
}
func readFile(t *testing.T, path string) string {
t.Helper()
contents, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
// Git checkouts on Windows may use CRLF; contract rules are line-based.
return strings.ReplaceAll(string(contents), "\r\n", "\n")
}