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

settings_test.gno

10.91 Kb · 321 lines
  1package config
  2
  3import (
  4	"strconv"
  5	"testing"
  6
  7	"chain/runtime"
  8
  9	"gno.land/p/moul/authz/v0"
 10	"gno.land/p/moul/mygnoscan/v0"
 11	"gno.land/p/nt/avl/v0"
 12	"gno.land/p/nt/uassert/v0"
 13)
 14
 15// resetSettings puts the realm back to a just-deployed state. Realm globals
 16// live for the whole test binary and gno runs every Example AFTER every Test,
 17// so without this the pinned ExampleRender output would depend on which tests
 18// ran before it and in what order.
 19func resetSettings() {
 20	settings = avl.NewTree()
 21	proxies = avl.NewTree()
 22	settingsRev = 0
 23	Authorizer = authz.NewWithAuthority(authz.NewMemberAuthority(originAddr))
 24}
 25
 26func TestValidKey(t *testing.T) {
 27	tests := []struct {
 28		key  string
 29		want bool
 30	}{
 31		{"mygnoscan.url", true},
 32		{"a", true},
 33		{"a-b_c.d0", true},
 34		{"", false},
 35		{"Upper", false},
 36		{"has space", false},
 37		{"tab\there", false},
 38		{"colon:here", false},
 39		// A bare slash is not a name; it is only legal inside a scope.
 40		{"slash/here", false},
 41		// Scoped keys.
 42		{"pause" + ScopeSep + "r/moul/home", true},
 43		{"block.top" + ScopeSep + "p/moul/kit/ui", true},
 44		{"pause" + ScopeSep, false},           // scope separator with no scope
 45		{ScopeSep + "r/moul/home", false},     // scope with no name
 46		{"pause" + ScopeSep + "r//moul", false},
 47		{"pause" + ScopeSep + "r/moul/", false},
 48		{"pause" + ScopeSep + "R/Moul/Home", false},
 49		{"pause" + ScopeSep + "a" + ScopeSep + "b", false},
 50	}
 51	for _, tt := range tests {
 52		uassert.Equal(t, tt.want, validKey(tt.key), "validKey("+tt.key+")")
 53	}
 54
 55	// 64 bytes is the last legal length, 65 the first illegal one.
 56	long := ""
 57	for i := 0; i < maxNameLen; i++ {
 58		long += "a"
 59	}
 60	uassert.True(t, validKey(long), "a name of exactly maxNameLen is legal")
 61	uassert.False(t, validKey(long+"a"), "one byte over maxNameLen is not")
 62}
 63
 64func TestSetGetUnset(cur realm, t *testing.T) {
 65	resetSettings()
 66
 67	uassert.Equal(t, "", Get("mygnoscan.url"))
 68	uassert.False(t, Has("mygnoscan.url"))
 69	uassert.Equal(t, "fallback", GetOr("mygnoscan.url", "fallback"))
 70	uassert.Equal(t, 0, SettingsRevision())
 71
 72	testing.SetRealm(testing.NewUserRealm(originAddr))
 73	Set(cross(cur), KeyMygnoscanURL, "https://scan.example.com")
 74
 75	uassert.Equal(t, "https://scan.example.com", Get(KeyMygnoscanURL))
 76	uassert.True(t, Has(KeyMygnoscanURL))
 77	uassert.Equal(t, "https://scan.example.com", GetOr(KeyMygnoscanURL, "fallback"))
 78	uassert.Equal(t, 1, SettingsRevision())
 79	uassert.Equal(t, 1, Size())
 80
 81	// Overwriting is an ordinary write and bumps the revision again.
 82	testing.SetRealm(testing.NewUserRealm(originAddr))
 83	Set(cross(cur), KeyMygnoscanURL, "https://scan2.example.com")
 84	uassert.Equal(t, "https://scan2.example.com", Get(KeyMygnoscanURL))
 85	uassert.Equal(t, 2, SettingsRevision())
 86	uassert.Equal(t, 1, Size())
 87
 88	testing.SetRealm(testing.NewUserRealm(originAddr))
 89	Unset(cross(cur), KeyMygnoscanURL)
 90	uassert.Equal(t, "", Get(KeyMygnoscanURL))
 91	uassert.False(t, Has(KeyMygnoscanURL))
 92	uassert.Equal(t, 0, Size())
 93	uassert.Equal(t, 3, SettingsRevision())
 94}
 95
 96// TestEmptyValueIsSet pins the one case GetOr cannot see: a key set to "" is
 97// set, and every reader falls back anyway. That is deliberate (a reader's
 98// default beats an empty string) but it means Unset and Set("") are not the
 99// same operation, and only Has tells them apart.
