UNPKG

genlayer

Version:
706 lines (583 loc) 28.7 kB
import {describe, beforeEach, test, expect, vi, Mock} from "vitest"; import * as path from "path"; import * as fs from "fs"; import * as dotenv from "dotenv"; import simulatorService from "../../src/lib/services/simulator"; import {getVersion, executeCommand, openUrl, checkCommand} from "../../src/lib/clients/system"; import { CONTAINERS_NAME_PREFIX, VERSION_REQUIREMENTS, STARTING_TIMEOUT_ATTEMPTS, DEFAULT_RUN_SIMULATOR_COMMAND, localnetCompatibleVersion, IMAGES_NAME_PREFIX, AiProviders, GENLAYER_REQUIRED_CONTAINERS, } from "../../src/lib/config/simulator"; import {rpcClient} from "../../src/lib/clients/jsonRpcClient"; import * as semver from "semver"; import Docker from "dockerode"; import {VersionRequiredError} from "../../src/lib/errors/versionRequired"; import updateCheck from "update-check"; vi.mock("../../package.json", () => ({ default: {version: "1.0.0", name: "genlayer"}, })); vi.mock("update-check", () => ({ default: vi.fn(), })); vi.mock("dockerode"); vi.mock("fs"); vi.mock("path"); vi.mock("dotenv"); vi.mock("semver", () => ({ satisfies: vi.fn(), })); vi.mock("../../src/lib/clients/system", () => ({ checkCommand: vi.fn(), getVersion: vi.fn(), executeCommand: vi.fn(), openUrl: vi.fn(), listDockerContainers: vi.fn(), stopDockerContainer: vi.fn(), removeDockerContainer: vi.fn(), })); vi.mock("../../src/lib/clients/jsonRpcClient", () => ({ rpcClient: { request: vi.fn(), }, })); describe("SimulatorService - Basic Tests", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(path.join).mockImplementation((...args) => args.join("/")); }); test("should read the correct frontend URL from .env config", () => { const mockEnvContent = "FRONTEND_PORT=8080"; const mockEnvConfig = {FRONTEND_PORT: "8080"}; vi.mocked(fs.readFileSync).mockReturnValue(mockEnvContent); vi.mocked(dotenv.parse).mockReturnValue(mockEnvConfig); const frontendUrl = simulatorService.getFrontendUrl(); expect(frontendUrl).toBe("http://localhost:8080"); }); test("should check version requirements and return missing versions", async () => { vi.mocked(getVersion).mockResolvedValueOnce("12.0.0").mockResolvedValueOnce("18.0.0"); vi.mocked(semver.satisfies).mockImplementation((version, range) => { if (range === VERSION_REQUIREMENTS.node) return version === "18.0.0"; return false; }); const missingVersions = await simulatorService.checkVersionRequirements(); expect(missingVersions.node).toBe(VERSION_REQUIREMENTS.node); expect(missingVersions.docker).toBe(VERSION_REQUIREMENTS.docker); }); test("should handle error when checkVersion throws VersionRequiredError", async () => { vi.mocked(getVersion).mockResolvedValueOnce("10.0.0"); vi.mocked(semver.satisfies).mockReturnValue(false); await expect(simulatorService.checkVersion("14.0.0", "node")).rejects.toThrow(); }); test("should return initialized true when simulator responds with OK (result.status = OK)", async () => { vi.mocked(rpcClient.request).mockResolvedValueOnce({result: {status: "OK"}}); const result = await simulatorService.waitForSimulatorToBeReady(STARTING_TIMEOUT_ATTEMPTS); expect(result).toEqual({initialized: true}); expect(rpcClient.request).toHaveBeenCalledWith({method: "ping", params: []}); }); test("should return initialized true when simulator responds with OK (result.data.status = OK)", async () => { vi.mocked(rpcClient.request).mockResolvedValueOnce({result: {data: {status: "OK"}}}); const result = await simulatorService.waitForSimulatorToBeReady(STARTING_TIMEOUT_ATTEMPTS); expect(result).toEqual({initialized: true}); expect(rpcClient.request).toHaveBeenCalledWith({method: "ping", params: []}); }); test("should return initialized true when simulator responds with OK (result = OK)", async () => { vi.mocked(rpcClient.request).mockResolvedValueOnce({result: "OK"}); const result = await simulatorService.waitForSimulatorToBeReady(STARTING_TIMEOUT_ATTEMPTS); expect(result).toEqual({initialized: true}); expect(rpcClient.request).toHaveBeenCalledWith({method: "ping", params: []}); }); test("should return initialized false with errorCode TIMEOUT after retries", async () => { vi.mocked(rpcClient.request).mockResolvedValue(undefined); const result = await simulatorService.waitForSimulatorToBeReady(1); expect(result).toEqual({initialized: false, errorCode: "TIMEOUT"}); }); test("should return initialized false with errorCode ERROR on non-retryable error", async () => { const nonRetryableError = new Error("Unexpected error"); vi.mocked(rpcClient.request).mockRejectedValue(nonRetryableError); const result = await simulatorService.waitForSimulatorToBeReady(STARTING_TIMEOUT_ATTEMPTS); expect(result).toEqual({initialized: false, errorCode: "ERROR", errorMessage: nonRetryableError.message}); }); test("should execute the correct run simulator command based on simulator location", async () => { (executeCommand as Mock).mockResolvedValue({ stdout: "Simulator started", stderr: "", }); const result = await simulatorService.runSimulator(); const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(simulatorService.location, ""); expect(executeCommand).toHaveBeenCalledWith(expectedCommand); expect(result).toEqual({stdout: "Simulator started", stderr: ""}); }); test("should execute the correct run simulator command based on headless option", async () => { (executeCommand as Mock).mockResolvedValue({ stdout: "Simulator started", stderr: "", }); simulatorService.setComposeOptions(true); const commandOption = simulatorService.getComposeOptions(); const result = await simulatorService.runSimulator(); const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(simulatorService.location, commandOption); expect(executeCommand).toHaveBeenCalledWith(expectedCommand); expect(result).toEqual({stdout: "Simulator started", stderr: ""}); }); test("should execute the correct run simulator command based on ollama option", async () => { (executeCommand as Mock).mockResolvedValue({ stdout: "Simulator started", stderr: "", }); simulatorService.setComposeOptions(false, true); let commandOptions = simulatorService.getComposeOptions(); expect(commandOptions).toBe("--profile frontend --profile ollama"); simulatorService.setComposeOptions(true, true); commandOptions = simulatorService.getComposeOptions(); expect(commandOptions).toBe("--profile ollama"); simulatorService.setComposeOptions(false, false); commandOptions = simulatorService.getComposeOptions(); expect(commandOptions).toBe("--profile frontend"); simulatorService.setComposeOptions(true, false); commandOptions = simulatorService.getComposeOptions(); expect(commandOptions).toBe(""); await simulatorService.runSimulator(); const expectedCommand = DEFAULT_RUN_SIMULATOR_COMMAND(simulatorService.location, commandOptions); expect(executeCommand).toHaveBeenCalledWith(expectedCommand); }); test("should create a backup of the .env file and add new config", () => { const envFilePath = `/.env`; const originalEnvContent = "KEY1=value1\nKEY2=value2"; const parsedEnvConfig = {KEY1: "value1", KEY2: "value2"}; const newConfig = {KEY3: "value3", KEY2: "newValue2"}; vi.mocked(fs.readFileSync).mockImplementation((filePath: any) => { if (filePath === envFilePath) return originalEnvContent; return ""; }); vi.mocked(dotenv.parse).mockReturnValue(parsedEnvConfig); const writeFileSyncMock = vi.mocked(fs.writeFileSync); simulatorService.addConfigToEnvFile(newConfig); const expectedUpdatedContent = `KEY1=value1\nKEY2=newValue2\nKEY3=value3`; expect(writeFileSyncMock).toHaveBeenCalledWith(envFilePath, expectedUpdatedContent); }); test("should handle empty .env file and add new config", () => { const envFilePath = `/.env`; const newConfig = {NEW_KEY: "newValue"}; vi.mocked(fs.readFileSync).mockReturnValue(""); vi.mocked(dotenv.parse).mockReturnValue({}); const writeFileSyncMock = vi.mocked(fs.writeFileSync); simulatorService.addConfigToEnvFile(newConfig); const expectedUpdatedContent = `NEW_KEY=newValue`; expect(writeFileSyncMock).toHaveBeenCalledWith(envFilePath, expectedUpdatedContent); }); test("should throw error when .env file does not exist", () => { vi.mocked(fs.readFileSync).mockImplementation(() => { throw new Error("File not found"); }); expect(() => simulatorService.addConfigToEnvFile({KEY: "value"})).toThrow("File not found"); }); test("should open the frontend URL and return true", async () => { vi.spyOn(simulatorService, "getFrontendUrl").mockReturnValue("http://localhost:8080"); const result = await simulatorService.openFrontend(); expect(simulatorService.getFrontendUrl).toHaveBeenCalled(); expect(openUrl).toHaveBeenCalledWith("http://localhost:8080"); expect(result).toBe(true); }); test("should call rpcClient.request with correct parameters and return the response", async () => { const mockResponse = {success: true}; vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); const result = await simulatorService.deleteAllValidators(); expect(rpcClient.request).toHaveBeenCalledWith({method: "sim_deleteAllValidators", params: []}); expect(result).toBe(mockResponse); }); test("should return node missing version", async () => { const unexpectedError = new VersionRequiredError("node", VERSION_REQUIREMENTS.node); vi.spyOn(simulatorService, "checkVersion").mockRejectedValueOnce(unexpectedError).mockResolvedValueOnce(); await expect(simulatorService.checkVersionRequirements()).resolves.toStrictEqual({ docker: "", node: VERSION_REQUIREMENTS.node, }); }); test("should return docker missing version", async () => { const unexpectedError = new VersionRequiredError("node", VERSION_REQUIREMENTS.docker); vi.spyOn(simulatorService, "checkVersion").mockResolvedValueOnce().mockRejectedValueOnce(unexpectedError); await expect(simulatorService.checkVersionRequirements()).resolves.toStrictEqual({ docker: VERSION_REQUIREMENTS.docker, node: "", }); }); test("should throw an unexpected error when checking node version requirements", async () => { const unexpectedError = new Error("Unexpected error (node)"); vi.spyOn(simulatorService, "checkVersion").mockRejectedValueOnce(unexpectedError); await expect(simulatorService.checkVersionRequirements()).rejects.toThrow("Unexpected error (node)"); }); test("should throw an unexpected error when checking docker version requirements", async () => { vi.spyOn(simulatorService, "checkVersion") .mockResolvedValueOnce(undefined) .mockRejectedValueOnce(new Error("Unexpected error (docker)")); await expect(simulatorService.checkVersionRequirements()).rejects.toThrow("Unexpected error (docker)"); }); test("should throw an unexpected error when checking git installation requirement", async () => { vi.mocked(checkCommand).mockRejectedValueOnce(new Error("Unexpected git error")); await expect(simulatorService.checkInstallRequirements()).rejects.toThrow("Unexpected git error"); const requirementsInstalled = {git: false, docker: false}; expect(requirementsInstalled.git).toBe(false); }); test("should retry when response is not 'OK' and reach sleep path", async () => { vi.mocked(rpcClient.request).mockResolvedValue({result: {status: "NOT_OK"}}); const result = await simulatorService.waitForSimulatorToBeReady(1); expect(result).toEqual({initialized: false, errorCode: "TIMEOUT"}); }); test("should retry on fetch error and reach sleep path", async () => { const fetchError = new Error("Fetch Error"); fetchError.name = "FetchError"; vi.mocked(rpcClient.request).mockRejectedValue(fetchError); const result = await simulatorService.waitForSimulatorToBeReady(1); expect(result).toEqual({initialized: false, errorCode: "ERROR", errorMessage: fetchError.message}); }); test("should call executeCommand if docker ps command fails", async () => { vi.mocked(checkCommand).mockResolvedValueOnce(undefined); const result = await simulatorService.checkInstallRequirements(); expect(result.docker).toBe(true); }); test("should return providers without errors", () => { expect(simulatorService.getAiProvidersOptions(true)).toEqual(expect.any(Array)); expect(simulatorService.getAiProvidersOptions(false)).toEqual(expect.any(Array)); }); test("should exclude specified providers from the options list", () => { const allProviders = simulatorService.getAiProvidersOptions(false); const providersWithoutOllama = simulatorService.getAiProvidersOptions(false, ["ollama"]); expect(providersWithoutOllama.length).toBeLessThan(allProviders.length); const ollamaProvider = providersWithoutOllama.find(p => p.value === "ollama"); expect(ollamaProvider).toBeUndefined(); const openaiProvider = providersWithoutOllama.find(p => p.value === "openai"); expect(openaiProvider).toBeDefined(); }); test("should exclude multiple providers when specified", () => { const providersWithoutMultiple = simulatorService.getAiProvidersOptions(false, ["ollama", "openai"]); const ollamaProvider = providersWithoutMultiple.find(p => p.value === "ollama"); const openaiProvider = providersWithoutMultiple.find(p => p.value === "openai"); expect(ollamaProvider).toBeUndefined(); expect(openaiProvider).toBeUndefined(); const heuristaiProvider = providersWithoutMultiple.find(p => p.value === "heuristai"); expect(heuristaiProvider).toBeDefined(); }); test("clean simulator should success", async () => { vi.mocked(rpcClient.request).mockResolvedValueOnce("Success"); await expect(simulatorService.cleanDatabase).not.toThrow(); expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_clearDbTables", params: [["current_state", "transactions"]], }); }); test("should create random validators", async () => { const numValidators = 5; const llmProviders = ["openai", "ollama"] as AiProviders[]; const mockResponse = {success: true}; vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); const result = await simulatorService.createRandomValidators(numValidators, llmProviders); expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_createRandomValidators", params: [numValidators, 1, 10, llmProviders], }); expect(result).toEqual(mockResponse); }); }); describe("SimulatorService - Docker Tests", () => { let mockGetContainer: Mock; let mockListContainers: Mock; let mockListImages: Mock; let mockGetImage: Mock; let mockPing: Mock; beforeEach(() => { vi.clearAllMocks(); mockGetContainer = vi.mocked(Docker.prototype.getContainer); mockListContainers = vi.mocked(Docker.prototype.listContainers); mockListImages = vi.mocked(Docker.prototype.listImages); mockGetImage = vi.mocked(Docker.prototype.getImage); mockPing = vi.mocked(Docker.prototype.ping); }); test("isLocalnetRunning should return true when all required containers are running", async () => { const mockContainers = [ {Id: "container1", Names: ["/genlayer-jsonrpc1"], State: "running"}, {Id: "container2", Names: ["/genlayer-webrequest1"], State: "running"}, {Id: "container3", Names: ["/genlayer-postgres1"], State: "running"}, {Id: "container4", Names: ["/genlayer-other-container1"], State: "running"}, {Id: "container5", Names: ["/genlayer-another-container1"], State: "exited"}, ]; mockListContainers.mockResolvedValue(mockContainers); const result = await simulatorService.isLocalnetRunning(); expect(result).toBe(true); }); test("isLocalnetRunning should return false when not all required containers are running", async () => { const mockContainers = [ {Id: "container1", Names: ["/genlayer-jsonrpc2"], State: "running"}, {Id: "container2", Names: ["/genlayer-webrequest2"], State: "running"}, {Id: "container3", Names: ["/genlayer-postgres2"], State: "exited"}, {Id: "container4", Names: ["/genlayer-other-container2"], State: "running"}, {Id: "container5", Names: ["/unrelated-container2"], State: "running"}, ]; mockListContainers.mockResolvedValue(mockContainers); const result = await simulatorService.isLocalnetRunning(); expect(result).toBe(false); }); test("should stop and remove Docker containers with the specified prefix", async () => { const mockContainers = [ { Id: "container1", Names: [`${CONTAINERS_NAME_PREFIX}container1`], State: "running", }, { Id: "container2", Names: [`${CONTAINERS_NAME_PREFIX}container2`], State: "exited", }, { Id: "container3", Names: ["/unrelated-container"], State: "running", }, ]; mockListContainers.mockResolvedValue(mockContainers); const mockStop = vi.fn().mockResolvedValue(undefined); const mockRemove = vi.fn().mockResolvedValue(undefined); mockGetContainer.mockImplementation( () => ({ stop: mockStop, remove: mockRemove, }) as unknown as Docker.Container, ); const result = await simulatorService.resetDockerContainers(); expect(result).toBe(undefined); expect(mockListContainers).toHaveBeenCalledWith({all: true}); // Ensure only the relevant containers were stopped and removed expect(mockGetContainer).toHaveBeenCalledWith("container1"); expect(mockGetContainer).toHaveBeenCalledWith("container2"); expect(mockGetContainer).not.toHaveBeenCalledWith("container3"); expect(mockStop).toHaveBeenCalledTimes(1); expect(mockRemove).toHaveBeenCalledTimes(2); }); test("should stop all running GenLayer containers", async () => { const mockContainers = [ { Id: "container1", Names: [`${CONTAINERS_NAME_PREFIX}container1`], State: "running", }, { Id: "container2", Names: [`${CONTAINERS_NAME_PREFIX}container2`], State: "exited", }, ]; vi.mocked(Docker.prototype.listContainers).mockResolvedValue(mockContainers as any); const mockStop = vi.fn().mockResolvedValue(undefined); const mockGetContainer = vi.mocked(Docker.prototype.getContainer); mockGetContainer.mockImplementation( () => ({ stop: mockStop, }) as unknown as Docker.Container, ); await simulatorService.stopDockerContainers(); expect(mockGetContainer).toHaveBeenCalledWith("container1"); expect(mockGetContainer).toHaveBeenCalledWith("container2"); expect(mockStop).toHaveBeenCalledTimes(1); }); test("should remove Docker images with the specified prefix", async () => { const mockImages = [ { Id: "image1", RepoTags: [`${IMAGES_NAME_PREFIX}image1:${localnetCompatibleVersion}`], }, { Id: "image2", RepoTags: [`${IMAGES_NAME_PREFIX}image2:${localnetCompatibleVersion}`], }, { Id: "image3", RepoTags: ["unrelated-image:latest"], }, ]; mockListImages.mockResolvedValue(mockImages); const mockRemove = vi.fn().mockResolvedValue(undefined); mockGetImage.mockImplementation( () => ({ remove: mockRemove, }) as unknown as Docker.Image, ); const result = await simulatorService.resetDockerImages(); expect(result).toBe(undefined); expect(mockListImages).toHaveBeenCalled(); expect(mockGetImage).toHaveBeenCalledWith("image1"); expect(mockGetImage).toHaveBeenCalledWith("image2"); expect(mockGetImage).not.toHaveBeenCalledWith("image3"); expect(mockRemove).toHaveBeenCalledTimes(2); expect(mockRemove).toHaveBeenCalledWith({force: true}); }); test("should remove Docker volumes with genlayer_ prefix", async () => { const mockVolumes = { Volumes: [ { Name: "genlayer_volume1" }, { Name: "genlayer_postgres" }, { Name: "genlayer_data" }, { Name: "unrelated_volume" }, { Name: "another_volume" }, { Name: "hardhat_artifacts" }, ], Warnings: [], }; const mockListVolumes = vi.mocked(Docker.prototype.listVolumes); const mockGetVolume = vi.mocked(Docker.prototype.getVolume); mockListVolumes.mockResolvedValue(mockVolumes as any); const mockRemove = vi.fn().mockResolvedValue(undefined); mockGetVolume.mockImplementation( () => ({ remove: mockRemove, }) as unknown as Docker.Volume, ); const result = await simulatorService.resetDockerVolumes(); expect(result).toBe(undefined); expect(mockListVolumes).toHaveBeenCalled(); expect(mockGetVolume).toHaveBeenCalledWith("genlayer_volume1"); expect(mockGetVolume).toHaveBeenCalledWith("genlayer_postgres"); expect(mockGetVolume).toHaveBeenCalledWith("genlayer_data"); expect(mockGetVolume).not.toHaveBeenCalledWith("unrelated_volume"); expect(mockGetVolume).not.toHaveBeenCalledWith("another_volume"); expect(mockGetVolume).not.toHaveBeenCalledWith("hardhat_artifacts"); expect(mockRemove).toHaveBeenCalledTimes(3); expect(mockRemove).toHaveBeenCalledWith({force: true}); }); test("should execute command when docker is installed but is not available", async () => { vi.mocked(checkCommand).mockResolvedValueOnce(undefined); mockPing.mockRejectedValueOnce(""); await simulatorService.checkInstallRequirements(); expect(executeCommand).toHaveBeenCalledTimes(1); }); test("should call execute command again to start docker service", async () => { vi.mocked(checkCommand).mockResolvedValueOnce(undefined).mockRejectedValue(undefined); mockPing.mockRejectedValueOnce(""); await expect(simulatorService.checkInstallRequirements()).resolves.toStrictEqual({docker: true}); }); test("should warn the user when an update is available", async () => { const update = {latest: "1.1.0"}; (updateCheck as any).mockResolvedValue(update); const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); await simulatorService.checkCliVersion(); expect(consoleWarnSpy).toHaveBeenCalledWith( `\nA new version (${update.latest}) is available! You're using version 1.0.0.\nRun npm install -g genlayer to update\n`, ); consoleWarnSpy.mockRestore(); }); test("should not warn the user when the CLI is up-to-date", async () => { const update = {latest: "1.0.0"}; (updateCheck as any).mockResolvedValue(update); const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); await simulatorService.checkCliVersion(); expect(consoleWarnSpy).not.toHaveBeenCalled(); consoleWarnSpy.mockRestore(); }); test("should handle update-check returning undefined", async () => { (updateCheck as any).mockResolvedValue(undefined); const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); await simulatorService.checkCliVersion(); expect(consoleWarnSpy).not.toHaveBeenCalled(); consoleWarnSpy.mockRestore(); }); }); describe("normalizeLocalnetVersion", () => { test('should add "v" if not present', () => { expect(simulatorService.normalizeLocalnetVersion("0.26.0")).toBe("v0.26.0"); }); test('should preserve "v" if already present', () => { expect(simulatorService.normalizeLocalnetVersion("v0.26.0")).toBe("v0.26.0"); }); test('should retain suffixes like "-test000"', () => { expect(simulatorService.normalizeLocalnetVersion("0.25.0-test000")).toBe("v0.25.0-test000"); expect(simulatorService.normalizeLocalnetVersion("v1.0.0-alpha")).toBe("v1.0.0-alpha"); }); test("should handle versions with numbers only", () => { expect(simulatorService.normalizeLocalnetVersion("1.0.0")).toBe("v1.0.0"); }); test("should throw an error and exit for invalid versions", () => { const mockExit = vi.spyOn(process, "exit").mockImplementation(() => { return undefined as never; }); const mockConsoleError = vi.spyOn(console, "error").mockImplementation(() => {}); simulatorService.normalizeLocalnetVersion("invalid-version"); expect(mockConsoleError).toHaveBeenCalledWith( "Invalid version format. Expected format: v0.0.0 or v0.0.0-suffix", ); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); mockConsoleError.mockRestore(); }); test("should log an error if an exception occurs while cleaning the database", async () => { const mockError = new Error("Database cleanup error"); vi.mocked(rpcClient.request).mockRejectedValue(mockError); console.error = vi.fn(); await simulatorService.cleanDatabase(); expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_clearDbTables", params: [["current_state", "transactions"]], }); expect(console.error).toHaveBeenCalledWith(mockError); }); }); describe("compareVersions", () => { test("should return 0 when versions are equal", () => { expect(simulatorService.compareVersions("v1.0.0", "v1.0.0")).toBe(0); expect(simulatorService.compareVersions("1.0.0", "v1.0.0")).toBe(0); expect(simulatorService.compareVersions("v1.0.0", "1.0.0")).toBe(0); expect(simulatorService.compareVersions("1.0.0", "1.0.0")).toBe(0); }); test("should return -1 when first version is less than second", () => { expect(simulatorService.compareVersions("v0.64.0", "v0.65.0")).toBe(-1); expect(simulatorService.compareVersions("v0.65.0", "v0.66.0")).toBe(-1); expect(simulatorService.compareVersions("v0.64.9", "v0.65.0")).toBe(-1); expect(simulatorService.compareVersions("v1.0.0", "v2.0.0")).toBe(-1); expect(simulatorService.compareVersions("v1.0.0", "v1.1.0")).toBe(-1); expect(simulatorService.compareVersions("v1.0.0", "v1.0.1")).toBe(-1); }); test("should return 1 when first version is greater than second", () => { expect(simulatorService.compareVersions("v0.66.0", "v0.65.0")).toBe(1); expect(simulatorService.compareVersions("v0.65.1", "v0.65.0")).toBe(1); expect(simulatorService.compareVersions("v2.0.0", "v1.0.0")).toBe(1); expect(simulatorService.compareVersions("v1.1.0", "v1.0.0")).toBe(1); expect(simulatorService.compareVersions("v1.0.1", "v1.0.0")).toBe(1); }); test("should handle versions with different number of parts", () => { expect(simulatorService.compareVersions("v1.0", "v1.0.0")).toBe(0); expect(simulatorService.compareVersions("v1.0.0", "v1.0")).toBe(0); expect(simulatorService.compareVersions("v1.0", "v1.0.1")).toBe(-1); expect(simulatorService.compareVersions("v1.0.1", "v1.0")).toBe(1); }); test("should handle versions without 'v' prefix", () => { expect(simulatorService.compareVersions("1.0.0", "1.0.0")).toBe(0); expect(simulatorService.compareVersions("1.0.0", "1.0.1")).toBe(-1); expect(simulatorService.compareVersions("1.0.1", "1.0.0")).toBe(1); }); test("should handle mixed prefix versions", () => { expect(simulatorService.compareVersions("v1.0.0", "1.0.0")).toBe(0); expect(simulatorService.compareVersions("1.0.0", "v1.0.1")).toBe(-1); expect(simulatorService.compareVersions("v1.0.1", "1.0.0")).toBe(1); }); test("should handle pre-release versions by comparing base version", () => { expect(simulatorService.compareVersions("v1.0.0-alpha", "v1.0.0")).toBe(0); expect(simulatorService.compareVersions("v1.0.0-beta", "v1.0.0-alpha")).toBe(0); expect(simulatorService.compareVersions("v1.0.0-alpha", "v1.0.1")).toBe(-1); expect(simulatorService.compareVersions("v1.0.1-beta", "v1.0.0")).toBe(1); expect(simulatorService.compareVersions("v1.0.0-test000", "v1.0.0-beta")).toBe(0); }); test("should handle mixed pre-release and regular versions", () => { expect(simulatorService.compareVersions("1.0.0-alpha", "v1.0.0")).toBe(0); expect(simulatorService.compareVersions("v1.0.0", "1.0.0-beta")).toBe(0); expect(simulatorService.compareVersions("1.0.0-alpha", "v1.0.1")).toBe(-1); expect(simulatorService.compareVersions("v1.0.1-beta", "1.0.0")).toBe(1); }); });