91 lines
3.0 KiB
Go
91 lines
3.0 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))
|
|
}
|
|
for _, match := range versions {
|
|
if match[1] != "2" {
|
|
t.Fatalf("device path %q bypasses the current v2 compatibility baseline", match[0])
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
return string(contents)
|
|
}
|