//go:build integration package main import ( "encoding/json" "reflect" "sort" "strconv" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" ) 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 } func TestNoArgumentAVerbIsGivenIsPassedOver(t *testing.T) { for name, v := range servedSchemas(t) { if inProcess[name] { continue } names, switches := declaredArguments(v) for _, subset := range subsetsOf(names) { args := argumentsFor(subset, switches) argv, err := argvFor(name, args) if err != nil { if strings.Contains(err.Error(), "does not declare") { t.Errorf("%s %v: %v", name, args, err) } continue } for _, dropped := range subset { fewer := map[string]any{} for k, val := range args { if k != dropped { fewer[k] = val } } without, err := argvFor(name, fewer) if err == nil && reflect.DeepEqual(argv, without) && saidOutright[name] != dropped { t.Errorf("%s ignores %q given with %v: %v either way", name, dropped, subset, argv) } } } } } func TestAnArgumentAVerbDoesNotDeclareIsRefused(t *testing.T) { for name, v := range servedSchemas(t) { names, _ := declaredArguments(v) strangers := []string{"no-such-argument"} if !contains(names, "node") { strangers = append(strangers, "node") } for _, stranger := range strangers { args := sampleArguments(v) args[stranger] = "x" _, err := argvFor(name, args) if inProcess[name] { _, err = readArguments(name, args) } if err == nil || !strings.Contains(err.Error(), strconv.Quote(stranger)) { t.Errorf("%s took %q, which it does not declare: %v", name, stranger, err) } } } if _, err := argvFor("clear", map[string]any{"dead": "yes"}); err == nil { t.Error("a switch took a word that is neither true nor false, and would have read it as false") } if _, err := argvFor("node", map[string]any{"node": 7}); err == nil { t.Error("a number was taken as a machine's name, or as no machine") } if argv, err := argvFor("clear", map[string]any{"dead": true}); err != nil || strings.Join(argv, " ") != "clear --dead" { t.Errorf("a switch given as JSON true: %v %v", argv, err) } } func TestEveryFlagOfAVerbsCommandIsAnArgumentOrAccountedFor(t *testing.T) { flags := commandFlags(t) if len(flags["push"]) == 0 || len(flags["plan"]) == 0 { t.Fatalf("reading the commands' flags found nothing for push or plan: %v", flags) } reached := map[string]map[string]bool{} // verb → flag sets its command lines reach served := servedSchemas(t) for name, v := range served { if inProcess[name] || name == "command" { continue } names, switches := declaredArguments(v) for _, subset := range subsetsOf(names) { argv, err := argvFor(name, argumentsFor(subset, switches)) if err != nil { continue } // The flag set is named by the longest run of leading words that names one. var words []string for _, w := range argv { if strings.HasPrefix(w, "-") { break } words = append(words, w) } for n := len(words); n > 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) } } } } 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) } } } }