package apply import ( "errors" "strings" "testing" "github.com/novox/mesh-host/internal/declaration" ) func twoModules(t *testing.T) *declaration.Declaration { t.Helper() d, err := declaration.Parse([]byte(`{"declaration":1,"builds":{"a":"b-1","b":"b-2"},"resources":[ {"id":"a.dir","type":"directory","path":"/srv/a","module":"a"}, {"id":"b.dir","type":"directory","path":"/srv/b","module":"b"}, {"id":"b.conf","type":"file","path":"/srv/b/c","content":"x","module":"b"}, {"id":"c.dir","type":"directory","path":"/srv/c","module":"c"}]}`)) if err != nil { t.Fatal(err) } return d } // 1132/AC3: every resource of module A applied and one of module B failed: A is a success, B failed with the failing // resource and its error named; a module gated by another's failure is failed too. func TestAnApplyIsGroupedPerModule(t *testing.T) { d := twoModules(t) done := Report{Outcomes: []Outcome{ {ID: "a.dir", Action: "created"}, {ID: "c.dir", Action: "skipped", Detail: "a step this module declares did not complete"}, }} err := &Error{Resource: "b.dir", Err: errors.New("no space left"), Done: done, Others: 1, Failures: []Failure{{Resource: "b.dir", Err: errors.New("no space left")}, {Resource: "b.conf", Err: errors.New("denied")}}, Modules: []string{"c"}} got := ModuleResults(d, Report{}, err) if len(got) != 3 { t.Fatalf("modules %v, want a, b and c", got) } if got["a"].Failed || got["a"].Detail != "" { t.Errorf("a: %+v, want a clean success", got["a"]) } b := got["b"] if !b.Failed || !strings.Contains(b.Detail, "b.dir") || !strings.Contains(b.Detail, "no space left") || !strings.Contains(b.Detail, "b.conf") || !strings.Contains(b.Detail, "denied") { t.Errorf("b: %+v, want both failing resources and their errors", b) } c := got["c"] if !c.Failed || !strings.Contains(c.Detail, "did not complete") { t.Errorf("c: %+v, want it failed as gated, with why", c) } } // 1132/AC3: an apply that did not fail is a success for every module the declaration names. func TestACleanApplyIsASuccessForEveryModule(t *testing.T) { got := ModuleResults(twoModules(t), Report{}, nil) for _, m := range []string{"a", "b", "c"} { if r, ok := got[m]; !ok || r.Failed { t.Errorf("%s: %+v (%v), want a success", m, r, ok) } } } // 1132/AC3: a step that belongs to no module stops the apply there; a module with a resource not attempted is not a // success. func TestAModuleNotAttemptedIsNotASuccess(t *testing.T) { d := twoModules(t) err := &Error{Resource: "boot", Err: errors.New("boom"), Stopped: true, Failures: []Failure{{Resource: "boot", Err: errors.New("boom")}}, Done: Report{Outcomes: []Outcome{{ID: "a.dir", Action: "created"}}}} got := ModuleResults(d, Report{}, err) if got["a"].Failed { t.Errorf("a was done: %+v", got["a"]) } if !got["b"].Failed || !strings.Contains(got["b"].Detail, "boot") { t.Errorf("b was not attempted: %+v", got["b"]) } }