genlayer 0.33.1 → 0.34.1
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.
- package/dist/index.js +68 -31
- package/package.json +11 -2
- package/.eslintignore +0 -2
- package/.github/workflows/cli-docs.yml +0 -121
- package/.github/workflows/publish-beta.yml +0 -41
- package/.github/workflows/publish.yml +0 -43
- package/.github/workflows/validate-code.yml +0 -47
- package/.prettierignore +0 -19
- package/.prettierrc +0 -12
- package/.release-it.json +0 -64
- package/CHANGELOG.md +0 -419
- package/CLAUDE.md +0 -55
- package/CONTRIBUTING.md +0 -117
- package/docker-compose.yml +0 -154
- package/docs/delegator-guide.md +0 -203
- package/docs/validator-guide.md +0 -329
- package/esbuild.config.dev.js +0 -17
- package/esbuild.config.js +0 -22
- package/esbuild.config.prod.js +0 -17
- package/eslint.config.js +0 -60
- package/renovate.json +0 -22
- package/src/commands/account/create.ts +0 -29
- package/src/commands/account/export.ts +0 -106
- package/src/commands/account/import.ts +0 -135
- package/src/commands/account/index.ts +0 -126
- package/src/commands/account/list.ts +0 -34
- package/src/commands/account/lock.ts +0 -39
- package/src/commands/account/remove.ts +0 -30
- package/src/commands/account/send.ts +0 -156
- package/src/commands/account/show.ts +0 -74
- package/src/commands/account/unlock.ts +0 -51
- package/src/commands/account/use.ts +0 -21
- package/src/commands/config/getSetReset.ts +0 -51
- package/src/commands/config/index.ts +0 -30
- package/src/commands/contracts/call.ts +0 -39
- package/src/commands/contracts/code.ts +0 -33
- package/src/commands/contracts/deploy.ts +0 -157
- package/src/commands/contracts/index.ts +0 -86
- package/src/commands/contracts/schema.ts +0 -31
- package/src/commands/contracts/write.ts +0 -49
- package/src/commands/general/index.ts +0 -45
- package/src/commands/general/init.ts +0 -179
- package/src/commands/general/start.ts +0 -116
- package/src/commands/general/stop.ts +0 -26
- package/src/commands/localnet/index.ts +0 -100
- package/src/commands/localnet/validators.ts +0 -269
- package/src/commands/network/index.ts +0 -29
- package/src/commands/network/setNetwork.ts +0 -77
- package/src/commands/scaffold/index.ts +0 -16
- package/src/commands/scaffold/new.ts +0 -34
- package/src/commands/staking/StakingAction.ts +0 -267
- package/src/commands/staking/delegatorClaim.ts +0 -41
- package/src/commands/staking/delegatorExit.ts +0 -56
- package/src/commands/staking/delegatorJoin.ts +0 -44
- package/src/commands/staking/index.ts +0 -346
- package/src/commands/staking/setIdentity.ts +0 -78
- package/src/commands/staking/setOperator.ts +0 -46
- package/src/commands/staking/stakingInfo.ts +0 -584
- package/src/commands/staking/validatorClaim.ts +0 -43
- package/src/commands/staking/validatorDeposit.ts +0 -48
- package/src/commands/staking/validatorExit.ts +0 -63
- package/src/commands/staking/validatorHistory.ts +0 -298
- package/src/commands/staking/validatorJoin.ts +0 -47
- package/src/commands/staking/validatorPrime.ts +0 -73
- package/src/commands/staking/wizard.ts +0 -809
- package/src/commands/transactions/appeal.ts +0 -39
- package/src/commands/transactions/index.ts +0 -39
- package/src/commands/transactions/receipt.ts +0 -90
- package/src/commands/update/index.ts +0 -25
- package/src/commands/update/ollama.ts +0 -103
- package/src/lib/actions/BaseAction.ts +0 -295
- package/src/lib/clients/jsonRpcClient.ts +0 -41
- package/src/lib/clients/system.ts +0 -73
- package/src/lib/config/ConfigFileManager.ts +0 -194
- package/src/lib/config/KeychainManager.ts +0 -89
- package/src/lib/config/simulator.ts +0 -68
- package/src/lib/config/text.ts +0 -2
- package/src/lib/errors/missingRequirement.ts +0 -9
- package/src/lib/errors/versionRequired.ts +0 -9
- package/src/lib/interfaces/ISimulatorService.ts +0 -37
- package/src/lib/services/simulator.ts +0 -351
- package/src/types/node-fetch.d.ts +0 -1
- package/tests/actions/appeal.test.ts +0 -99
- package/tests/actions/call.test.ts +0 -94
- package/tests/actions/code.test.ts +0 -87
- package/tests/actions/create.test.ts +0 -65
- package/tests/actions/deploy.test.ts +0 -420
- package/tests/actions/getSetReset.test.ts +0 -88
- package/tests/actions/init.test.ts +0 -467
- package/tests/actions/lock.test.ts +0 -86
- package/tests/actions/new.test.ts +0 -80
- package/tests/actions/ollama.test.ts +0 -193
- package/tests/actions/receipt.test.ts +0 -261
- package/tests/actions/schema.test.ts +0 -94
- package/tests/actions/setNetwork.test.ts +0 -160
- package/tests/actions/staking.test.ts +0 -279
- package/tests/actions/start.test.ts +0 -235
- package/tests/actions/stop.test.ts +0 -77
- package/tests/actions/unlock.test.ts +0 -139
- package/tests/actions/validators.test.ts +0 -750
- package/tests/actions/write.test.ts +0 -102
- package/tests/commands/account.test.ts +0 -146
- package/tests/commands/appeal.test.ts +0 -58
- package/tests/commands/call.test.ts +0 -78
- package/tests/commands/code.test.ts +0 -69
- package/tests/commands/config.test.ts +0 -54
- package/tests/commands/deploy.test.ts +0 -83
- package/tests/commands/init.test.ts +0 -101
- package/tests/commands/localnet.test.ts +0 -131
- package/tests/commands/network.test.ts +0 -60
- package/tests/commands/new.test.ts +0 -68
- package/tests/commands/receipt.test.ts +0 -142
- package/tests/commands/schema.test.ts +0 -67
- package/tests/commands/staking.test.ts +0 -329
- package/tests/commands/stop.test.ts +0 -27
- package/tests/commands/up.test.ts +0 -105
- package/tests/commands/update.test.ts +0 -45
- package/tests/commands/write.test.ts +0 -76
- package/tests/index.test.ts +0 -56
- package/tests/libs/baseAction.test.ts +0 -516
- package/tests/libs/configFileManager.test.ts +0 -117
- package/tests/libs/jsonRpcClient.test.ts +0 -59
- package/tests/libs/keychainManager.test.ts +0 -156
- package/tests/libs/system.test.ts +0 -148
- package/tests/services/simulator.test.ts +0 -705
- package/tests/utils.ts +0 -13
- package/tsconfig.json +0 -120
- package/vitest.config.ts +0 -12
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
import {describe, test, vi, beforeEach, afterEach, expect, Mock} from "vitest";
|
|
2
|
-
import {BaseAction} from "../../src/lib/actions/BaseAction";
|
|
3
|
-
import inquirer from "inquirer";
|
|
4
|
-
import ora, {Ora} from "ora";
|
|
5
|
-
import chalk from "chalk";
|
|
6
|
-
import {inspect} from "util";
|
|
7
|
-
import { ethers } from "ethers";
|
|
8
|
-
import { writeFileSync, existsSync, readFileSync } from "fs";
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import { createAccount } from "genlayer-js";
|
|
12
|
-
|
|
13
|
-
vi.mock("inquirer");
|
|
14
|
-
vi.mock("ora");
|
|
15
|
-
vi.mock("fs");
|
|
16
|
-
vi.mock("os");
|
|
17
|
-
vi.mock("ethers");
|
|
18
|
-
vi.mock("genlayer-js", () => ({
|
|
19
|
-
createAccount: vi.fn(),
|
|
20
|
-
createClient: vi.fn(),
|
|
21
|
-
localnet: {}
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
describe("BaseAction", () => {
|
|
25
|
-
let baseAction: BaseAction;
|
|
26
|
-
let mockSpinner: Ora;
|
|
27
|
-
let consoleSpy: any;
|
|
28
|
-
let consoleErrorSpy: any;
|
|
29
|
-
let processExitSpy: any;
|
|
30
|
-
|
|
31
|
-
// Standard web3 keystore format
|
|
32
|
-
const mockKeystoreData = {
|
|
33
|
-
address: "1234567890123456789012345678901234567890",
|
|
34
|
-
crypto: {
|
|
35
|
-
cipher: "aes-128-ctr",
|
|
36
|
-
ciphertext: "test",
|
|
37
|
-
cipherparams: {iv: "test"},
|
|
38
|
-
kdf: "scrypt",
|
|
39
|
-
kdfparams: {},
|
|
40
|
-
mac: "test"
|
|
41
|
-
},
|
|
42
|
-
version: 3
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const mockWallet = {
|
|
46
|
-
privateKey: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
|
47
|
-
address: "0x1234567890123456789012345678901234567890",
|
|
48
|
-
encrypt: vi.fn().mockResolvedValue('{"address":"test","crypto":{"cipher":"aes-128-ctr"}}'),
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
vi.clearAllMocks();
|
|
53
|
-
consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
54
|
-
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
55
|
-
processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
|
|
56
|
-
throw new Error("process exited");
|
|
57
|
-
});
|
|
58
|
-
mockSpinner = {
|
|
59
|
-
start: vi.fn(),
|
|
60
|
-
stop: vi.fn(),
|
|
61
|
-
succeed: vi.fn(),
|
|
62
|
-
fail: vi.fn(),
|
|
63
|
-
text: "",
|
|
64
|
-
} as unknown as Ora;
|
|
65
|
-
|
|
66
|
-
(ora as unknown as Mock).mockReturnValue(mockSpinner);
|
|
67
|
-
|
|
68
|
-
vi.mocked(os.homedir).mockReturnValue("/mocked/home");
|
|
69
|
-
vi.mocked(os.tmpdir).mockReturnValue("/mocked/tmp");
|
|
70
|
-
|
|
71
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
72
|
-
vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockKeystoreData));
|
|
73
|
-
vi.mocked(writeFileSync).mockImplementation(() => {});
|
|
74
|
-
vi.mocked(fs.readdirSync).mockReturnValue([] as any);
|
|
75
|
-
vi.mocked(fs.mkdirSync).mockImplementation(() => "/mocked/path");
|
|
76
|
-
|
|
77
|
-
// Mock ethers
|
|
78
|
-
vi.mocked(ethers.Wallet.createRandom).mockReturnValue(mockWallet as any);
|
|
79
|
-
vi.mocked(ethers.Wallet.fromEncryptedJson).mockResolvedValue(mockWallet as any);
|
|
80
|
-
|
|
81
|
-
vi.mocked(createAccount).mockReturnValue({
|
|
82
|
-
privateKey: mockWallet.privateKey,
|
|
83
|
-
address: mockWallet.address
|
|
84
|
-
} as any);
|
|
85
|
-
|
|
86
|
-
baseAction = new BaseAction();
|
|
87
|
-
|
|
88
|
-
// Mock config methods
|
|
89
|
-
vi.spyOn(baseAction as any, "getConfigByKey").mockReturnValue("./test-keypair.json");
|
|
90
|
-
vi.spyOn(baseAction as any, "getFilePath").mockImplementation(() => "./test-keypair.json");
|
|
91
|
-
vi.spyOn(baseAction as any, "writeConfig").mockImplementation(() => {});
|
|
92
|
-
vi.spyOn(baseAction as any, "getConfig").mockReturnValue({activeAccount: "default"});
|
|
93
|
-
vi.spyOn(baseAction as any, "resolveAccountName").mockReturnValue("default");
|
|
94
|
-
vi.spyOn(baseAction as any, "getKeystorePath").mockReturnValue("/mocked/home/.genlayer/keystores/default.json");
|
|
95
|
-
vi.spyOn(baseAction as any, "getActiveAccount").mockReturnValue("default");
|
|
96
|
-
|
|
97
|
-
// Mock keychainManager methods
|
|
98
|
-
vi.spyOn(baseAction["keychainManager"], "isKeychainAvailable").mockResolvedValue(false);
|
|
99
|
-
vi.spyOn(baseAction["keychainManager"], "getPrivateKey").mockResolvedValue(null);
|
|
100
|
-
vi.spyOn(baseAction["keychainManager"], "storePrivateKey").mockResolvedValue();
|
|
101
|
-
vi.spyOn(baseAction["keychainManager"], "removePrivateKey").mockResolvedValue(true);
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
afterEach(() => {
|
|
106
|
-
vi.restoreAllMocks();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("should start the spinner with a message", () => {
|
|
110
|
-
baseAction["startSpinner"]("Loading...");
|
|
111
|
-
expect(mockSpinner.start).toHaveBeenCalled();
|
|
112
|
-
expect(mockSpinner.text).toBe(chalk.blue("Loading..."));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("should succeed the spinner with a message", () => {
|
|
116
|
-
baseAction["succeedSpinner"]("Success");
|
|
117
|
-
expect(consoleSpy).toHaveBeenCalledWith("");
|
|
118
|
-
expect(mockSpinner.succeed).toHaveBeenCalledWith(expect.stringContaining("Success"));
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("should fail the spinner with an error message", () => {
|
|
122
|
-
const error = new Error("Something went wrong");
|
|
123
|
-
baseAction["failSpinner"]("Failure", error, false); // Don't exit for test
|
|
124
|
-
|
|
125
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error:"));
|
|
126
|
-
expect(consoleSpy).toHaveBeenCalledWith(inspect(error, {depth: null, colors: false}));
|
|
127
|
-
expect(consoleSpy).toHaveBeenCalledWith("");
|
|
128
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(expect.stringContaining("Failure"));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("should fail the spinner and exit by default", () => {
|
|
132
|
-
const error = new Error("Fatal error");
|
|
133
|
-
expect(() => baseAction["failSpinner"]("Fatal", error)).toThrow("process exited");
|
|
134
|
-
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("should stop the spinner", () => {
|
|
138
|
-
baseAction["stopSpinner"]();
|
|
139
|
-
expect(mockSpinner.stop).toHaveBeenCalled();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("should set spinner text", () => {
|
|
143
|
-
baseAction["setSpinnerText"]("Updated text");
|
|
144
|
-
expect(mockSpinner.text).toBe(chalk.blue("Updated text"));
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("should confirm prompt and proceed when confirmed", async () => {
|
|
148
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({confirmAction: true});
|
|
149
|
-
|
|
150
|
-
await expect(baseAction["confirmPrompt"]("Are you sure?")).resolves.not.toThrow();
|
|
151
|
-
expect(inquirer.prompt).toHaveBeenCalled();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("should confirm prompt and exit when declined", async () => {
|
|
155
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({confirmAction: false});
|
|
156
|
-
const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
|
|
157
|
-
throw new Error("process exited");
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
await expect(baseAction["confirmPrompt"]("Are you sure?")).rejects.toThrow("process exited");
|
|
161
|
-
expect(inquirer.prompt).toHaveBeenCalled();
|
|
162
|
-
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("should log a success message", () => {
|
|
166
|
-
baseAction["logSuccess"]("Success message");
|
|
167
|
-
|
|
168
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("✔ Success message"));
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("should log an error message", () => {
|
|
172
|
-
baseAction["logError"]("Error message");
|
|
173
|
-
|
|
174
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("✖ Error message"));
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("should log a info message", () => {
|
|
178
|
-
baseAction["logInfo"]("Info message");
|
|
179
|
-
|
|
180
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("ℹ Info message"));
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("should log a warning message", () => {
|
|
184
|
-
baseAction["logWarning"]("Warning message");
|
|
185
|
-
|
|
186
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("⚠ Warning message"));
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("should log a success message with data", () => {
|
|
190
|
-
const data = {key: "value"};
|
|
191
|
-
|
|
192
|
-
baseAction["logSuccess"]("Success message", data);
|
|
193
|
-
|
|
194
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("✔ Success message"));
|
|
195
|
-
expect(consoleSpy).toHaveBeenCalledWith(chalk.green(inspect(data, {depth: null, colors: false})));
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("should log an error message with error details", () => {
|
|
199
|
-
const error = new Error("Something went wrong");
|
|
200
|
-
|
|
201
|
-
baseAction["logError"]("Error message", error);
|
|
202
|
-
|
|
203
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("✖ Error message"));
|
|
204
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(chalk.red(inspect(error, {depth: null, colors: false})));
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("should log an info message with data", () => {
|
|
208
|
-
const data = {info: "This is some info"};
|
|
209
|
-
|
|
210
|
-
baseAction["logInfo"]("Info message", data);
|
|
211
|
-
|
|
212
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("ℹ Info message"));
|
|
213
|
-
expect(consoleSpy).toHaveBeenCalledWith(chalk.blue(inspect(data, {depth: null, colors: false})));
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("should log a warning message with data", () => {
|
|
217
|
-
const data = {warning: "This is a warning"};
|
|
218
|
-
|
|
219
|
-
baseAction["logWarning"]("Warning message", data);
|
|
220
|
-
|
|
221
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("⚠ Warning message"));
|
|
222
|
-
expect(consoleSpy).toHaveBeenCalledWith(chalk.yellow(inspect(data, {depth: null, colors: false})));
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test("should succeed the spinner with a message and log result if data is provided", () => {
|
|
226
|
-
const mockData = {key: "value"};
|
|
227
|
-
|
|
228
|
-
baseAction["succeedSpinner"]("Success", mockData);
|
|
229
|
-
|
|
230
|
-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Result:"));
|
|
231
|
-
expect(consoleSpy).toHaveBeenCalledWith(inspect(mockData, {depth: null, colors: false}));
|
|
232
|
-
expect(consoleSpy).toHaveBeenCalledWith("");
|
|
233
|
-
expect(mockSpinner.succeed).toHaveBeenCalledWith(expect.stringContaining("Success"));
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
test("should return a string representation of a primitive", () => {
|
|
237
|
-
expect((baseAction as any).formatOutput("Hello")).toBe("Hello");
|
|
238
|
-
expect((baseAction as any).formatOutput(42)).toBe("42");
|
|
239
|
-
expect((baseAction as any).formatOutput(true)).toBe("true");
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test("should prompt for password successfully", async () => {
|
|
243
|
-
const mockPassword = "test-password";
|
|
244
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({password: mockPassword});
|
|
245
|
-
|
|
246
|
-
const result = await baseAction["promptPassword"]("Enter password:");
|
|
247
|
-
|
|
248
|
-
expect(result).toBe(mockPassword);
|
|
249
|
-
expect(inquirer.prompt).toHaveBeenCalledWith([{
|
|
250
|
-
type: "password",
|
|
251
|
-
name: "password",
|
|
252
|
-
message: chalk.yellow("Enter password:"),
|
|
253
|
-
mask: "*",
|
|
254
|
-
validate: expect.any(Function),
|
|
255
|
-
}]);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("should validate password input is not empty", async () => {
|
|
259
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({password: "valid-password"});
|
|
260
|
-
|
|
261
|
-
await baseAction["promptPassword"]("Enter password:");
|
|
262
|
-
|
|
263
|
-
const mockCall = vi.mocked(inquirer.prompt).mock.calls[0];
|
|
264
|
-
const questions = mockCall[0] as any;
|
|
265
|
-
const validateFn = questions[0].validate;
|
|
266
|
-
expect(validateFn("")).toBe("Password cannot be empty");
|
|
267
|
-
expect(validateFn("valid")).toBe(true);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("should return private key when keystore exists and is valid", async () => {
|
|
271
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({password: "correct-password"});
|
|
272
|
-
|
|
273
|
-
const account = await baseAction["getAccount"](false);
|
|
274
|
-
|
|
275
|
-
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
276
|
-
expect(existsSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json");
|
|
277
|
-
expect(readFileSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json", "utf-8");
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test("should return address when called with readOnly=true", async () => {
|
|
281
|
-
const address = await baseAction["getAccount"](true);
|
|
282
|
-
|
|
283
|
-
expect(address).toBe(mockKeystoreData.address);
|
|
284
|
-
expect(existsSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json");
|
|
285
|
-
expect(readFileSync).toHaveBeenCalledWith("/mocked/home/.genlayer/keystores/default.json", "utf-8");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("should create new keypair when keystore file does not exist", async () => {
|
|
289
|
-
vi.mocked(existsSync).mockReturnValue(false);
|
|
290
|
-
vi.mocked(inquirer.prompt)
|
|
291
|
-
.mockResolvedValueOnce({confirmAction: true}) // confirm create new
|
|
292
|
-
.mockResolvedValueOnce({password: "new-password"}) // encrypt password
|
|
293
|
-
.mockResolvedValueOnce({password: "new-password"}); // confirm password
|
|
294
|
-
|
|
295
|
-
const account = await baseAction["getAccount"](false);
|
|
296
|
-
|
|
297
|
-
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
298
|
-
expect(inquirer.prompt).toHaveBeenCalledWith(expect.arrayContaining([
|
|
299
|
-
expect.objectContaining({message: chalk.yellow("Account 'default' not found. Would you like to create it?")})
|
|
300
|
-
]));
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
test("should fail when keystore format is invalid and user declines", async () => {
|
|
304
|
-
vi.mocked(readFileSync).mockReturnValue('{"invalid": "format"}');
|
|
305
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({confirmAction: false});
|
|
306
|
-
|
|
307
|
-
await expect(baseAction["getAccount"](false)).rejects.toThrow("process exited");
|
|
308
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Invalid keystore format. Expected encrypted keystore file."));
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("should use cached key when available", async () => {
|
|
312
|
-
vi.spyOn(baseAction["keychainManager"], "isKeychainAvailable").mockResolvedValue(true);
|
|
313
|
-
vi.spyOn(baseAction["keychainManager"], "getPrivateKey").mockResolvedValue(mockWallet.privateKey);
|
|
314
|
-
|
|
315
|
-
const account = await baseAction["getAccount"](false);
|
|
316
|
-
|
|
317
|
-
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
318
|
-
expect(baseAction["keychainManager"].getPrivateKey).toHaveBeenCalledWith("default");
|
|
319
|
-
expect(inquirer.prompt).not.toHaveBeenCalled();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test("should create new keypair when keystore format is invalid and user confirms", async () => {
|
|
323
|
-
vi.mocked(readFileSync).mockReturnValue('{"invalid": "format"}');
|
|
324
|
-
vi.mocked(inquirer.prompt)
|
|
325
|
-
.mockResolvedValueOnce({confirmAction: true})
|
|
326
|
-
.mockResolvedValueOnce({password: "new-password"})
|
|
327
|
-
.mockResolvedValueOnce({password: "new-password"});
|
|
328
|
-
|
|
329
|
-
const account = await baseAction["getAccount"](false);
|
|
330
|
-
|
|
331
|
-
expect((account as any).privateKey).toBe(mockWallet.privateKey);
|
|
332
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Invalid keystore format. Expected encrypted keystore file."));
|
|
333
|
-
expect(inquirer.prompt).toHaveBeenCalledWith(expect.arrayContaining([
|
|
334
|
-
expect.objectContaining({message: "Would you like to recreate account 'default'?"})
|
|
335
|
-
]));
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("should decrypt keystore successfully on first attempt", async () => {
|
|
339
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({password: "correct-password"});
|
|
340
|
-
|
|
341
|
-
const result = await baseAction["decryptKeystore"](JSON.stringify(mockKeystoreData));
|
|
342
|
-
|
|
343
|
-
expect(result).toBe(mockWallet.privateKey);
|
|
344
|
-
expect(inquirer.prompt).toHaveBeenCalledWith(expect.arrayContaining([
|
|
345
|
-
expect.objectContaining({message: chalk.yellow("Enter password to decrypt keystore:")})
|
|
346
|
-
]));
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
test("should retry on wrong password and succeed on second attempt", async () => {
|
|
350
|
-
vi.mocked(ethers.Wallet.fromEncryptedJson)
|
|
351
|
-
.mockRejectedValueOnce(new Error("Incorrect password"))
|
|
352
|
-
.mockResolvedValueOnce(mockWallet as any);
|
|
353
|
-
|
|
354
|
-
vi.mocked(inquirer.prompt)
|
|
355
|
-
.mockResolvedValueOnce({password: "wrong-password"})
|
|
356
|
-
.mockResolvedValueOnce({password: "correct-password"});
|
|
357
|
-
|
|
358
|
-
const result = await baseAction["decryptKeystore"](JSON.stringify(mockKeystoreData));
|
|
359
|
-
|
|
360
|
-
expect(result).toBe(mockWallet.privateKey);
|
|
361
|
-
expect(inquirer.prompt).toHaveBeenCalledTimes(2);
|
|
362
|
-
expect(inquirer.prompt).toHaveBeenNthCalledWith(2, expect.arrayContaining([
|
|
363
|
-
expect.objectContaining({message: chalk.yellow("Invalid password. Attempt 2/3 - Enter password to decrypt keystore:")})
|
|
364
|
-
]));
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test("should exit after 3 failed password attempts", async () => {
|
|
368
|
-
vi.mocked(ethers.Wallet.fromEncryptedJson).mockRejectedValue(new Error("Incorrect password"));
|
|
369
|
-
vi.mocked(inquirer.prompt).mockResolvedValue({password: "wrong-password"});
|
|
370
|
-
|
|
371
|
-
await expect(baseAction["decryptKeystore"](JSON.stringify(mockKeystoreData))).rejects.toThrow("process exited");
|
|
372
|
-
|
|
373
|
-
expect(inquirer.prompt).toHaveBeenCalledTimes(3);
|
|
374
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Maximum password attempts exceeded (3/3)."));
|
|
375
|
-
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("should create new keypair successfully", async () => {
|
|
379
|
-
vi.mocked(existsSync).mockReturnValue(false);
|
|
380
|
-
vi.mocked(inquirer.prompt)
|
|
381
|
-
.mockResolvedValueOnce({password: "test-password"})
|
|
382
|
-
.mockResolvedValueOnce({password: "test-password"});
|
|
383
|
-
|
|
384
|
-
const result = await baseAction["createKeypairByName"]("test-account", false);
|
|
385
|
-
|
|
386
|
-
expect(result).toBe(mockWallet.privateKey);
|
|
387
|
-
expect(ethers.Wallet.createRandom).toHaveBeenCalled();
|
|
388
|
-
expect(mockWallet.encrypt).toHaveBeenCalledWith("test-password");
|
|
389
|
-
expect(writeFileSync).toHaveBeenCalled();
|
|
390
|
-
expect(baseAction["keychainManager"].removePrivateKey).toHaveBeenCalledWith("test-account");
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
test("should fail when account exists and overwrite is false", async () => {
|
|
394
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
395
|
-
|
|
396
|
-
await expect(baseAction["createKeypairByName"]("test-account", false)).rejects.toThrow("process exited");
|
|
397
|
-
|
|
398
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(
|
|
399
|
-
chalk.red("Account 'test-account' already exists. Use '--overwrite' to replace it.")
|
|
400
|
-
);
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
test("should fail when passwords do not match", async () => {
|
|
404
|
-
vi.mocked(existsSync).mockReturnValue(false);
|
|
405
|
-
vi.mocked(inquirer.prompt)
|
|
406
|
-
.mockResolvedValueOnce({password: "password1"})
|
|
407
|
-
.mockResolvedValueOnce({password: "password2"});
|
|
408
|
-
|
|
409
|
-
await expect(baseAction["createKeypairByName"]("test-account", false)).rejects.toThrow("process exited");
|
|
410
|
-
|
|
411
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Passwords do not match"));
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
test("should fail when password is too short", async () => {
|
|
415
|
-
vi.mocked(existsSync).mockReturnValue(false);
|
|
416
|
-
vi.mocked(inquirer.prompt)
|
|
417
|
-
.mockResolvedValueOnce({password: "short"})
|
|
418
|
-
.mockResolvedValueOnce({password: "short"});
|
|
419
|
-
|
|
420
|
-
await expect(baseAction["createKeypairByName"]("test-account", false)).rejects.toThrow("process exited");
|
|
421
|
-
|
|
422
|
-
expect(mockSpinner.fail).toHaveBeenCalledWith(chalk.red("Password must be at least 8 characters long"));
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
test("should overwrite existing account when overwrite is true", async () => {
|
|
426
|
-
vi.mocked(existsSync).mockReturnValue(true);
|
|
427
|
-
vi.mocked(inquirer.prompt)
|
|
428
|
-
.mockResolvedValueOnce({password: "test-password"})
|
|
429
|
-
.mockResolvedValueOnce({password: "test-password"});
|
|
430
|
-
|
|
431
|
-
const result = await baseAction["createKeypairByName"]("test-account", true);
|
|
432
|
-
|
|
433
|
-
expect(result).toBe(mockWallet.privateKey);
|
|
434
|
-
expect(writeFileSync).toHaveBeenCalled();
|
|
435
|
-
expect(baseAction["keychainManager"].removePrivateKey).toHaveBeenCalledWith("test-account");
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
test("should return true for valid keystore format", () => {
|
|
439
|
-
// Standard web3 keystore format
|
|
440
|
-
const validKeystore = {
|
|
441
|
-
address: "1234567890123456789012345678901234567890",
|
|
442
|
-
crypto: {cipher: "aes-128-ctr", ciphertext: "test"},
|
|
443
|
-
version: 3,
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
const result = baseAction["isValidKeystoreFormat"](validKeystore);
|
|
447
|
-
expect(result).toBe(true);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test("should return true for keystore with uppercase Crypto field", () => {
|
|
451
|
-
// Some tools use uppercase 'Crypto'
|
|
452
|
-
const validKeystore = {
|
|
453
|
-
address: "1234567890123456789012345678901234567890",
|
|
454
|
-
Crypto: {cipher: "aes-128-ctr", ciphertext: "test"},
|
|
455
|
-
version: 3,
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
const result = baseAction["isValidKeystoreFormat"](validKeystore);
|
|
459
|
-
expect(result).toBe(true);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test("should return false for keystore missing crypto field", () => {
|
|
463
|
-
const invalidKeystore = {
|
|
464
|
-
address: "1234567890123456789012345678901234567890",
|
|
465
|
-
version: 3,
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
const result = baseAction["isValidKeystoreFormat"](invalidKeystore);
|
|
469
|
-
expect(result).toBe(false);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
test("should return false for keystore missing address", () => {
|
|
473
|
-
const invalidKeystore = {
|
|
474
|
-
crypto: {cipher: "aes-128-ctr"},
|
|
475
|
-
version: 3,
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
const result = baseAction["isValidKeystoreFormat"](invalidKeystore);
|
|
479
|
-
expect(result).toBe(false);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test("should return false for null or undefined keystore", () => {
|
|
483
|
-
expect(baseAction["isValidKeystoreFormat"](null)).toBe(false);
|
|
484
|
-
expect(baseAction["isValidKeystoreFormat"](undefined)).toBe(false);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
describe("formatOutput", () => {
|
|
488
|
-
test("should return string as is", () => {
|
|
489
|
-
expect((baseAction as any).formatOutput("Hello")).toBe("Hello");
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
test("should format an object", () => {
|
|
493
|
-
const data = {key: "value", num: 42};
|
|
494
|
-
const result = (baseAction as any).formatOutput(data);
|
|
495
|
-
expect(result).toBe("{ key: 'value', num: 42 }");
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
test("should format an error object", () => {
|
|
499
|
-
const error = new Error("Test Error");
|
|
500
|
-
const result = (baseAction as any).formatOutput(error);
|
|
501
|
-
expect(result).toContain("Error: Test Error");
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test("should format a Map object", () => {
|
|
505
|
-
const testMap = new Map([["key1", "value1"]]);
|
|
506
|
-
const result = (baseAction as any).formatOutput(testMap);
|
|
507
|
-
expect(result).toBe("Map(1) { 'key1' => 'value1' }");
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
test("should format a BigInt object", () => {
|
|
511
|
-
const bigIntValue = BigInt(9007199254740991);
|
|
512
|
-
const result = (baseAction as any).formatOutput(bigIntValue);
|
|
513
|
-
expect(result).toBe("9007199254740991n");
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
|
|
2
|
-
import { ConfigFileManager } from "../../src/lib/config/ConfigFileManager";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import os from "os";
|
|
5
|
-
import path from "path";
|
|
6
|
-
|
|
7
|
-
vi.mock("fs");
|
|
8
|
-
|
|
9
|
-
vi.mock("os")
|
|
10
|
-
|
|
11
|
-
describe("ConfigFileManager", () => {
|
|
12
|
-
const mockFolderPath = "/mocked/home/.genlayer";
|
|
13
|
-
const mockKeystoresPath = `${mockFolderPath}/keystores`;
|
|
14
|
-
const mockConfigFilePath = `${mockFolderPath}/genlayer-config.json`;
|
|
15
|
-
|
|
16
|
-
let configFileManager: ConfigFileManager;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
vi.clearAllMocks();
|
|
20
|
-
vi.mocked(os.homedir).mockReturnValue("/mocked/home");
|
|
21
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
22
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({}));
|
|
23
|
-
configFileManager = new ConfigFileManager();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
vi.restoreAllMocks();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("ensures folder and config file are created if they don't exist", () => {
|
|
31
|
-
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
32
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({}));
|
|
33
|
-
|
|
34
|
-
new ConfigFileManager();
|
|
35
|
-
|
|
36
|
-
expect(fs.existsSync).toHaveBeenCalledWith(mockFolderPath);
|
|
37
|
-
expect(fs.mkdirSync).toHaveBeenCalledWith(mockFolderPath, { recursive: true });
|
|
38
|
-
|
|
39
|
-
expect(fs.existsSync).toHaveBeenCalledWith(mockKeystoresPath);
|
|
40
|
-
expect(fs.mkdirSync).toHaveBeenCalledWith(mockKeystoresPath, { recursive: true });
|
|
41
|
-
|
|
42
|
-
expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFilePath);
|
|
43
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(mockConfigFilePath, JSON.stringify({}, null, 2));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("does not recreate folder or config file if they exist", () => {
|
|
47
|
-
vi.clearAllMocks();
|
|
48
|
-
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
49
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({}));
|
|
50
|
-
|
|
51
|
-
new ConfigFileManager();
|
|
52
|
-
|
|
53
|
-
expect(fs.mkdirSync).not.toHaveBeenCalled();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("getFolderPath returns the correct folder path", () => {
|
|
57
|
-
expect(configFileManager.getFolderPath()).toBe(mockFolderPath);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("getFilePath returns the correct file path for a given file name", () => {
|
|
61
|
-
const fileName = "example.json";
|
|
62
|
-
const expectedFilePath = path.resolve(mockFolderPath, fileName);
|
|
63
|
-
|
|
64
|
-
expect(configFileManager.getFilePath(fileName)).toBe(expectedFilePath);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("getConfig returns the parsed content of the config file", () => {
|
|
68
|
-
const mockConfig = { key: "value" };
|
|
69
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
70
|
-
|
|
71
|
-
const config = configFileManager.getConfig();
|
|
72
|
-
|
|
73
|
-
expect(fs.readFileSync).toHaveBeenCalledWith(mockConfigFilePath, "utf-8");
|
|
74
|
-
expect(config).toEqual(mockConfig);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("getConfigByKey returns the value for a given key", () => {
|
|
78
|
-
const mockConfig = { key: "value" };
|
|
79
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
80
|
-
|
|
81
|
-
const value = configFileManager.getConfigByKey("key");
|
|
82
|
-
|
|
83
|
-
expect(value).toBe("value");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("getConfigByKey returns null for a non-existing key", () => {
|
|
87
|
-
const mockConfig = { key: "value" };
|
|
88
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
89
|
-
|
|
90
|
-
const value = configFileManager.getConfigByKey("nonExistingKey");
|
|
91
|
-
|
|
92
|
-
expect(value).toBeNull();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("writeConfig updates the config file with a new key-value pair", () => {
|
|
96
|
-
const mockConfig = { existingKey: "existingValue" };
|
|
97
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
|
|
98
|
-
|
|
99
|
-
configFileManager.writeConfig("newKey", "newValue");
|
|
100
|
-
|
|
101
|
-
const expectedConfig = { existingKey: "existingValue", newKey: "newValue" };
|
|
102
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
103
|
-
mockConfigFilePath,
|
|
104
|
-
JSON.stringify(expectedConfig, null, 2)
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("writeConfig overwrites an existing key in the config file", () => {
|
|
109
|
-
const existingConfig = { existingKey: "existingValue" };
|
|
110
|
-
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(existingConfig));
|
|
111
|
-
|
|
112
|
-
configFileManager.writeConfig("existingKey", "newValue");
|
|
113
|
-
|
|
114
|
-
const expectedConfig = { existingKey: "newValue" };
|
|
115
|
-
expect(fs.writeFileSync).toHaveBeenCalledWith(mockConfigFilePath, JSON.stringify(expectedConfig, null, 2));
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, beforeEach, test, expect, vi, Mock } from "vitest";
|
|
2
|
-
import fetch from "node-fetch";
|
|
3
|
-
import { JsonRpcClient, JsonRPCParams } from "../../src/lib/clients/jsonRpcClient";
|
|
4
|
-
|
|
5
|
-
vi.mock("node-fetch", () => ({
|
|
6
|
-
default: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
|
|
9
|
-
describe("JsonRpcClient - Successful and Unsuccessful Requests", () => {
|
|
10
|
-
let rpcClient: JsonRpcClient;
|
|
11
|
-
const mockServerUrl = "http://mock-server-url.com";
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
rpcClient = new JsonRpcClient(mockServerUrl);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("should make a successful JSON-RPC request and return the JSON response", async () => {
|
|
18
|
-
const mockResponse = { result: "success" };
|
|
19
|
-
|
|
20
|
-
(fetch as Mock).mockResolvedValueOnce({
|
|
21
|
-
ok: true,
|
|
22
|
-
json: async () => mockResponse,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const params: JsonRPCParams = {
|
|
26
|
-
method: "testMethod",
|
|
27
|
-
params: ["param1", "param2"],
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const response = await rpcClient.request(params);
|
|
31
|
-
|
|
32
|
-
expect(fetch).toHaveBeenCalledWith(mockServerUrl, {
|
|
33
|
-
method: "POST",
|
|
34
|
-
headers: { "Content-Type": "application/json" },
|
|
35
|
-
body: expect.stringContaining('"method":"testMethod"'),
|
|
36
|
-
});
|
|
37
|
-
expect(response).toEqual(mockResponse);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("should return null when the fetch response is not ok", async () => {
|
|
41
|
-
(fetch as Mock).mockResolvedValueOnce({
|
|
42
|
-
ok: false,
|
|
43
|
-
statusText: "Something went wrong",
|
|
44
|
-
json: async () => ({ error: "Something went wrong" }),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const params: JsonRPCParams = {
|
|
48
|
-
method: "testMethod",
|
|
49
|
-
params: ["param1", "param2"],
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
await expect(rpcClient.request(params)).rejects.toThrowError("Something went wrong");
|
|
53
|
-
expect(fetch).toHaveBeenCalledWith(mockServerUrl, {
|
|
54
|
-
method: "POST",
|
|
55
|
-
headers: { "Content-Type": "application/json" },
|
|
56
|
-
body: expect.stringContaining('"method":"testMethod"'),
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
});
|