carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/carlosframework/platform/internal/release"
9)
10
11func TestMutationBoundaryRefusesEveryWriteWithoutAuthority(t *testing.T) {
12 ctx := t.Context()
13 raw := release.NewDirStore(t.TempDir())
14 key := "apps/bab/demo/deleted.json"
15 tag, err := raw.Put(ctx, key, []byte("old"), "")
16 if err != nil {
17 t.Fatal(err)
18 }
19 calls := 0
20 guarded, err := NewMutationBoundary(true, raw, func(context.Context, MutationTarget) error { calls++; return ErrPending })
21 if err != nil {
22 t.Fatal(err)
23 }
24 if _, err = guarded.Put(ctx, key, []byte("new"), tag); !errors.Is(err, ErrPending) {
25 t.Fatal(err)
26 }
27 if err = guarded.Delete(ctx, key); !errors.Is(err, ErrPending) {
28 t.Fatal(err)
29 }
30 if err = release.DeleteIfMatch(ctx, guarded, key, tag); !errors.Is(err, ErrPending) {
31 t.Fatal(err)
32 }
33 if err = guarded.Delete(ctx, "apps/bab/demo/absent.json"); !errors.Is(err, ErrPending) {
34 t.Fatal(err)
35 }
36 if calls != 4 {
37 t.Fatalf("only %d writes reached the boundary", calls)
38 }
39 body, after, err := raw.Get(ctx, key)
40 if err != nil || string(body) != "old" || after != tag {
41 t.Fatalf("refused write changed storage: %q %q %v", body, after, err)
42 }
43}
44func TestMutationBoundaryDeleteCannotEraseSuccessor(t *testing.T) {
45 ctx := t.Context()
46 raw := release.NewDirStore(t.TempDir())
47 key := "domains/one.example.json"
48 tag, err := raw.Put(ctx, key, []byte("old"), "")
49 if err != nil {
50 t.Fatal(err)
51 }
52 guarded, err := NewMutationBoundary(true, raw, func(_ context.Context, m MutationTarget) error {
53 if string(m.Before) != "old" || m.ETag != tag {
54 t.Fatal("not the inspected incarnation")
55 }
56 _, e := raw.Put(ctx, key, []byte("successor"), tag)
57 return e
58 })
59 if err != nil {
60 t.Fatal(err)
61 }
62 if err = guarded.Delete(ctx, key); !errors.Is(err, release.ErrCASConflict) {
63 t.Fatalf("successor delete: %v", err)
64 }
65 body, _, err := raw.Get(ctx, key)
66 if err != nil || string(body) != "successor" {
67 t.Fatalf("successor: %q %v", body, err)
68 }
69}
70func TestMutationBoundaryDisabledIsOriginalHandle(t *testing.T) {
71 raw := release.NewDirStore(t.TempDir())
72 s, err := NewMutationBoundary(false, raw, func(context.Context, MutationTarget) error { t.Fatal("flag-off check"); return nil })
73 if err != nil || s != raw {
74 t.Fatal("flag-off was wrapped")
75 }
76 if _, err = NewMutationBoundary(true, raw, nil); !errors.Is(err, ErrContract) {
77 t.Fatal("enabled without production authority")
78 }
79}
80
81func TestBoundaryForwardsReadExtensions(t *testing.T) {
82 ctx := t.Context()
83 raw := release.NewDirStore(t.TempDir())
84 const key = "apps/bab/demo/a.json"
85 if _, err := raw.Put(ctx, key, []byte("a"), ""); err != nil {
86 t.Fatal(err)
87 }
88 // The production chain: boundary over the record store over the bucket.
89 records, err := NewConditionalRecordStore(raw, make([]byte, 32))
90 if err != nil {
91 t.Fatal(err)
92 }
93 guarded, err := NewMutationBoundary(true, records, func(context.Context, MutationTarget) error { return nil })
94 if err != nil {
95 t.Fatal(err)
96 }
97 cg, ok := guarded.(release.ConditionalGetter)
98 if !ok {
99 t.Fatal("boundary hides ConditionalGetter")
100 }
101 el, ok := guarded.(release.ETagLister)
102 if !ok {
103 t.Fatal("boundary hides ETagLister")
104 }
105 body, e, changed, err := cg.GetIfChanged(ctx, key, "")
106 if err != nil || !changed || string(body) != "a" {
107 t.Fatalf("GetIfChanged: %q %v %v", body, changed, err)
108 }
109 if _, _, changed, err = cg.GetIfChanged(ctx, key, e); err != nil || changed {
110 t.Fatalf("unchanged: %v %v", changed, err)
111 }
112 got, err := el.ListETags(ctx, "apps/")
113 if err != nil || len(got) != 1 || got[0].Key != key || got[0].ETag != e {
114 t.Fatalf("ListETags: %v %v", got, err)
115 }
116}
117
118// bareStore has none of the optional extensions.
119type bareStore struct{ release.Store }
120
121func TestBoundaryWithoutReadExtensionsIsContractError(t *testing.T) {
122 ctx := t.Context()
123 guarded, err := NewMutationBoundary(true, bareStore{release.NewDirStore(t.TempDir())}, func(context.Context, MutationTarget) error { return nil })
124 if err != nil {
125 t.Fatal(err)
126 }
127 if _, _, _, err = guarded.(release.ConditionalGetter).GetIfChanged(ctx, "apps/bab/demo/a.json", ""); !errors.Is(err, ErrContract) {
128 t.Fatalf("GetIfChanged: %v", err)
129 }
130 if _, err = guarded.(release.ETagLister).ListETags(ctx, "apps/"); !errors.Is(err, ErrContract) {
131 t.Fatalf("ListETags: %v", err)
132 }
133}
134
135func TestRecordStoreWithoutReadExtensionsIsContractError(t *testing.T) {
136 ctx := t.Context()
137 records, err := NewConditionalRecordStore(bareStore{release.NewDirStore(t.TempDir())}, make([]byte, 32))
138 if err != nil {
139 t.Fatal(err)
140 }
141 if _, _, _, err = records.(release.ConditionalGetter).GetIfChanged(ctx, "apps/bab/demo/a.json", ""); !errors.Is(err, ErrContract) {
142 t.Fatalf("GetIfChanged: %v", err)
143 }
144 if _, err = records.(release.ETagLister).ListETags(ctx, "apps/"); !errors.Is(err, ErrContract) {
145 t.Fatalf("ListETags: %v", err)
146 }
147}
148