hive-stream 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/DOCUMENTATION.md +50 -2
  2. package/README.md +282 -4
  3. package/dist/adapters/base.adapter.d.ts +5 -0
  4. package/dist/adapters/base.adapter.js +9 -0
  5. package/dist/adapters/base.adapter.js.map +1 -1
  6. package/dist/adapters/mongodb.adapter.d.ts +6 -6
  7. package/dist/adapters/mongodb.adapter.js +36 -21
  8. package/dist/adapters/mongodb.adapter.js.map +1 -1
  9. package/dist/adapters/postgresql.adapter.d.ts +7 -0
  10. package/dist/adapters/postgresql.adapter.js +46 -19
  11. package/dist/adapters/postgresql.adapter.js.map +1 -1
  12. package/dist/adapters/sqlite.adapter.d.ts +4 -0
  13. package/dist/adapters/sqlite.adapter.js +10 -0
  14. package/dist/adapters/sqlite.adapter.js.map +1 -1
  15. package/dist/api.d.ts +13 -3
  16. package/dist/api.js +96 -62
  17. package/dist/api.js.map +1 -1
  18. package/dist/builders.d.ts +176 -0
  19. package/dist/builders.js +727 -0
  20. package/dist/builders.js.map +1 -0
  21. package/dist/config.d.ts +16 -1
  22. package/dist/config.js +95 -3
  23. package/dist/config.js.map +1 -1
  24. package/dist/contracts/auctionhouse.contract.d.ts +4 -0
  25. package/dist/contracts/auctionhouse.contract.js +234 -0
  26. package/dist/contracts/auctionhouse.contract.js.map +1 -0
  27. package/dist/contracts/booking.contract.d.ts +4 -0
  28. package/dist/contracts/booking.contract.js +225 -0
  29. package/dist/contracts/booking.contract.js.map +1 -0
  30. package/dist/contracts/bountyboard.contract.d.ts +4 -0
  31. package/dist/contracts/bountyboard.contract.js +233 -0
  32. package/dist/contracts/bountyboard.contract.js.map +1 -0
  33. package/dist/contracts/bundlemarketplace.contract.d.ts +4 -0
  34. package/dist/contracts/bundlemarketplace.contract.js +195 -0
  35. package/dist/contracts/bundlemarketplace.contract.js.map +1 -0
  36. package/dist/contracts/charitymatch.contract.d.ts +4 -0
  37. package/dist/contracts/charitymatch.contract.js +172 -0
  38. package/dist/contracts/charitymatch.contract.js.map +1 -0
  39. package/dist/contracts/coinflip.contract.js +25 -22
  40. package/dist/contracts/coinflip.contract.js.map +1 -1
  41. package/dist/contracts/crowdfund.contract.d.ts +4 -0
  42. package/dist/contracts/crowdfund.contract.js +290 -0
  43. package/dist/contracts/crowdfund.contract.js.map +1 -0
  44. package/dist/contracts/dcabot.contract.d.ts +4 -0
  45. package/dist/contracts/dcabot.contract.js +217 -0
  46. package/dist/contracts/dcabot.contract.js.map +1 -0
  47. package/dist/contracts/dice.contract.js +25 -22
  48. package/dist/contracts/dice.contract.js.map +1 -1
  49. package/dist/contracts/domainregistry.contract.d.ts +4 -0
  50. package/dist/contracts/domainregistry.contract.js +232 -0
  51. package/dist/contracts/domainregistry.contract.js.map +1 -0
  52. package/dist/contracts/exchange.contract.js +209 -168
  53. package/dist/contracts/exchange.contract.js.map +1 -1
  54. package/dist/contracts/fanclub.contract.d.ts +4 -0
  55. package/dist/contracts/fanclub.contract.js +193 -0
  56. package/dist/contracts/fanclub.contract.js.map +1 -0
  57. package/dist/contracts/giftcard.contract.d.ts +4 -0
  58. package/dist/contracts/giftcard.contract.js +158 -0
  59. package/dist/contracts/giftcard.contract.js.map +1 -0
  60. package/dist/contracts/grantrounds.contract.d.ts +4 -0
  61. package/dist/contracts/grantrounds.contract.js +265 -0
  62. package/dist/contracts/grantrounds.contract.js.map +1 -0
  63. package/dist/contracts/groupbuy.contract.d.ts +4 -0
  64. package/dist/contracts/groupbuy.contract.js +198 -0
  65. package/dist/contracts/groupbuy.contract.js.map +1 -0
  66. package/dist/contracts/helpers.d.ts +66 -0
  67. package/dist/contracts/helpers.js +166 -0
  68. package/dist/contracts/helpers.js.map +1 -0
  69. package/dist/contracts/insurancepool.contract.d.ts +4 -0
  70. package/dist/contracts/insurancepool.contract.js +281 -0
  71. package/dist/contracts/insurancepool.contract.js.map +1 -0
  72. package/dist/contracts/invoice.contract.d.ts +4 -0
  73. package/dist/contracts/invoice.contract.js +193 -0
  74. package/dist/contracts/invoice.contract.js.map +1 -0
  75. package/dist/contracts/launchpad.contract.d.ts +4 -0
  76. package/dist/contracts/launchpad.contract.js +225 -0
  77. package/dist/contracts/launchpad.contract.js.map +1 -0
  78. package/dist/contracts/lotto.contract.js +53 -37
  79. package/dist/contracts/lotto.contract.js.map +1 -1
  80. package/dist/contracts/multisigtreasury.contract.d.ts +4 -0
  81. package/dist/contracts/multisigtreasury.contract.js +245 -0
  82. package/dist/contracts/multisigtreasury.contract.js.map +1 -0
  83. package/dist/contracts/nft.contract.d.ts +1 -0
  84. package/dist/contracts/nft.contract.js +234 -195
  85. package/dist/contracts/nft.contract.js.map +1 -1
  86. package/dist/contracts/oraclebounty.contract.d.ts +4 -0
  87. package/dist/contracts/oraclebounty.contract.js +250 -0
  88. package/dist/contracts/oraclebounty.contract.js.map +1 -0
  89. package/dist/contracts/payroll.contract.d.ts +4 -0
  90. package/dist/contracts/payroll.contract.js +232 -0
  91. package/dist/contracts/payroll.contract.js.map +1 -0
  92. package/dist/contracts/paywall.contract.d.ts +4 -0
  93. package/dist/contracts/paywall.contract.js +185 -0
  94. package/dist/contracts/paywall.contract.js.map +1 -0
  95. package/dist/contracts/poll.contract.js +2 -0
  96. package/dist/contracts/poll.contract.js.map +1 -1
  97. package/dist/contracts/predictionmarket.contract.d.ts +4 -0
  98. package/dist/contracts/predictionmarket.contract.js +213 -0
  99. package/dist/contracts/predictionmarket.contract.js.map +1 -0
  100. package/dist/contracts/proposaltimelock.contract.d.ts +4 -0
  101. package/dist/contracts/proposaltimelock.contract.js +250 -0
  102. package/dist/contracts/proposaltimelock.contract.js.map +1 -0
  103. package/dist/contracts/questpass.contract.d.ts +4 -0
  104. package/dist/contracts/questpass.contract.js +214 -0
  105. package/dist/contracts/questpass.contract.js.map +1 -0
  106. package/dist/contracts/referral.contract.d.ts +4 -0
  107. package/dist/contracts/referral.contract.js +238 -0
  108. package/dist/contracts/referral.contract.js.map +1 -0
  109. package/dist/contracts/rental.contract.d.ts +4 -0
  110. package/dist/contracts/rental.contract.js +221 -0
  111. package/dist/contracts/rental.contract.js.map +1 -0
  112. package/dist/contracts/revenuesplit.contract.d.ts +4 -0
  113. package/dist/contracts/revenuesplit.contract.js +211 -0
  114. package/dist/contracts/revenuesplit.contract.js.map +1 -0
  115. package/dist/contracts/rps.contract.js +48 -20
  116. package/dist/contracts/rps.contract.js.map +1 -1
  117. package/dist/contracts/savings.contract.d.ts +4 -0
  118. package/dist/contracts/savings.contract.js +208 -0
  119. package/dist/contracts/savings.contract.js.map +1 -0
  120. package/dist/contracts/subscription.contract.d.ts +4 -0
  121. package/dist/contracts/subscription.contract.js +241 -0
  122. package/dist/contracts/subscription.contract.js.map +1 -0
  123. package/dist/contracts/sweepstakes.contract.d.ts +4 -0
  124. package/dist/contracts/sweepstakes.contract.js +209 -0
  125. package/dist/contracts/sweepstakes.contract.js.map +1 -0
  126. package/dist/contracts/ticketing.contract.d.ts +4 -0
  127. package/dist/contracts/ticketing.contract.js +185 -0
  128. package/dist/contracts/ticketing.contract.js.map +1 -0
  129. package/dist/contracts/tipjar.contract.js +2 -0
  130. package/dist/contracts/tipjar.contract.js.map +1 -1
  131. package/dist/contracts/token.contract.js +135 -125
  132. package/dist/contracts/token.contract.js.map +1 -1
  133. package/dist/index.d.ts +40 -0
  134. package/dist/index.js +72 -1
  135. package/dist/index.js.map +1 -1
  136. package/dist/metadata.d.ts +20 -0
  137. package/dist/metadata.js +320 -1
  138. package/dist/metadata.js.map +1 -1
  139. package/dist/providers/block-provider.d.ts +22 -0
  140. package/dist/providers/block-provider.js +3 -0
  141. package/dist/providers/block-provider.js.map +1 -0
  142. package/dist/providers/haf-client.d.ts +30 -0
  143. package/dist/providers/haf-client.js +119 -0
  144. package/dist/providers/haf-client.js.map +1 -0
  145. package/dist/providers/haf-provider.d.ts +49 -0
  146. package/dist/providers/haf-provider.js +256 -0
  147. package/dist/providers/haf-provider.js.map +1 -0
  148. package/dist/providers/hive-provider.d.ts +13 -0
  149. package/dist/providers/hive-provider.js +25 -0
  150. package/dist/providers/hive-provider.js.map +1 -0
  151. package/dist/providers/index.d.ts +4 -0
  152. package/dist/providers/index.js +21 -0
  153. package/dist/providers/index.js.map +1 -0
  154. package/dist/streamer.d.ts +65 -4
  155. package/dist/streamer.js +768 -72
  156. package/dist/streamer.js.map +1 -1
  157. package/dist/types/hive-stream.d.ts +317 -0
  158. package/dist/utils.d.ts +33 -0
  159. package/dist/utils.js +198 -2
  160. package/dist/utils.js.map +1 -1
  161. package/package.json +16 -1
  162. package/.claude/settings.local.json +0 -12
  163. package/.env.example +0 -3
  164. package/.travis.yml +0 -11
  165. package/AGENTS.md +0 -35
  166. package/CLAUDE.md +0 -75
  167. package/ecosystem.config.js +0 -17
  168. package/examples/contracts/README.md +0 -8
  169. package/examples/contracts/exchange.ts +0 -38
  170. package/examples/contracts/poll.ts +0 -21
  171. package/examples/contracts/rps.ts +0 -19
  172. package/examples/contracts/tipjar.ts +0 -19
  173. package/jest.config.js +0 -9
  174. package/test-contract-block.md +0 -19
  175. package/tests/actions.spec.ts +0 -252
  176. package/tests/adapters/actions-persistence.spec.ts +0 -144
  177. package/tests/adapters/postgresql.adapter.spec.ts +0 -127
  178. package/tests/adapters/sqlite.adapter.spec.ts +0 -181
  179. package/tests/config-input.spec.ts +0 -90
  180. package/tests/contracts/coinflip.contract.spec.ts +0 -94
  181. package/tests/contracts/dice.contract.spec.ts +0 -87
  182. package/tests/contracts/entrants.json +0 -729
  183. package/tests/contracts/exchange.contract.spec.ts +0 -84
  184. package/tests/contracts/lotto.contract.spec.ts +0 -59
  185. package/tests/contracts/nft.contract.spec.ts +0 -948
  186. package/tests/contracts/token.contract.spec.ts +0 -90
  187. package/tests/exchanges/coingecko.exchange.spec.ts +0 -169
  188. package/tests/exchanges/exchange.base.spec.ts +0 -246
  189. package/tests/helpers/mock-adapter.ts +0 -214
  190. package/tests/helpers/mock-fetch.ts +0 -165
  191. package/tests/hive-chain-features.spec.ts +0 -319
  192. package/tests/hive-rates.spec.ts +0 -443
  193. package/tests/integration/hive-rates.integration.spec.ts +0 -35
  194. package/tests/metadata.spec.ts +0 -63
  195. package/tests/setup.ts +0 -30
  196. package/tests/streamer-actions.spec.ts +0 -274
  197. package/tests/streamer.spec.ts +0 -342
  198. package/tests/types/rates.spec.ts +0 -216
  199. package/tests/utils.spec.ts +0 -113
  200. package/tsconfig.build.json +0 -4
  201. package/tslint.json +0 -21
  202. package/wallaby.js +0 -26
