186 lines
5.5 KiB
Go
186 lines
5.5 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/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)
|
|
}
|
|
}
|
|
}
|
|
}
|