1package goldenticket23import (4 "strings"5 "testing"67 "gno.land/p/nt/grc721/v0"8 "gno.land/p/nt/testutils/v0"9 "gno.land/p/nt/uassert/v0"10)1112var (13 aliceAddr = testutils.TestAddress("alice")14 bobAddr = testutils.TestAddress("bob")15)1617// reset redoes what init does on chain, with alice as the publisher.18func reset(cur realm) {19 func(cur realm) {20 bootstrap(aliceAddr, cur)21 }(cross(cur))22}2324// The reads, called as Adena calls them: the id a quoted string, two values.25func TestReads(cur realm, t *testing.T) {26 reset(cur)2728 owner, err := OwnerOf("1")29 uassert.NoError(t, err)30 uassert.Equal(t, aliceAddr, owner)3132 n, err := BalanceOf(aliceAddr)33 uassert.NoError(t, err)34 uassert.Equal(t, int64(1), n)35 n, err = BalanceOf(bobAddr)36 uassert.NoError(t, err)37 uassert.Equal(t, int64(0), n)3839 uri, err := TokenURI("1")40 uassert.NoError(t, err)41 uassert.Equal(t, defaultImage, uri)42 uassert.True(t, strings.HasPrefix(uri, "https://"))4344 meta, err := TokenMetadata("1")45 uassert.NoError(t, err)46 uassert.Equal(t, `{"name":"Golden Ticket #1","description":"A 1/1 on gno.land, minted to check that collections show in Adena.",`+47 `"image":"`+defaultImage+`","attributes":[{"traitType":"edition","value":"1 of 1"},{"traitType":"metal","value":"gold"}]}`, meta)4849 uassert.Equal(t, int64(1), Token.TotalSupply())50 uassert.Equal(t, "Golden Ticket", Name())51 uassert.Equal(t, "GOLD", Symbol())5253 // Any other id is an error, not a panic.54 for _, bad := range []grc721.TokenID{"", "0", "2", "01", " 1"} {55 _, err = OwnerOf(bad)56 uassert.ErrorContains(t, err, errNoTicket, string(bad))57 _, err = TokenURI(bad)58 uassert.ErrorContains(t, err, errNoTicket, string(bad))59 _, err = TokenMetadata(bad)60 uassert.ErrorContains(t, err, errNoTicket, string(bad))61 }62}6364func TestTransfer(cur realm, t *testing.T) {65 reset(cur)6667 // Only the holder moves the ticket.68 testing.SetRealm(testing.NewUserRealm(bobAddr))69 uassert.AbortsWithMessage(t, cur, errNotOwner, func() {70 Transfer(cross(cur), bobAddr)71 })7273 testing.SetRealm(testing.NewUserRealm(aliceAddr))74 uassert.AbortsWithMessage(t, cur, errBadOwner, func() {75 Transfer(cross(cur), address("nope"))76 })77 Transfer(cross(cur), bobAddr)78 owner, err := OwnerOf("1")79 uassert.NoError(t, err)80 uassert.Equal(t, bobAddr, owner)81 n, _ := BalanceOf(aliceAddr)82 uassert.Equal(t, int64(0), n)83 n, _ = BalanceOf(bobAddr)84 uassert.Equal(t, int64(1), n)8586 // Not through another realm.87 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/relay"))88 uassert.AbortsWithMessage(t, cur, errNotOwner, func() {89 Transfer(cross(cur), aliceAddr)90 })91}9293func TestRender(cur realm, t *testing.T) {94 reset(cur)95 out := Render("")96 uassert.True(t, strings.Contains(out, "# Golden Ticket\n"))97 uassert.True(t, strings.Contains(out, ""))98 uassert.True(t, strings.Contains(out, "Held by `"+aliceAddr.String()+"`."))99}100101func TestSetImage(cur realm, t *testing.T) {102 reset(cur)103 uassert.Equal(t, aliceAddr, Admin())104 uassert.Equal(t, defaultImage, Image())105106 // Only the admin, and not through another realm.107 testing.SetRealm(testing.NewUserRealm(bobAddr))108 uassert.AbortsWithMessage(t, cur, errNotAdmin, func() {109 SetImage(cross(cur), "https://example.org/a.png")110 })111 testing.SetRealm(testing.NewCodeRealm("gno.land/r/demo/relay"))112 uassert.AbortsWithMessage(t, cur, errNotAdmin, func() {113 SetImage(cross(cur), "https://example.org/a.png")114 })115116 testing.SetRealm(testing.NewUserRealm(aliceAddr))117 for _, bad := range []string{118 "", "http://example.org/a.png", "https://", "javascript:alert(1)",119 "https://example.org/a b.png", `https://example.org/"a.png`, "https://example.org/a.png),121 } {122 uassert.AbortsWithMessage(t, cur, errBadImage, func() {123 SetImage(cross(cur), bad)124 })125 }126127 next := "https://ipfs.io/ipfs/bafy123/ticket.png?size=350&v=2"128 SetImage(cross(cur), next)129 uassert.Equal(t, next, Image())130 uri, err := TokenURI("1")131 uassert.NoError(t, err)132 uassert.Equal(t, next, uri)133 meta, _ := TokenMetadata("1")134 uassert.True(t, strings.Contains(meta, `"image":"`+next+`"`))135 uassert.True(t, strings.Contains(Render(""), "("+next+")"))136137 // The admin stays the publisher after the ticket moves.138 Transfer(cross(cur), bobAddr)139 testing.SetRealm(testing.NewUserRealm(bobAddr))140 uassert.AbortsWithMessage(t, cur, errNotAdmin, func() {141 SetImage(cross(cur), defaultImage)142 })143}144Admin() string
BalanceOf(addr string) (int64, interface {Error func() string})
Image() string
Name() string
OwnerOf(tid string) (string, interface {Error func() string})
Render(string) string
SetImage(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}, url string)
Symbol() string
TokenMetadata(tid string) (string, interface {Error func() string})
TokenURI(tid string) (string, interface {Error func() string})
Transfer(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)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.