forked from StellarRouter/StellarRouter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontract.test.ts
More file actions
148 lines (131 loc) · 5.46 KB
/
Copy pathcontract.test.ts
File metadata and controls
148 lines (131 loc) · 5.46 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
import { Keypair, Networks, xdr } from "@stellar/stellar-sdk";
import { ContractInvoker } from "../src/contract.js";
import { RouterSdkError } from "../src/errors.js";
import { toStringVal, toU32, toU64 } from "../src/scval.js";import { LocalSigner } from "../src/signer.js";
import { FakeRpc } from "./helpers/fake-rpc.js";
const CONTRACT_ID = "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4";
const keypair = Keypair.random();
function makeInvoker(overrides: Partial<ConstructorParameters<typeof ContractInvoker>[0]> = {}) {
const fake = new FakeRpc();
const invoker = new ContractInvoker({
contractId: CONTRACT_ID,
rpc: fake,
networkPassphrase: Networks.TESTNET,
signer: new LocalSigner(keypair),
label: "core",
timeoutSeconds: 1,
...overrides,
});
return { fake, invoker };
}
describe("ContractInvoker", () => {
describe("simulate", () => {
it("returns the decoded value on success", async () => {
const { fake, invoker } = makeInvoker();
fake.simulate.set("total_routed", { retval: toU64(42n) });
await expect(invoker.simulate("total_routed", [])).resolves.toBe(42n);
});
it("returns undefined when there is no result value", async () => {
const { fake, invoker } = makeInvoker();
fake.simulate.set("noop", {});
await expect(invoker.simulate("noop", [])).resolves.toBeUndefined();
});
it("maps an on-chain contract error to its SDK error code", async () => {
const { fake, invoker } = makeInvoker();
fake.simulate.set("resolve", { error: "HostError: Error(Contract, #4)" });
await expect(invoker.simulate("resolve", [toStringVal("x")])).rejects.toMatchObject({
code: "RouteNotFound",
});
});
it("wraps non-contract simulation errors", async () => {
const { fake, invoker } = makeInvoker();
fake.simulate.set("resolve", { error: "host error: ValueNotFound" });
await expect(invoker.simulate("resolve", [toStringVal("x")])).rejects.toMatchObject({
code: "SimulationFailed",
});
});
it("wraps network-level RPC failures", async () => {
const { fake, invoker } = makeInvoker();
fake.simulateError = new Error("connection refused");
await expect(invoker.simulate("resolve", [toStringVal("x")])).rejects.toMatchObject({
code: "RpcError",
});
});
});
describe("invoke", () => {
it("prepares, signs, submits, and confirms a call", async () => {
const { fake, invoker } = makeInvoker();
fake.getTransaction = async () => ({ status: "SUCCESS" });
await expect(invoker.invoke("register_route", [toU32(1), toStringVal("oracle")])).resolves.toBeUndefined();
expect(fake.prepareCalls).toHaveLength(1);
expect(fake.sendCalls).toHaveLength(1);
expect(fake.lastInvocation?.method).toBe("register_route");
expect(fake.lastInvocation?.args).toHaveLength(2);
});
it("returns the confirmed return value when present", async () => {
const { fake, invoker } = makeInvoker();
fake.getTransaction = async () => ({ status: "SUCCESS", returnValue: toU32(7) });
await expect(invoker.invoke("do_something", [])).resolves.toBe(7);
});
it("throws NoSigner when no signer is configured", async () => {
const { invoker } = makeInvoker({ signer: undefined });
await expect(invoker.invoke("register_route", [])).rejects.toMatchObject({ code: "NoSigner" });
});
it("throws TransactionRejected when the network rejects submission", async () => {
const { fake, invoker } = makeInvoker();
fake.sendStatus = "ERROR";
await expect(invoker.invoke("do_something", [])).rejects.toMatchObject({
code: "TransactionRejected",
});
});
it("throws TryAgainLater when the network asks for a retry", async () => {
const { fake, invoker } = makeInvoker();
fake.sendStatus = "TRY_AGAIN_LATER";
await expect(invoker.invoke("do_something", [])).rejects.toMatchObject({
code: "TryAgainLater",
});
});
it("throws TransactionFailed when the transaction fails on-chain", async () => {
const { fake, invoker } = makeInvoker();
fake.getTransactionStatuses = [{ status: "FAILED" }];
await expect(invoker.invoke("do_something", [])).rejects.toMatchObject({
code: "TransactionFailed",
});
});
it("throws TransactionTimeout when confirmation never arrives", async () => {
const { fake, invoker } = makeInvoker();
fake.getTransactionStatuses = [{ status: "NOT_FOUND" }, { status: "NOT_FOUND" }];
const fast = new ContractInvoker({
contractId: CONTRACT_ID,
rpc: fake,
networkPassphrase: Networks.TESTNET,
signer: new LocalSigner(keypair),
timeoutSeconds: 0.1,
pollIntervalMs: 10,
});
await expect(fast.invoke("do_something", [])).rejects.toMatchObject({ code: "TransactionTimeout" });
});
});
describe("configuration", () => {
it("rejects an invalid contract id", () => {
expect(
() =>
new ContractInvoker({
contractId: "not-a-contract",
rpc: new FakeRpc(),
networkPassphrase: Networks.TESTNET,
}),
).toThrow(RouterSdkError);
});
it("rejects missing rpc/network config", () => {
expect(
() =>
new ContractInvoker({
contractId: CONTRACT_ID,
rpc: undefined as never,
networkPassphrase: Networks.TESTNET,
}),
).toThrow(/rpc/);
});
});
});