-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtransactionAction.test.ts
More file actions
237 lines (194 loc) · 10.1 KB
/
transactionAction.test.ts
File metadata and controls
237 lines (194 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import { describe, expect, it } from "vitest";
import { setupTrufNetwork, testWithDefaultWallet, waitForTxSuccess, normalizeTransactionId } from "./utils";
import { StreamId } from "../../src/util/StreamId";
import { StreamType } from "../../src/contracts-api/contractValues";
import { ethers } from "ethers";
import { NodeTNClient } from "../../src/client/nodeClient";
describe.sequential(
"TransactionAction Integration Tests",
{ timeout: 360000 },
() => {
// Spin up/tear down the local TN+Postgres containers once for this suite.
setupTrufNetwork();
testWithDefaultWallet(
"should throw error for non-existent transaction",
async ({ defaultClient }) => {
const txAction = defaultClient.loadTransactionAction();
// Try to fetch a non-existent transaction
const fakeHash = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
await expect(
txAction.getTransactionEvent({ txId: fakeHash })
).rejects.toThrow("Transaction not found");
console.log(`✅ Correctly threw error for non-existent transaction`);
}
);
testWithDefaultWallet(
"should throw error for empty tx_id",
async ({ defaultClient }) => {
const txAction = defaultClient.loadTransactionAction();
await expect(
txAction.getTransactionEvent({ txId: "" })
).rejects.toThrow("tx_id is required");
console.log(`✅ Correctly threw error for empty tx_id`);
}
);
testWithDefaultWallet(
"should record and fetch transaction for deployStream",
async ({ defaultClient }) => {
const txAction = defaultClient.loadTransactionAction();
// Create a unique stream
const streamId = await StreamId.generate(`test-tx-deploy-${Date.now()}`);
console.log(`Creating stream: ${streamId.getId()}`);
// Deploy a primitive stream (this should record a transaction with method_id=1)
const deployResult = await defaultClient.deployStream(streamId, StreamType.Primitive);
expect(deployResult.status).toBe(200);
const txHash = deployResult.data?.tx_hash;
expect(txHash).toBeDefined();
console.log(`Deployment tx hash: ${txHash}`);
// Wait for the transaction to be confirmed
await waitForTxSuccess(deployResult, defaultClient);
// Now query the transaction event
const txEvent = await txAction.getTransactionEvent({ txId: txHash! });
// Validate the transaction event
expect(txEvent.txId).toBe(normalizeTransactionId(txHash!));
expect(txEvent.method).toBe("deployStream");
expect(txEvent.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
expect(txEvent.blockHeight).toBeGreaterThan(0);
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
expect(txEvent.feeAmount).toBeDefined();
// Test wallet has system:network_writer role (fee exempt)
expect(txEvent.feeAmount).toBe("0");
expect(txEvent.feeRecipient).toBeUndefined();
console.log(`✅ Successfully fetched deployStream transaction:`);
console.log(` Method: ${txEvent.method}`);
console.log(` Caller: ${txEvent.caller}`);
console.log(` Fee: ${txEvent.feeAmount}`);
console.log(` Fee Recipient: ${txEvent.feeRecipient}`);
console.log(` Block Height: ${txEvent.blockHeight}`);
}
);
testWithDefaultWallet(
"should record and fetch transaction for insertRecords",
async ({ defaultClient }) => {
const txAction = defaultClient.loadTransactionAction();
// Create a stream first
const streamId = await StreamId.generate(`test-tx-insert-${Date.now()}`);
const deployResult = await defaultClient.deployStream(streamId, StreamType.Primitive);
await waitForTxSuccess(deployResult, defaultClient);
console.log(`Inserting records into stream: ${streamId.getId()}`);
// Insert records (this should record a transaction with method_id=2)
const primitiveStream = defaultClient.loadPrimitiveAction();
const streamLocator = defaultClient.ownStreamLocator(streamId);
const insertResult = await primitiveStream.insertRecords([
{ stream: streamLocator, eventTime: Date.now() / 1000, value: "1.23" },
{ stream: streamLocator, eventTime: (Date.now() + 1000) / 1000, value: "4.56" },
]);
expect(insertResult.status).toBe(200);
const txHash = insertResult.data?.tx_hash;
expect(txHash).toBeDefined();
console.log(`Insert records tx hash: ${txHash}`);
// Wait for the transaction to be confirmed
await waitForTxSuccess(insertResult, defaultClient);
// Query the transaction event
const txEvent = await txAction.getTransactionEvent({ txId: txHash! });
// Validate the transaction event
expect(txEvent.txId).toBe(normalizeTransactionId(txHash!));
expect(txEvent.method).toBe("insertRecords");
expect(txEvent.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
expect(txEvent.blockHeight).toBeGreaterThan(0);
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
expect(txEvent.feeAmount).toBeDefined();
// Test wallet has system:network_writer role (fee exempt)
expect(txEvent.feeAmount).toBe("0");
expect(txEvent.feeRecipient).toBeUndefined();
console.log(`✅ Successfully fetched insertRecords transaction:`);
console.log(` Method: ${txEvent.method}`);
console.log(` Caller: ${txEvent.caller}`);
console.log(` Fee: ${txEvent.feeAmount}`);
console.log(` Fee Recipient: ${txEvent.feeRecipient}`);
console.log(` Block Height: ${txEvent.blockHeight}`);
}
);
testWithDefaultWallet(
"should accept tx hash without 0x prefix",
async ({ defaultClient }) => {
const txAction = defaultClient.loadTransactionAction();
// Create a transaction
const streamId = await StreamId.generate(`test-tx-noprefix-${Date.now()}`);
const deployResult = await defaultClient.deployStream(streamId, StreamType.Primitive);
const txHash = deployResult.data?.tx_hash!;
await waitForTxSuccess(deployResult, defaultClient);
// Remove 0x prefix
const txHashWithoutPrefix = txHash.startsWith("0x") ? txHash.substring(2) : txHash;
// Query with hash without prefix
const txEvent = await txAction.getTransactionEvent({ txId: txHashWithoutPrefix });
// Validate that txEvent.txId is normalized with 0x prefix
expect(txEvent.txId).toBe(normalizeTransactionId(txHash));
expect(txEvent.method).toBe("deployStream");
expect(txEvent.stampMs).toBeGreaterThanOrEqual(0);
console.log(`✅ Successfully queried transaction with hash without 0x prefix`);
}
);
testWithDefaultWallet(
"should list transactions for wallet using list_transaction_fees",
async ({ defaultClient }) => {
const kwilClient = defaultClient.getKwilClient() as any;
// Create some transactions first
const streamId1 = await StreamId.generate(`test-tx-list-1-${Date.now()}`);
const streamId2 = await StreamId.generate(`test-tx-list-2-${Date.now()}`);
const deploy1 = await defaultClient.deployStream(streamId1, StreamType.Primitive);
await waitForTxSuccess(deploy1, defaultClient);
const deploy2 = await defaultClient.deployStream(streamId2, StreamType.Primitive);
await waitForTxSuccess(deploy2, defaultClient);
// Now query transactions for this wallet
const result = await kwilClient.call(
{
namespace: "main",
name: "list_transaction_fees",
inputs: {
$wallet: defaultClient.address().getAddress(),
$mode: "paid",
$limit: 10,
$offset: 0,
},
},
defaultClient.getKwilSigner()
);
expect(result.status).toBe(200);
expect(result.data?.result).toBeDefined();
expect(Array.isArray(result.data?.result)).toBe(true);
// Should have at least 2 transactions (the 2 deployments we just created)
const transactions = result.data?.result;
expect(transactions.length).toBeGreaterThanOrEqual(2);
// Validate transaction structure
const firstTx = transactions[0];
expect(firstTx.tx_id).toBeDefined();
// block_height is returned as string from SQL
expect(parseInt(firstTx.block_height)).toBeGreaterThan(0);
expect(firstTx.method).toBe("deployStream");
expect(firstTx.caller.toLowerCase()).toBe(defaultClient.address().getAddress().toLowerCase());
expect(firstTx.total_fee).toBeDefined();
console.log(`✅ list_transaction_fees returned ${transactions.length} transactions`);
console.log(` First transaction: ${firstTx.method} at block ${firstTx.block_height}`);
}
);
// TODO: Add test for non-exempt wallet that actually pays fees
//
// kwil-db has Go test utilities for balance injection:
// - `ForTestingCreditBalance()` (for_test_shims.go:126-134)
// - `InjectERC20Transfer()` (inject.go:23)
//
// kwil-js does not have equivalent test simulation utilities.
// Further testing needs investment in building test simulation in kwil-js
// similar to Go counterpart (kwil-db/node/exts/erc20-bridge/erc20/for_test_shims.go).
//
// For now, test fee-paying wallets in Go integration tests (node/tests/streams/).
//
// Current tests verify:
// - Fee-exempt wallets: fee=0, feeRecipient=undefined ✅
// - Transaction recording works for all actions ✅
// - Query functionality works correctly ✅
// - Fee fields are properly typed and returned ✅
// - string_agg() aggregates fee distributions correctly ✅
}
);