100func TestEmptyValueIsSet(cur realm, t *testing.T) {
101	resetSettings()
102
103	testing.SetRealm(testing.NewUserRealm(originAddr))
104	Set(cross(cur), "some.key", "")
105
106	uassert.True(t, Has("some.key"))
107	uassert.Equal(t, "", Get("some.key"))
108	uassert.Equal(t, "fallback", GetOr("some.key", "fallback"))
109}
110
111func TestSetRequiresAuthority(cur realm, t *testing.T) {
112	resetSettings()
113
114	testing.SetRealm(testing.NewUserRealm(nonManagerAddr))
115	uassert.AbortsContains(t, cur, "unauthorized", func() {
116		Set(cross(cur), "some.key", "value")
117	})
118	uassert.Equal(t, 0, Size(), "a rejected write leaves nothing behind")
119	uassert.Equal(t, 0, SettingsRevision(), "and does not move the revision")
120
121	// A manager added by the origin can write, which is the whole point of
122	// routing through the Authorizer instead of a hardcoded address.
123	testing.SetRealm(testing.NewUserRealm(originAddr))
124	uassert.NoError(t, AddManager(cross(cur), manager1Addr))
125
126	testing.SetRealm(testing.NewUserRealm(manager1Addr))
127	Set(cross(cur), "some.key", "value")
128	uassert.Equal(t, "value", Get("some.key"))
129
130	// And loses it again when removed.
131	testing.SetRealm(testing.NewUserRealm(originAddr))
132	uassert.NoError(t, RemoveManager(cross(cur), manager1Addr))
133
134	testing.SetRealm(testing.NewUserRealm(manager1Addr))
135	uassert.AbortsContains(t, cur, "unauthorized", func() {
136		Set(cross(cur), "other.key", "value")
137	})
138}
139
140func TestUnsetRequiresAuthority(cur realm, t *testing.T) {
141	resetSettings()
142
143	testing.SetRealm(testing.NewUserRealm(originAddr))
144	Set(cross(cur), "some.key", "value")
145
146	testing.SetRealm(testing.NewUserRealm(nonManagerAddr))
147	uassert.AbortsContains(t, cur, "unauthorized", func() {
148		Unset(cross(cur), "some.key")
149	})
150	uassert.Equal(t, "value", Get("some.key"), "a rejected unset leaves the value")
151}
152
153func TestUnsetMissingKeyAborts(cur realm, t *testing.T) {
154	resetSettings()
155
156	testing.SetRealm(testing.NewUserRealm(originAddr))
157	uassert.AbortsWithMessage(t, cur, "no such setting: never.set", func() {
158		Unset(cross(cur), "never.set")
159	})
160}
161
162func TestInvalidInputAborts(cur realm, t *testing.T) {
163	resetSettings()
164
165	testing.SetRealm(testing.NewUserRealm(originAddr))
166	uassert.AbortsContains(t, cur, "invalid key", func() {
167		Set(cross(cur), "Bad Key", "value")
168	})
169
170	long := ""
171	for i := 0; i < maxValueLen+1; i++ {
172		long += "x"
173	}
174	testing.SetRealm(testing.NewUserRealm(originAddr))
175	uassert.AbortsContains(t, cur, "value too long", func() {
176		Set(cross(cur), "some.key", long)
177	})
178
179	uassert.Equal(t, 0, Size(), "neither rejection wrote anything")
180}
181
182// TestKeyValidatedBeforeAuthority pins the check order: an unauthorised caller
183// passing a malformed key is told the key is wrong, not that they are. Nothing
184// secret leaks either way, and validating first means a manager's typo gets
185// the useful message rather than a misleading "unauthorized".
186func TestKeyValidatedBeforeAuthority(cur realm, t *testing.T) {
187	resetSettings()
188
189	testing.SetRealm(testing.NewUserRealm(nonManagerAddr))
190	uassert.AbortsContains(t, cur, "invalid key", func() {
191		Set(cross(cur), "Bad Key", "value")
192	})
193}
194
195func TestKeysAndManifest(cur realm, t *testing.T) {
196	resetSettings()
197
198	testing.SetRealm(testing.NewUserRealm(originAddr))
199	Set(cross(cur), "zzz.last", "z")
200	testing.SetRealm(testing.NewUserRealm(originAddr))
201	Set(cross(cur), "aaa.first", "a")
202
203	keys := Keys()
204	uassert.Equal(t, 2, len(keys))
205	uassert.Equal(t, "aaa.first", keys[0], "Keys is sorted, not insertion-ordered")
206	uassert.Equal(t, "zzz.last", keys[1])
207
208	// rev is the realm-wide counter at the time of that key's last write, so
209	// zzz.last carries 1 and aaa.first carries 2 despite sorting first. The
210	// height column is read back from the chain rather than hardcoded: gno's
211	// test harness has no absolute height setter, so tests inherit whatever
212	// height ran before them and a literal here would break on the next test
213	// added above this one.
214	h := strconv.FormatInt(runtime.ChainHeight(), 10)
215	uassert.Equal(t,
216		"aaa.first\t2\t"+h+"\ta\nzzz.last\t1\t"+h+"\tz\n",
217		Manifest())
218}
219
220func TestScannerFollowsSettings(cur realm, t *testing.T) {
221	resetSettings()
222
223	// Unset, every reader is on the package default.
224	uassert.Equal(t, mygnoscan.DefaultBase, MygnoscanURL())
225	uassert.Equal(t,
226		mygnoscan.DefaultBase+"/realm/r/moul/home",
227		MygnoscanFor("gno.land/r/moul/home"))
228
229	// One transaction moves every realm that reads this one.
230	testing.SetRealm(testing.NewUserRealm(originAddr))
231	Set(cross(cur), KeyMygnoscanURL, "https://scan.example.com")
232
233	uassert.Equal(t, "https://scan.example.com", MygnoscanURL())
234	uassert.Equal(t,
235		"https://scan.example.com/realm/r/moul/home",
236		MygnoscanFor("gno.land/r/moul/home"))
237
238	// The network override is independent of the base, for an instance that
239	// names the chain differently. The test harness runs as chain-id "dev",
240	// which maps to no network at all, so this is the only way to see one.
241	uassert.Equal(t, "", Scanner().Network())
242	testing.SetRealm(testing.NewUserRealm(originAddr))
243	Set(cross(cur), KeyMygnoscanNetwork, "gnoland1")
244	uassert.Equal(t, "gnoland1", Scanner().Network())
245	uassert.Equal(t,
246		"https://scan.example.com/realm/r/moul/home?network=gnoland1",
247		MygnoscanFor("gno.land/r/moul/home"))
248}
249
250func TestMygnoscanFooter(cur realm, t *testing.T) {
251	resetSettings()
252	uassert.Equal(t,
253		mygnoscan.DefaultBase+"/realm/r/moul/config",
254		mygnoscan.Default().Realm("gno.land/r/moul/config"),
255		"the footer and a direct build agree while nothing is configured")
256	uassert.True(t, len(MygnoscanFooterFor("gno.land/r/moul/config")) > 0)
257	uassert.Equal(t, "", MygnoscanFooterFor(""))
258}
259
260// ExampleRender pins the page as it looks right after deployment. It resets
261// first: examples run after every Test in the binary and would otherwise
262// render whatever state they left.
263func ExampleRender() {
264	resetSettings()
265	print(Render(""))
266	// Output:
267	// # gno.land/r/moul/config
268	//
269	// moul's settings, read by his other realms. rev 0 · 0 setting(s)
270	//
271	// ## Settings
272	//
273	// _none set; every reader is on its built-in default_
274	//
275	// ## Pause
276	//
277	// _running; no global pause_
278	//
279	// ## Authority
280	//
281	// `member_authority[g1daexjemfde047h6lta047h6lta047h6ld2pug6]`
282	//
283	// ---
284	//
285	// [explorer](https://mygnoscan.moul.p2p.team/realm/r/moul/config/v1) · [source](https://mygnoscan.moul.p2p.team/realm/r/moul/config/v1?tab=source) · [calls](https://mygnoscan.moul.p2p.team/realm/r/moul/config/v1?tab=calls) · [deps](https://mygnoscan.moul.p2p.team/realm/r/moul/config/v1?tab=deps)
286}
287
288// ExampleRender_populated pins the settings table and the global pause banner,
289// which together are the view a manager actually looks at during an incident.
290func ExampleRender_populated() {
291	resetSettings()
292	settings.Set(KeyMygnoscanURL, &setting{value: "https://scan.example.com", rev: 1, updated: 42})
293	settings.Set(KeyPause, &setting{value: "readonly: migrating storage", rev: 2, updated: 43})
294	settings.Set(KeyFor(KeyBlockTop, "gno.land/r/moul/gns"), &setting{value: "> v2 shipped", rev: 3, updated: 44})
295	settingsRev = 3
296	print(Render(""))
297	// Output:
298	// # gno.land/r/moul/config
299	//
300	// moul's settings, read by his other realms. rev 3 · 3 setting(s)
301	//
302	// ## Settings
303	//
304	// | key | value | rev | height |
305	// | --- | --- | ---: | ---: |
306	// | `block.top@r/moul/gns` | `> v2 shipped` | 3 | 44 |
307	// | `mygnoscan.url` | `https://scan.example.com` | 1 | 42 |
308	// | `pause` | `readonly: migrating storage` | 2 | 43 |
309	//
310	// ## Pause
311	//
312	// > **Read-only.** This realm is still readable, but not accepting changes. migrating storage
313	//
314	// ## Authority
315	//
316	// `member_authority[g1daexjemfde047h6lta047h6lta047h6ld2pug6]`
317	//
318	// ---
319	//
320	// [explorer](https://scan.example.com/realm/r/moul/config/v1) · [source](https://scan.example.com/realm/r/moul/config/v1?tab=source) · [calls](https://scan.example.com/realm/r/moul/config/v1?tab=calls) · [deps](https://scan.example.com/realm/r/moul/config/v1?tab=deps)
321}