package main import ( "encoding/json" "os" "path/filepath" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/compose" "git.novox.be/novox/mesh-sdk/go/compose/frombus" "git.novox.be/novox/mesh-sdk/go/delivery" ) // Issue 1126 (ADR 0549 §4, ADR 0555 §6): the seed writes one placement file per node of the export, as the catalogue's // check and the indexer read it, from the verb's answer bare or wrapped; it writes nothing for an export the check // refuses, and never over a file already there. func TestTheSeedWritesEachNodesPlacementFileOnce(t *testing.T) { export := `{"answer":{"ace":{"v":1,"node":"ace","revision":"x","decided":{"address":"10.77.0.2"},"modules":[{"module":"plex","ports":{"32400":32400}}]},` + `"novox":{"v":1,"node":"novox","decided":{"hub":true,"address":"10.77.0.1"},"modules":[]}}}` root := t.TempDir() written, err := Seed(strings.NewReader(export), root) if err != nil || len(written) != 2 { t.Fatalf("written %v, %v", written, err) } raw, err := os.ReadFile(filepath.Join(root, "placement", "ace.json")) if err != nil { t.Fatal(err) } p, err := delivery.DecodePlacement(raw) if err != nil || p.Node != "ace" || p.Revision != "" || p.HostPorts()["plex"][32400] != 32400 { t.Errorf("ace's file reads %+v, %v", p, err) } if _, err := Seed(strings.NewReader(export), root); err == nil || !strings.Contains(err.Error(), "already there") { t.Errorf("a second seed over the files: %v", err) } clash := `{"ace":{"v":1,"node":"ace","decided":{"address":"10.77.0.1"},"modules":[]},"novox":{"v":1,"node":"novox","decided":{"address":"10.77.0.1"},"modules":[]}}` other := t.TempDir() if _, err := Seed(strings.NewReader(clash), other); err == nil || !strings.Contains(err.Error(), "both have the address") { t.Errorf("an export the check refuses: %v", err) } if entries, _ := os.ReadDir(filepath.Join(other, "placement")); len(entries) != 0 { t.Errorf("a refused export wrote %v", entries) } // What the controller's placement-export prints, frombus.Export per node, is what the seed takes: a node with no // module placed included. in := compose.Input{Nodes: []compose.Node{{Name: "solo", Place: compose.Place{Hub: true, Address: "10.77.0.9"}}}} exported, err := frombus.Export(in, "solo") if err != nil { t.Fatal(err) } answer, _ := json.Marshal(map[string]any{"answer": map[string]delivery.Placement{"solo": exported}}) if written, err := Seed(strings.NewReader(string(answer)), t.TempDir()); err != nil || len(written) != 1 { t.Errorf("Export's answer %s was not seeded: %v, %v", answer, written, err) } for _, bad := range []string{`[]`, `{}`, `{"ace":{"v":1,"node":"novox","modules":[]}}`} { if _, err := Seed(strings.NewReader(bad), t.TempDir()); err == nil { t.Errorf("%s was seeded", bad) } } }