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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

mygnoscan_test.gno

10.60 Kb · 266 lines
  1package mygnoscan
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9// mainnet is what a Scanner looks like on the chain that matters. Tests build
 10// it explicitly rather than calling Default(), because the test harness runs
 11// with chain-id "dev", which NetworkFor maps to no network at all: pinning
 12// Default()'s output would pin the absence of the parameter and prove nothing
 13// about the case every real link is in.
 14func mainnet() Scanner { return New(DefaultBase).WithNetwork("mainnet") }
 15
 16func TestNetworkFor(t *testing.T) {
 17	tests := []struct {
 18		chainID string
 19		want    string
 20	}{
 21		{"gnoland-1", "mainnet"},
 22		{"pearl-1", "pearl"},
 23		{"sapphire-1", "sapphire"},
 24		{"staging", "staging"},
 25		{"dev", ""},          // gnodev, and what `gno test` reports
 26		{"", ""},             // no chain at all
 27		{"gnoland1", ""},     // the upstream default instance's id, not a chain-id
 28		{"test3", ""},        // a retired testnet
 29		{"mainnet", ""},      // the network id, fed back in by mistake
 30	}
 31	for _, tt := range tests {
 32		uassert.Equal(t, tt.want, NetworkFor(tt.chainID), "NetworkFor("+tt.chainID+")")
 33	}
 34}
 35
 36func TestTrimDomain(t *testing.T) {
 37	tests := []struct {
 38		in   string
 39		want string
 40	}{
 41		{"gno.land/r/moul/home", "r/moul/home"},
 42		{"r/moul/home", "r/moul/home"},
 43		{"/r/moul/home", "r/moul/home"},
 44		{"gno.land/p/moul/mygnoscan/v0", "p/moul/mygnoscan/v0"},
 45		{"gno.land/r/gnoland/blog", "r/gnoland/blog"},
 46		{"", ""},
 47		{"gno.land/", ""},
 48		// A path whose own name starts with the domain is only trimmed once,
 49		// so a realm cannot be linked out of its own namespace by its name.
 50		{"gno.land/r/moul/gno.land", "r/moul/gno.land"},
 51		// Anything not shaped like a package path comes back empty, so the
 52		// caller falls back to a list page instead of emitting it.
 53		{"r/moul/home)", ""},
 54		{"r/moul/ho me", ""},
 55		{"r/moul//home", ""},
 56		{"r/moul/home\n", ""},
 57	}
 58	for _, tt := range tests {
 59		uassert.Equal(t, tt.want, TrimDomain(tt.in), "TrimDomain("+tt.in+")")
 60	}
 61}
 62
 63func TestBaseNormalisation(t *testing.T) {
 64	uassert.Equal(t, DefaultBase, New(DefaultBase+"/").Base())
 65	uassert.Equal(t, DefaultBase, New(DefaultBase).Base())
 66	uassert.Equal(t, "https://scan.example.com", New("https://scan.example.com/").Base())
 67}
 68
 69func TestTargets(t *testing.T) {
 70	s := mainnet()
 71	const b = DefaultBase
 72	tests := []struct {
 73		name string
 74		got  string
 75		want string
 76	}{
 77		{"home", s.Home(), b + "/?network=mainnet"},
 78		{"page", s.Page(PageTxs), b + "/txs?network=mainnet"},
 79		{"page nested", s.Page(PageGasRealms), b + "/gas/realms?network=mainnet"},
 80		{"page slashed", s.Page("/blocks/"), b + "/blocks?network=mainnet"},
 81		{"realm qualified", s.Realm("gno.land/r/moul/home"), b + "/realm/r/moul/home?network=mainnet"},
 82		{"realm bare", s.Realm("r/moul/home"), b + "/realm/r/moul/home?network=mainnet"},
 83		{"realm tab", s.Realm("r/moul/home", TabSource), b + "/realm/r/moul/home?network=mainnet&tab=source"},
 84		// TabInfo is the tab the explorer lands on, so asking for it adds nothing.
 85		{"realm tab info", s.Realm("r/moul/home", TabInfo), b + "/realm/r/moul/home?network=mainnet"},
 86		{"realm empty", s.Realm(""), b + "/?network=mainnet"},
 87		{"realm func", s.RealmFunc("r/moul/config", "Set"), b + "/realm/r/moul/config?fn=Set&network=mainnet&tab=source"},
 88		{"realm file", s.RealmFile("r/moul/home", "render.gno", 42), b + "/realm/r/moul/home?file=render.gno&line=42&network=mainnet&tab=source"},
 89		{"realm file no line", s.RealmFile("r/moul/home", "render.gno", 0), b + "/realm/r/moul/home?file=render.gno&network=mainnet&tab=source"},
 90		{"address", s.Address("g1manfred47kzduec920z88wfr64ylksmdcedlf5"), b + "/address/g1manfred47kzduec920z88wfr64ylksmdcedlf5?network=mainnet"},
 91		{"address empty", s.Address(""), b + "/accounts?network=mainnet"},
 92		{"block", s.Block(80618), b + "/block/80618?network=mainnet"},
 93		{"block zero", s.Block(0), b + "/blocks?network=mainnet"},
 94		{"validator", s.Validator("g1manfred47kzduec920z88wfr64ylksmdcedlf5"), b + "/validator/g1manfred47kzduec920z88wfr64ylksmdcedlf5?network=mainnet"},
 95		{"proposal", s.Proposal(7), b + "/govdao/7?network=mainnet"},
 96		{"proposal negative", s.Proposal(-1), b + "/govdao/proposals?network=mainnet"},
 97		{"token empty", s.Token(""), b + "/grc20?network=mainnet"},
 98	}
 99	for _, tt := range tests {
100		uassert.Equal(t, tt.want, tt.got, tt.name)
101	}
102}
103
104// TestEscaping covers the two routes whose argument is not URL-safe. Both are
105// read back with decodeURIComponent by the explorer, so percent-escaping the
106// whole segment is what round-trips; leaving the slashes alone would split the
107// segment and land on a different route entirely.
108func TestEscaping(t *testing.T) {
109	s := mainnet()
110
111	// A 32-byte hash in the base64 form the indexer and gnokey print, picked
112	// to carry all three of the characters that make base64 awkward in a URL.
113	// Everything outside the unreserved set moves, which is what the
114	// explorer's decodeURIComponent undoes byte for byte.
115	uassert.Equal(t,
116		DefaultBase+"/tx/BppSxd7qRiAPsMHzDx5mys%2BYpeDCMUzxI1qeb%2FEhaYc%3D?network=mainnet",
117		s.Tx("BppSxd7qRiAPsMHzDx5mys+YpeDCMUzxI1qeb/EhaYc="))
118	uassert.Equal(t, DefaultBase+"/txs?network=mainnet", s.Tx(""))
119
120	// A GRC20 ledger key carries both slashes and dots.
121	uassert.Equal(t,
122		DefaultBase+"/grc20/gno.land%2Fr%2Fgnoswap%2Fgns.GNS.0000000?network=mainnet",
123		s.Token("gno.land/r/gnoswap/gns.GNS.0000000"))
124}
125
126func TestURLQuery(cur realm, t *testing.T) {
127	s := mainnet()
128
129	uassert.Equal(t, DefaultBase+"/anything?network=mainnet", s.URL("anything"))
130	uassert.Equal(t, DefaultBase+"/anything?network=mainnet", s.URL("/anything"))
131	uassert.Equal(t, DefaultBase+"/a?k=v&network=mainnet", s.URL("/a", "k", "v"))
132
133	// An empty key or value is dropped, so an optional parameter needs no
134	// branch at the call site.
135	uassert.Equal(t, DefaultBase+"/a?network=mainnet", s.URL("/a", "k", ""))
136	uassert.Equal(t, DefaultBase+"/a?network=mainnet", s.URL("/a", "", "v"))
137
138	uassert.PanicsWithMessage(t, cur, "mygnoscan: odd number of query arguments", func() {
139		s.URL("/a", "k")
140	})
141}
142
143// TestNoNetwork pins what a Scanner emits when it does not know the chain:
144// nothing. The explorer then answers for every chain it indexes at once, which
145// is a real answer to a different question, so this is worth being explicit
146// about rather than discovering from a plausible-looking page.
147func TestNoNetwork(t *testing.T) {
148	s := New(DefaultBase).WithNetwork("")
149	uassert.Equal(t, "", s.Network())
150	uassert.Equal(t, DefaultBase+"/realm/r/moul/home", s.Realm("r/moul/home"))
151	uassert.Equal(t, DefaultBase+"/realm/r/moul/home?tab=calls", s.Realm("r/moul/home", TabCalls))
152	uassert.Equal(t, DefaultBase+"/", s.Home())
153}
154
155// TestDefaultUnderTest pins Default() as the test harness sees it: chain-id
156// "dev" is indexed nowhere, so the links carry no network. A realm rendering
157// under gnodev gets exactly this, which is why a local preview link is not
158// proof that the mainnet one is right.
159func TestDefaultUnderTest(t *testing.T) {
160	s := Default()
161	uassert.Equal(t, DefaultBase, s.Base())
162	uassert.Equal(t, "", s.Network())
163	uassert.Equal(t, DefaultBase+"/realm/r/moul/home", s.Realm("gno.land/r/moul/home"))
164}
165
166func TestTabArity(cur realm, t *testing.T) {
167	s := mainnet()
168	uassert.PanicsWithMessage(t, cur, "mygnoscan: at most one tab", func() {
169		s.Realm("r/moul/home", TabSource, TabCalls)
170	})
171}
172
173func TestLink(t *testing.T) {
174	uassert.Equal(t, "[text](https://example.com)", Link("text", "https://example.com"))
175}
176
177func TestRealmFooter(t *testing.T) {
178	s := mainnet()
179	const want = "[explorer](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet)" +
180		" · [source](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet&tab=source)" +
181		" · [calls](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet&tab=calls)" +
182		" · [deps](https://mygnoscan.moul.p2p.team/realm/r/moul/home?network=mainnet&tab=deps)"
183	uassert.Equal(t, want, s.RealmFooter("gno.land/r/moul/home"))
184	uassert.Equal(t, want, s.RealmFooter("r/moul/home"))
185	uassert.Equal(t, "", s.RealmFooter(""))
186}
187
188// TestOtherInstance is the whole reason base and network are separate knobs:
189// a second instance serving the same chain under a different id.
190func TestOtherInstance(t *testing.T) {
191	s := New("https://scan.example.com").WithNetwork("gnoland1")
192	uassert.Equal(t,
193		"https://scan.example.com/realm/r/moul/home?network=gnoland1",
194		s.Realm("gno.land/r/moul/home"))
195}
196
197// TestMarkdownBreakout is the reason safePath and escapeSegment exist. A realm
198// that links a path or a hash a caller typed would otherwise let that caller
199// close the markdown link early and write page text of their choosing.
200//
201// url.PathEscape is not enough on its own: "(" and ")" are legal URL
202// sub-delims and it leaves both alone.
203func TestMarkdownBreakout(t *testing.T) {
204	s := mainnet()
205
206	// A path is refused outright, because escaping its separators would break
207	// the route. The caller gets the front page, not a broken link.
208	uassert.Equal(t, DefaultBase+"/?network=mainnet",
209		s.Realm("r/evil/x)](https://evil.example.com"))
210	uassert.Equal(t, "", s.RealmFooter("r/evil/x)](https://evil.example.com"))
211
212	// A hash and a token key are opaque, so they are escaped rather than
213	// refused, and the parentheses move.
214	uassert.Equal(t,
215		DefaultBase+"/tx/x%29%5D%28evil?network=mainnet",
216		s.Tx("x)](evil"))
217	uassert.Equal(t,
218		DefaultBase+"/grc20/x%29%5D%28evil?network=mainnet",
219		s.Token("x)](evil"))
220
221	// An address is a string type; nothing stops a realm casting into it.
222	uassert.Equal(t,
223		DefaultBase+"/address/g1%29%5D%28evil?network=mainnet",
224		s.Address(address("g1)](evil")))
225
226	// A page name that is not a page name lands on the front page, rather
227	// than on the explorer's own unknown-path-renders-home behaviour, which
228	// would look identical and mean something else.
229	uassert.Equal(t, DefaultBase+"/?network=mainnet", s.Page("txs)](evil"))
230	uassert.Equal(t, DefaultBase+"/?network=mainnet", s.Page(""))
231}
232
233func TestSafePath(t *testing.T) {
234	tests := []struct {
235		in   string
236		want bool
237	}{
238		{"r/moul/home", true},
239		{"p/moul/mygnoscan/v0", true},
240		{"gas/realms", true},
241		{"a", true},
242		{"a.b-c_d", true},
243		{"", false},
244		{"a//b", false},
245		{"a/", false},
246		{"a b", false},
247		{"a)b", false},
248		{"a(b", false},
249		{"a]b", false},
250		{"a\nb", false},
251		{"a?b", false},
252		{"a#b", false},
253		{"a%b", false},
254	}
255	for _, tt := range tests {
256		uassert.Equal(t, tt.want, safePath(tt.in), "safePath("+tt.in+")")
257	}
258}
259
260func TestSubViews(t *testing.T) {
261	s := mainnet()
262	uassert.Equal(t, DefaultBase+"/packages?network=mainnet&pv=inert", s.Packages(PackagesInert))
263	uassert.Equal(t, DefaultBase+"/packages?network=mainnet", s.Packages(""))
264	uassert.Equal(t, DefaultBase+"/accounts?av=balances&network=mainnet", s.Accounts(AccountsBalances))
265	uassert.Equal(t, DefaultBase+"/accounts?network=mainnet", s.Accounts(""))
266}