1package cli
2
3import (
4 "go/ast"
5 "go/parser"
6 "go/token"
7 "os"
8 "strings"
9 "testing"
10)
11
12type uiAdoptionExpectation struct {
13 family string
14 file string
15 functions []string
16}
17
18var uiAdoptionExpectations = []uiAdoptionExpectation{
19 {
20 family: "root/server",
21 file: "root.go",
22 functions: []string{"newVersionCmd", "runReloadRemote", "runLogsRemote", "streamLogsRemote", "showContainerLogsLocal"},
23 },
24 {
25 family: "backups",
26 file: "backup.go",
27 functions: []string{"newBackupListCmd", "newBackupRunCmd", "newBackupDetectCmd", "newBackupStatusCmd"},
28 },
29 {
30 family: "images",
31 file: "images.go",
32 functions: []string{"runImagesList", "runImagesPrune"},
33 },
34}
35
36func TestLocalParityMatrix(t *testing.T) {
37 checks := []struct {
38 file string
39 legacyText string
40 command string
41 }{
42 {file: "push.go", legacyText: "push requires remote mode", command: "gordon push"},
43 // Guards against reintroducing the old rollback-only remote-mode error text during the command rename.
44 {file: "pin.go", legacyText: "rollback requires remote mode", command: "gordon pin"},
45 {file: "backup.go", legacyText: "backup commands require a configured remote target", command: "gordon backups"},
46 {file: "routes.go", legacyText: "status command requires --remote flag or GORDON_REMOTE env var", command: "gordon status"},
47 {file: "restart.go", legacyText: "local restart does not support --with-attachments; use --remote", command: "gordon restart --with-attachments"},
48 }
49
50 for _, check := range checks {
51 content, err := os.ReadFile(check.file)
52 if err != nil {
53 t.Fatalf("failed to read %s: %v", check.file, err)
54 }
55 if strings.Contains(string(content), check.legacyText) {
56 t.Fatalf("local parity gap: %s still blocks local mode (%q found in %s)", check.command, check.legacyText, check.file)
57 }
58 }
59}
60
61func TestUIAdoptionMatrixCoverage(t *testing.T) {
62 for _, expect := range uiAdoptionExpectations {
63 t.Run(expect.family, func(t *testing.T) {
64 if len(expect.functions) == 0 {
65 t.Fatalf("ui adoption matrix for %s is empty", expect.family)
66 }
67
68 fset := token.NewFileSet()
69 fileNode, err := parser.ParseFile(fset, expect.file, nil, parser.AllErrors)
70 if err != nil {
71 t.Fatalf("failed to parse %s: %v", expect.file, err)
72 }
73
74 found := make(map[string]bool, len(expect.functions))
75 ast.Inspect(fileNode, func(n ast.Node) bool {
76 fn, ok := n.(*ast.FuncDecl)
77 if !ok {
78 return true
79 }
80 found[fn.Name.Name] = true
81 return true
82 })
83
84 for _, fn := range expect.functions {
85 if !found[fn] {
86 t.Fatalf("ui adoption matrix references missing function %s in %s", fn, expect.file)
87 }
88 }
89 })
90 }
91}