carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostclaim
2
3import (
4 "context"
5 "errors"
6 "reflect"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/release"
12)
13
14type retainedLossStore struct {
15 release.Store
16 after bool
17}
18
19func (s retainedLossStore) Put(ctx context.Context, key string, body []byte, match string) (string, error) {
20 if s.after {
21 if _, err := s.Store.Put(ctx, key, body, match); err != nil {
22 return "", err
23 }
24 }
25 return "", release.ErrCASConflict
26}
27
28func TestNativeAdmissionLossSurvivesRetirement(t *testing.T) {
29 for _, failure := range []string{"none", "before-CAS", "lost-reply"} {
30 t.Run(failure, func(t *testing.T) {
31 ctx := t.Context()
32 s := release.NewDirStore(t.TempDir())
33 o := servingClaim(t, ctx, s)
34 for _, generation := range []string{"first", "second"} {
35 h := o.Claim.Holder
36 var err error
37 o, err = Update(ctx, s, o, AdmitWriter(Admission{Region: "ie", Box: "adi-knitting", Mode: "cold", NewGeneration: generation, ObservedSession: h.Session, ObservedGeneration: h.Generation, RemoteMaxTXID: 42}))
38 if err != nil {
39 t.Fatal(err)
40 }
41 }
42 h := o.Claim.Holder
43 want := RetainedAdmissionLoss{Incarnation: h.Incarnation, Location: h.Location, Account: h.Account, App: h.App, Admission: h.Admissions[1]}
44 if want.Admission.WritesLost == nil {
45 t.Fatal("cold wake did not declare predecessor loss")
46 }
47 var err error
48 o, err = Update(ctx, s, o, BeginDelete("delete", "test", "etag", time.Now(), nil, nil, ""))
49 if err != nil {
50 t.Fatal(err)
51 }
52 o, err = Update(ctx, s, o, ToParking("delete", nil, nil, counter()))
53 if err != nil {
54 t.Fatal(err)
55 }
56 if _, err = Update(ctx, s, o, FreeAfterPark("delete")); !errors.Is(err, ErrWrongState) {
57 t.Fatalf("outstanding parks: %v", err)
58 }
59 for _, ob := range o.Claim.Obligations {
60 o, err = Update(ctx, s, o, CompleteObligation(ob.Op, ob.ID))
61 if err != nil {
62 t.Fatal(err)
63 }
64 }
65 if failure != "none" {
66 _, err = Update(ctx, retainedLossStore{s, failure == "lost-reply"}, o, FreeAfterPark("delete"))
67 if !errors.Is(err, ErrConflict) {
68 t.Fatalf("fault: %v", err)
69 }
70 o, err = Read(ctx, s, testHost)
71 if err != nil {
72 t.Fatal(err)
73 }
74 if failure == "before-CAS" && (o.Claim.Holder == nil || len(o.Claim.RetainedAdmissionLosses) != 0) {
75 t.Fatal("failed CAS changed evidence")
76 }
77 }
78 if !o.Claim.Free() {
79 o, err = Update(ctx, s, o, FreeAfterPark("delete"))
80 if err != nil {
81 t.Fatal(err)
82 }
83 }
84 o, err = Read(ctx, s, testHost)
85 if err != nil || !o.Claim.Free() || !reflect.DeepEqual(o.Claim.RetainedAdmissionLosses, []RetainedAdmissionLoss{want}) {
86 t.Fatalf("retained loss: %+v, %v", o.Claim, err)
87 }
88 for i := 0; i < retiredCap+1; i++ {
89 holder := newHolder()
90 holder.CreatedAt = holder.CreatedAt.Add(time.Duration(i+1) * time.Hour)
91 o, err = Update(ctx, s, o, BeginCreate(holder, []byte(strings.Replace(string(record), "2026-10-04T01:00:00Z", holder.CreatedAt.Format(time.RFC3339), 1)), "create", "test", time.Now()))
92 if err != nil {
93 t.Fatal(err)
94 }
95 o, err = Update(ctx, s, o, CancelCreate("create", time.Now()))
96 if err != nil {
97 t.Fatal(err)
98 }
99 }
100 if !reflect.DeepEqual(o.Claim.RetainedAdmissionLosses, []RetainedAdmissionLoss{want}) {
101 t.Fatal("holder replacement or retired cap erased loss")
102 }
103 })
104 }
105}
106
107func TestNativeRetentionDoesNotReplaceSignedLossArchive(t *testing.T) {
108 c := &Claim{Holder: &Holder{Admissions: []AdmissionRecord{{Session: 3, LossID: "loss_1", WritesLost: &WritesLost{Box: "box"}}}}}
109 if err := retainNativeAdmissionLosses(c); !errors.Is(err, ErrLossEvidencePending) || len(c.RetainedAdmissionLosses) != 0 {
110 t.Fatalf("signed archive bypass: %v", err)
111 }
112}
113