Files
mesh-catalog/modules/restic/cmd/restic-backups/items_test.go
T

128 lines
4.5 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"reflect"
"testing"
"time"
)
// itemBusFake records every call, in order, as the backup holder makes them.
type itemBusFake struct {
log []string
restoring []string // what each read of the recovery module's restores answers, in turn
unended []ItemRef
runFailure error
}
func (f *itemBusFake) Say(event string, ref ItemRef, body map[string]any) error {
line := event + " " + ref.Module + "." + ref.Item
if o, ok := body["outcome"]; ok {
line += " " + fmt.Sprint(o)
}
if r, ok := body["restore"]; ok {
line += " restore=" + fmt.Sprint(r)
}
if e, ok := body["error"]; ok {
line += " error=" + fmt.Sprint(e)
}
if _, ok := body["at"]; !ok {
line += " NO-AT"
}
f.log = append(f.log, line)
return nil
}
func (f *itemBusFake) Unended() ([]ItemRef, error) { return f.unended, nil }
func (f *itemBusFake) Restoring(module string) (string, error) {
f.log = append(f.log, "read restores of "+module)
if len(f.restoring) == 0 {
return "", nil
}
act := f.restoring[0]
f.restoring = f.restoring[1:]
return act, nil
}
func (f *itemBusFake) WaitRestored(_ context.Context, act string) error {
f.log = append(f.log, "restored "+act)
return nil
}
func itemsBackups(t *testing.T, bus *itemBusFake, broker string, runErr error) *Backups {
t.Helper()
return &Backups{Where: withData(t, composed, composedData), Node: "ace", Now: func() time.Time { return time.Date(2026, 11, 12, 3, 0, 0, 0, time.UTC) },
Say: quiet, Bus: bus, BrokerModule: func() (string, error) { return broker, nil },
Run: func(_ context.Context, name string, args ...string) (string, error) {
if name == "restic" && len(args) > 5 && args[5] == "backup" {
return "", runErr
}
return "[]", nil
}}
}
func declaredOf(t *testing.T, b *Backups, module string) Declared {
t.Helper()
all, err := b.Declared()
must(t, err)
for _, d := range all {
if d.Module == module {
return d
}
}
t.Fatalf("%s is not declared", module)
return Declared{}
}
// Design 47 G §563 (BKP4): every item of a backed-up module says item-started, then item-ended with its outcome and `at`; a
// cache item says nothing; a failed backup ends its items failed with why; the holder reads no restore for a module that does
// not hold the seat mesh-broker.
func TestEveryItemOfABackedUpModuleSaysItsStartAndItsEnd(t *testing.T) {
bus := &itemBusFake{}
b := itemsBackups(t, bus, "somebody-else", nil)
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); !n.OK {
t.Fatalf("%+v", n)
}
want := []string{"item-started postgres.store", "item-started postgres.dumps", "item-ended postgres.store done", "item-ended postgres.dumps done"}
if !reflect.DeepEqual(bus.log, want) {
t.Fatalf("said %q", bus.log)
}
bus.log = nil
b = itemsBackups(t, bus, "somebody-else", errors.New("disk full"))
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); n.OK {
t.Fatal("a failed backup was ok")
}
if len(bus.log) != 4 || bus.log[2] != "item-ended postgres.store failed error=disk full" {
t.Fatalf("said %q", bus.log)
}
}
// Design 47 G §563 (BKP5): for the holder of mesh-broker the holder says its start, then reads the recovery module's restores;
// a restore that runs puts the items off naming its act, waits for that act's restored, starts again, and reads once more.
func TestTheBackupOfTheBusGivesWayToARestoreThatRuns(t *testing.T) {
bus := &itemBusFake{restoring: []string{"act-7"}}
b := itemsBackups(t, bus, "postgres", nil)
if n := b.one(context.Background(), declaredOf(t, b, "postgres"), false); !n.OK {
t.Fatalf("%+v", n)
}
want := []string{
"item-started postgres.store", "item-started postgres.dumps", "read restores of postgres",
"item-ended postgres.store put-off restore=act-7", "item-ended postgres.dumps put-off restore=act-7",
"restored act-7",
"item-started postgres.store", "item-started postgres.dumps", "read restores of postgres",
"item-ended postgres.store done", "item-ended postgres.dumps done"}
if !reflect.DeepEqual(bus.log, want) {
t.Fatalf("said %q", bus.log)
}
}
// Design 47 G §563 (BKP4): at its start the holder ends every item it said started and never ended, failed with the error stopped.
func TestItemsLeftStartedAreEndedFailedStoppedAtTheStart(t *testing.T) {
bus := &itemBusFake{unended: []ItemRef{{"postgres", "store"}}}
b := itemsBackups(t, bus, "postgres", nil)
must(t, b.StopStale())
if !reflect.DeepEqual(bus.log, []string{"item-ended postgres.store failed error=stopped"}) {
t.Fatalf("said %q", bus.log)
}
}