package main import ( "context" "strings" "testing" ) // to-be 55 §10: "A drafted clause is never served as in force — the module's test: holds over a record with only // drafted clauses answers none, and ask marks them." And §7: two separate judgements, the second seeing only the // record and the drafts; the operator told once per topic; return drafts again. // draftUntil takes drafting steps until the record's drafting reaches a state, at most a few. func draftUntil(t *testing.T, w *world, k Key, state string) *Record { t.Helper() for i := 0; i < 40; i++ { r := mustGet(t, w.k.Store, k).Current if r.Drafting != nil && r.Drafting.State == state { return r } if _, err := w.k.DraftStep(); err != nil { t.Fatal(err) } } t.Fatalf("%s's drafting never reached %s", k, state) return nil } func TestADraftedClauseIsNeverServedAsInForce(t *testing.T) { eachStore(t, func(t *testing.T, s Store) { w := imported(t, s) k := Key{KindDecision, 240} r := draftUntil(t, w, k, DraftDrafted) if len(r.Clauses) == 0 || r.Clauses[0].Source != SourceDrafted { t.Fatalf("drafted: %+v", r.Clauses) } h, err := w.k.Holds("", &k, 0) if err != nil { t.Fatal(err) } if len(h.Clauses) != 0 || !strings.Contains(h.NotYet, "drafted clause(s) are not served") { t.Fatalf("holds over a record with only drafted clauses: %+v", h) } // The topic's holds leaves them out too. ht, _ := w.k.Holds(r.Topic, nil, 64<<10) for _, c := range ht.Clauses { if c.Record == k.String() { t.Fatalf("the topic's holds served a drafted clause: %+v", c) } } a, err := w.k.Ask("a module says how it is healthy and the node-engine judges it", 0) if err != nil { t.Fatal(err) } marked := false for _, rec := range a.Records { for _, c := range rec.Clauses { if rec.Record == k.String() { if !strings.HasPrefix(c.Holds, "drafted, not confirmed") { t.Fatalf("ask served a drafted clause as %q", c.Holds) } marked = true } } if rec.Record == k.String() && len(rec.Clauses) == 0 && strings.Contains(rec.Note, "drafted") { marked = true } } if !marked { t.Fatalf("ask did not show 0240's drafted clauses as drafted: %+v", a) } // get marks each one. g, _ := w.k.Get(GetAsk{Kind: KindDecision, Number: 240}) for _, c := range g.Clauses { if !strings.HasPrefix(c.Holds, "drafted") { t.Fatalf("get: %+v", c) } } }) } func TestClausesAreDraftedThenConfirmedByASeparateJudgementThatSeesOnlyTheRecordAndTheDrafts(t *testing.T) { eachStore(t, func(t *testing.T, s Store) { w := imported(t, s) k := Key{KindDecision, 240} r := draftUntil(t, w, k, DraftDone) var draftQ, confirmQ string n := 0 for _, q := range w.judge.questions { if !strings.Contains(q, "ADR 0240") { continue } n++ if strings.Contains(q, "drafted from it by someone else") { confirmQ = q } else { draftQ = q } } if n != 2 || draftQ == "" || confirmQ == "" { t.Fatalf("0240 was not drafted and confirmed by two separate judgements: %d questions", n) } section, _ := decisionSection(r) if !strings.Contains(confirmQ, section) || !strings.Contains(confirmQ, r.Clauses[0].Text) { t.Fatal("the confirming judgement was not shown the record and the drafts") } // It sees only those: nothing of the mesh but the record, and none of the first judgement's own words. for _, other := range []string{"Write what it decides as numbered", "An earlier draft"} { if strings.Contains(confirmQ, other) { t.Fatalf("the confirming judgement was shown %q", other) } } if !strings.Contains(draftQ, section) || strings.Contains(draftQ, "The drafted clauses") { t.Fatal("the drafting judgement's question is not the section alone") } for _, c := range r.Clauses { if c.Source != SourceConfirmed { t.Fatalf("not confirmed: %+v", c) } } if len(w.said.of(EventDrafted)) == 0 || len(w.said.of(EventConfirmed)) == 0 { t.Fatalf("events: %v", w.said.events) } // Now confirmed, holds serves them, marked. h, _ := w.k.Holds("", &k, 0) if len(h.Clauses) != len(r.Clauses) || !strings.Contains(h.Clauses[0].Holds, "confirmed") { t.Fatalf("holds after confirming: %+v", h) } }) } func TestAClauseTheSecondJudgementRejectsStaysDraftedWithTheReason(t *testing.T) { w := imported(t, newMemStore()) k := Key{KindDecision, 240} r := draftUntil(t, w, k, DraftDrafted) ctx := context.Background() e := mustGet(t, w.k.Store, k) r = e.Current.Clone() r.Clauses[0].Text += " NOT EXACT" if err := w.k.Store.Save(ctx, Version{By: "test", Why: "a draft the checker will reject", Record: r}, true, false, e.Revision); err != nil { t.Fatal(err) } r = draftUntil(t, w, k, DraftDone) if r.Clauses[0].Source != SourceDrafted || r.Clauses[0].Unconfirmed == "" { t.Fatalf("a rejected clause: %+v", r.Clauses[0]) } h, _ := w.k.Holds("", &k, 0) for _, c := range h.Clauses { if c.N == 1 { t.Fatal("a clause the second judgement rejected is served") } } } func TestTheOperatorIsToldOncePerTopicAndReturnDraftsAgain(t *testing.T) { w := imported(t, newMemStore()) for i := 0; i < 40; i++ { said, err := w.k.DraftStep() if err != nil { t.Fatal(err) } if said == "" { break } } // Every prose record of the fixture's topics drafted and confirmed; each topic told once. topics := map[string]int{} for _, s := range w.told { topics[s]++ } if len(w.told) == 0 { t.Fatal("the operator was told of no topic") } for s, n := range topics { if n != 1 { t.Fatalf("told %d times: %s", n, s) } } told := len(w.told) k := Key{KindDecision, 240} if _, err := w.k.Return("operator@shanks", Key{KindDecision, 276}, "x"); err == nil { t.Fatal("a record whose author wrote its clauses was returned") } if _, err := w.k.Return("operator@shanks", k, "clause 2 says more than the section"); err != nil { t.Fatal(err) } r := mustGet(t, w.k.Store, k).Current if len(r.Clauses) != 0 || r.Drafting.State != DraftWaiting || len(w.said.of(EventReturned)) != 1 { t.Fatalf("returned: %+v", r) } r = draftUntil(t, w, k, DraftDone) last := "" for _, q := range w.judge.questions { if strings.Contains(q, "An earlier draft was sent back") { last = q } } if !strings.Contains(last, "clause 2 says more than the section") { t.Fatal("the drafting after a return did not read its reason") } if len(w.told) != told { t.Fatal("the operator was told of the topic again") } } // A supersedes-in-part hq wrote names the record; which clause it changes is judged, then confirmed, and only then // is the older clause changed. func TestWhichClauseAPartialSupersessionChangesIsJudgedThenConfirmed(t *testing.T) { w := imported(t, newMemStore()) for i := 0; i < 40; i++ { if said, err := w.k.DraftStep(); err != nil { t.Fatal(err) } else if said == "" { break } } r := mustGet(t, w.k.Store, Key{KindDecision, 282}).Current var l *Link for i := range r.Links { if r.Links[i].Kind == LinkChanges && r.Links[i].To.Number == 276 { l = &r.Links[i] } } if l == nil || l.Source != SourceConfirmed || l.To.Clause != 1 { t.Fatalf("0282's change of 0276: %+v", l) } c := mustGet(t, w.k.Store, Key{KindDecision, 276}).Current.Clause(1) if c.Status != ClauseChanged || c.ChangedBy[0] != "ADR 0282" { t.Fatalf("0276 §1: %+v", c) } } func TestADraftingThatFailsIsSaidAndTriedAgainLater(t *testing.T) { w := imported(t, newMemStore()) w.judge.fail = true if _, err := w.k.DraftStep(); err == nil { t.Fatal("a failed judgement was not said") } r := mustGet(t, w.k.Store, Key{KindDecision, 11}).Current if r.Drafting.State != DraftFailed || r.Drafting.Tries != 1 || !strings.Contains(r.Drafting.Reason, "licence") { t.Fatalf("%+v", r.Drafting) } }