home_test.gno
8.96 Kb · 274 lines
1// Package home_test drives the realm from outside, the way transactions do:
2// every write and every theme operation crosses into gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home,
3// so the authority check sees whoever testing.SetRealm names as the caller.
4package home_test
5
6import (
7 "strings"
8 "testing"
9
10 "gno.land/p/nt/testutils/v0"
11 "gno.land/p/nt/uassert/v0"
12 "gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home"
13)
14
15var stranger = testutils.TestAddress("mallory")
16
17const (
18 themeA = "gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home/theme/v0"
19 themeB = "gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home/theme/v1"
20)
21
22// stubTheme wraps the page so a test can tell who answered.
23type stubTheme struct{ tag string }
24
25func (s stubTheme) Render(path string) string {
26 if strings.HasPrefix(path, "custom") {
27 return "custom by " + s.tag
28 }
29 if path != "" {
30 return home.Fallback(path)
31 }
32 f := home.NewFiller()
33 f.Add("widget", func() string { return "<" + s.tag + ">" })
34 layout := home.Layout()
35 if layout == "" {
36 layout = "default of " + s.tag
37 }
38 return f.Fill(layout)
39}
40
41// brokenTheme panics on the profile page.
42type brokenTheme struct{}
43
44func (brokenTheme) Render(path string) string {
45 if path == "" {
46 panic("kaboom")
47 }
48 return home.Fallback(path)
49}
50
51// testing.SetRealm applies to the frame that calls it, so every test names
52// its caller inline, right before the crossing call, rather than through a
53// helper. A code realm stands in for a theme deployment: on chain the theme's
54// init cross-calls Register, which the harness does not deliver as a
55// transaction.
56var (
57 admin = testing.NewUserRealm(home.Admin)
58 mallory = testing.NewUserRealm(stranger)
59)
60
61// ---------------------------------------------------------------------------
62// Slots
63
64func TestSetGetDelete(cur realm, t *testing.T) {
65 home.ResetForTest()
66 testing.SetRealm(admin)
67
68 home.Set(cross(cur), "intro", "Building on gno.")
69 uassert.Equal(t, "Building on gno.", home.Get("intro"))
70 uassert.Equal(t, 1, home.Revision())
71 uassert.True(t, home.Has("intro"))
72
73 home.Set(cross(cur), "intro", "Still building.")
74 uassert.Equal(t, "Still building.", home.Get("intro"))
75 uassert.Equal(t, 2, home.Revision())
76
77 s, ok := home.Lookup("intro")
78 uassert.True(t, ok)
79 uassert.Equal(t, 2, s.Rev)
80
81 home.Delete(cross(cur), "intro")
82 uassert.Equal(t, "", home.Get("intro"))
83 uassert.False(t, home.Has("intro"))
84 uassert.Equal(t, 3, home.Revision())
85
86 uassert.AbortsContains(t, cur, "no such slot: intro", func() {
87 home.Delete(cross(cur), "intro")
88 })
89}
90
91func TestAppendChunks(cur realm, t *testing.T) {
92 home.ResetForTest()
93 testing.SetRealm(admin)
94
95 home.Set(cross(cur), "long", "part one. ")
96 home.Append(cross(cur), "long", "part two.")
97 uassert.Equal(t, "part one. part two.", home.Get("long"))
98
99 home.Append(cross(cur), "fresh", "hello")
100 uassert.Equal(t, "hello", home.Get("fresh"))
101}
102
103func TestWritesAreAuthorityOnly(cur realm, t *testing.T) {
104 home.ResetForTest()
105 testing.SetRealm(mallory)
106
107 uassert.AbortsContains(t, cur, "restricted to the authority", func() {
108 home.Set(cross(cur), "intro", "pwned")
109 })
110 uassert.AbortsContains(t, cur, "restricted to the authority", func() {
111 home.Append(cross(cur), "intro", "pwned")
112 })
113 uassert.AbortsContains(t, cur, "restricted to the authority", func() {
114 home.Delete(cross(cur), "intro")
115 })
116 uassert.Equal(t, 0, home.Revision())
117}
118
119func TestTransferAuthorityMovesEditing(cur realm, t *testing.T) {
120 home.ResetForTest()
121 testing.SetRealm(admin)
122 home.TransferAuthority(cross(cur), stranger)
123
124 uassert.AbortsContains(t, cur, "restricted to the authority", func() {
125 home.Set(cross(cur), "intro", "old owner")
126 })
127
128 testing.SetRealm(mallory)
129 home.Set(cross(cur), "intro", "new owner")
130 uassert.Equal(t, "new owner", home.Get("intro"))
131 uassert.True(t, strings.Contains(home.Authority(), stranger.String()))
132}
133
134func TestSlugRules(cur realm, t *testing.T) {
135 home.ResetForTest()
136 testing.SetRealm(admin)
137
138 for _, bad := range []string{"", "Intro", "a:b", "with space", strings.Repeat("x", 65)} {
139 uassert.AbortsContains(t, cur, "invalid slug", func() {
140 home.Set(cross(cur), bad, "x")
141 }, bad)
142 }
143 for _, reserved := range home.ReservedSlugsForTest() {
144 uassert.AbortsContains(t, cur, "reserved slug", func() {
145 home.Set(cross(cur), reserved, "x")
146 }, reserved)
147 }
148 home.Set(cross(cur), "style.accent", "#D9A441")
149 uassert.Equal(t, "#D9A441", home.Style("accent", "#000000"))
150 uassert.Equal(t, 1, home.Revision())
151}
152
153// ---------------------------------------------------------------------------
154// Themes
155
156func TestThemeLifecycle(cur realm, t *testing.T) {
157 home.ResetForTest()
158 home.SeedForTest("layout", "page :widget: :rev:")
159
160 testing.SetRealm(testing.NewCodeRealm(themeA))
161 home.Register(cross(cur), stubTheme{"A"})
162 testing.SetRealm(testing.NewCodeRealm(themeB))
163 home.Register(cross(cur), stubTheme{"B"})
164 uassert.Equal(t, "", home.LivePath())
165 uassert.Equal(t, 2, len(home.PendingPaths()))
166 uassert.Equal(t, "page :widget: 1", home.Render(""), "nothing serves before Accept")
167
168 testing.SetRealm(admin)
169 home.Accept(cross(cur), themeA)
170 uassert.Equal(t, themeA, home.LivePath())
171 uassert.Equal(t, "page <A> 1", home.Render(""))
172 uassert.True(t, strings.Contains(home.Render("slots"), "# Slots"), "themes delegate other paths")
173 uassert.True(t, strings.Contains(home.Render("system"), "["+themeA+"]"), home.Render("system"))
174 uassert.True(t, strings.Contains(home.Render("system"), "value=\""+themeB+"\""), "pending themes get an Accept form")
175
176 home.Accept(cross(cur), themeB)
177 uassert.Equal(t, "page <B> 1", home.Render(""))
178 uassert.Equal(t, 1, len(home.HistoryPaths()))
179 uassert.Equal(t, themeA, home.HistoryPaths()[0])
180
181 home.Rollback(cross(cur))
182 uassert.Equal(t, "page <A> 1", home.Render(""))
183 uassert.Equal(t, themeB, home.PendingPaths()[0], "the displaced theme returns to pending")
184
185 // The slots were never in the theme, so switching never touched them.
186 home.Set(cross(cur), "layout", ":widget: only")
187 uassert.Equal(t, "<A> only", home.Render(""))
188}
189
190func TestBrokenThemeLeavesOperatorViews(cur realm, t *testing.T) {
191 home.ResetForTest()
192 home.SeedForTest("layout", "hello :rev:")
193 testing.SetRealm(testing.NewCodeRealm(themeA))
194 home.Register(cross(cur), stubTheme{"A"})
195 testing.SetRealm(testing.NewCodeRealm(themeB))
196 home.Register(cross(cur), brokenTheme{})
197 testing.SetRealm(admin)
198 home.Accept(cross(cur), themeA)
199 uassert.Equal(t, "hello 1", home.Render(""))
200 home.Accept(cross(cur), themeB)
201
202 // The page is down: on chain this is a VM abort, not something this
203 // realm can catch.
204 uassert.AbortsContains(t, cur, "kaboom", func() { home.Render("") })
205
206 // The views the rollback is done from never touch the theme.
207 uassert.True(t, strings.Contains(home.Render("system"), "["+themeA+"]"))
208 uassert.True(t, strings.Contains(home.Render("slots"), "# Slots"))
209 uassert.True(t, strings.Contains(home.Render("edit"), "<gno-form exec=\"Set\">"))
210 uassert.Equal(t, home.Manifest(), home.Render("manifest"))
211
212 home.Rollback(cross(cur))
213 uassert.Equal(t, themeA, home.LivePath())
214 uassert.Equal(t, "hello 1", home.Render(""))
215}
216
217func TestThemeOwnsEveryOtherPath(cur realm, t *testing.T) {
218 home.ResetForTest()
219 testing.SetRealm(testing.NewCodeRealm(themeA))
220 home.Register(cross(cur), stubTheme{"A"})
221 testing.SetRealm(admin)
222 home.Accept(cross(cur), themeA)
223
224 uassert.Equal(t, "custom by A", home.Render("custom"), "a path the realm does not reserve reaches the theme")
225 uassert.Equal(t, "custom by A", home.Render("custom?x=1"))
226 uassert.True(t, strings.Contains(home.Render("slots?x=1"), "# Slots"), "reserved paths are matched without the query")
227 uassert.True(t, strings.Contains(home.Render("slots/nope"), "# Not found"))
228}
229
230func TestRegisterRefusals(cur realm, t *testing.T) {
231 home.ResetForTest()
232
233 // A user cannot nominate a theme; only a deployed realm can.
234 testing.SetRealm(admin)
235 uassert.AbortsContains(t, cur, "only a deployed realm", func() {
236 home.Register(cross(cur), stubTheme{"user"})
237 })
238
239 // A realm outside this path cannot either.
240 testing.SetRealm(testing.NewCodeRealm("gno.land/r/mallory/theme"))
241 uassert.AbortsContains(t, cur, "not nested", func() {
242 home.Register(cross(cur), stubTheme{"foreign"})
243 })
244 uassert.Equal(t, 0, len(home.PendingPaths()))
245}
246
247func TestAcceptIsAuthorityOnly(cur realm, t *testing.T) {
248 home.ResetForTest()
249 testing.SetRealm(testing.NewCodeRealm(themeA))
250 home.Register(cross(cur), stubTheme{"A"})
251
252 testing.SetRealm(mallory)
253 uassert.AbortsContains(t, cur, "not the authority", func() {
254 home.Accept(cross(cur), themeA)
255 })
256 uassert.Equal(t, "", home.LivePath())
257}
258
259func TestFreezeKeepsEditing(cur realm, t *testing.T) {
260 home.ResetForTest()
261 testing.SetRealm(testing.NewCodeRealm(themeA))
262 home.Register(cross(cur), stubTheme{"A"})
263 testing.SetRealm(admin)
264 home.Accept(cross(cur), themeA)
265 home.Freeze(cross(cur))
266 uassert.True(t, home.Frozen())
267
268 uassert.AbortsContains(t, cur, "frozen", func() {
269 home.Rollback(cross(cur))
270 })
271 home.Set(cross(cur), "layout", "still editable :widget:")
272 uassert.Equal(t, "still editable <A>", home.Render(""))
273 uassert.True(t, strings.Contains(home.Render("system"), "**frozen**"))
274}