package grants import ( "context" "encoding/json" "errors" "slices" "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // Design 47 G §565, A-230: the part reads points[].names and asides[].names of kept-points, keeps them in its state memberships, // grants the names it kept until the call first answers after a start, takes a name away only by an answer, and asks again on // the seat's ended (outcome done), point-removed and expiry-ended alone. func TestKeptPointsNamesAreKeptUntilTheCallAnswers(t *testing.T) { store := memStore{} answer := func(s string) func(context.Context) (json.RawMessage, error) { return func(context.Context) (json.RawMessage, error) { return json.RawMessage(s), nil } } p := &Part{Store: store, Now: func() time.Time { return time.Unix(100, 0) }} v := &KeptPointsView{Ask: answer(`{"points":[{"names":["KV_a_x","KV_b_y"]}],"asides":[{"names":["OBJ_c_z"]}]}`)} p.KeptPoints = v v.askUntil(context.Background(), p, "start") var in Inputs if err := p.withKeptNames(&in); err != nil || !slices.Equal(in.KeptNames, []string{"KV_a_x", "KV_b_y", "OBJ_c_z"}) { t.Fatalf("names after the first answer %v (%v)", in.KeptNames, err) } // A restart: the call fails, and the names last kept stay; the status says it waits. p2 := &Part{Store: store} failing := &KeptPointsView{Ask: func(context.Context) (json.RawMessage, error) { return nil, errors.New("no responders") }, Pause: func(context.Context, time.Duration) bool { return false }} p2.KeptPoints = failing failing.Start(context.Background(), p2) failing.askUntil(context.Background(), p2, "start") in = Inputs{} if err := p2.withKeptNames(&in); err != nil || len(in.KeptNames) != 3 { t.Fatalf("names kept before the call answers %v (%v)", in.KeptNames, err) } if s := failing.Status(); !s.Waiting || s.Names != 3 || s.Error == "" { t.Errorf("status %+v, want waiting with 3 names and the error", s) } // An answer takes a name away that no point names any more. failing.Ask = answer(`{"points":[{"names":["KV_a_x"]}],"asides":[]}`) failing.askUntil(context.Background(), p2, "the seat's point-removed") in = Inputs{} _ = p2.withKeptNames(&in) if !slices.Equal(in.KeptNames, []string{"KV_a_x"}) || failing.Status().Waiting { t.Errorf("names after the answer %v, status %+v", in.KeptNames, failing.Status()) } // Which events ask again. for event, body := range map[string]string{"mesh-recovery.ended": `{"outcome":"done"}`, "mesh.seat.mesh-recovery.event.point-removed": `{}`, "mesh-recovery.expiry-ended": `{}`} { if !IsKeptPointsEvent(event, []byte(body)) { t.Errorf("%s does not ask again", event) } } for event, body := range map[string]string{"mesh-recovery.ended": `{"outcome":"failed"}`, "mesh-recovery.restored": `{"outcome":"done"}`, "mesh-broker.created": `{}`} { if IsKeptPointsEvent(event, []byte(body)) { t.Errorf("%s asks again", event) } } } // Design 47 G §554, §565: the names the recovery account is granted are those of every manifest of the catalog graph (an old // section's and a template entry's) and those a point names, once each; a name a manifest left stays while a point names it. func TestTheRecoveryNamesAreTheCatalogGraphsAndThePointsNames(t *testing.T) { trunk := catalogue.Manifest{Module: "a", State: []catalogue.StateDeclaration{{Name: "x"}}} old := catalogue.Manifest{Module: "a", State: []catalogue.StateDeclaration{{Name: "old"}}, BusState: []catalogue.BusStateEntry{{Name: "tpl", Storage: catalogue.StorageKeyValue}}} got := recoveryNamesOf(Inputs{Manifests: map[string]catalogue.Manifest{"a": trunk}, RanByFingerprint: map[string]catalogue.Manifest{"fp": old}, KeptNames: []string{"KV_a_gone", "KV_a_x"}}) want := []string{"KV_a_gone", "KV_a_old", "KV_a_tpl", "KV_a_x"} if !slices.Equal(got, want) { t.Errorf("names %v, want %v", got, want) } }