package main import ( "bytes" "context" "errors" "os" "testing" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" ) func TestPlan9B8MigrationCannotReplaySequencedRecord(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", "1") ctx := context.Background() for _, family := range []string{"instances", "sidecars"} { t.Run(family, func(t *testing.T) { store := release.NewDirStore(t.TempDir()) key := "apps/bab/app/" + family + "/record.json" legacy := []byte("{\"schema\": 1}\n") if _, err := putMigrationObject(ctx, store, key, legacy); err != nil { t.Fatal(err) } raw, _, err := store.Get(ctx, key) if err != nil || !bytes.Equal(raw, legacy) { t.Fatalf("legacy bytes: %s %v", raw, err) } key = "apps/bab/app/" + family + "/new.json" if _, err := putMigrationObject(ctx, store, key, []byte(`{"record_seq":9}`)); !errors.Is(err, provision.ErrRecordSequence) { t.Fatalf("replayed sequence: %v", err) } if _, _, err := store.Get(ctx, key); !errors.Is(err, os.ErrNotExist) { t.Fatalf("replay wrote: %v", err) } }) } } func TestPlan9B8MigrationPreflightRefusesBeforeAnyCopy(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", "1") ctx := context.Background() store := release.NewDirStore(t.TempDir()) source := "apps/bab/app/instances/source.json" dest := "apps/cac/app/instances/source.json" for _, body := range []string{`{"record_seq":0}`, `{"record_seq":9}`} { if err := store.Delete(ctx, source); err != nil { t.Fatal(err) } if _, err := store.Put(ctx, source, []byte(body), ""); err != nil { t.Fatal(err) } plan := []copyOp{{from: "apps/bab/app/channel.json", to: "apps/cac/app/channel.json"}, {from: source, to: dest}} if err := validateMigrationRecords(ctx, store, plan); !errors.Is(err, provision.ErrRecordSequence) { t.Fatalf("preflight: %v", err) } keys, err := store.List(ctx, "apps/cac/") if err != nil || len(keys) != 0 { t.Fatalf("preflight wrote: %v %v", keys, err) } } } // Claim mode refuses a present record_seq; legacy bodies without one // still pass preflight and are copied byte for byte. func TestPlan9M1MigrationToleratesBodiesMainCopied(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", "1") ctx := context.Background() for name, body := range map[string]string{ "malformed": `{"schema": 1`, "array": `[1,2]`, "empty": ``, "duplicate": `{"a":1,"a":2}`, "text": "not json\n", "open-seq": `{"record_seq":1`, } { t.Run(name, func(t *testing.T) { store := release.NewDirStore(t.TempDir()) source := "apps/bab/app/instances/source.json" dest := "apps/cac/app/instances/source.json" if _, err := store.Put(ctx, source, []byte(body), ""); err != nil { t.Fatal(err) } if err := validateMigrationRecords(ctx, store, []copyOp{{from: source, to: dest}}); err != nil { t.Fatalf("preflight refused a body main copied: %v", err) } if _, err := putMigrationObject(ctx, store, dest, []byte(body)); err != nil { t.Fatalf("copy: %v", err) } raw, _, err := store.Get(ctx, dest) if err != nil || !bytes.Equal(raw, []byte(body)) { t.Fatalf("bytes changed: %q %v", raw, err) } }) } for name, body := range map[string]string{ "duplicate-seq": `{"record_seq":1,"record_seq":2}`, "case-seq": `{"Record_Seq":1}`, } { t.Run(name, func(t *testing.T) { store := release.NewDirStore(t.TempDir()) dest := "apps/cac/app/instances/source.json" if _, err := putMigrationObject(ctx, store, dest, []byte(body)); !errors.Is(err, provision.ErrRecordSequence) { t.Fatalf("sequence-bearing body: %v", err) } }) } }