two/internal/migration/state_test.go
GnomeZworc fef04b10fc
f-37: migration: add state change at boot #37
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
2026-08-13 23:43:16 +02:00

175 lines
4.7 KiB
Go

package migration
import (
"io"
"log/slog"
"testing"
"git.g3e.fr/syonad/two/internal/state"
"git.g3e.fr/syonad/two/pkg/db/kv"
"github.com/dgraph-io/badger/v4"
)
func newTestDB(t *testing.T) *badger.DB {
t.Helper()
db := kv.InitDB(kv.Config{Path: t.TempDir()}, false)
t.Cleanup(func() { db.Close() })
return db
}
func discardLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
func TestMigrateStates_LegacyMapping(t *testing.T) {
db := newTestDB(t)
cases := map[string]struct {
resource string
from string
want state.State
}{
"vpc created": {"vpc/vpc-1", "created", state.Running},
"subnet created": {"subnet/sn-1", "created", state.Running},
"vm started": {"vm/vm-1", "started", state.Running},
"vm stopped": {"vm/vm-2", "stopped", state.Deleted},
// Les états transitoires hérités sont orphelins après un redémarrage.
"vm starting": {"vm/vm-3", "starting", state.Error},
"vm stopping": {"vm/vm-4", "stopping", state.Error},
"vpc creating": {"vpc/vpc-2", "creating", state.Error},
"subnet deleting": {"subnet/sn-2", "deleting", state.Error},
}
for _, c := range cases {
kv.AddInDB(db, c.resource+"/state", c.from)
}
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("MigrateStates a échoué : %v", err)
}
for name, c := range cases {
got, err := state.Get(db, c.resource)
if err != nil {
t.Errorf("%s : Get a échoué : %v", name, err)
continue
}
if got != c.want {
t.Errorf("%s : %q → %q, attendu %q", name, c.from, got, c.want)
}
}
}
func TestMigrateStates_StableStatesUntouched(t *testing.T) {
db := newTestDB(t)
stable := map[string]state.State{
"vpc/vpc-1": state.Running,
"subnet/sn-1": state.Deleted,
"vm/vm-1": state.Error,
}
for resource, s := range stable {
if err := state.Set(db, resource, s); err != nil {
t.Fatalf("préparation du test : %v", err)
}
}
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("MigrateStates a échoué : %v", err)
}
for resource, want := range stable {
got, err := state.Get(db, resource)
if err != nil {
t.Fatalf("Get(%s) a échoué : %v", resource, err)
}
if got != want {
t.Errorf("%s : état modifié %q, attendu %q", resource, got, want)
}
}
}
func TestMigrateStates_Idempotent(t *testing.T) {
db := newTestDB(t)
kv.AddInDB(db, "vpc/vpc-1/state", "created")
kv.AddInDB(db, "vm/vm-1/state", "starting")
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("premier passage : %v", err)
}
first := map[string]state.State{}
for _, r := range []string{"vpc/vpc-1", "vm/vm-1"} {
s, err := state.Get(db, r)
if err != nil {
t.Fatalf("Get(%s) : %v", r, err)
}
first[r] = s
}
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("second passage : %v", err)
}
for r, want := range first {
got, err := state.Get(db, r)
if err != nil {
t.Fatalf("Get(%s) : %v", r, err)
}
if got != want {
t.Errorf("%s : le second passage a modifié l'état (%q → %q)", r, want, got)
}
}
}
func TestMigrateStates_UnknownValueBecomesError(t *testing.T) {
db := newTestDB(t)
kv.AddInDB(db, "vpc/vpc-corrompu/state", "n'importe quoi")
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("MigrateStates a échoué : %v", err)
}
got, err := state.Get(db, "vpc/vpc-corrompu")
if err != nil {
t.Fatalf("Get a échoué : %v", err)
}
if got != state.Error {
t.Errorf("une valeur inconnue devrait devenir %q, obtenu %q", state.Error, got)
}
}
func TestMigrateStates_IgnoresNonStateKeys(t *testing.T) {
db := newTestDB(t)
kv.AddInDB(db, "vpc/vpc-1/state", "created")
kv.AddInDB(db, "vpc/vpc-1/cidr", "10.0.0.0/16")
kv.AddInDB(db, "subnet/sn-1/local_iface", "br-vms")
kv.AddInDB(db, "vm/vm-1/disk/sda", "/data/root.qcow2")
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("MigrateStates a échoué : %v", err)
}
untouched := map[string]string{
"vpc/vpc-1/cidr": "10.0.0.0/16",
"subnet/sn-1/local_iface": "br-vms",
"vm/vm-1/disk/sda": "/data/root.qcow2",
}
for key, want := range untouched {
got, err := kv.GetFromDB(db, key)
if err != nil {
t.Errorf("la clé %s devrait exister : %v", key, err)
continue
}
if got != want {
t.Errorf("%s = %q, attendu %q", key, got, want)
}
}
// Aucune clé /state parasite ne doit apparaître sur ces ressources.
if _, err := kv.GetFromDB(db, "vm/vm-1/state"); err == nil {
t.Error("aucune clé state ne devrait être créée pour vm-1")
}
}
func TestMigrateStates_EmptyDB(t *testing.T) {
db := newTestDB(t)
if err := MigrateStates(db, discardLogger()); err != nil {
t.Fatalf("MigrateStates devrait réussir sur une DB vide : %v", err)
}
}