token_test.gno
7.37 Kb · 247 lines
1package metadata
2
3import (
4 "testing"
5
6 "gno.land/p/nt/grc721/v0"
7 "gno.land/p/nt/seqid/v0"
8 "gno.land/p/nt/testutils/v0"
9 "gno.land/p/nt/uassert/v0"
10 "gno.land/p/nt/urequire/v0"
11)
12
13// newToken builds via same-realm cross (NewToken requires rlm.IsCurrent()).
14func newToken(name, symbol string, id seqid.ID, rlm realm) (tok *grc721.Token, led *grc721.PrivateLedger) {
15 func(cur realm) {
16 tok, led = grc721.NewToken(name, symbol, id, cur)
17 }(cross(rlm))
18 return
19}
20
21func newMeta(id seqid.ID, rlm realm) (*grc721.PrivateLedger, *Metadata, *Ledger) {
22 _, tokenLedger := newToken("Foo", "FOO", id, rlm)
23 meta, metaLedger := NewMetadata(tokenLedger)
24 return tokenLedger, meta, metaLedger
25}
26
27func TestNewMetadata(cur realm, t *testing.T) {
28 tests := []struct {
29 name string
30 nilLedger bool
31 }{
32 {"nil token ledger panics", true},
33 {"valid token ledger constructs read view and ledger", false},
34 }
35
36 for _, tt := range tests {
37 t.Run(tt.name, func(t *testing.T) {
38 if tt.nilLedger {
39 uassert.PanicsWithMessage(t, cur, "metadata: nil core ledger", func() {
40 NewMetadata(nil)
41 })
42 return
43 }
44
45 _, tokenLedger := newToken("Foo", "FOO", 0, cur)
46 meta, metaLedger := NewMetadata(tokenLedger)
47
48 uassert.True(t, meta != nil)
49 uassert.True(t, metaLedger != nil)
50 uassert.Equal(t, Kind, meta.ExtensionKind())
51 uassert.Equal(t, "metadata", meta.ExtensionKind())
52 uassert.Equal(t, tokenLedger.ReadToken().ID(), meta.TokenID())
53 })
54 }
55}
56
57func TestSetTokenURI(cur realm, t *testing.T) {
58 tests := []struct {
59 name string
60 mint bool
61 tid grc721.TokenID
62 uri string
63 wantErr error
64 }{
65 {"unminted token returns ErrTokenNotMinted", false, "404", "ipfs://x", ErrTokenNotMinted},
66 {"minted token sets uri", true, "1", "ipfs://uri-1", nil},
67 {"empty uri on minted token is allowed", true, "1", "", nil},
68 }
69
70 for _, tt := range tests {
71 t.Run(tt.name, func(t *testing.T) {
72 tokenLedger, meta, metaLedger := newMeta(0, cur)
73 if tt.mint {
74 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), tt.tid))
75 }
76
77 err := metaLedger.SetTokenURI(tt.tid, tt.uri)
78 if tt.wantErr != nil {
79 uassert.ErrorIs(t, err, tt.wantErr)
80 uassert.False(t, meta.HasTokenURI(tt.tid))
81 return
82 }
83
84 urequire.NoError(t, err)
85 uassert.True(t, meta.HasTokenURI(tt.tid))
86 got, err := meta.TokenURI(tt.tid)
87 urequire.NoError(t, err)
88 uassert.Equal(t, tt.uri, got)
89 })
90 }
91}
92
93func TestSetTokenURIOverwrite(cur realm, t *testing.T) {
94 tokenLedger, meta, metaLedger := newMeta(0, cur)
95 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), "1"))
96
97 urequire.NoError(t, metaLedger.SetTokenURI("1", "ipfs://first"))
98 urequire.NoError(t, metaLedger.SetTokenURI("1", "ipfs://second"))
99
100 got, err := meta.TokenURI("1")
101 urequire.NoError(t, err)
102 uassert.Equal(t, "ipfs://second", got)
103}
104
105func TestSetTokenMetadata(cur realm, t *testing.T) {
106 full := Data{
107 Image: "ipfs://img-1",
108 ImageData: "<svg/>",
109 ExternalURL: "https://example.com/1",
110 Description: "first",
111 Name: "Token One",
112 Attributes: []Trait{{DisplayType: "string", TraitType: "rarity", Value: "rare"}},
113 BackgroundColor: "ffffff",
114 AnimationURL: "ipfs://anim-1",
115 YoutubeURL: "https://youtu.be/abc",
116 }
117
118 tests := []struct {
119 name string
120 mint bool
121 tid grc721.TokenID
122 data Data
123 wantErr error
124 }{
125 {"unminted token returns ErrTokenNotMinted", false, "404", Data{Name: "x"}, ErrTokenNotMinted},
126 {"minted token stores full metadata", true, "1", full, nil},
127 {"minted token stores zero-value metadata", true, "2", Data{}, nil},
128 }
129
130 for _, tt := range tests {
131 t.Run(tt.name, func(t *testing.T) {
132 tokenLedger, meta, metaLedger := newMeta(0, cur)
133 if tt.mint {
134 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), tt.tid))
135 }
136
137 err := metaLedger.SetTokenMetadata(tt.tid, tt.data)
138 if tt.wantErr != nil {
139 uassert.ErrorIs(t, err, tt.wantErr)
140 uassert.False(t, meta.HasMetadata(tt.tid))
141 return
142 }
143
144 urequire.NoError(t, err)
145 uassert.True(t, meta.HasMetadata(tt.tid))
146 got, err := meta.TokenMetadata(tt.tid)
147 urequire.NoError(t, err)
148 uassert.Equal(t, tt.data.Name, got.Name)
149 uassert.Equal(t, tt.data.Description, got.Description)
150 uassert.Equal(t, tt.data.Image, got.Image)
151 uassert.Equal(t, len(tt.data.Attributes), len(got.Attributes))
152 if len(tt.data.Attributes) > 0 {
153 uassert.Equal(t, tt.data.Attributes[0].Value, got.Attributes[0].Value)
154 uassert.Equal(t, tt.data.Attributes[0].TraitType, got.Attributes[0].TraitType)
155 }
156 })
157 }
158}
159
160func TestSetTokenMetadataOverwrite(cur realm, t *testing.T) {
161 tokenLedger, meta, metaLedger := newMeta(0, cur)
162 urequire.NoError(t, tokenLedger.Mint(testutils.TestAddress("alice"), "1"))
163
164 urequire.NoError(t, metaLedger.SetTokenMetadata("1", Data{Name: "first"}))
165 urequire.NoError(t, metaLedger.SetTokenMetadata("1", Data{Name: "second"}))
166
167 got, err := meta.TokenMetadata("1")
168 urequire.NoError(t, err)
169 uassert.Equal(t, "second", got.Name)
170}
171
172func TestReadsOnUnsetToken(cur realm, t *testing.T) {
173 tests := []struct {
174 name string
175 fn func(t *testing.T, meta *Metadata)
176 }{
177 {"TokenURI on unset token returns ErrNoTokenURI", func(t *testing.T, meta *Metadata) {
178 uri, err := meta.TokenURI("1")
179 uassert.Equal(t, "", uri)
180 uassert.ErrorIs(t, err, ErrNoTokenURI)
181 }},
182 {"TokenMetadata on unset token returns ErrNoMetadata", func(t *testing.T, meta *Metadata) {
183 data, err := meta.TokenMetadata("1")
184 uassert.Equal(t, "", data.Name)
185 uassert.ErrorIs(t, err, ErrNoMetadata)
186 }},
187 {"HasTokenURI is false on unset token", func(t *testing.T, meta *Metadata) {
188 uassert.False(t, meta.HasTokenURI("1"))
189 }},
190 {"HasMetadata is false on unset token", func(t *testing.T, meta *Metadata) {
191 uassert.False(t, meta.HasMetadata("1"))
192 }},
193 }
194
195 for _, tt := range tests {
196 t.Run(tt.name, func(t *testing.T) {
197 _, meta, _ := newMeta(0, cur)
198 tt.fn(t, meta)
199 })
200 }
201}
202
203func TestOnBurnClearsMetadata(cur realm, t *testing.T) {
204 alice := testutils.TestAddress("alice")
205 tokenLedger, meta, metaLedger := newMeta(0, cur)
206
207 urequire.NoError(t, tokenLedger.Mint(alice, "1"))
208 urequire.NoError(t, metaLedger.SetTokenURI("1", "ipfs://uri-1"))
209 urequire.NoError(t, metaLedger.SetTokenMetadata("1", Data{Name: "one"}))
210 uassert.True(t, meta.HasTokenURI("1"))
211 uassert.True(t, meta.HasMetadata("1"))
212
213 urequire.NoError(t, tokenLedger.Burn("1"))
214 uassert.False(t, meta.HasTokenURI("1"))
215 uassert.False(t, meta.HasMetadata("1"))
216
217 _, err := meta.TokenURI("1")
218 uassert.ErrorIs(t, err, ErrNoTokenURI)
219 _, err = meta.TokenMetadata("1")
220 uassert.ErrorIs(t, err, ErrNoMetadata)
221
222 urequire.NoError(t, tokenLedger.Mint(alice, "1"))
223 uassert.False(t, meta.HasTokenURI("1"))
224 uassert.False(t, meta.HasMetadata("1"))
225}
226
227func TestHooks(cur realm, t *testing.T) {
228 alice := testutils.TestAddress("alice")
229 bob := testutils.TestAddress("bob")
230 tokenLedger, meta, metaLedger := newMeta(0, cur)
231
232 urequire.NoError(t, tokenLedger.Mint(alice, "1"))
233 urequire.NoError(t, metaLedger.SetTokenURI("1", "ipfs://uri-1"))
234 urequire.NoError(t, metaLedger.SetTokenMetadata("1", Data{Name: "one"}))
235
236 metaLedger.OnMint(alice, "1")
237 metaLedger.OnTransfer(alice, bob, "1")
238 uassert.True(t, meta.HasTokenURI("1"))
239 uassert.True(t, meta.HasMetadata("1"))
240 uri, err := meta.TokenURI("1")
241 urequire.NoError(t, err)
242 uassert.Equal(t, "ipfs://uri-1", uri)
243
244 metaLedger.OnBurn("1")
245 uassert.False(t, meta.HasTokenURI("1"))
246 uassert.False(t, meta.HasMetadata("1"))
247}