carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package registry
2
3import (
4 "errors"
5 "path/filepath"
6 "reflect"
7 "testing"
8)
9
10func TestPlan14TimerMaterializesIntendedIdentity(t *testing.T) {
11 for _, transport := range []string{"local", "child"} {
12 t.Run(transport, func(t *testing.T) {
13 var r *Registry
14 if transport == "child" {
15 r, _ = remoteTestRegistry(t)
16 } else {
17 var err error
18 r, err = Open(filepath.Join(t.TempDir(), "registry.db"))
19 if err != nil {
20 t.Fatal(err)
21 }
22 defer r.Close()
23 }
24 host := "shop.example"
25 routeOwner := ClaimOwnership{"inc", 3, "owner", "route"}
26 if err := r.Upsert(Route{Host: host, ClaimOwnership: routeOwner}); err != nil {
27 t.Fatal(err)
28 }
29 for _, kind := range []string{"schedule", "once", "landing"} {
30 timerKind := kind
31 if kind == "landing" {
32 timerKind = "schedule"
33 }
34 old := Timer{ClaimOwnership: ClaimOwnership{"inc", 3, "owner", "first-" + kind}, Host: host, Name: kind, Kind: timerKind, Spec: "every 1h", Path: "/tick", NextAt: 123, DueAt: 120, CreatedAt: 90, LastAt: 80, LastMs: 7, LastStatus: "ok", Attempts: 2, Registration: "first-" + kind}
35 installed, err := r.MaterializeTimerRegistration(old, "")
36 if err != nil || installed == nil || *installed != old {
37 t.Fatalf("intended identity changed: %+v %v", installed, err)
38 }
39 if got, err := r.MaterializeTimerRegistration(old, ""); err != nil || got == nil || *got != old {
40 t.Fatalf("lost-reply replay: %+v %v", got, err)
41 }
42 changed := old
43 changed.Path = "/collision"
44 if _, err := r.MaterializeTimerRegistration(changed, old.Registration); !errors.Is(err, ErrTimerChanged) {
45 t.Fatal("identity reused for changed timer", err)
46 }
47 for _, column := range []string{"incarnation", "stamp", "owner"} {
48 wrong := old
49 switch column {
50 case "incarnation":
51 wrong.ClaimIncarnation = "other"
52 case "stamp":
53 wrong.ClaimStamp++
54 case "owner":
55 wrong.ClaimOwner = "other"
56 }
57 if err := r.DeleteTimerOwnedRegistration(wrong); !errors.Is(err, ErrTimerChanged) {
58 t.Fatal("copied registration removed another owner", err)
59 }
60 }
61 next := old
62 next.Registration = "next-" + kind
63 next.ClaimRowID = next.Registration
64 next.Path = "/new"
65 if _, err := r.MaterializeTimerRegistration(next, "wrong"); !errors.Is(err, ErrTimerChanged) {
66 t.Fatal("stale update accepted", err)
67 }
68 if _, err := r.MaterializeTimerRegistration(next, old.Registration); err != nil {
69 t.Fatal(err)
70 }
71 if err := r.DeleteTimerOwnedRegistration(old); !errors.Is(err, ErrTimerChanged) {
72 t.Fatal("successor removed", err)
73 }
74 got, err := r.GetTimer(host, kind)
75 if err != nil || !reflect.DeepEqual(got, &next) {
76 t.Fatalf("successor changed: %+v %v", got, err)
77 }
78 if err := r.DeleteTimerOwnedRegistration(next); err != nil {
79 t.Fatal(err)
80 }
81 }
82 })
83 }
84}
85
86func TestPlan14TimerMaterializationRefusesOwnershipChanges(t *testing.T) {
87 for _, attack := range []string{"missing owner", "other incarnation", "other stamp", "other owner", "row differs", "empty registration", "absent route", "replaced route"} {
88 t.Run(attack, func(t *testing.T) {
89 r, err := Open(filepath.Join(t.TempDir(), "registry.db"))
90 if err != nil {
91 t.Fatal(err)
92 }
93 defer r.Close()
94 owner := ClaimOwnership{"inc", 3, "owner", "route"}
95 if attack != "absent route" {
96 if err := r.Upsert(Route{Host: "shop.example", ClaimOwnership: owner}); err != nil {
97 t.Fatal(err)
98 }
99 }
100 timer := Timer{Host: "shop.example", Name: "once", Kind: "once", Path: "/tick", Registration: "intended", ClaimOwnership: ClaimOwnership{"inc", 3, "owner", "intended"}}
101 switch attack {
102 case "missing owner":
103 timer.ClaimOwnership = ClaimOwnership{}
104 case "other incarnation":
105 timer.ClaimIncarnation = "other"
106 case "other stamp":
107 timer.ClaimStamp++
108 case "other owner":
109 timer.ClaimOwner = "other"
110 case "row differs":
111 timer.ClaimRowID = "other"
112 case "empty registration":
113 timer.Registration = ""
114 case "replaced route":
115 if _, err := r.sql.Exec(`UPDATE routes SET claim_stamp=claim_stamp+1 WHERE host=?`, timer.Host); err != nil {
116 t.Fatal(err)
117 }
118 }
119 if _, err := r.MaterializeTimerRegistration(timer, ""); err == nil {
120 t.Fatal("unintended ownership materialized")
121 }
122 if rows, err := r.TimersFor(timer.Host); err != nil || len(rows) != 0 {
123 t.Fatal("refusal wrote rows", rows, err)
124 }
125 })
126 }
127}
128