genlayer 0.5.1-beta.0 → 0.7.0

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.
@@ -2,7 +2,7 @@ version: "3.8"
2
2
 
3
3
  services:
4
4
  frontend:
5
- image: yeagerai/simulator-frontend:latest
5
+ image: yeagerai/simulator-frontend:${LOCALNETVERSION:-latest}
6
6
  ports:
7
7
  - "${FRONTEND_PORT:-8080}:8080"
8
8
  environment:
@@ -25,7 +25,7 @@ services:
25
25
 
26
26
 
27
27
  jsonrpc:
28
- image: yeagerai/simulator-jsonrpc:latest
28
+ image: yeagerai/simulator-jsonrpc:${LOCALNETVERSION:-latest}
29
29
  environment:
30
30
  - FLASK_SERVER_PORT=${RPCPORT:-5000}
31
31
  - PYTHONUNBUFFERED=1
@@ -63,7 +63,7 @@ services:
63
63
  replicas: ${JSONRPC_REPLICAS:-1}
64
64
 
65
65
  webrequest:
66
- image: yeagerai/simulator-webrequest:latest
66
+ image: yeagerai/simulator-webrequest:${LOCALNETVERSION:-latest}
67
67
  shm_size: 2gb
68
68
  environment:
69
69
  - FLASK_SERVER_PORT=${WEBREQUESTPORT:-5002}
@@ -131,7 +131,7 @@ services:
131
131
  # - "./data/postgres:/var/lib/postgresql/data"
132
132
 
133
133
  database-migration:
134
- image: yeagerai/simulator-database-migration:latest
134
+ image: yeagerai/simulator-database-migration:${LOCALNETVERSION:-latest}
135
135
  environment:
136
136
  - DB_URL=postgresql://${DBUSER:-postgres}:${DBPASSWORD:-postgres}@postgres/${DBNAME:-simulator_db}
137
137
  - WEBREQUESTPORT=${WEBREQUESTPORT:-5002}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genlayer",
3
- "version": "0.5.1-beta.0",
3
+ "version": "0.7.0",
4
4
  "description": "GenLayer Command Line Tool",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -59,6 +59,7 @@
59
59
  "commander": "^12.0.0",
60
60
  "dockerode": "^4.0.2",
61
61
  "dotenv": "^16.4.5",
62
+ "ethers": "^6.13.4",
62
63
  "inquirer": "^9.2.19",
63
64
  "node-fetch": "^2.7.0",
64
65
  "open": "^10.1.0",
@@ -1,9 +1,8 @@
1
- import {Command} from "commander";
1
+ import { Command } from "commander";
2
2
 
3
3
  import simulatorService from "../../lib/services/simulator";
4
-
5
- import {initAction, InitActionOptions} from "./init";
6
- import {startAction, StartActionOptions} from "./start";
4
+ import { initAction, InitActionOptions } from "./init";
5
+ import { startAction, StartActionOptions } from "./start";
7
6
 
