pausable_test.gno
5.71 Kb · 161 lines
1package pausable
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7)
8
9func TestParse(t *testing.T) {
10 tests := []struct {
11 raw string
12 wantLevel Level
13 wantReason string
14 wantOK bool
15 }{
16 {"", Running, "", true},
17 {" ", Running, "", true},
18 {"running", Running, "", true},
19 {"readonly", ReadOnly, "", true},
20 {"paused", Paused, "", true},
21 {"Paused", Paused, "", true}, // case-insensitive
22 {" paused ", Paused, "", true},
23 {"paused: migrating storage", Paused, "migrating storage", true},
24 {"readonly: spaced ", ReadOnly, "spaced", true},
25 {"running: nothing wrong", Running, "nothing wrong", true},
26 // A reason containing the separator keeps everything after the first
27 // one, so a URL or a time survives.
28 {"paused: back at 14:00 UTC", Paused, "back at 14:00 UTC", true},
29 // Not recognised. These are exactly the values that must NOT read as
30 // Running.
31 {"yes", Running, "", false},
32 {"true", Running, "", false},
33 {"1", Running, "", false},
34 {"stop", Running, "", false},
35 {"read-only", Running, "", false},
36 }
37 for _, tt := range tests {
38 got, ok := Parse(tt.raw)
39 uassert.Equal(t, tt.wantOK, ok, "Parse("+tt.raw+") ok")
40 if !ok {
41 continue
42 }
43 uassert.Equal(t, int(tt.wantLevel), int(got.Level), "Parse("+tt.raw+") level")
44 uassert.Equal(t, tt.wantReason, got.Reason, "Parse("+tt.raw+") reason")
45 }
46}
47
48// TestMustParseFailsClosed is the whole safety argument of this package. Every
49// value here is something a person might plausibly type, and every one of them
50// has to stop the realm rather than quietly leave it running.
51func TestMustParseFailsClosed(t *testing.T) {
52 for _, raw := range []string{"yes", "true", "1", "on", "stop", "PAUSE", "pasued"} {
53 got := MustParse(raw)
54 uassert.Equal(t, int(Paused), int(got.Level), "MustParse("+raw+") must fail closed")
55 uassert.False(t, got.AllowsWrite(), "MustParse("+raw+") must refuse writes")
56 uassert.True(t, len(got.Reason) > 0, "MustParse("+raw+") must say why")
57 }
58
59 // And the values it does understand are untouched.
60 uassert.Equal(t, int(Running), int(MustParse("").Level))
61 uassert.Equal(t, int(ReadOnly), int(MustParse("readonly").Level))
62}
63
64func TestAllows(t *testing.T) {
65 tests := []struct {
66 level Level
67 read bool
68 write bool
69 isPaused bool
70 }{
71 {Running, true, true, false},
72 {ReadOnly, true, false, true},
73 {Paused, false, false, true},
74 }
75 for _, tt := range tests {
76 s := State{Level: tt.level}
77 uassert.Equal(t, tt.read, s.AllowsRead(), tt.level.String()+" read")
78 uassert.Equal(t, tt.write, s.AllowsWrite(), tt.level.String()+" write")
79 uassert.Equal(t, tt.isPaused, s.IsPaused(), tt.level.String()+" isPaused")
80 }
81}
82
83func TestStrictest(t *testing.T) {
84 run := State{Level: Running}
85 ro := State{Level: ReadOnly, Reason: "scoped"}
86 paused := State{Level: Paused, Reason: "global"}
87
88 uassert.Equal(t, int(ReadOnly), int(Strictest(run, ro).Level))
89 uassert.Equal(t, int(ReadOnly), int(Strictest(ro, run).Level))
90 uassert.Equal(t, int(Paused), int(Strictest(paused, ro).Level))
91 uassert.Equal(t, int(Paused), int(Strictest(ro, paused).Level))
92
93 // A stale per-realm entry can never re-open a realm during a global halt.
94 // This is the property the whole precedence rule exists for.
95 uassert.Equal(t, int(Paused), int(Strictest(paused, run).Level),
96 "a per-realm 'running' cannot defeat a global pause")
97
98 // The reason travels with the level that won.
99 uassert.Equal(t, "global", Strictest(paused, ro).Reason)
100 uassert.Equal(t, "scoped", Strictest(run, ro).Reason)
101
102 // On a tie the first argument wins, so a caller passing (global, scoped)
103 // keeps the global explanation.
104 a := State{Level: Paused, Reason: "first"}
105 b := State{Level: Paused, Reason: "second"}
106 uassert.Equal(t, "first", Strictest(a, b).Reason)
107}
108
109func TestStringRoundTrips(t *testing.T) {
110 for _, raw := range []string{"", "readonly", "paused", "paused: migrating storage"} {
111 s, ok := Parse(raw)
112 uassert.True(t, ok, "Parse("+raw+")")
113 again, ok := Parse(s.String())
114 uassert.True(t, ok, "Parse(String()) for "+raw)
115 uassert.Equal(t, int(s.Level), int(again.Level), "level round-trip for "+raw)
116 uassert.Equal(t, s.Reason, again.Reason, "reason round-trip for "+raw)
117 }
118
119 // The zero State renders empty, so an unset setting stays unset rather
120 // than being written back as the word "running".
121 uassert.Equal(t, "", State{}.String())
122 uassert.Equal(t, "running: why", State{Reason: "why"}.String())
123}
124
125func TestAsserts(cur realm, t *testing.T) {
126 State{}.AssertWritable()
127 State{}.AssertReadable()
128 State{Level: ReadOnly}.AssertReadable()
129
130 uassert.PanicsWithMessage(t, cur, "writes are paused", func() {
131 State{Level: ReadOnly}.AssertWritable()
132 })
133 uassert.PanicsWithMessage(t, cur, "writes are paused: migrating", func() {
134 State{Level: Paused, Reason: "migrating"}.AssertWritable()
135 })
136 uassert.PanicsWithMessage(t, cur, "paused: migrating", func() {
137 State{Level: Paused, Reason: "migrating"}.AssertReadable()
138 })
139}
140
141func TestNotice(t *testing.T) {
142 uassert.Equal(t, "", State{}.Notice(), "a running realm shows no banner")
143
144 uassert.Equal(t,
145 "> **Read-only.** This realm is still readable, but not accepting changes.",
146 State{Level: ReadOnly}.Notice())
147
148 uassert.Equal(t,
149 "> **Paused.** This realm is not accepting anything right now. back in an hour",
150 State{Level: Paused, Reason: "back in an hour"}.Notice())
151}
152
153// TestNoticeStaysOneBlock covers the reason a manager typed. It lands inside a
154// markdown blockquote, where a newline ends the quote and lets the rest render
155// as ordinary page content, so it is folded to one line.
156func TestNoticeStaysOneBlock(t *testing.T) {
157 s := State{Level: Paused, Reason: "line one\n\nline two\n# not a heading"}
158 uassert.Equal(t,
159 "> **Paused.** This realm is not accepting anything right now. line one line two # not a heading",
160 s.Notice())
161}