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

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}