Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}