107 lines
4.3 KiB
Go
107 lines
4.3 KiB
Go
//go:build integration
|
|
|
|
package inventory
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/store"
|
|
)
|
|
|
|
// **Migration 0105 refuses a store whose rows were not moved, and drops the tables of a moved one** (novox/hq ADR 0408 §8,
|
|
// issue 1111). The store is built as the mesh holds it before this migration, with an assignment row in the table and no
|
|
// record of the move: the migration raises, and nothing it did is kept. Then the move is recorded, a pending assignment
|
|
// still open refuses it again, and once it has settled the tables go and build_gate stays.
|
|
func TestMigration0105RefusesAStoreWhoseRowsWereNotMovedAndDropsTheTablesOfAMovedOne(t *testing.T) {
|
|
inv, _ := ForTestBefore(t, 105)
|
|
ctx := t.Context()
|
|
if _, err := inv.AddNode(ctx, "anchor"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "ui", Version: "1"}, Source{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inv.ExecForTest(t, `insert into assignment (node, module) select id, 'ui' from node where name = 'anchor'`)
|
|
every, err := Migrations()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Migration 105 alone: the ones after it (106, issue 1145) are not what this test judges.
|
|
var all []store.Migration
|
|
for _, m := range every {
|
|
if m.Number <= 105 {
|
|
all = append(all, m)
|
|
}
|
|
}
|
|
tables := func() map[string]bool {
|
|
t.Helper()
|
|
out := map[string]bool{}
|
|
for _, row := range inv.QueryForTest(t, `select table_name::text from information_schema.tables where table_schema = 'public'`) {
|
|
out[strings.Trim(row[0], `"`)] = true
|
|
}
|
|
return out
|
|
}
|
|
refused := func(why string) {
|
|
t.Helper()
|
|
_, err := inv.store.Migrate(ctx, all)
|
|
if err == nil || !strings.Contains(err.Error(), why) {
|
|
t.Fatalf("the migration was not refused for %q: %v", why, err)
|
|
}
|
|
if !tables()["assignment"] || !tables()["chain_moved"] {
|
|
t.Fatalf("a refused migration kept a drop")
|
|
}
|
|
if rows := inv.QueryForTest(t, `select count(*) from assignment`); rows[0][0] != "1" {
|
|
t.Fatalf("a refused migration lost the assignment: %v", rows)
|
|
}
|
|
}
|
|
|
|
// No move recorded: the rows of assignments were never copied.
|
|
refused("assignments")
|
|
|
|
// The move recorded for every kind, and a pending assignment still waiting for its build: no mover carries it.
|
|
inv.ExecForTest(t, `insert into chain_moved (counts, largest) values ($1::jsonb, $1::jsonb)`,
|
|
`{"builds": 0, "rollouts": 0, "assignments": 1}`)
|
|
inv.ExecForTest(t, `insert into pending_assignment (node, module, build_id)
|
|
select id, 'ui', 'b1' from node where name = 'anchor'`)
|
|
refused("pending assignment")
|
|
|
|
// Expired and raised as a condition, and the condition neither answered nor cleared: still refused.
|
|
inv.ExecForTest(t, `update pending_assignment set state = 'expired', raised_at = now()`)
|
|
refused("pending assignment")
|
|
|
|
// Its condition cleared: the guard lets the migration through.
|
|
inv.ExecForTest(t, `update pending_assignment set cleared_at = now()`)
|
|
done, err := inv.store.Migrate(ctx, all)
|
|
if err != nil || len(done) != 1 || done[0].Number != 105 {
|
|
t.Fatalf("the migration did %v (%v)", done, err)
|
|
}
|
|
left := tables()
|
|
for _, gone := range append(append([]string{}, chainTables...), "chain_moved") {
|
|
if left[gone] {
|
|
t.Errorf("the table %s is still in the store", gone)
|
|
}
|
|
}
|
|
for _, stays := range []string{"build_gate", "assignment_generation", "node", "module"} {
|
|
if !left[stays] {
|
|
t.Errorf("the table %s is gone", stays)
|
|
}
|
|
}
|
|
if rows := inv.QueryForTest(t, `select count(*) from information_schema.columns where table_name = 'node' and column_name = 'sent_builds'`); rows[0][0] != "1" {
|
|
t.Errorf("node.sent_builds, which stays as the source of the copy, is gone")
|
|
}
|
|
if rows := inv.QueryForTest(t, `select count(*) from pg_proc where proname = 'raise_assignment_generation'`); rows[0][0] != "0" {
|
|
t.Errorf("the function raise_assignment_generation is still in the store")
|
|
}
|
|
}
|
|
|
|
// A store that never held a row of the chain — a mesh raised after the move — passes the guard with no record of a move.
|
|
func TestMigration0105PassesAStoreThatNeverHeldARow(t *testing.T) {
|
|
inv, migrate := ForTestBefore(t, 105)
|
|
migrate()
|
|
if rows := inv.QueryForTest(t, `select count(*) from information_schema.tables where table_name in ('build', 'release_plan', 'assignment', 'chain_moved')`); rows[0][0] != "0" {
|
|
t.Fatalf("tables of the chain remain: %v", rows)
|
|
}
|
|
}
|