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 "encoding/json"
6 "errors"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestReadRefusesMalformed(t *testing.T) {
14 ctx := context.Background()
15 s := release.NewDirStore(t.TempDir())
16 obs, err := Read(ctx, s, testHost)
17 if err != nil || !obs.Absent || obs.Host != testHost {
18 t.Fatalf("absent read = %+v, %v", obs, err)
19 }
20 key, _ := Key(testHost)
21 good := `{"v":2,"host":"` + testHost + `","gen":1,"op":{},"touched":{"complete":false}}`
22 unknownChore := servingClaim(t, ctx, s).Claim
23 unknownChore.Op = Op{Kind: KindParking, ID: "x"}
24 unknownChore.Obligations = []Obligation{{ID: "o", Op: "x", Kind: "park-replica", Region: "ie", Retirement: "r", Owner: "region:ie"}}
25 if err := unknownChore.Validate(); err != nil {
26 t.Fatalf("invalid fixture before changing chore kind: %v", err)
27 }
28 unknownChore.Obligations[0].Kind = "teleport"
29 unknownChoreBody, err := json.Marshal(unknownChore)
30 if err != nil {
31 t.Fatal(err)
32 }
33 for name, body := range map[string]string{
34 "unknown field": `{"v":2,"host":"` + testHost + `","gen":1,"op":{},"touched":{"complete":false},"surprise":1}`,
35 "trailing data": good + ` {"v":2}`,
36 "wrong host": `{"v":2,"host":"other.bab.oncarlos.com","gen":1,"op":{},"touched":{"complete":false}}`,
37 "free with chores": `{"v":2,"host":"` + testHost + `","gen":1,"op":{},"touched":{"complete":false},` +
38 `"obligations":[{"id":"o","op":"x","kind":"park-local","box":"b","retirement":"r","owner":"box:b"}]}`,
39 "unknown chore kind": string(unknownChoreBody),
40 } {
41 s2 := release.NewDirStore(t.TempDir())
42 if _, err := s2.Put(ctx, key, []byte(body), ""); err != nil {
43 t.Fatal(err)
44 }
45 _, err := Read(ctx, s2, testHost)
46 if name == "unknown chore kind" && (err == nil || !strings.Contains(err.Error(), `obligation kind "teleport"`)) {
47 t.Errorf("%s: read = %v, want obligation-kind rejection", name, err)
48 }
49 if !errors.Is(err, ErrMalformed) {
50 t.Errorf("%s: read = %v, want ErrMalformed", name, err)
51 }
52 }
53}
54
55func TestUpdateDerivesGenAndBindsHost(t *testing.T) {
56 ctx := context.Background()
57 s := release.NewDirStore(t.TempDir())
58 obs, _ := Read(ctx, s, testHost)
59 noop := func(c *Claim) error { return nil }
60 o1, err := Update(ctx, s, obs, noop)
61 if err != nil || o1.Claim.Gen != 1 || o1.Claim.Host != testHost {
62 t.Fatalf("first write = %+v, %v", o1, err)
63 }
64 if _, err := Update(ctx, s, obs, noop); !errors.Is(err, ErrConflict) {
65 t.Fatalf("stale absent write = %v, want ErrConflict (C5a)", err)
66 }
67 if _, err := Update(ctx, s, o1, func(c *Claim) error { c.Gen = 99; return nil }); !errors.Is(err, ErrMalformed) {
68 t.Fatalf("transition changing gen = %v", err)
69 }
70 if _, err := Update(ctx, s, o1, func(c *Claim) error { c.Host = "other.bab.oncarlos.com"; return nil }); !errors.Is(err, ErrMalformed) {
71 t.Fatalf("transition changing host = %v", err)
72 }
73 o2, err := Update(ctx, s, o1, noop)
74 if err != nil || o2.Claim.Gen != 2 {
75 t.Fatalf("second write = %+v, %v", o2, err)
76 }
77 if _, err := Update(ctx, s, o1, noop); !errors.Is(err, ErrConflict) {
78 t.Fatalf("stale etag write = %v, want ErrConflict", err)
79 }
80}
81
82func TestUpdateRejectsNonAbsentObservationWithoutETag(t *testing.T) {
83 ctx := context.Background()
84 s := release.NewDirStore(t.TempDir())
85 obs := Observed{
86 Host: testHost,
87 Claim: &Claim{V: 2, Host: testHost, Gen: 1},
88 Absent: false,
89 ETag: "",
90 }
91 if err := obs.Claim.Validate(); err != nil {
92 t.Fatalf("invalid test claim: %v", err)
93 }
94 transitionCalled := false
95 _, err := Update(ctx, s, obs, func(c *Claim) error {
96 transitionCalled = true
97 return nil
98 })
99 if !errors.Is(err, ErrMalformed) {
100 t.Errorf("Update = %v, want ErrMalformed", err)
101 }
102 if transitionCalled {
103 t.Error("transition called for observation without ETag")
104 }
105 after, err := Read(ctx, s, testHost)
106 if err != nil || !after.Absent {
107 t.Errorf("read after rejected update = %+v, %v; want absent", after, err)
108 }
109}
110