Files
mesh-catalog/modules/mesh-knowledge/cmd/mesh-knowledge/review_owed_test.go
T
jschoubben c394ecd9fd
mesh/merge-gate pass: builds mesh-knowledge → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery failed: its walk failed, carrying its own commit: a gate on a first machine (what it carried put back), a build, a machine
mesh-knowledge: give a never-reviewed notice its resolved line when raised, and test a refused clear and local time in a set zone (review of #219)
The operator's channel edits a cleared message with the resolved line it was raised with, so one sent only with
the clear was never shown.
2026-10-11 19:41:45 +02:00

200 lines
8.3 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"strings"
"testing"
"time"
)
// novox/hq issue 497: a review that could not be had is owed. What is owed is kept in the knowledge base's store,
// with its try count and when its next try is due, so the background work takes it up after a restart of the tool
// runner as it would have without one; the history says when it is asked again, so nobody takes a pause for a
// loss; and after its last try the history says so and the operator is told once. It is never an approval.
func TestAReviewOwedOutlivesARestartAndItsLastTryIsSaid(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
inZone(t)
w := imported(t, s)
n := proposed(t, w, "author@g14", false)
key := Key{KindDecision, n}
lastWhy := func() string {
vs, err := s.Versions(context.Background(), key)
if err != nil || len(vs) == 0 {
t.Fatalf("versions of %s: %v", key, err)
}
return vs[len(vs)-1].Why
}
var due time.Time
for try := 1; try <= MostReviewTries; try++ {
if try > 1 {
// The tool runner restarts: a new knowledge base over the same store, nothing of the old one's memory.
w = newWorld(t, s, fixture())
w.clock = due.Add(-time.Second)
if said := w.reviewAll(t); len(said) != 0 {
t.Fatalf("try %d asked before it was due: %v", try, said)
}
w.clock = due
}
w.judge.reviewFail = true
if said := w.reviewAll(t); len(said) != 1 || !strings.Contains(said[0], "not reviewed") {
t.Fatalf("try %d after a restart was not taken up: %v", try, said)
}
e := mustGet(t, s, key)
if e.Current.Status != StatusProposed || e.Current.Judging == nil || e.Current.Judging.State != JudgingFailed ||
e.Current.Judging.Tries != try || len(e.Current.Reviews) != 0 {
t.Fatalf("try %d is not counted in the store: %s, %+v", try, e.Current.Status, e.Current.Judging)
}
why := lastWhy()
if !strings.Contains(why, fmt.Sprintf("try %d of %d", try, MostReviewTries)) {
t.Fatalf("try %d: the history does not count it: %q", try, why)
}
if try < MostReviewTries {
due = w.clock.Add(time.Duration(try) * 10 * time.Minute)
when := "asked again at " + due.Local().Format("2006-01-02 15:04 MST")
if utc := due.UTC().Format("15:04"); strings.Contains(when, utc) || !strings.Contains(when, "CEST") {
t.Fatalf("the time is not said locally: %q (UTC %s)", when, utc)
}
if !strings.Contains(why, when) {
t.Fatalf("try %d: the history does not say when it is asked again (%q): %q", try, when, why)
}
if g, _ := w.k.Get(GetAsk{Number: n}); !strings.Contains(g.Review, when) {
t.Fatalf("try %d: get does not say when it is asked again (%q): %q", try, when, g.Review)
}
if len(w.told) != 0 {
t.Fatalf("try %d: the operator was told before the last try: %v", try, w.told)
}
}
}
// After its last try: the history says it was the last, and the operator is told, once.
if why := lastWhy(); !strings.Contains(why, "the last") || !strings.Contains(why, "the operator is told") {
t.Fatalf("the last try is not said in the history: %q", why)
}
if len(w.told) != 1 || !strings.Contains(w.told[0], key.String()) || !strings.Contains(w.told[0], "never reviewed") {
t.Fatalf("the operator is not told once of the last try: %v", w.told)
}
w.clock = w.clock.Add(24 * time.Hour)
if said := w.reviewAll(t); len(said) != 0 || len(w.told) != 1 {
t.Fatalf("after the last try: asked %v, told %v", said, w.told)
}
// Another restart neither asks it again nor tells the operator again.
clock := w.clock
w = newWorld(t, s, fixture())
w.clock = clock
if said := w.reviewAll(t); len(said) != 0 || len(w.told) != 0 {
t.Fatalf("after a restart past the last try: asked %v, told %v", said, w.told)
}
// Never an approval.
if e := mustGet(t, s, key); e.Current.Status != StatusProposed || len(w.said.of(EventAccepted)) != 0 {
t.Fatalf("a review never had approved it: %s", e.Current.Status)
}
})
}
// novox/hq issue 497: the notice of a proposal never reviewed says it stays proposed, so it is cleared once that is
// no longer so; and a later version's run of tries is a notice of its own, told once whatever else is written.
func TestProposedAgainAfterTheLastTryClearsTheNotice(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
w := imported(t, s)
n := proposed(t, w, "author@g14", false)
key := Key{KindDecision, n}
runOut := func(from int) {
t.Helper()
w.judge.reviewFail = true
for try := from; try <= MostReviewTries; try++ {
if said := w.reviewAll(t); len(said) != 1 {
t.Fatalf("try %d: %v", try, said)
}
w.clock = w.clock.Add(time.Duration(try) * ReviewPause)
}
}
runOut(1)
e := mustGet(t, s, key)
first := fmt.Sprintf("knowledge.unreviewed.decision-%d.v%d", n, e.CurrentVersion)
if len(w.toldKeys) != 1 || w.toldKeys[0] != first || len(w.cleared) != 0 ||
w.resolved[0] != key.String()+" no longer waits for its review" {
t.Fatalf("after the last try: told %v, cleared %v", w.toldKeys, w.cleared)
}
// Another version written while it waits (as a link change on it writes one) tells nothing again.
r := e.Current.Clone()
if err := s.Save(context.Background(), Version{By: "test", At: w.clock, Why: "a link changed", Record: r}, true, false, e.Revision); err != nil {
t.Fatal(err)
}
if w.reviewAll(t); len(w.toldKeys) != 1 || len(w.cleared) != 0 {
t.Fatalf("a version written while it waits: told %v, cleared %v", w.toldKeys, w.cleared)
}
// Proposed again: the notice is cleared, saying so.
if _, err := w.k.Propose("author@g14", valid(n)); err != nil {
t.Fatal(err)
}
w.clock = w.clock.Add(time.Second)
w.reviewAll(t) // its try 1, which fails, and the clearing
w.clock = w.clock.Add(ReviewPause)
if len(w.cleared) != 1 || w.cleared[0][0] != first || !strings.Contains(w.cleared[0][1], "proposed again") {
t.Fatalf("proposed again: cleared %v", w.cleared)
}
// Its own run of tries out is a notice of its own, under its own version.
runOut(2)
if len(w.toldKeys) != 2 || w.toldKeys[1] == first || len(w.cleared) != 1 {
t.Fatalf("a later version ran out: told %v, cleared %v", w.toldKeys, w.cleared)
}
// Proposed again and accepted at last: that notice is cleared too, and nothing is cleared twice, also after
// a restart.
w.judge.reviewFail = false
w.accepted(t, "author@g14", valid(n))
if len(w.cleared) != 2 || w.cleared[1][0] != w.toldKeys[1] || !strings.Contains(w.cleared[1][1], "proposed again") {
t.Fatalf("accepted: cleared %v", w.cleared)
}
clock := w.clock
w = newWorld(t, s, fixture())
w.clock = clock
if w.reviewAll(t); len(w.cleared) != 0 || len(w.told) != 0 {
t.Fatalf("after a restart: told %v, cleared %v", w.told, w.cleared)
}
})
}
// inZone sets the local zone to one that is not UTC for one test, so a time said locally is proven to be, wherever
// the test runs.
func inZone(t *testing.T) {
t.Helper()
was := time.Local
time.Local = time.FixedZone("CEST", 2*60*60)
t.Cleanup(func() { time.Local = was })
}
// novox/hq issue 497: a clear the operator's channel refused keeps its notice as told, and is sent again at the next
// step until it is taken.
func TestAClearThatFailsIsSentAgain(t *testing.T) {
eachStore(t, func(t *testing.T, s Store) {
w := imported(t, s)
n := proposed(t, w, "author@g14", false)
w.judge.reviewFail = true
for try := 1; try <= MostReviewTries; try++ {
w.reviewAll(t)
w.clock = w.clock.Add(time.Duration(try) * ReviewPause)
}
if len(w.toldKeys) != 1 {
t.Fatalf("after the last try: told %v", w.toldKeys)
}
prefix := fmt.Sprintf("knowledge.unreviewed.decision-%d.", n)
refused := 0
w.k.Clear = func(key, resolved string) error { refused++; return errors.New("the channel is down") }
if _, err := w.k.Propose("author@g14", valid(n)); err != nil {
t.Fatal(err)
}
w.reviewAll(t)
if told, err := s.ToldOf(context.Background(), prefix); err != nil || len(told) != 1 || refused == 0 {
t.Fatalf("a refused clear: told %v (%v), refused %d", told, err, refused)
}
w.k.Clear = func(key, resolved string) error { w.cleared = append(w.cleared, [2]string{key, resolved}); return nil }
w.clock = w.clock.Add(time.Second)
w.reviewAll(t)
if told, err := s.ToldOf(context.Background(), prefix); err != nil || len(told) != 0 || len(w.cleared) != 1 ||
w.cleared[0][0] != w.toldKeys[0] {
t.Fatalf("the next step: told %v (%v), cleared %v", told, err, w.cleared)
}
})
}