8
7
  export function initializeGeneralCommands(program: Command) {
9
8
  program
@@ -12,6 +11,7 @@ export function initializeGeneralCommands(program: Command) {
12
11
  .option("--numValidators <numValidators>", "Number of validators", "5")
13
12
  .option("--headless", "Headless mode", false)
14
13
  .option("--reset-db", "Reset Database", false)
14
+ .option("--localnet-version <localnetVersion>", "Select a specific localnet version", 'latest')
15
15
  .action((options: InitActionOptions) => initAction(options, simulatorService));
16
16
 
17
17
  program
@@ -6,6 +6,7 @@ export interface InitActionOptions {
6
6
  numValidators: number;
7
7
  headless: boolean;
8
8
  resetDb: boolean;
9
+ localnetVersion: string;
9
10
  }
10
11
 
11
12
  function getRequirementsErrorMessage({docker}: Record<string, boolean>): string {
@@ -34,6 +35,11 @@ function getVersionErrorMessage({docker, node}: Record<string, string>): string
34
35
  export async function initAction(options: InitActionOptions, simulatorService: ISimulatorService) {
35
36
  simulatorService.setComposeOptions(options.headless);
36
37
 
38
+ let localnetVersion = options.localnetVersion;
39
+
40
+ if(localnetVersion !== 'latest'){
41
+ localnetVersion = simulatorService.normalizeLocalnetVersion(localnetVersion);
42
+ }
37
43
  await simulatorService.checkCliVersion();
38
44
 
39
45
  // Check if requirements are installed
@@ -139,6 +145,7 @@ export async function initAction(options: InitActionOptions, simulatorService: I
139
145
 
140
146
  console.log("Configuring GenLayer Simulator environment...");
141
147
  simulatorService.addConfigToEnvFile(aiProvidersEnvVars);
148
+ simulatorService.addConfigToEnvFile({LOCALNETVERSION: localnetVersion});
142
149
 
143
150
 
144
151
  // Run the GenLayer Simulator
@@ -0,0 +1,44 @@
1
+ import { writeFileSync, existsSync } from "fs";
2
+ import { ethers } from "ethers";
3
+ import { ConfigFileManager } from "../../lib/config/ConfigFileManager";
4
+
5
+ export interface CreateKeypairOptions {
6
+ output: string;
7
+ overwrite: boolean;
8
+ }
9
+
10
+ export class KeypairCreator {
11
+ private filePathManager: ConfigFileManager;
12
+
13
+ constructor() {
14
+ this.filePathManager = new ConfigFileManager();
15
+ }
16
+
17
+ createKeypairAction(options: CreateKeypairOptions) {
18
+ try {
19
+
20
+ const outputPath = this.filePathManager.getFilePath(options.output);
21
+
22
+ if(existsSync(outputPath) && !options.overwrite) {
23
+ console.warn(
24
+ `The file at ${outputPath} already exists. Use the '--overwrite' option to replace it.`
25
+ );
26
+ return;
27
+ }
28
+
29
+ const wallet = ethers.Wallet.createRandom();
30
+ const keypairData = {
31
+ address: wallet.address,
32
+ privateKey: wallet.privateKey,
33
+ };
34
+
35
+ writeFileSync(outputPath, JSON.stringify(keypairData, null, 2));
36
+
37
+ this.filePathManager.writeConfig('keyPairPath', outputPath);
38
+ console.log(`Keypair successfully created and saved to: ${outputPath}`);
39
+ } catch (error) {
40
+ console.error("Failed to generate keypair:", error);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,21 @@
1
+ import { Command } from "commander";
2
+ import { CreateKeypairOptions, KeypairCreator } from "./create";
3
+
4
+ export function initializeKeygenCommands(program: Command) {
5
+
6
+ const keygenCommand = program
7
+ .command("keygen")
8
+ .description("Manage keypair generation");
9
+
10
+ keygenCommand
11
+ .command("create")
12
+ .description("Generates a new keypair and saves it to a file")
13
+ .option("--output <path>", "Path to save the keypair", "./keypair.json")
14
+ .option("--overwrite", "Overwrite the existing file if it already exists", false)
15
+ .action((options: CreateKeypairOptions) => {
16
+ const keypairCreator = new KeypairCreator();
17
+ keypairCreator.createKeypairAction(options);
18
+ });
19
+
20
+ return program;
21
+ }
package/src/index.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  import {program} from "commander";
3
3
  import {version} from "../package.json";
4
4
  import {CLI_DESCRIPTION} from "../src/lib/config/text";
5
- import {initializeGeneralCommands} from "../src/commands/general";
5
+ import { initializeGeneralCommands } from "../src/commands/general";
6
+ import { initializeKeygenCommands } from "../src/commands/keygen";
6
7
 
7
8
  export function initializeCLI() {
8
9
  program.version(version).description(CLI_DESCRIPTION);
9
10
  initializeGeneralCommands(program);
11
+ initializeKeygenCommands(program);
10
12
  program.parse(process.argv);
11
13
  }
12
14
 
@@ -0,0 +1,51 @@
1
+ import path from "path";
2
+ import os from "os";
3
+ import fs from "fs";
4
+
5
+ export class ConfigFileManager {
6
+ private folderPath: string;
7
+ private configFilePath: string;
8
+
9
+ constructor(baseFolder: string = ".genlayer/", configFileName: string = "genlayer-config.json") {
10
+ this.folderPath = path.resolve(os.homedir(), baseFolder);
11
+ this.configFilePath = path.resolve(this.folderPath, configFileName);
12
+ this.ensureFolderExists();
13
+ this.ensureConfigFileExists();
14
+ }
15
+
16
+ private ensureFolderExists(): void {
17
+ if (!fs.existsSync(this.folderPath)) {
18
+ fs.mkdirSync(this.folderPath, { recursive: true });
19
+ }
20
+ }
21
+
22
+ private ensureConfigFileExists(): void {
23
+ if (!fs.existsSync(this.configFilePath)) {
24
+ fs.writeFileSync(this.configFilePath, JSON.stringify({}, null, 2));
25
+ }
26
+ }
27
+
28
+ getFolderPath(): string {
29
+ return this.folderPath;
30
+ }
31
+
32
+ getFilePath(fileName: string): string {
33
+ return path.resolve(this.folderPath, fileName);
34
+ }
35
+
36
+ getConfig(): Record<string, any> {
37
+ const configContent = fs.readFileSync(this.configFilePath, "utf-8");
38
+ return JSON.parse(configContent);
39
+ }
40
+
41
+ getConfigByKey(key: string): any {
42
+ const config = this.getConfig();
43
+ return config[key] !== undefined ? config[key] : null;
44
+ }
45
+
46
+ writeConfig(key: string, value: any): void {
47
+ const config = this.getConfig();
48
+ config[key] = value;
49
+ fs.writeFileSync(this.configFilePath, JSON.stringify(config, null, 2));
50
+ }
51
+ }
@@ -18,6 +18,7 @@ export interface ISimulatorService {
18
18
  checkCliVersion(): Promise<void>;
19
19
  cleanDatabase(): Promise<boolean>;
20
20
  addConfigToEnvFile(newConfig: Record<string, string>): void;
21
+ normalizeLocalnetVersion(version: string): string;
21
22
  }
22
23
 
23
24
 
@@ -295,6 +295,23 @@ export class SimulatorService implements ISimulatorService {
295
295
  return true;
296
296
  }
297
297
 
298
+ public normalizeLocalnetVersion(version: string) {
299
+
300
+ if (!version.startsWith('v')) {
301
+ version = 'v' + version;
302
+ }
303
+
304
+ const versionRegex = /^v(\d+)\.(\d+)\.(\d+)(-.+)?$/;
305
+ const match = version.match(versionRegex);
306
+
307
+ if (!match) {
308
+ console.error('Invalid version format. Expected format: v0.0.0 or v0.0.0-suffix');
309
+ process.exit(1);
310
+ }
311
+
312
+ return version
313
+ }
314
+
298
315
  }
299
316
 
300
317
  export default new SimulatorService();
@@ -0,0 +1,140 @@
1
+ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest";
2
+ import { KeypairCreator } from "../../src/commands/keygen/create";
3
+ import { writeFileSync, existsSync } from "fs";
4
+ import { ethers } from "ethers";
5
+
6
+ vi.mock("fs");
7
+
8
+ vi.mock("ethers", () => ({
9
+ ethers: {
10
+ Wallet: {
11
+ createRandom: vi.fn(),
12
+ },
13
+ },
14
+ }));
15
+
16
+ vi.mock("../../src/lib/config/ConfigFileManager", () => ({
17
+ ConfigFileManager: vi.fn().mockImplementation(() => ({
18
+ getFilePath: vi.fn((fileName) => `/mocked/path/${fileName}`),
19
+ writeConfig: vi.fn(),
20
+ })),
21
+ }));
22
+
23
+ describe("KeypairCreator", () => {
24
+ let keypairCreator: KeypairCreator;
25
+
26
+ const mockWallet: any = {
27
+ address: "0xMockedAddress",
28
+ privateKey: "0xMockedPrivateKey",
29
+ };
30
+ keypairCreator = new KeypairCreator();
31
+
32
+ beforeEach(() => {
33
+ vi.clearAllMocks();
34
+ vi.mocked(ethers.Wallet.createRandom).mockReturnValue(mockWallet);
35
+ });
36
+
37
+ afterEach(() => {
38
+ vi.restoreAllMocks();
39
+ });
40
+
41
+ test("successfully creates and saves a keypair", () => {
42
+ const consoleLogSpy = vi.spyOn(console, "log");
43
+ vi.mocked(existsSync).mockReturnValue(false);
44
+ const options = { output: "keypair.json", overwrite: false };
45
+
46
+ keypairCreator.createKeypairAction(options);
47
+
48
+ expect(ethers.Wallet.createRandom).toHaveBeenCalledTimes(1);
49
+
50
+ expect(keypairCreator["filePathManager"].getFilePath).toHaveBeenCalledWith("keypair.json");
51
+
52
+ expect(writeFileSync).toHaveBeenCalledWith(
53
+ "/mocked/path/keypair.json",
54
+ JSON.stringify(
55
+ {
56
+ address: mockWallet.address,
57
+ privateKey: mockWallet.privateKey,
58
+ },
59
+ null,
60
+ 2
61
+ )
62
+ );
63
+
64
+ expect(keypairCreator["filePathManager"].writeConfig).toHaveBeenCalledWith(
65
+ "keyPairPath",
66
+ "/mocked/path/keypair.json"
67
+ );
68
+
69
+ expect(consoleLogSpy).toHaveBeenCalledWith(
70
+ "Keypair successfully created and saved to: /mocked/path/keypair.json"
71
+ );
72
+ });
73
+
74
+ test("skips creation if file exists and overwrite is false", () => {
75
+
76
+
77
+ const consoleWarnSpy = vi.spyOn(console, "warn");
78
+ vi.mocked(existsSync).mockReturnValue(true);
79
+ const options = { output: "keypair.json", overwrite: false };
80
+
81
+ keypairCreator.createKeypairAction(options);
82
+
83
+ expect(ethers.Wallet.createRandom).not.toHaveBeenCalled();
84
+ expect(writeFileSync).not.toHaveBeenCalled();
85
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
86
+ "The file at /mocked/path/keypair.json already exists. Use the '--overwrite' option to replace it."
87
+ );
88
+ });
89
+
90
+ test("overwrites the file if overwrite is true", () => {
91
+ const consoleLogSpy = vi.spyOn(console, "log");
92
+ vi.mocked(existsSync).mockReturnValue(true); // Simulate file exists
93
+ const options = { output: "keypair.json", overwrite: true };
94
+
95
+ keypairCreator.createKeypairAction(options);
96
+
97
+ expect(ethers.Wallet.createRandom).toHaveBeenCalledTimes(1);
98
+
99
+ expect(keypairCreator["filePathManager"].getFilePath).toHaveBeenCalledWith("keypair.json");
100
+
101
+ expect(writeFileSync).toHaveBeenCalledWith(
102
+ "/mocked/path/keypair.json",
103
+ JSON.stringify(
104
+ {
105
+ address: mockWallet.address,
106
+ privateKey: mockWallet.privateKey,
107
+ },
108
+ null,
109
+ 2
110
+ )
111
+ );
112
+
113
+ expect(keypairCreator["filePathManager"].writeConfig).toHaveBeenCalledWith(
114
+ "keyPairPath",
115
+ "/mocked/path/keypair.json"
116
+ );
117
+
118
+ expect(consoleLogSpy).toHaveBeenCalledWith(
119
+ "Keypair successfully created and saved to: /mocked/path/keypair.json"
120
+ );
121
+ });
122
+
123
+ test("handles errors during keypair creation", () => {
124
+ const consoleErrorSpy = vi.spyOn(console, "error");
125
+ const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
126
+ throw new Error("process.exit");
127
+ });
128
+
129
+ vi.mocked(writeFileSync).mockImplementation(() => {
130
+ throw new Error("Mocked write error");
131
+ });
132
+
133
+ expect(() => {
134
+ keypairCreator.createKeypairAction({ output: "keypair.json", overwrite: true });
135
+ }).toThrowError("process.exit");
136
+
137
+ expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to generate keypair:", expect.any(Error));
138
+ expect(processExitSpy).toHaveBeenCalledWith(1);
139
+ });
140
+ });
@@ -14,7 +14,7 @@ vi.mock("dotenv");
14
14
 
15
15
 
16
16
  const tempDir = mkdtempSync(join(tmpdir(), "test-initAction-"));
17
- const defaultActionOptions = { numValidators: 5, branch: "main", location: tempDir, headless: false, resetDb: false };
17
+ const defaultActionOptions = { numValidators: 5, branch: "main", location: tempDir, headless: false, resetDb: false, localnetVersion: 'latest' };
18
18
 
19
19
  describe("init action", () => {
20
20
  let error: ReturnType<any>;
@@ -201,7 +201,7 @@ describe("init action", () => {
201
201
  simServResetDockerContainers.mockResolvedValue(true);
202
202
  simServResetDockerImages.mockResolvedValue(true);
203
203
 
204
- await initAction({...defaultActionOptions, headless: true, resetDb: true}, simulatorService);
204
+ await initAction({...defaultActionOptions, headless: true, resetDb: true, localnetVersion: "v1.0.0"}, simulatorService);
205
205
 
206
206
  expect(log).toHaveBeenCalledWith(
207
207
  `GenLayer simulator initialized successfully! `
@@ -441,4 +441,5 @@ describe("init action", () => {
441
441
 
442
442
  expect(error).toHaveBeenCalledWith(errorMsg);
443
443
  });
444
+
444
445
  });
@@ -8,7 +8,8 @@ const openFrontendSpy = vi.spyOn(simulatorService, "openFrontend");
8
8
  const defaultOptions = {
9
9
  numValidators: "5",
10
10
  headless: false,
11
- resetDb: false
11
+ resetDb: false,
12
+ localnetVersion: 'latest'
12
13
  }
13
14
 
14
15
  vi.mock("inquirer", () => ({
@@ -73,4 +74,11 @@ describe("init command", () => {
73
74
  expect(action).toHaveBeenCalledWith({...defaultOptions, headless: true});
74
75
  expect(openFrontendSpy).not.toHaveBeenCalled();
75
76
  });
77
+
78
+ test("option --localnet-version is accepted", async () => {
79
+ program.parse(["node", "test", "init", "--localnet-version", "v1.0.0"]);
80
+ expect(action).toHaveBeenCalledTimes(1);
81
+ expect(action).toHaveBeenCalledWith({...defaultOptions, localnetVersion: "v1.0.0"});
82
+ expect(openFrontendSpy).not.toHaveBeenCalled();
83
+ });
76
84
  });
@@ -0,0 +1,77 @@
1
+ import { Command } from "commander";
2
+ import { vi, describe, beforeEach, afterEach, test, expect } from "vitest";
3
+ import { initializeKeygenCommands } from "../../src/commands/keygen";
4
+ import { KeypairCreator } from "../../src/commands/keygen/create";
5
+
6
+ vi.mock("../../src/commands/keygen/create");
7
+
8
+ describe("keygen create command", () => {
9
+ let program: Command;
10
+
11
+ beforeEach(() => {
12
+ program = new Command();
13
+ initializeKeygenCommands(program);
14
+ vi.clearAllMocks();
15
+ });
16
+
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ test("keypairCreator.createKeypairAction is called with default options", async () => {
22
+ program.parse(["node", "test", "keygen", "create"]);
23
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
24
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
25
+ output: "./keypair.json",
26
+ overwrite: false,
27
+ });
28
+ });
29
+
30
+ test("keypairCreator.createKeypairAction is called with custom output option", async () => {
31
+ program.parse(["node", "test", "keygen", "create", "--output", "./custom.json"]);
32
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
33
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
34
+ output: "./custom.json",
35
+ overwrite: false,
36
+ });
37
+ });
38
+
39
+ test("keypairCreator.createKeypairAction is called with overwrite enabled", async () => {
40
+ program.parse(["node", "test", "keygen", "create", "--overwrite"]);
41
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
42
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
43
+ output: "./keypair.json",
44
+ overwrite: true,
45
+ });
46
+ });
47
+
48
+ test("keypairCreator.createKeypairAction is called with custom output and overwrite enabled", async () => {
49
+ program.parse(["node", "test", "keygen", "create", "--output", "./custom.json", "--overwrite"]);
50
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
51
+ expect(KeypairCreator.prototype.createKeypairAction).toHaveBeenCalledWith({
52
+ output: "./custom.json",
53
+ overwrite: true,
54
+ });
55
+ });
56
+
57
+ test("KeypairCreator is instantiated when the command is executed", async () => {
58
+ program.parse(["node", "test", "keygen", "create"]);
59
+ expect(KeypairCreator).toHaveBeenCalledTimes(1);
60
+ });
61
+
62
+ test("throws error for unrecognized options", async () => {
63
+ const keygenCommand = program.commands.find((cmd) => cmd.name() === "keygen");
64
+ const createCommand = keygenCommand?.commands.find((cmd) => cmd.name() === "create");
65
+
66
+ createCommand?.exitOverride();
67
+ expect(() => program.parse(["node", "test", "keygen", "create", "--unknown"])).toThrowError(
68
+ "error: unknown option '--unknown'"
69
+ );
70
+ });
71
+
72
+ test("keypairCreator.createKeypairAction is called without throwing errors for default options", async () => {
73
+ program.parse(["node", "test", "keygen", "create"]);
74
+ vi.mocked(KeypairCreator.prototype.createKeypairAction).mockReturnValue();
75
+ expect(() => program.parse(["node", "test", "keygen", "create"])).not.toThrow();
76
+ });
77
+ });
@@ -13,6 +13,10 @@ vi.mock("../src/commands/general", () => ({
13
13
  initializeGeneralCommands: vi.fn(),
14
14
  }));
15
15
 
16
+ vi.mock("../src/commands/keygen", () => ({
17
+ initializeKeygenCommands: vi.fn(),
18
+ }));
19
+
16
20
 
17
21
  describe("CLI", () => {
18
22
  it("should initialize CLI", () => {
@@ -0,0 +1,113 @@
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 mockConfigFilePath = `${mockFolderPath}/genlayer-config.json`;
14
+
15
+ let configFileManager: ConfigFileManager;
16
+
17
+ beforeEach(() => {
18
+ vi.clearAllMocks();
19
+ vi.mocked(os.homedir).mockReturnValue("/mocked/home");
20
+ configFileManager = new ConfigFileManager();
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+
27
+ test("ensures folder and config file are created if they don't exist", () => {
28
+ vi.mocked(fs.existsSync).mockReturnValue(false);
29
+
30
+ new ConfigFileManager();
31
+
32
+ expect(fs.existsSync).toHaveBeenCalledWith(mockFolderPath);
33
+ expect(fs.mkdirSync).toHaveBeenCalledWith(mockFolderPath, { recursive: true });
34
+
35
+ expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFilePath);
36
+ expect(fs.writeFileSync).toHaveBeenCalledWith(mockConfigFilePath, JSON.stringify({}, null, 2));
37
+ });
38
+
39
+ test("does not recreate folder or config file if they exist", () => {
40
+ vi.clearAllMocks();
41
+ vi.mocked(fs.existsSync).mockReturnValue(true);
42
+
43
+ new ConfigFileManager();
44
+
45
+ expect(fs.mkdirSync).not.toHaveBeenCalled();
46
+ expect(fs.writeFileSync).not.toHaveBeenCalled();
47
+ });
48
+
49
+ test("getFolderPath returns the correct folder path", () => {
50
+ expect(configFileManager.getFolderPath()).toBe(mockFolderPath);
51
+ });
52
+
53
+ test("getFilePath returns the correct file path for a given file name", () => {
54
+ const fileName = "example.json";
55
+ const expectedFilePath = path.resolve(mockFolderPath, fileName);
56
+
57
+ expect(configFileManager.getFilePath(fileName)).toBe(expectedFilePath);
58
+ });
59
+
60
+ test("getConfig returns the parsed content of the config file", () => {
61
+ const mockConfig = { key: "value" };
62
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
63
+
64
+ const config = configFileManager.getConfig();
65
+
66
+ expect(fs.readFileSync).toHaveBeenCalledWith(mockConfigFilePath, "utf-8");
67
+ expect(config).toEqual(mockConfig);
68
+ });
69
+
70
+ test("getConfigByKey returns the value for a given key", () => {
71
+ const mockConfig = { key: "value" };
72
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
73
+
74
+ const value = configFileManager.getConfigByKey("key");
75
+
76
+ expect(value).toBe("value");
77
+ });
78
+
79
+ test("getConfigByKey returns null for a non-existing key", () => {
80
+ const mockConfig = { key: "value" };
81
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
82
+
83
+ const value = configFileManager.getConfigByKey("nonExistingKey");
84
+
85
+ expect(value).toBeNull();
86
+ });
87
+
88
+ test("writeConfig updates the config file with a new key-value pair", () => {
89
+ const mockConfig = { existingKey: "existingValue" };
90
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
91
+
92
+ configFileManager.writeConfig("newKey", "newValue");
93
+
94
+ const expectedConfig = { existingKey: "existingValue", newKey: "newValue" };
95
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
96
+ mockConfigFilePath,
97
+ JSON.stringify(expectedConfig, null, 2)
98
+ );
99
+ });
100
+
101
+ test("writeConfig overwrites an existing key in the config file", () => {
102
+ const mockConfig = { existingKey: "existingValue" };
103
+ vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
104
+
105
+ configFileManager.writeConfig("existingKey", "updatedValue");
106
+
107
+ const expectedConfig = { existingKey: "updatedValue" };
108
+ expect(fs.writeFileSync).toHaveBeenCalledWith(
109
+ mockConfigFilePath,
110
+ JSON.stringify(expectedConfig, null, 2)
111
+ );
112
+ });
113
+ });