package main import ( "encoding/json" "go/ast" "go/parser" "go/token" "path/filepath" "reflect" "sort" "strconv" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // The schemas the controller serves, verb by verb, as the console receives them: from the // announcement, not the table — what is checked is what a caller is shown (novox/hq issue 244). func servedSchemas(t *testing.T) map[string]catalogue.Verb { t.Helper() handlers, behind, err := seatToolHandlers() if err != nil { t.Fatal(err) } if len(behind) != 0 { t.Fatalf("this build cannot run %v of its own seat's verbs", behind) } served := map[string]catalogue.Verb{} for _, e := range seatAnnouncement(handlers).Endpoints { var input map[string]any if err := json.Unmarshal([]byte(e.Metadata["schema"]), &input); err != nil { t.Fatalf("%s announces a schema that is not JSON: %v", e.Name, err) } served[e.Metadata["tool"]] = catalogue.Verb{Name: e.Metadata["tool"], Input: input} } if len(served) != len(catalogue.ControllerVerbs) { t.Fatalf("%d verbs served for %d in the table", len(served), len(catalogue.ControllerVerbs)) } return served } // subsetsOf is every subset of the names, the empty one included. func subsetsOf(names []string) [][]string { var out [][]string for mask := 0; mask < 1< 0; n-- { if _, has := flags[strings.Join(words[:n], " ")]; has { if reached[name] == nil { reached[name] = map[string]bool{} } reached[name][strings.Join(words[:n], " ")] = true break } } } } used := map[string]map[string]bool{} verbs := make([]string, 0, len(reached)) for verb := range reached { verbs = append(verbs, verb) } sort.Strings(verbs) for _, verb := range verbs { declared, _ := declaredArguments(served[verb]) for set := range reached[verb] { if used[set] == nil { used[set] = map[string]bool{} } for _, flagName := range flags[set] { why, accounted := accountedFlags[set][flagName] switch { case accounted && strings.HasPrefix(why, "="): used[set][flagName] = true if !contains(declared, strings.TrimPrefix(why, "=")) { t.Errorf("%s: --%s of `%s` is said to be carried by %q, which the schema does not declare", verb, flagName, set, strings.TrimPrefix(why, "=")) } case accounted: used[set][flagName] = true case contains(declared, flagName): default: t.Errorf("%s runs `%s`, which takes --%s, and the verb's schema has no %q: declare it, "+ "or say in accountedFlags why a caller does not reach it", verb, set, flagName, flagName) } } } } for set, fs := range accountedFlags { for flagName := range fs { if !used[set][flagName] { t.Errorf("accountedFlags names --%s of `%s`, which no verb's command takes any more", flagName, set) } } } } // Every verb's required arguments are properties of its schema: a schema that requires what it // does not describe is the uncallable verb of issue 244 from the other side. func TestEveryRequiredArgumentIsDescribed(t *testing.T) { for name, v := range servedSchemas(t) { names, _ := declaredArguments(v) for k := range sampleArguments(v) { if !contains(names, k) { t.Errorf("%s requires %q and does not describe it", name, k) } } } }