Kind Realm (renderable)
Name home
Namespace g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm
Exported functions 30
Module gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home
gno 0.9 home_test.gno gno
⧉
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. 4 package home_test 5 6 import ( 7 "strings" Accept (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pkgPath string)
Append (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string, body string)
Authority () string
Delete (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string)
Each (fn func(string, gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home.Slot) bool)
Fallback (path string) string
Forget (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
Freeze (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
Frozen () bool
Get (slug string) string
Has (slug string) bool
HistoryPaths () []string
LastHeight () int64
Layout () string
Link (sub string) string
LivePath () string
Lookup (slug string) (struct{Body string; Rev int; Height int64}, bool)
Manifest () string
NewFiller () *gno.land/r/g1lnkytfqcjwllws63gvf0mv9yt04aswy4y9amhm/home.Filler
Path () string
PendingPaths () []string
Register (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, t interface {Render func(string) string})
Render (path string) string
Revision () int
Rollback (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
Set (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string, body string)
Slugs () []string
Style (key string, fallback string) string
TransferAuthority (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, to string)
Withdraw (cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pkgPath string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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
15 var stranger = testutils.TestAddress( "mallory" )
16
17 const (
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.
23 type stubTheme struct { tag string }
24
25 func (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.
42 type brokenTheme struct {}
43
44 func (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.
56 var (
57 admin = testing.NewUserRealm(home.Admin)
58 mallory = testing.NewUserRealm(stranger)
59 )
60
61 // ---------------------------------------------------------------------------
62 // Slots
63
64 func 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
91 func 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
103 func 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
119 func 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
134 func 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
156 func 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
190 func 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
217 func 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
230 func 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
247 func 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
259 func 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 }
275
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.