@@ -1,948 +0,0 @@
1
- import { NFTContract } from '../../src/contracts/nft.contract';
2
- import { MockAdapter } from '../helpers/mock-adapter';
3
-
4
- describe('NFTContract', () => {
5
- let nftContract: NFTContract;
6
- let mockAdapter: MockAdapter;
7
- let mockStreamer: any;
8
-
9
- beforeEach(() => {
10
- mockAdapter = new MockAdapter();
11
- mockStreamer = {
12
- getAdapter: () => mockAdapter
13
- };
14
-
15
- nftContract = new NFTContract();
16
- nftContract._instance = mockStreamer;
17
- nftContract.updateBlockInfo(12345, 'block123', 'prevblock123', 'txn123');
18
- });
19
-
20
- describe('Contract Lifecycle', () => {
21
- it('should initialize NFT tables on create', async () => {
22
- mockAdapter.reset();
23
- await nftContract.create();
24
-
25
- // Wait for async table creation to complete
26
- await new Promise(resolve => setTimeout(resolve, 10));
27
-
28
- const createTableQueries = mockAdapter.queries.filter(q => q.includes('CREATE TABLE'));
29
- expect(createTableQueries.length).toBeGreaterThanOrEqual(4);
30
- expect(createTableQueries.some(q => q.includes('nft_collections'))).toBe(true);
31
- expect(createTableQueries.some(q => q.includes('nft_tokens'))).toBe(true);
32
- expect(createTableQueries.some(q => q.includes('nft_listings'))).toBe(true);
33
- expect(createTableQueries.some(q => q.includes('nft_transfers'))).toBe(true);
34
- });
35
-
36
- it('should handle destroy method', () => {
37
- expect(() => nftContract.destroy()).not.toThrow();
38
- });
39
-
40
- it('should update block information', () => {
41
- nftContract.updateBlockInfo(54321, 'newblock', 'prevnewblock', 'newtxn');
42
- expect((nftContract as any).blockNumber).toBe(54321);
43
- expect((nftContract as any).blockId).toBe('newblock');
44
- });
45
- });
46
-
47
- describe('createCollection', () => {
48
- beforeEach(() => {
49
- mockAdapter.reset();
50
- nftContract.create();
51
- });
52
-
53
- it('should create a new collection successfully', async () => {
54
- mockAdapter.setQueryResult([]); // No existing collection with same symbol
55
-
56
- const payload = {
57
- symbol: 'TESTNFT',
58
- name: 'Test NFT Collection',
59
- description: 'A test NFT collection',
60
- maxSupply: 1000,
61
- royalty: 0.05,
62
- allowUpdates: true,
63
- updateableByOwner: false
64
- };
65
-
66
- await (nftContract as any).createCollection(payload, { sender: 'alice' });
67
-
68
- const insertQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_collections'));
69
- expect(insertQuery).toBeDefined();
70
- expect(mockAdapter.events.length).toBe(1);
71
- expect(mockAdapter.events[0].action).toBe('createCollection');
72
- expect(mockAdapter.events[0].data.action).toBe('collection_created');
73
- });
74
-
75
- it('should reject invalid collection symbol', async () => {
76
- const payload = {
77
- symbol: 'invalid-symbol-too-long',
78
- name: 'Test Collection',
79
- maxSupply: 1000
80
- };
81
-
82
- await expect((nftContract as any).createCollection(payload, { sender: 'alice' }))
83
- .rejects.toThrow('Symbol must be 1-20 uppercase alphanumeric characters');
84
- });
85
-
86
- it('should reject empty collection name', async () => {
87
- const payload = {
88
- symbol: 'TESTNFT',
89
- name: '',
90
- maxSupply: 1000
91
- };
92
-
93
- await expect((nftContract as any).createCollection(payload, { sender: 'alice' }))
94
- .rejects.toThrow('Name is required and must be 100 characters or less');
95
- });
96
-
97
- it('should reject excessive royalty', async () => {
98
- const payload = {
99
- symbol: 'TESTNFT',
100
- name: 'Test Collection',
101
- royalty: 0.3
102
- };
103
-
104
- await expect((nftContract as any).createCollection(payload, { sender: 'alice' }))
105
- .rejects.toThrow('Royalty must be between 0 and 25%');
106
- });
107
-
108
- it('should reject duplicate collection symbol', async () => {
109
- mockAdapter.setQueryResult([{ symbol: 'TESTNFT' }]); // Existing collection
110
-
111
- const payload = {
112
- symbol: 'TESTNFT',
113
- name: 'Test Collection',
114
- maxSupply: 1000
115
- };
116
-
117
- await expect((nftContract as any).createCollection(payload, { sender: 'alice' }))
118
- .rejects.toThrow('Collection with symbol TESTNFT already exists');
119
- });
120
- });
121
-
122
- describe('mintNFT', () => {
123
- beforeEach(() => {
124
- mockAdapter.reset();
125
- nftContract.create();
126
- });
127
-
128
- it('should mint NFT successfully', async () => {
129
- // Mock collection exists and creator matches sender
130
- mockAdapter.setQueryResults([
131
- [{ symbol: 'TESTNFT', creator: 'alice', max_supply: 1000, current_supply: 0 }],
132
- [] // No existing token with same ID
133
- ]);
134
-
135
- const payload = {
136
- collectionSymbol: 'TESTNFT',
137
- tokenId: 'token001',
138
- to: 'bob',
139
- metadata: '{"name": "Test NFT", "image": "https://example.com/nft.jpg"}',
140
- attributes: '{"rarity": "common", "power": 10}'
141
- };
142
-
143
- await (nftContract as any).mintNFT(payload, { sender: 'alice' });
144
-
145
- const insertTokenQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_tokens'));
146
- const updateSupplyQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_collections SET current_supply'));
147
- const insertTransferQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_transfers'));
148
-
149
- expect(insertTokenQuery).toBeDefined();
150
- expect(updateSupplyQuery).toBeDefined();
151
- expect(insertTransferQuery).toBeDefined();
152
- expect(mockAdapter.events.length).toBe(1);
153
- expect(mockAdapter.events[0].action).toBe('mintNFT');
154
- expect(mockAdapter.events[0].data.action).toBe('nft_minted');
155
- });
156
-
157
- it('should reject minting for non-existent collection', async () => {
158
- mockAdapter.setQueryResult([]); // No collection found
159
-
160
- const payload = {
161
- collectionSymbol: 'NONEXISTENT',
162
- tokenId: 'token001',
163
- to: 'bob'
164
- };
165
-
166
- await expect((nftContract as any).mintNFT(payload, { sender: 'alice' }))
167
- .rejects.toThrow('Collection NONEXISTENT does not exist');
168
- });
169
-
170
- it('should reject minting by non-creator', async () => {
171
- mockAdapter.setQueryResult([{ symbol: 'TESTNFT', creator: 'alice', max_supply: 1000, current_supply: 0 }]);
172
-
173
- const payload = {
174
- collectionSymbol: 'TESTNFT',
175
- tokenId: 'token001',
176
- to: 'bob'
177
- };
178
-
179
- await expect((nftContract as any).mintNFT(payload, { sender: 'charlie' }))
180
- .rejects.toThrow('Only the collection creator can mint NFTs');
181
- });
182
-
183
- it('should reject minting when max supply reached', async () => {
184
- mockAdapter.setQueryResult([{ symbol: 'TESTNFT', creator: 'alice', max_supply: 1000, current_supply: 1000 }]);
185
-
186
- const payload = {
187
- collectionSymbol: 'TESTNFT',
188
- tokenId: 'token001',
189
- to: 'bob'
190
- };
191
-
192
- await expect((nftContract as any).mintNFT(payload, { sender: 'alice' }))
193
- .rejects.toThrow('Collection has reached maximum supply');
194
- });
195
-
196
- it('should reject duplicate token ID', async () => {
197
- mockAdapter.setQueryResults([
198
- [{ symbol: 'TESTNFT', creator: 'alice', max_supply: 1000, current_supply: 0 }],
199
- [{ token_id: 'token001' }] // Existing token
200
- ]);
201
-
202
- const payload = {
203
- collectionSymbol: 'TESTNFT',
204
- tokenId: 'token001',
205
- to: 'bob'
206
- };
207
-
208
- await expect((nftContract as any).mintNFT(payload, { sender: 'alice' }))
209
- .rejects.toThrow('Token token001 already exists in collection TESTNFT');
210
- });
211
- });
212
-
213
- describe('transferNFT', () => {
214
- beforeEach(() => {
215
- mockAdapter.reset();
216
- nftContract.create();
217
- });
218
-
219
- it('should transfer NFT successfully', async () => {
220
- mockAdapter.setQueryResults([
221
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }],
222
- [] // No active listings
223
- ]);
224
-
225
- const payload = {
226
- collectionSymbol: 'TESTNFT',
227
- tokenId: 'token001',
228
- to: 'bob'
229
- };
230
-
231
- await (nftContract as any).transferNFT(payload, { sender: 'alice' });
232
-
233
- const updateOwnerQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET owner'));
234
- const insertTransferQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_transfers'));
235
-
236
- expect(updateOwnerQuery).toBeDefined();
237
- expect(insertTransferQuery).toBeDefined();
238
- expect(mockAdapter.events.length).toBe(1);
239
- expect(mockAdapter.events[0].action).toBe('transferNFT');
240
- expect(mockAdapter.events[0].data.action).toBe('nft_transferred');
241
- });
242
-
243
- it('should reject transfer of non-existent token', async () => {
244
- mockAdapter.setQueryResult([]); // No token found
245
-
246
- const payload = {
247
- collectionSymbol: 'TESTNFT',
248
- tokenId: 'nonexistent',
249
- to: 'bob'
250
- };
251
-
252
- await expect((nftContract as any).transferNFT(payload, { sender: 'alice' }))
253
- .rejects.toThrow('Token nonexistent does not exist or has been burned');
254
- });
255
-
256
- it('should reject transfer by non-owner', async () => {
257
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
258
-
259
- const payload = {
260
- collectionSymbol: 'TESTNFT',
261
- tokenId: 'token001',
262
- to: 'bob'
263
- };
264
-
265
- await expect((nftContract as any).transferNFT(payload, { sender: 'charlie' }))
266
- .rejects.toThrow('Only the token owner can transfer the NFT');
267
- });
268
-
269
- it('should reject transfer to same address', async () => {
270
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
271
-
272
- const payload = {
273
- collectionSymbol: 'TESTNFT',
274
- tokenId: 'token001',
275
- to: 'alice'
276
- };
277
-
278
- await expect((nftContract as any).transferNFT(payload, { sender: 'alice' }))
279
- .rejects.toThrow('Cannot transfer to the same address');
280
- });
281
-
282
- it('should cancel active listings when transferring', async () => {
283
- mockAdapter.setQueryResults([
284
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }],
285
- [{ id: 1 }] // Active listing exists
286
- ]);
287
-
288
- const payload = {
289
- collectionSymbol: 'TESTNFT',
290
- tokenId: 'token001',
291
- to: 'bob'
292
- };
293
-
294
- await (nftContract as any).transferNFT(payload, { sender: 'alice' });
295
-
296
- const cancelListingQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_listings SET active = FALSE'));
297
- expect(cancelListingQuery).toBeDefined();
298
- });
299
- });
300
-
301
- describe('updateNFT', () => {
302
- beforeEach(() => {
303
- mockAdapter.reset();
304
- nftContract.create();
305
- });
306
-
307
- it('should update NFT metadata successfully by collection creator', async () => {
308
- mockAdapter.setQueryResults([
309
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
310
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
311
- ]);
312
-
313
- const payload = {
314
- collectionSymbol: 'TESTNFT',
315
- tokenId: 'token001',
316
- metadata: '{"name": "Updated NFT", "image": "https://example.com/updated.jpg"}',
317
- attributes: '{"rarity": "rare", "power": 20}'
318
- };
319
-
320
- await (nftContract as any).updateNFT(payload, { sender: 'alice' });
321
-
322
- const updateQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET'));
323
- expect(updateQuery).toBeDefined();
324
- expect(mockAdapter.events.length).toBe(1);
325
- expect(mockAdapter.events[0].action).toBe('updateNFT');
326
- expect(mockAdapter.events[0].data.action).toBe('nft_updated');
327
- });
328
-
329
- it('should update NFT successfully by token owner when allowed', async () => {
330
- mockAdapter.setQueryResults([
331
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
332
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: true }]
333
- ]);
334
-
335
- const payload = {
336
- collectionSymbol: 'TESTNFT',
337
- tokenId: 'token001',
338
- metadata: '{"name": "Owner Updated NFT"}'
339
- };
340
-
341
- await (nftContract as any).updateNFT(payload, { sender: 'bob' });
342
-
343
- const updateQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET'));
344
- expect(updateQuery).toBeDefined();
345
- expect(mockAdapter.events.length).toBe(1);
346
- expect(mockAdapter.events[0].data.data.updatedBy).toBe('bob');
347
- });
348
-
349
- it('should update only metadata when attributes not provided', async () => {
350
- mockAdapter.setQueryResults([
351
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
352
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
353
- ]);
354
-
355
- const payload = {
356
- collectionSymbol: 'TESTNFT',
357
- tokenId: 'token001',
358
- metadata: '{"name": "Only Metadata Update"}'
359
- };
360
-
361
- await (nftContract as any).updateNFT(payload, { sender: 'alice' });
362
-
363
- const updateQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET metadata = ?'));
364
- expect(updateQuery).toBeDefined();
365
- expect(mockAdapter.events[0].data.data.attributes).toBe('unchanged');
366
- });
367
-
368
- it('should update only attributes when metadata not provided', async () => {
369
- mockAdapter.setQueryResults([
370
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
371
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
372
- ]);
373
-
374
- const payload = {
375
- collectionSymbol: 'TESTNFT',
376
- tokenId: 'token001',
377
- attributes: '{"power": 50}'
378
- };
379
-
380
- await (nftContract as any).updateNFT(payload, { sender: 'alice' });
381
-
382
- const updateQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET attributes = ?'));
383
- expect(updateQuery).toBeDefined();
384
- expect(mockAdapter.events[0].data.data.metadata).toBe('unchanged');
385
- });
386
-
387
- it('should reject updating non-existent token', async () => {
388
- mockAdapter.setQueryResult([]); // No token found
389
-
390
- const payload = {
391
- collectionSymbol: 'TESTNFT',
392
- tokenId: 'nonexistent',
393
- metadata: '{"name": "Test"}'
394
- };
395
-
396
- await expect((nftContract as any).updateNFT(payload, { sender: 'alice' }))
397
- .rejects.toThrow('Token nonexistent does not exist or has been burned');
398
- });
399
-
400
- it('should reject updating when collection does not exist', async () => {
401
- mockAdapter.setQueryResults([
402
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
403
- [] // No collection found
404
- ]);
405
-
406
- const payload = {
407
- collectionSymbol: 'TESTNFT',
408
- tokenId: 'token001',
409
- metadata: '{"name": "Test"}'
410
- };
411
-
412
- await expect((nftContract as any).updateNFT(payload, { sender: 'alice' }))
413
- .rejects.toThrow('Collection TESTNFT does not exist');
414
- });
415
-
416
- it('should reject updating when updates are disabled for collection', async () => {
417
- mockAdapter.setQueryResults([
418
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
419
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: false, updateable_by_owner: false }]
420
- ]);
421
-
422
- const payload = {
423
- collectionSymbol: 'TESTNFT',
424
- tokenId: 'token001',
425
- metadata: '{"name": "Test"}'
426
- };
427
-
428
- await expect((nftContract as any).updateNFT(payload, { sender: 'alice' }))
429
- .rejects.toThrow('Updates are not allowed for this collection');
430
- });
431
-
432
- it('should reject updating by token owner when not allowed', async () => {
433
- mockAdapter.setQueryResults([
434
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
435
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
436
- ]);
437
-
438
- const payload = {
439
- collectionSymbol: 'TESTNFT',
440
- tokenId: 'token001',
441
- metadata: '{"name": "Test"}'
442
- };
443
-
444
- await expect((nftContract as any).updateNFT(payload, { sender: 'bob' }))
445
- .rejects.toThrow('Only the collection creator or token owner (if allowed) can update the NFT');
446
- });
447
-
448
- it('should reject updating by unauthorized user', async () => {
449
- mockAdapter.setQueryResults([
450
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
451
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: true }]
452
- ]);
453
-
454
- const payload = {
455
- collectionSymbol: 'TESTNFT',
456
- tokenId: 'token001',
457
- metadata: '{"name": "Test"}'
458
- };
459
-
460
- await expect((nftContract as any).updateNFT(payload, { sender: 'charlie' }))
461
- .rejects.toThrow('Only the collection creator or token owner (if allowed) can update the NFT');
462
- });
463
-
464
- it('should reject metadata that is too long', async () => {
465
- mockAdapter.setQueryResults([
466
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
467
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
468
- ]);
469
-
470
- const longMetadata = 'x'.repeat(2001);
471
- const payload = {
472
- collectionSymbol: 'TESTNFT',
473
- tokenId: 'token001',
474
- metadata: longMetadata
475
- };
476
-
477
- await expect((nftContract as any).updateNFT(payload, { sender: 'alice' }))
478
- .rejects.toThrow('Metadata must be 2000 characters or less');
479
- });
480
-
481
- it('should reject attributes that are too long', async () => {
482
- mockAdapter.setQueryResults([
483
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
484
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
485
- ]);
486
-
487
- const longAttributes = 'x'.repeat(1001);
488
- const payload = {
489
- collectionSymbol: 'TESTNFT',
490
- tokenId: 'token001',
491
- attributes: longAttributes
492
- };
493
-
494
- await expect((nftContract as any).updateNFT(payload, { sender: 'alice' }))
495
- .rejects.toThrow('Attributes must be 1000 characters or less');
496
- });
497
-
498
- it('should reject update with no fields provided', async () => {
499
- mockAdapter.setQueryResults([
500
- [{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'bob', burned: false }],
501
- [{ symbol: 'TESTNFT', creator: 'alice', allow_updates: true, updateable_by_owner: false }]
502
- ]);
503
-
504
- const payload = {
505
- collectionSymbol: 'TESTNFT',
506
- tokenId: 'token001'
507
- };
508
-
509
- await expect((nftContract as any).updateNFT(payload, { sender: 'alice' }))
510
- .rejects.toThrow('No update fields provided');
511
- });
512
- });
513
-
514
- describe('burnNFT', () => {
515
- beforeEach(() => {
516
- mockAdapter.reset();
517
- nftContract.create();
518
- });
519
-
520
- it('should burn NFT successfully', async () => {
521
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
522
-
523
- const payload = {
524
- collectionSymbol: 'TESTNFT',
525
- tokenId: 'token001'
526
- };
527
-
528
- await (nftContract as any).burnNFT(payload, { sender: 'alice' });
529
-
530
- const cancelListingsQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_listings SET active = FALSE'));
531
- const burnTokenQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET burned = TRUE'));
532
- const updateSupplyQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_collections SET current_supply = current_supply - 1'));
533
- const insertTransferQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_transfers'));
534
-
535
- expect(cancelListingsQuery).toBeDefined();
536
- expect(burnTokenQuery).toBeDefined();
537
- expect(updateSupplyQuery).toBeDefined();
538
- expect(insertTransferQuery).toBeDefined();
539
- expect(mockAdapter.events.length).toBe(1);
540
- expect(mockAdapter.events[0].action).toBe('burnNFT');
541
- expect(mockAdapter.events[0].data.action).toBe('nft_burned');
542
- });
543
-
544
- it('should reject burning non-existent token', async () => {
545
- mockAdapter.setQueryResult([]); // No token found
546
-
547
- const payload = {
548
- collectionSymbol: 'TESTNFT',
549
- tokenId: 'nonexistent'
550
- };
551
-
552
- await expect((nftContract as any).burnNFT(payload, { sender: 'alice' }))
553
- .rejects.toThrow('Token nonexistent does not exist or has already been burned');
554
- });
555
-
556
- it('should reject burning by non-owner', async () => {
557
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
558
-
559
- const payload = {
560
- collectionSymbol: 'TESTNFT',
561
- tokenId: 'token001'
562
- };
563
-
564
- await expect((nftContract as any).burnNFT(payload, { sender: 'charlie' }))
565
- .rejects.toThrow('Only the token owner can burn the NFT');
566
- });
567
- });
568
-
569
- describe('listNFT', () => {
570
- beforeEach(() => {
571
- mockAdapter.reset();
572
- nftContract.create();
573
- });
574
-
575
- it('should list NFT successfully', async () => {
576
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
577
-
578
- const payload = {
579
- collectionSymbol: 'TESTNFT',
580
- tokenId: 'token001',
581
- price: '10.000',
582
- currency: 'HIVE'
583
- };
584
-
585
- await (nftContract as any).listNFT(payload, { sender: 'alice' });
586
-
587
- const cancelPreviousListingsQuery = mockAdapter.queries.find(q =>
588
- q.includes('UPDATE nft_listings SET active = FALSE') &&
589
- q.includes('seller = ?')
590
- );
591
- const insertListingQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_listings'));
592
-
593
- expect(cancelPreviousListingsQuery).toBeDefined();
594
- expect(insertListingQuery).toBeDefined();
595
- expect(mockAdapter.events.length).toBe(1);
596
- expect(mockAdapter.events[0].action).toBe('listNFT');
597
- expect(mockAdapter.events[0].data.action).toBe('nft_listed');
598
- });
599
-
600
- it('should reject listing non-existent token', async () => {
601
- mockAdapter.setQueryResult([]); // No token found
602
-
603
- const payload = {
604
- collectionSymbol: 'TESTNFT',
605
- tokenId: 'nonexistent',
606
- price: '10.000'
607
- };
608
-
609
- await expect((nftContract as any).listNFT(payload, { sender: 'alice' }))
610
- .rejects.toThrow('Token nonexistent does not exist or has been burned');
611
- });
612
-
613
- it('should reject listing by non-owner', async () => {
614
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
615
-
616
- const payload = {
617
- collectionSymbol: 'TESTNFT',
618
- tokenId: 'token001',
619
- price: '10.000'
620
- };
621
-
622
- await expect((nftContract as any).listNFT(payload, { sender: 'charlie' }))
623
- .rejects.toThrow('Only the token owner can list the NFT');
624
- });
625
-
626
- it('should reject invalid price', async () => {
627
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
628
-
629
- const payload = {
630
- collectionSymbol: 'TESTNFT',
631
- tokenId: 'token001',
632
- price: '0'
633
- };
634
-
635
- await expect((nftContract as any).listNFT(payload, { sender: 'alice' }))
636
- .rejects.toThrow('Price must be a positive number');
637
- });
638
-
639
- it('should reject invalid currency', async () => {
640
- mockAdapter.setQueryResult([{ token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice', burned: false }]);
641
-
642
- const payload = {
643
- collectionSymbol: 'TESTNFT',
644
- tokenId: 'token001',
645
- price: '10.000',
646
- currency: 'INVALID'
647
- };
648
-
649
- await expect((nftContract as any).listNFT(payload, { sender: 'alice' }))
650
- .rejects.toThrow('Currency must be HIVE or HBD');
651
- });
652
- });
653
-
654
- describe('unlistNFT', () => {
655
- beforeEach(() => {
656
- mockAdapter.reset();
657
- nftContract.create();
658
- });
659
-
660
- it('should unlist NFT successfully', async () => {
661
- mockAdapter.setQueryResult([{ id: 1, token_id: 'token001', collection_symbol: 'TESTNFT', seller: 'alice', active: true }]);
662
-
663
- const payload = {
664
- collectionSymbol: 'TESTNFT',
665
- tokenId: 'token001'
666
- };
667
-
668
- await (nftContract as any).unlistNFT(payload, { sender: 'alice' });
669
-
670
- const updateListingQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_listings SET active = FALSE'));
671
- expect(updateListingQuery).toBeDefined();
672
- expect(mockAdapter.events.length).toBe(1);
673
- expect(mockAdapter.events[0].action).toBe('unlistNFT');
674
- expect(mockAdapter.events[0].data.action).toBe('nft_unlisted');
675
- });
676
-
677
- it('should reject unlisting non-existent listing', async () => {
678
- mockAdapter.setQueryResult([]); // No active listing found
679
-
680
- const payload = {
681
- collectionSymbol: 'TESTNFT',
682
- tokenId: 'token001'
683
- };
684
-
685
- await expect((nftContract as any).unlistNFT(payload, { sender: 'alice' }))
686
- .rejects.toThrow('No active listing found for token token001');
687
- });
688
- });
689
-
690
- describe('buyNFT', () => {
691
- beforeEach(() => {
692
- mockAdapter.reset();
693
- nftContract.create();
694
- });
695
-
696
- it('should buy NFT successfully without royalty', async () => {
697
- mockAdapter.setQueryResults([
698
- [{ id: 1, token_id: 'token001', collection_symbol: 'TESTNFT', seller: 'alice', price: '10.000', currency: 'HIVE', active: true }],
699
- [{ royalty: 0, creator: 'alice' }] // Collection with no royalty
700
- ]);
701
-
702
- const payload = {
703
- collectionSymbol: 'TESTNFT',
704
- tokenId: 'token001'
705
- };
706
-
707
- await (nftContract as any).buyNFT(payload, { sender: 'bob', amount: '10.000', asset: 'HIVE' });
708
-
709
- const updateOwnerQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_tokens SET owner'));
710
- const deactivateListingQuery = mockAdapter.queries.find(q => q.includes('UPDATE nft_listings SET active = FALSE'));
711
- const insertTransferQuery = mockAdapter.queries.find(q => q.includes('INSERT INTO nft_transfers'));
712
-
713
- expect(updateOwnerQuery).toBeDefined();
714
- expect(deactivateListingQuery).toBeDefined();
715
- expect(insertTransferQuery).toBeDefined();
716
- expect(mockAdapter.events.length).toBe(1);
717
- expect(mockAdapter.events[0].action).toBe('buyNFT');
718
- expect(mockAdapter.events[0].data.action).toBe('nft_sold');
719
- });
720
-
721
- it('should buy NFT successfully with royalty', async () => {
722
- mockAdapter.setQueryResults([
723
- [{ id: 1, token_id: 'token001', collection_symbol: 'TESTNFT', seller: 'alice', price: '10.000', currency: 'HIVE', active: true }],
724
- [{ royalty: 0.05, creator: 'charlie' }] // Collection with 5% royalty to different creator
725
- ]);
726
-
727
- const payload = {
728
- collectionSymbol: 'TESTNFT',
729
- tokenId: 'token001'
730
- };
731
-
732
- await (nftContract as any).buyNFT(payload, { sender: 'bob', amount: '10.000', asset: 'HIVE' });
733
-
734
- expect(mockAdapter.events[0].data.data.royaltyAmount).toBe('0.500');
735
- expect(mockAdapter.events[0].data.data.sellerAmount).toBe('9.500');
736
- });
737
-
738
- it('should reject buying non-existent listing', async () => {
739
- mockAdapter.setQueryResult([]); // No active listing found
740
-
741
- const payload = {
742
- collectionSymbol: 'TESTNFT',
743
- tokenId: 'token001'
744
- };
745
-
746
- await expect((nftContract as any).buyNFT(payload, { sender: 'bob', amount: '10.000', asset: 'HIVE' }))
747
- .rejects.toThrow('No active listing found for token token001');
748
- });
749
-
750
- it('should reject buying own NFT', async () => {
751
- mockAdapter.setQueryResult([{ id: 1, token_id: 'token001', collection_symbol: 'TESTNFT', seller: 'alice', price: '10.000', currency: 'HIVE', active: true }]);
752
-
753
- const payload = {
754
- collectionSymbol: 'TESTNFT',
755
- tokenId: 'token001'
756
- };
757
-
758
- await expect((nftContract as any).buyNFT(payload, { sender: 'alice', amount: '10.000', asset: 'HIVE' }))
759
- .rejects.toThrow('Cannot buy your own NFT');
760
- });
761
-
762
- it('should reject incorrect payment amount', async () => {
763
- mockAdapter.setQueryResult([{ id: 1, token_id: 'token001', collection_symbol: 'TESTNFT', seller: 'alice', price: '10.000', currency: 'HIVE', active: true }]);
764
-
765
- const payload = {
766
- collectionSymbol: 'TESTNFT',
767
- tokenId: 'token001'
768
- };
769
-
770
- await expect((nftContract as any).buyNFT(payload, { sender: 'bob', amount: '5.000', asset: 'HIVE' }))
771
- .rejects.toThrow('Incorrect payment amount. Required: 10.000 HIVE');
772
- });
773
-
774
- it('should reject incorrect currency', async () => {
775
- mockAdapter.setQueryResult([{ id: 1, token_id: 'token001', collection_symbol: 'TESTNFT', seller: 'alice', price: '10.000', currency: 'HIVE', active: true }]);
776
-
777
- const payload = {
778
- collectionSymbol: 'TESTNFT',
779
- tokenId: 'token001'
780
- };
781
-
782
- await expect((nftContract as any).buyNFT(payload, { sender: 'bob', amount: '10.000', asset: 'HBD' }))
783
- .rejects.toThrow('Incorrect currency. Required: HIVE');
784
- });
785
- });
786
-
787
- describe('Query Methods', () => {
788
- beforeEach(() => {
789
- mockAdapter.reset();
790
- nftContract.create();
791
- });
792
-
793
- it('should get token info successfully', async () => {
794
- const tokenData = {
795
- token_id: 'token001',
796
- collection_symbol: 'TESTNFT',
797
- owner: 'alice',
798
- metadata: '{"name": "Test NFT"}',
799
- burned: false
800
- };
801
- const listingData = {
802
- id: 1,
803
- price: '10.000',
804
- currency: 'HIVE',
805
- active: true
806
- };
807
-
808
- mockAdapter.setQueryResults([
809
- [tokenData],
810
- [listingData]
811
- ]);
812
-
813
- const payload = {
814
- collectionSymbol: 'TESTNFT',
815
- tokenId: 'token001'
816
- };
817
-
818
- await (nftContract as any).getTokenInfo(payload, { sender: 'bob' });
819
-
820
- expect(mockAdapter.events.length).toBe(1);
821
- expect(mockAdapter.events[0].action).toBe('getTokenInfo');
822
- expect(mockAdapter.events[0].data.action).toBe('token_info_query');
823
- expect(mockAdapter.events[0].data.data.token_info).toEqual(tokenData);
824
- expect(mockAdapter.events[0].data.data.listing_info).toEqual(listingData);
825
- });
826
-
827
- it('should reject getting info for non-existent token', async () => {
828
- mockAdapter.setQueryResult([]); // No token found
829
-
830
- const payload = {
831
- collectionSymbol: 'TESTNFT',
832
- tokenId: 'nonexistent'
833
- };
834
-
835
- await expect((nftContract as any).getTokenInfo(payload, { sender: 'bob' }))
836
- .rejects.toThrow('Token nonexistent does not exist in collection TESTNFT');
837
- });
838
-
839
- it('should get collection info successfully', async () => {
840
- const collectionData = {
841
- symbol: 'TESTNFT',
842
- name: 'Test Collection',
843
- creator: 'alice',
844
- max_supply: 1000,
845
- current_supply: 50
846
- };
847
-
848
- mockAdapter.setQueryResult([collectionData]);
849
-
850
- const payload = {
851
- symbol: 'TESTNFT'
852
- };
853
-
854
- await (nftContract as any).getCollectionInfo(payload, { sender: 'bob' });
855
-
856
- expect(mockAdapter.events.length).toBe(1);
857
- expect(mockAdapter.events[0].action).toBe('getCollectionInfo');
858
- expect(mockAdapter.events[0].data.action).toBe('collection_info_query');
859
- expect(mockAdapter.events[0].data.data.collection_info).toEqual(collectionData);
860
- });
861
-
862
- it('should reject getting info for non-existent collection', async () => {
863
- mockAdapter.setQueryResult([]); // No collection found
864
-
865
- const payload = {
866
- symbol: 'NONEXISTENT'
867
- };
868
-
869
- await expect((nftContract as any).getCollectionInfo(payload, { sender: 'bob' }))
870
- .rejects.toThrow('Collection NONEXISTENT does not exist');
871
- });
872
-
873
- it('should get user tokens successfully', async () => {
874
- const userTokens = [
875
- { token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice' },
876
- { token_id: 'token002', collection_symbol: 'TESTNFT', owner: 'alice' }
877
- ];
878
-
879
- mockAdapter.setQueryResult(userTokens);
880
-
881
- const payload = {
882
- account: 'alice',
883
- collectionSymbol: 'TESTNFT'
884
- };
885
-
886
- await (nftContract as any).getUserTokens(payload, { sender: 'bob' });
887
-
888
- expect(mockAdapter.events.length).toBe(1);
889
- expect(mockAdapter.events[0].action).toBe('getUserTokens');
890
- expect(mockAdapter.events[0].data.action).toBe('user_tokens_query');
891
- expect(mockAdapter.events[0].data.data.token_count).toBe(2);
892
- });
893
-
894
- it('should get all user tokens when no collection specified', async () => {
895
- const userTokens = [
896
- { token_id: 'token001', collection_symbol: 'TESTNFT', owner: 'alice' },
897
- { token_id: 'token003', collection_symbol: 'ANOTHERNFT', owner: 'alice' }
898
- ];
899
-
900
- mockAdapter.setQueryResult(userTokens);
901
-
902
- const payload = {
903
- account: 'alice'
904
- };
905
-
906
- await (nftContract as any).getUserTokens(payload, { sender: 'bob' });
907
-
908
- expect(mockAdapter.events.length).toBe(1);
909
- expect(mockAdapter.events[0].data.data.token_count).toBe(2);
910
- });
911
- });
912
-
913
- describe('Error Handling', () => {
914
- beforeEach(() => {
915
- mockAdapter.reset();
916
- nftContract.create();
917
- });
918
-
919
- it('should handle database errors gracefully during table initialization', async () => {
920
- const originalQuery = mockAdapter.query;
921
- mockAdapter.query = jest.fn().mockRejectedValue(new Error('Database error'));
922
-
923
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
924
-
925
- await nftContract.create();
926
-
927
- // Wait for async operations to complete
928
- await new Promise(resolve => setTimeout(resolve, 10));
929
-
930
- expect(consoleSpy).toHaveBeenCalledWith('[NFTContract] Error initializing tables:', expect.any(Error));
931
-
932
- consoleSpy.mockRestore();
933
- mockAdapter.query = originalQuery;
934
- });
935
-
936
- it('should propagate errors from contract methods', async () => {
937
- mockAdapter.query = jest.fn().mockRejectedValue(new Error('Database connection failed'));
938
-
939
- const payload = {
940
- symbol: 'TESTNFT',
941
- name: 'Test Collection'
942
- };
943
-
944
- await expect((nftContract as any).createCollection(payload, { sender: 'alice' }))
945
- .rejects.toThrow('Database connection failed');
946
- });
947
- });
948
- });