package provision import ( "bytes" "context" "encoding/json" "errors" "testing" "github.com/carlosframework/platform/internal/release" ) func TestPlan9B8BoxRecordSequenceAndLegacyBytes(t *testing.T) { s := release.NewDirStore(t.TempDir()) ctx := context.Background() r := acmeRecord("bdm") if err := CreateRecord(ctx, s, r); err != nil { t.Fatal(err) } before, tag, err := s.Get(ctx, RecordKey(r.Account, r.App, r.Host)) if err != nil { t.Fatal(err) } if err := SetRecordRegion(ctx, s, r.Account, r.App, r.Host, r.Region); err != nil { t.Fatal(err) } unchanged, same, err := s.Get(ctx, RecordKey(r.Account, r.App, r.Host)) if err != nil || string(before) != string(unchanged) || tag != same { t.Fatalf("legacy bytes changed: %v", err) } for want := uint64(1); want <= 2; want++ { got, err := SetRecordMove(WithRecordSequence(ctx), s, r.Account, r.App, r.Host, func(*Record) error { return nil }) if err != nil || got.RecordSeq != want { t.Fatalf("sequence: %+v %v", got, err) } } if _, err := SetRecordMove(WithRecordSequence(ctx), s, r.Account, r.App, r.Host, func(r *Record) error { r.RecordSeq = 0; return nil }); !errors.Is(err, ErrMaintenanceProjection) { t.Fatalf("reset: %v", err) } current, etag, err := GetRecordETag(ctx, s, r.Account, r.App, r.Host) if err != nil { t.Fatal(err) } if _, err := s.Put(ctx, RecordKey(r.Account, r.App, r.Host), before, tag); !errors.Is(err, release.ErrCASConflict) { t.Fatalf("old Put: %v", err) } current.RecordSeq = 0 if err := UpdateRecord(ctx, s, *current, etag); !errors.Is(err, ErrMaintenanceProjection) { t.Fatalf("whole record removed sequence: %v", err) } } func TestPlan9B8PopulatedSequenceAdvancesWithoutClaimContext(t *testing.T) { s := release.NewDirStore(t.TempDir()) ctx := context.Background() rec := acmeRecord("bdm") if err := CreateRecord(WithRecordSequence(ctx), s, rec); err != nil { t.Fatal(err) } // Every box transition, including future commit/open/close callbacks, uses // SetRecordMove on the raw store. A populated record needs no flag context. for want := uint64(2); want <= 5; want++ { got, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, func(*Record) error { return nil }) if err != nil || got.RecordSeq != want { t.Fatalf("box: %+v %v", got, err) } } got, tag, err := GetRecordETag(ctx, s, rec.Account, rec.App, rec.Host) if err != nil { t.Fatal(err) } got.Channel = "new-channel" if err := UpdateRecord(ctx, s, *got, tag); err != nil { t.Fatal(err) } got, err = GetRecord(ctx, s, rec.Account, rec.App, rec.Host) if err != nil || got.RecordSeq != 6 { t.Fatalf("whole record: %+v %v", got, err) } if err := SetRecordRegion(ctx, s, rec.Account, rec.App, rec.Host, rec.Region); err != nil { t.Fatal(err) } got, err = GetRecord(ctx, s, rec.Account, rec.App, rec.Host) if err != nil || got.RecordSeq != 7 { t.Fatalf("placement: %+v %v", got, err) } } func TestPlan9B8CreateBytesAndSidecarSequence(t *testing.T) { for _, claim := range []bool{false, true} { ctx := context.Background() if claim { ctx = WithRecordSequence(ctx) } s := release.NewDirStore(t.TempDir()) rec := acmeRecord("bdm") side := SidecarRecord{Schema: 1, Account: rec.Account, App: rec.App, Name: "worker", Host: SidecarHost("worker", rec.App, rec.Account), CreatedAt: rec.CreatedAt} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatal(err) } if err := CreateSidecarRecord(ctx, s, side); err != nil { t.Fatal(err) } for _, tc := range []struct { key string value any }{{RecordKey(rec.Account, rec.App, rec.Host), rec}, {SidecarRecordKey(side.Account, side.App, side.Name), side}} { raw, _, err := s.Get(ctx, tc.key) if err != nil { t.Fatal(err) } _, seq, err := RecordSequence(raw) if err != nil { t.Fatal(err) } want := uint64(0) if claim { want = 1 } if seq != want { t.Fatalf("%s sequence=%d", tc.key, seq) } if !claim { expected, err := json.Marshal(tc.value) if err != nil || !bytes.Equal(raw, expected) || bytes.Contains(raw, []byte("record_seq")) { t.Fatalf("legacy bytes %s: %s", tc.key, raw) } } } } } type plan9SequenceRetry struct { release.Store key string raced bool } func (s *plan9SequenceRetry) Put(ctx context.Context, key string, body []byte, tag string) (string, error) { if key == s.key && !s.raced { s.raced = true // A different box wins the first CAS. Its write advances the same counter. raw, current, err := s.Store.Get(ctx, key) if err != nil { return "", err } if _, _, err = WriteRecord(context.Background(), s.Store, key, raw, raw, current); err != nil { return "", err } } return s.Store.Put(ctx, key, body, tag) } func TestPlan9B8BoxCASRetryRereadsSequence(t *testing.T) { ctx := context.Background() base := release.NewDirStore(t.TempDir()) rec := acmeRecord("bdm") if err := CreateRecord(WithRecordSequence(ctx), base, rec); err != nil { t.Fatal(err) } s := &plan9SequenceRetry{Store: base, key: RecordKey(rec.Account, rec.App, rec.Host)} got, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, func(*Record) error { return nil }) if err != nil || !s.raced || got.RecordSeq != 3 { t.Fatalf("retry: %+v %v", got, err) } } func TestPlan9B8WholeRecordUsesRawSequenceBase(t *testing.T) { for _, field := range []string{`"record_seq":0`, `"Record_Seq":0`, `"record_seq":0,"record_seq":0`, `"record_seq":null`} { t.Run(field, func(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) r := acmeRecord("bdm") body, _ := json.Marshal(r) body = append(append([]byte(nil), body[:len(body)-1]...), []byte(","+field+"}")...) key := RecordKey(r.Account, r.App, r.Host) tag, err := s.Put(ctx, key, body, "") if err != nil { t.Fatal(err) } err = UpdateRecord(ctx, s, r, tag) if field == `"record_seq":0` { got, getErr := GetRecord(ctx, s, r.Account, r.App, r.Host) if err != nil || getErr != nil || got.RecordSeq != 1 { t.Fatalf("present zero: %+v %v %v", got, err, getErr) } } else { if !errors.Is(err, ErrRecordSequence) { t.Fatalf("malformed base: %v", err) } got, _, getErr := s.Get(ctx, key) if getErr != nil || !bytes.Equal(got, body) { t.Fatalf("refusal wrote: %s %v", got, getErr) } } }) } } func TestPlan9B8BoxPopulatedCounterNeedsNoContext(t *testing.T) { ctx := context.Background() store := release.NewDirStore(t.TempDir()) r := acmeRecord("bdm") if err := CreateRecord(ctx, store, r); err != nil { t.Fatal(err) } first, err := SetRecordMove(WithRecordSequence(ctx), store, r.Account, r.App, r.Host, func(*Record) error { return nil }) if err != nil || first.RecordSeq != 1 { t.Fatalf("seed: %+v %v", first, err) } got, err := SetRecordMove(ctx, store, r.Account, r.App, r.Host, func(*Record) error { return nil }) if err != nil || got.RecordSeq != 2 { t.Fatalf("unflagged populated write: %+v %v", got, err) } }