From 85fe5387698cbfa0b52ed0af25e6e66ef6e42481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veith=20R=C3=B6thlingsh=C3=B6fer?= Date: Fri, 19 Dec 2025 17:06:48 +0100 Subject: [PATCH] Run biome in tests --- biome.json | 14 +- tests/cli.spec.ts | 6 +- tests/completion.spec.ts | 10 +- tests/config.spec.ts | 11 +- tests/debugtopous/index.spec.ts | 474 +++++++++++++---------- tests/mocks.ts | 18 +- tests/scripts/generate-docs.spec.ts | 3 +- tests/tools/client.spec.ts | 78 ++-- tests/tools/init.spec.ts | 33 +- tests/tools/sync/consistency.spec.ts | 227 ++++++----- tests/tools/sync/git.integration.spec.ts | 59 +-- tests/tools/sync/git.spec.ts | 273 ++++++------- tests/tools/sync/push.spec.ts | 208 +++++----- tests/tools/sync/yaml.spec.ts | 458 +++++++++++++--------- tests/tools/test-cases.spec.ts | 2 +- tests/tools/test-targets.spec.ts | 112 +++--- tests/tools/update.spec.ts | 66 ++-- tsconfig.json | 5 +- 18 files changed, 1143 insertions(+), 914 deletions(-) diff --git a/biome.json b/biome.json index 74a58e7..5ba9215 100644 --- a/biome.json +++ b/biome.json @@ -8,7 +8,7 @@ "files": { "includes": [ "src/**", - "test/**", + "tests/**", "*.json", "jest.config.ts", "!src/api.ts", @@ -128,7 +128,17 @@ "attributePosition": "auto", "bracketSpacing": true }, - "globals": [] + "globals": [ + "afterEach", + "afterAll", + "beforeEach", + "beforeAll", + "describe", + "expect", + "it", + "test", + "jest" + ] }, "assist": { "enabled": true, diff --git a/tests/cli.spec.ts b/tests/cli.spec.ts index 06c1049..b1e02cb 100644 --- a/tests/cli.spec.ts +++ b/tests/cli.spec.ts @@ -1,8 +1,9 @@ import { Command } from "commander"; + import { buildCmd } from "../src/cli"; -import { executeTests } from "../src/tools"; -import { runDebugtopus } from "../src/debugtopus"; import { loadConfig } from "../src/config"; +import { runDebugtopus } from "../src/debugtopus"; +import { executeTests } from "../src/tools"; jest.mock("../src/tools"); jest.mock("../src/debugtopus"); @@ -173,5 +174,4 @@ describe("config overwrite behaviour", () => { }), ); }); - }); diff --git a/tests/completion.spec.ts b/tests/completion.spec.ts index 8dd2081..ebbdd6a 100644 --- a/tests/completion.spec.ts +++ b/tests/completion.spec.ts @@ -1,18 +1,18 @@ +import { log, TabtabEnv } from "tabtab"; + import { + CompletableCommand, environmentIdCompleter, optionsCompleter, testCaseIdCompleter, testReportIdCompleter, testTargetIdCompleter, - CompletableCommand, } from "../src/completion"; - -import { TabtabEnv, log } from "tabtab"; -import { getTestTargets } from "../src/tools/test-targets"; -import { getEnvironments } from "../src/tools/environments"; import { loadConfig } from "../src/config"; +import { getEnvironments } from "../src/tools/environments"; import { getTestCases } from "../src/tools/test-cases"; import { getTestReports } from "../src/tools/test-reports"; +import { getTestTargets } from "../src/tools/test-targets"; jest.mock("../src/tools/test-targets"); jest.mock("tabtab"); diff --git a/tests/config.spec.ts b/tests/config.spec.ts index 7576981..6332315 100644 --- a/tests/config.spec.ts +++ b/tests/config.spec.ts @@ -1,7 +1,9 @@ import fs from "fs/promises"; -import path from "path"; -import { loadConfig, Config, resetConfig } from "../src/config"; import { homedir } from "os"; +import path from "path"; + +import { Config, loadConfig, resetConfig } from "../src/config"; + jest.mock("fs/promises"); const mockedFs = fs as jest.Mocked; @@ -34,12 +36,11 @@ describe("Config", () => { expect(result).toEqual(mockConfig); expect(mockedFs.readFile).toHaveBeenCalledWith( - path.join(homedir(), ".config", configFileName), - "utf8", + path.join(homedir(), ".config", configFileName), + "utf8", ); }); - it("should load and parse a valid config file", async () => { const mockConfig: Config = { apiKey: "test-api-key-12345", diff --git a/tests/debugtopous/index.spec.ts b/tests/debugtopous/index.spec.ts index d167fdb..e01b2ce 100644 --- a/tests/debugtopous/index.spec.ts +++ b/tests/debugtopous/index.spec.ts @@ -1,15 +1,20 @@ -import { readZipFromResponseBody, executeLocalTestCases } from "../../src/debugtopus/index"; -import { ReadableStream } from "stream/web"; -import fs from "fs/promises"; +import { pipeline } from "node:stream/promises"; import { createWriteStream, existsSync, writeFileSync } from "fs"; +import fs from "fs/promises"; +import { ReadableStream } from "stream/web"; + +import { DeepMockProxy, mock, mockDeep } from "jest-mock-extended"; import { Open } from "unzipper"; -import { pipeline } from "node:stream/promises"; + +import { + executeLocalTestCases, + readZipFromResponseBody, +} from "../../src/debugtopus/index"; +import { ensureChromiumIsInstalled } from "../../src/debugtopus/installation"; import { client, handleError } from "../../src/tools/client"; -import { readTestCasesFromDir } from "../../src/tools/sync/yml"; import { getPlaywrightConfig } from "../../src/tools/playwright"; +import { readTestCasesFromDir } from "../../src/tools/sync/yml"; import { createMockSyncTestCase } from "../mocks"; -import { DeepMockProxy, mockDeep, mock } from "jest-mock-extended"; -import { ensureChromiumIsInstalled } from "../../src/debugtopus/installation"; jest.mock("fs/promises"); jest.mock("fs"); @@ -22,218 +27,259 @@ jest.mock("child_process"); jest.mock("node:stream/promises"); const mockedFs = fs as jest.Mocked; -const mockedCreateWriteStream = createWriteStream as jest.MockedFunction; +const mockedCreateWriteStream = createWriteStream as jest.MockedFunction< + typeof createWriteStream +>; const mockedExistsSync = existsSync as jest.MockedFunction; -const mockedWriteFileSync = writeFileSync as jest.MockedFunction; +const mockedWriteFileSync = writeFileSync as jest.MockedFunction< + typeof writeFileSync +>; const mockedOpen = Open as jest.Mocked; const mockedPipeline = pipeline as jest.MockedFunction; const mockedClient = client as DeepMockProxy; -const mockedReadTestCasesFromDir = readTestCasesFromDir as jest.MockedFunction; -const mockedGetPlaywrightConfig = getPlaywrightConfig as jest.MockedFunction; -const mockedHandleError = handleError as jest.MockedFunction; -const mockedEnsureChromiumIsInstalled = ensureChromiumIsInstalled as jest.MockedFunction; +const mockedReadTestCasesFromDir = readTestCasesFromDir as jest.MockedFunction< + typeof readTestCasesFromDir +>; +const mockedGetPlaywrightConfig = getPlaywrightConfig as jest.MockedFunction< + typeof getPlaywrightConfig +>; +const mockedEnsureChromiumIsInstalled = + ensureChromiumIsInstalled as jest.MockedFunction< + typeof ensureChromiumIsInstalled + >; describe("debugtopus", () => { - - beforeEach(() => { - jest.clearAllMocks(); + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("readZipFromResponseBody", () => { + it("should read a zip from a response body", async () => { + // Create a mock zip buffer (minimal valid zip file) + // This is a minimal ZIP file structure: local file header + central directory + const mockZipBuffer = Buffer.from([ + 0x50, + 0x4b, + 0x03, + 0x04, // Local file header signature + 0x14, + 0x00, // Version needed to extract + 0x00, + 0x00, // General purpose bit flag + 0x08, + 0x00, // Compression method + 0x00, + 0x00, + 0x00, + 0x00, // Last mod file time and date + 0x00, + 0x00, + 0x00, + 0x00, // CRC-32 + 0x05, + 0x00, + 0x00, + 0x00, // Compressed size + 0x05, + 0x00, + 0x00, + 0x00, // Uncompressed size + 0x04, + 0x00, // File name length + 0x00, + 0x00, // Extra field length + 0x74, + 0x65, + 0x73, + 0x74, // File name: "test" + 0x68, + 0x65, + 0x6c, + 0x6c, + 0x6f, // File content: "hello" + ]); + + const mockReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(mockZipBuffer); + controller.close(); + }, + }); + + const mockResponse = { + body: mockReadableStream, + } as Response; + + const mockDirs = { + testDirectory: "/tmp/test-dir", + outputDir: "/tmp/test-dir/output", + configFilePath: "/tmp/test-dir/config.ts", + packageRootDir: "/tmp", + }; + + const mockWriteStream = { + write: jest.fn(), + end: jest.fn(), + on: jest.fn((event, callback) => { + if (event === "finish") { + setTimeout(() => callback(), 0); + } + return mockWriteStream; + }), + } as unknown as NodeJS.WritableStream; + + const mockDirectory = { + extract: jest.fn().mockResolvedValue(undefined), + }; + + // @ts-expect-error - mockWriteStream is a WritableStream + mockedCreateWriteStream.mockReturnValue(mockWriteStream); + mockedPipeline.mockResolvedValue(undefined); + mockedFs.readFile.mockResolvedValue(mockZipBuffer); + mockedOpen.buffer = jest.fn().mockResolvedValue(mockDirectory); + + await readZipFromResponseBody(mockDirs, mockResponse); + + expect(mockedCreateWriteStream).toHaveBeenCalledWith( + "/tmp/test-dir/bundle.zip", + ); + expect(mockedPipeline).toHaveBeenCalled(); + expect(mockedFs.readFile).toHaveBeenCalledWith( + "/tmp/test-dir/bundle.zip", + ); + expect(mockedOpen.buffer).toHaveBeenCalledWith(mockZipBuffer); + expect(mockDirectory.extract).toHaveBeenCalledWith({ + path: "/tmp/test-dir", + }); }); - describe("readZipFromResponseBody", () => { - - it("should read a zip from a response body", async () => { - // Create a mock zip buffer (minimal valid zip file) - // This is a minimal ZIP file structure: local file header + central directory - const mockZipBuffer = Buffer.from([ - 0x50, 0x4B, 0x03, 0x04, // Local file header signature - 0x14, 0x00, // Version needed to extract - 0x00, 0x00, // General purpose bit flag - 0x08, 0x00, // Compression method - 0x00, 0x00, 0x00, 0x00, // Last mod file time and date - 0x00, 0x00, 0x00, 0x00, // CRC-32 - 0x05, 0x00, 0x00, 0x00, // Compressed size - 0x05, 0x00, 0x00, 0x00, // Uncompressed size - 0x04, 0x00, // File name length - 0x00, 0x00, // Extra field length - 0x74, 0x65, 0x73, 0x74, // File name: "test" - 0x68, 0x65, 0x6C, 0x6C, 0x6F, // File content: "hello" - ]); - - const mockReadableStream = new ReadableStream({ - start(controller) { - controller.enqueue(mockZipBuffer); - controller.close(); - }, - }); - - const mockResponse = { - body: mockReadableStream, - } as Response; - - const mockDirs = { - testDirectory: "/tmp/test-dir", - outputDir: "/tmp/test-dir/output", - configFilePath: "/tmp/test-dir/config.ts", - packageRootDir: "/tmp", - }; - - const mockWriteStream = { - write: jest.fn(), - end: jest.fn(), - on: jest.fn((event, callback) => { - if (event === "finish") { - setTimeout(() => callback(), 0); - } - return mockWriteStream; - }), - } as unknown as NodeJS.WritableStream; - - const mockDirectory = { - extract: jest.fn().mockResolvedValue(undefined), - }; - - mockedCreateWriteStream.mockReturnValue(mockWriteStream as any); - mockedPipeline.mockResolvedValue(undefined); - mockedFs.readFile.mockResolvedValue(mockZipBuffer); - mockedOpen.buffer = jest.fn().mockResolvedValue(mockDirectory); - - await readZipFromResponseBody(mockDirs, mockResponse); - - expect(mockedCreateWriteStream).toHaveBeenCalledWith( - "/tmp/test-dir/bundle.zip" - ); - expect(mockedPipeline).toHaveBeenCalled(); - expect(mockedFs.readFile).toHaveBeenCalledWith("/tmp/test-dir/bundle.zip"); - expect(mockedOpen.buffer).toHaveBeenCalledWith(mockZipBuffer); - expect(mockDirectory.extract).toHaveBeenCalledWith({ - path: "/tmp/test-dir", - }); - }); - - it("should throw an error if zip extraction fails", async () => { - const mockZipBuffer = Buffer.from([0x50, 0x4B, 0x03, 0x04]); - - const mockReadableStream = new ReadableStream({ - start(controller) { - controller.enqueue(mockZipBuffer); - controller.close(); - }, - }); - - const mockResponse = { - body: mockReadableStream, - } as Response; - - const mockDirs = { - testDirectory: "/tmp/test-dir", - outputDir: "/tmp/test-dir/output", - configFilePath: "/tmp/test-dir/config.ts", - packageRootDir: "/tmp", - }; - - const mockWriteStream = { - write: jest.fn(), - end: jest.fn(), - on: jest.fn((event, callback) => { - if (event === "finish") { - setTimeout(() => callback(), 0); - } - return mockWriteStream; - }), - } as unknown as NodeJS.WritableStream; - - mockedCreateWriteStream.mockReturnValue(mockWriteStream as any); - mockedPipeline.mockResolvedValue(undefined); - mockedFs.readFile.mockResolvedValue(mockZipBuffer); - mockedOpen.buffer = jest.fn().mockRejectedValue(new Error("Invalid zip")); - - await expect(readZipFromResponseBody(mockDirs, mockResponse)).rejects.toThrow( - "Failed to extract ZIP at /tmp/test-dir/bundle.zip" - ); - }); - - }) - - describe("executeLocalTestCases", () => { - - it("should execute local test cases from zip response body", async () => { - const mockTestCases = [createMockSyncTestCase()]; - const mockZipBuffer = Buffer.from([0x50, 0x4B, 0x03, 0x04]); - const mockConfig = "export default { testDir: './tests' };"; - const mockReadableStream = new ReadableStream({ - start(controller) { - controller.enqueue(mockZipBuffer); - controller.close(); - }, - }); - const mockResponse = mockDeep(); - Object.defineProperty(mockResponse, "body", { value: mockReadableStream, writable: true, configurable: true }); - const mockWriteStream = { write: jest.fn(), end: jest.fn(), on: jest.fn() } as any; - const mockDirectory = { extract: jest.fn().mockResolvedValue(undefined) }; - - mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); - mockedClient.POST.mockResolvedValue({ error: undefined, response: mockResponse, data: undefined }); - mockedExistsSync.mockImplementation((path: Parameters[0]) => { - const pathStr = typeof path === "string" ? path : path.toString(); - return pathStr.includes("node_modules"); - }); - mockedFs.mkdir.mockResolvedValue(undefined); - mockedFs.rm.mockResolvedValue(undefined); - mockedCreateWriteStream.mockReturnValue(mockWriteStream); - mockedPipeline.mockResolvedValue(undefined); - mockedFs.readFile.mockResolvedValue(mockZipBuffer); - mockedOpen.buffer = jest.fn().mockResolvedValue(mockDirectory); - mockedGetPlaywrightConfig.mockResolvedValue(mockConfig); - mockedEnsureChromiumIsInstalled.mockResolvedValue(undefined); - - const { exec } = require("child_process"); - (exec as jest.MockedFunction).mockImplementation((_command: string, _options: any, callback?: any) => { - if (callback) setImmediate(() => callback(null, { stdout: "", stderr: "" })); - return {} as any; - }); - - await executeLocalTestCases({ - testTargetId: "test-target-id", - url: "https://example.com", - source: "/test/source", - headless: true, - }); - - expect(mockedReadTestCasesFromDir).toHaveBeenCalledWith("/test/source"); - expect(mockedClient.POST).toHaveBeenCalledWith( - "/apiKey/beta/test-targets/{testTargetId}/code", - expect.objectContaining({ - params: { path: { testTargetId: "test-target-id" } }, - body: { testCases: mockTestCases, testTargetId: "test-target-id", executionUrl: "https://example.com", environmentId: undefined }, - parseAs: "stream", - }) - ); - expect(mockedGetPlaywrightConfig).toHaveBeenCalled(); - expect(mockedWriteFileSync).toHaveBeenCalledWith(expect.stringContaining("config.ts"), mockConfig); - }); - - it("should handle error when response is missing", async () => { - const mockTestCases = [createMockSyncTestCase()]; - - mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); - mockedClient.POST.mockResolvedValue({ - error: undefined, - response: null as any, - data: undefined, - }); - - await expect( - executeLocalTestCases({ - testTargetId: "test-target-id", - url: "https://example.com", - source: "/test/source", - headless: true, - }) - ).rejects.toThrow(); - - expect(mockedHandleError).toHaveBeenCalled(); - }); - - }) - -}) + it("should throw an error if zip extraction fails", async () => { + const mockZipBuffer = Buffer.from([0x50, 0x4b, 0x03, 0x04]); + + const mockReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(mockZipBuffer); + controller.close(); + }, + }); + + const mockResponse = { + body: mockReadableStream, + } as Response; + + const mockDirs = { + testDirectory: "/tmp/test-dir", + outputDir: "/tmp/test-dir/output", + configFilePath: "/tmp/test-dir/config.ts", + packageRootDir: "/tmp", + }; + + const mockWriteStream = { + write: jest.fn(), + end: jest.fn(), + on: jest.fn((event, callback) => { + if (event === "finish") { + setTimeout(() => callback(), 0); + } + return mockWriteStream; + }), + } as unknown as NodeJS.WritableStream; + + // @ts-expect-error - mockWriteStream is a WritableStream + mockedCreateWriteStream.mockReturnValue(mockWriteStream); + mockedPipeline.mockResolvedValue(undefined); + mockedFs.readFile.mockResolvedValue(mockZipBuffer); + mockedOpen.buffer = jest.fn().mockRejectedValue(new Error("Invalid zip")); + + await expect( + readZipFromResponseBody(mockDirs, mockResponse), + ).rejects.toThrow("Failed to extract ZIP at /tmp/test-dir/bundle.zip"); + }); + }); + + describe("executeLocalTestCases", () => { + it("should execute local test cases from zip response body", async () => { + const mockTestCases = [createMockSyncTestCase()]; + const mockZipBuffer = Buffer.from([0x50, 0x4b, 0x03, 0x04]); + const mockConfig = "export default { testDir: './tests' };"; + const mockReadableStream = new ReadableStream({ + start(controller) { + controller.enqueue(mockZipBuffer); + controller.close(); + }, + }); + const mockResponse = mockDeep(); + Object.defineProperty(mockResponse, "body", { + value: mockReadableStream, + writable: true, + configurable: true, + }); + const mockDirectory = { extract: jest.fn().mockResolvedValue(undefined) }; + + mockedReadTestCasesFromDir.mockReturnValue(mockTestCases); + mockedClient.POST.mockResolvedValue({ + error: undefined, + response: mockResponse, + data: undefined, + }); + mockedExistsSync.mockImplementation( + (path: Parameters[0]) => { + const pathStr = typeof path === "string" ? path : path.toString(); + return pathStr.includes("node_modules"); + }, + ); + mockedFs.mkdir.mockResolvedValue(undefined); + mockedFs.rm.mockResolvedValue(undefined); + mockedCreateWriteStream.mockReturnValue(mockDeep()); + mockedPipeline.mockResolvedValue(undefined); + mockedFs.readFile.mockResolvedValue(mockZipBuffer); + mockedOpen.buffer = jest.fn().mockResolvedValue(mockDirectory); + mockedGetPlaywrightConfig.mockResolvedValue(mockConfig); + mockedEnsureChromiumIsInstalled.mockResolvedValue(undefined); + + // biome-ignore lint/style/noCommonJs: jest + const { exec } = require("child_process"); + (exec as jest.MockedFunction).mockImplementation( + ( + _command: string, + _options: unknown, + callback?: ( + error: Error | null, + outputs: { stdout: string; stderr: string } | null, + ) => void, + ) => { + if (callback) + setImmediate(() => callback(null, { stdout: "", stderr: "" })); + return mock(); + }, + ); + + await executeLocalTestCases({ + testTargetId: "test-target-id", + url: "https://example.com", + source: "/test/source", + headless: true, + }); + + expect(mockedReadTestCasesFromDir).toHaveBeenCalledWith("/test/source"); + expect(mockedClient.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/code", + expect.objectContaining({ + params: { path: { testTargetId: "test-target-id" } }, + body: { + testCases: mockTestCases, + testTargetId: "test-target-id", + executionUrl: "https://example.com", + environmentId: undefined, + }, + parseAs: "stream", + }), + ); + expect(mockedGetPlaywrightConfig).toHaveBeenCalled(); + expect(mockedWriteFileSync).toHaveBeenCalledWith( + expect.stringContaining("config.ts"), + mockConfig, + ); + }); + }); +}); diff --git a/tests/mocks.ts b/tests/mocks.ts index fa776f2..f819f43 100644 --- a/tests/mocks.ts +++ b/tests/mocks.ts @@ -1,13 +1,13 @@ import { SyncTestCase } from "../src/tools/sync/types"; export const createMockSyncTestCase = ( - overrides?: Partial, + overrides?: Partial, ): SyncTestCase => ({ - id: crypto.randomUUID(), - description: "some description", - elements: [], - version: "1", - prompt: "prompt", - runStatus: "ON", - ...overrides, -}); \ No newline at end of file + id: crypto.randomUUID(), + description: "some description", + elements: [], + version: "1", + prompt: "prompt", + runStatus: "ON", + ...overrides, +}); diff --git a/tests/scripts/generate-docs.spec.ts b/tests/scripts/generate-docs.spec.ts index e9da33d..5797372 100644 --- a/tests/scripts/generate-docs.spec.ts +++ b/tests/scripts/generate-docs.spec.ts @@ -1,6 +1,7 @@ -import { generateCommandDocs } from "../../scripts/generate-docs"; import { Command } from "commander"; +import { generateCommandDocs } from "../../scripts/generate-docs"; + describe("generateCommandDocs", () => { const mockCommand = new Command("test-command"); mockCommand.version("1.0.0"); diff --git a/tests/tools/client.spec.ts b/tests/tools/client.spec.ts index f545178..7995d68 100644 --- a/tests/tools/client.spec.ts +++ b/tests/tools/client.spec.ts @@ -1,38 +1,48 @@ -import {createClientFromUrlAndApiKey} from "../../src/tools/client"; -import {mock} from "jest-mock-extended"; -import {createMockSyncTestCase} from "../mocks"; -import { version } from "../../src/version"; +import { mock } from "jest-mock-extended"; +import { createClientFromUrlAndApiKey } from "../../src/tools/client"; +import { version } from "../../src/version"; +import { createMockSyncTestCase } from "../mocks"; describe("client", () => { - let mockedFetch: jest.MockedFunction; - - beforeEach(() => { - mockedFetch = jest.fn(); - global.fetch = mockedFetch - - mockedFetch.mockResolvedValue(new Response(JSON.stringify({ testCases: [createMockSyncTestCase()]}), { status: 200})); + let mockedFetch: jest.MockedFunction; + + beforeEach(() => { + mockedFetch = jest.fn(); + global.fetch = mockedFetch; + + mockedFetch.mockResolvedValue( + new Response(JSON.stringify({ testCases: [createMockSyncTestCase()] }), { + status: 200, + }), + ); + }); + + describe(createClientFromUrlAndApiKey.name, () => { + it("should allow using a pre-defined token", async () => { + const baseUrl = "https://url.com"; + + const apiKey = "my-token"; + const client = createClientFromUrlAndApiKey({ baseUrl, apiKey }); + + await client.GET("/apiKey/beta/test-targets/{testTargetId}/pull", { + params: { + path: { + testTargetId: "someId", + }, + }, + }); + + expect(mockedFetch).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${baseUrl}/apiKey/beta/test-targets/someId/pull`, + headers: new Headers({ + "x-api-key": apiKey, + "user-agent": `octomind-cli/${version}`, + }), + }), + undefined, + ); }); - - describe(createClientFromUrlAndApiKey.name, () => { - it("should allow using a pre-defined token", async () => { - let baseUrl = "https://url.com"; - - let apiKey = "my-token"; - const client = createClientFromUrlAndApiKey({ baseUrl, apiKey}) - - await client.GET("/apiKey/beta/test-targets/{testTargetId}/pull", { - params: { - path: { - testTargetId: "someId" - } - } - }) - - expect(mockedFetch).toHaveBeenCalledWith(expect.objectContaining({ url: `${baseUrl}/apiKey/beta/test-targets/someId/pull`, headers: new Headers({ - "x-api-key": apiKey, - "user-agent": `octomind-cli/${version}` - }) }), undefined) - }) - }) -}) + }); +}); diff --git a/tests/tools/init.spec.ts b/tests/tools/init.spec.ts index 3a3a892..e650e42 100644 --- a/tests/tools/init.spec.ts +++ b/tests/tools/init.spec.ts @@ -1,7 +1,8 @@ -import { init } from "../../src/tools/init"; import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; +import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; + jest.mock("../../src/config"); jest.mock("../../src/helpers"); jest.mock("../../src/tools/test-targets"); @@ -22,14 +23,24 @@ describe("init", () => { const promptUserMock = promptUser as jest.Mock; promptUserMock.mockResolvedValue("1"); const getTestTargetsMock = getTestTargets as jest.Mock; - getTestTargetsMock.mockResolvedValue([{ id: "testTargetId", app: "testTargetApp" }]); + getTestTargetsMock.mockResolvedValue([ + { id: "testTargetId", app: "testTargetApp" }, + ]); it("should initialize the configuration with one test target", async () => { await init({ apiKey: "newApiKey", force: true }); expect(loadConfigMock).toHaveBeenCalledWith(true); - expect(saveConfig).toHaveBeenCalledWith({ apiKey: "newApiKey", testTargetId: "testTargetId" }); - expect(console.log).toHaveBeenCalledWith("Only one test target found, using it: testTargetApp (testTargetId)"); - expect(console.log).toHaveBeenNthCalledWith(3, "\n✨ Initialization complete!"); + expect(saveConfig).toHaveBeenCalledWith({ + apiKey: "newApiKey", + testTargetId: "testTargetId", + }); + expect(console.log).toHaveBeenCalledWith( + "Only one test target found, using it: testTargetApp (testTargetId)", + ); + expect(console.log).toHaveBeenNthCalledWith( + 3, + "\n✨ Initialization complete!", + ); }); it("should initialize the configuration with multiple test targets", async () => { @@ -39,7 +50,13 @@ describe("init", () => { ]); await init({ apiKey: "newApiKey", force: true }); expect(loadConfigMock).toHaveBeenCalledWith(true); - expect(saveConfig).toHaveBeenCalledWith({ apiKey: "newApiKey", testTargetId: "testTargetId1" }); - expect(console.log).toHaveBeenNthCalledWith(2, "\n✨ Initialization complete!"); + expect(saveConfig).toHaveBeenCalledWith({ + apiKey: "newApiKey", + testTargetId: "testTargetId1", + }); + expect(console.log).toHaveBeenNthCalledWith( + 2, + "\n✨ Initialization complete!", + ); }); -}); \ No newline at end of file +}); diff --git a/tests/tools/sync/consistency.spec.ts b/tests/tools/sync/consistency.spec.ts index 22ce88d..d42daee 100644 --- a/tests/tools/sync/consistency.spec.ts +++ b/tests/tools/sync/consistency.spec.ts @@ -1,119 +1,140 @@ -import {checkForConsistency} from "../../../src/tools/sync/consistency"; -import {createMockSyncTestCase} from "../../mocks"; +import { checkForConsistency } from "../../../src/tools/sync/consistency"; +import { createMockSyncTestCase } from "../../mocks"; describe("checkForConsistency", () => { + it("passes when all test cases have unique ids", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1" }), + createMockSyncTestCase({ id: "tc-2" }), + createMockSyncTestCase({ id: "tc-3" }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); - it("passes when all test cases have unique ids", () => { - const testCases = [ - createMockSyncTestCase({ id: "tc-1" }), - createMockSyncTestCase({id: "tc-2" }), - createMockSyncTestCase({id: "tc-3" }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); + it("throws when duplicate test case ids exist", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1" }), + createMockSyncTestCase({ id: "tc-2" }), + createMockSyncTestCase({ id: "tc-1" }), // duplicate + ]; + expect(() => checkForConsistency(testCases)).toThrow(/duplicate id tc-1/); + }); - it("throws when duplicate test case ids exist", () => { - const testCases = [ - createMockSyncTestCase({ id: "tc-1" }), - createMockSyncTestCase({id: "tc-2" }), - createMockSyncTestCase({id: "tc-1" }), // duplicate - ]; - expect(() => checkForConsistency(testCases)).toThrow(/duplicate id tc-1/); - }); + it("passes when dependencyId references an existing test case", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1" }), + createMockSyncTestCase({ id: "tc-2", dependencyId: "tc-1" }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); - it("passes when dependencyId references an existing test case", () => { - const testCases = [ - createMockSyncTestCase({ id: "tc-1" }), - createMockSyncTestCase({id: "tc-2", dependencyId: "tc-1" }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); + it("throws when dependencyId references a non-existing test case", () => { + const testCases = [ + createMockSyncTestCase(), + createMockSyncTestCase({ id: "tc-2", dependencyId: "tc-999" }), + ]; + expect(() => checkForConsistency(testCases)).toThrow( + /dependency not found tc-999/, + ); + }); - it("throws when dependencyId references a non-existing test case", () => { - const testCases = [ - createMockSyncTestCase(), - createMockSyncTestCase({id: "tc-2", dependencyId: "tc-999" }), - ]; - expect(() => checkForConsistency(testCases)).toThrow(/dependency not found tc-999/); - }); + it("passes when teardownId references an existing test case", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1" }), + createMockSyncTestCase({ id: "tc-2", teardownId: "tc-1" }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); - it("passes when teardownId references an existing test case", () => { - const testCases = [ - createMockSyncTestCase({ id: "tc-1" }), - createMockSyncTestCase({id: "tc-2", teardownId: "tc-1" }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); + it("throws when teardownId references a non-existing test case", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1" }), + createMockSyncTestCase({ id: "tc-2", teardownId: "tc-999" }), + ]; + expect(() => checkForConsistency(testCases)).toThrow( + /tear down not found tc-999/, + ); + }); - it("throws when teardownId references a non-existing test case", () => { - const testCases = [ - createMockSyncTestCase({ id: "tc-1" }), - createMockSyncTestCase({id: "tc-2", teardownId: "tc-999" }), - ]; - expect(() => checkForConsistency(testCases)).toThrow(/tear down not found tc-999/); - }); + it("passes with complex dependency chains", () => { + const testCases = [ + createMockSyncTestCase({ id: "login" }), + createMockSyncTestCase({ id: "teardown" }), + createMockSyncTestCase({ + id: "test-1", + dependencyId: "login", + teardownId: "teardown", + }), + createMockSyncTestCase({ + id: "test-2", + dependencyId: "login", + teardownId: "teardown", + }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); - it("passes with complex dependency chains", () => { - const testCases = [ - createMockSyncTestCase({ id: "login" }), - createMockSyncTestCase({ id: "teardown" }), - createMockSyncTestCase({ id: "test-1", dependencyId: "login", teardownId: "teardown" }), - createMockSyncTestCase( {id: "test-2", dependencyId: "login", teardownId: "teardown" }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); + it("passes with empty test cases array", () => { + expect(() => checkForConsistency([])).not.toThrow(); + }); - it("passes with empty test cases array", () => { - expect(() => checkForConsistency([])).not.toThrow(); - }); + it("passes when dependencyId and teardownId are undefined", () => { + const testCases = [ + createMockSyncTestCase({ + id: "tc-1", + dependencyId: undefined, + teardownId: undefined, + }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); - it("passes when dependencyId and teardownId are undefined", () => { - const testCases = [ - createMockSyncTestCase( {id: "tc-1", dependencyId: undefined, teardownId: undefined }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); + it("throws when there is a direct cyclic dependency (A -> A)", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1", dependencyId: "tc-1" }), + ]; + expect(() => checkForConsistency(testCases)).toThrow( + "loop detected, [tc-1] -> [tc-1]", + ); + }); - it("throws when there is a direct cyclic dependency (A -> A)", () => { - const testCases = [ - createMockSyncTestCase( {id: "tc-1", dependencyId: "tc-1" }), - ]; - expect(() => checkForConsistency(testCases)).toThrow("loop detected, [tc-1] -> [tc-1]"); - }); + it("throws when there is a two-node cycle (A -> B -> A)", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1", dependencyId: "tc-2" }), + createMockSyncTestCase({ id: "tc-2", dependencyId: "tc-1" }), + ]; + expect(() => checkForConsistency(testCases)).toThrow( + "loop detected, [tc-1] -> [tc-2] -> [tc-1]", + ); + }); - it("throws when there is a two-node cycle (A -> B -> A)", () => { - const testCases = [ - createMockSyncTestCase( {id: "tc-1", dependencyId: "tc-2" }), - createMockSyncTestCase( {id: "tc-2", dependencyId: "tc-1" }), - ]; - expect(() => checkForConsistency(testCases)).toThrow("loop detected, [tc-1] -> [tc-2] -> [tc-1]"); - }); + it("throws when there is a multi-node cycle (A -> B -> C -> A)", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1", dependencyId: "tc-2" }), + createMockSyncTestCase({ id: "tc-2", dependencyId: "tc-3" }), + createMockSyncTestCase({ id: "tc-3", dependencyId: "tc-1" }), + ]; + expect(() => checkForConsistency(testCases)).toThrow( + "loop detected, [tc-1] -> [tc-3] -> [tc-2] -> [tc-1]", + ); + }); - it("throws when there is a multi-node cycle (A -> B -> C -> A)", () => { - const testCases = [ - createMockSyncTestCase({id: "tc-1", dependencyId: "tc-2" }), - createMockSyncTestCase( {id: "tc-2", dependencyId: "tc-3" }), - createMockSyncTestCase( {id: "tc-3", dependencyId: "tc-1" }), - ]; - expect(() => checkForConsistency(testCases)).toThrow("loop detected, [tc-1] -> [tc-3] -> [tc-2] -> [tc-1]"); - }); + it("passes with valid linear dependency chain (A -> B -> C)", () => { + const testCases = [ + createMockSyncTestCase({ id: "tc-1" }), + createMockSyncTestCase({ id: "tc-2", dependencyId: "tc-1" }), + createMockSyncTestCase({ id: "tc-3", dependencyId: "tc-2" }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); - it("passes with valid linear dependency chain (A -> B -> C)", () => { - const testCases = [ - createMockSyncTestCase({id: "tc-1" }), - createMockSyncTestCase( {id: "tc-2", dependencyId: "tc-1" }), - createMockSyncTestCase( {id: "tc-3", dependencyId: "tc-2" }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); - - it("passes with multiple test cases depending on the same dependency", () => { - const testCases = [ - createMockSyncTestCase({ id: "login" }), - createMockSyncTestCase( { dependencyId: "login" }), - createMockSyncTestCase( { dependencyId: "login" }), - createMockSyncTestCase( { dependencyId: "login" }), - ]; - expect(() => checkForConsistency(testCases)).not.toThrow(); - }); + it("passes with multiple test cases depending on the same dependency", () => { + const testCases = [ + createMockSyncTestCase({ id: "login" }), + createMockSyncTestCase({ dependencyId: "login" }), + createMockSyncTestCase({ dependencyId: "login" }), + createMockSyncTestCase({ dependencyId: "login" }), + ]; + expect(() => checkForConsistency(testCases)).not.toThrow(); + }); }); diff --git a/tests/tools/sync/git.integration.spec.ts b/tests/tools/sync/git.integration.spec.ts index 4706687..2b372bb 100644 --- a/tests/tools/sync/git.integration.spec.ts +++ b/tests/tools/sync/git.integration.spec.ts @@ -1,27 +1,32 @@ -import {getDefaultBranch, getGitContext, parseGitRemote} from "../../../src/tools/sync/git"; - -describe('git', () => { - - beforeEach(() => { - console.warn = jest.fn(); - }) - - it("should return the actual owner and repo", async () => { - const remote = await parseGitRemote(); - - expect(remote.owner).toEqual("OctoMind-dev"); - expect(remote.repo).toEqual("cli"); - }) - - it.each(["origin"] as const)("should return the actual default branch for method '%s'", async (allowedMethod) => { - const defaultBranch = await getDefaultBranch(allowedMethod); - - expect(defaultBranch).toEqual("refs/heads/main"); - }); - - it("returns a context", async () => { - const context = await getGitContext() - - expect(context).toBeDefined() - }); -}) +import { + getDefaultBranch, + getGitContext, + parseGitRemote, +} from "../../../src/tools/sync/git"; + +describe("git", () => { + beforeEach(() => { + console.warn = jest.fn(); + }); + + it("should return the actual owner and repo", async () => { + const remote = await parseGitRemote(); + + expect(remote.owner).toEqual("OctoMind-dev"); + expect(remote.repo).toEqual("cli"); + }); + + it.each([ + "origin", + ] as const)("should return the actual default branch for method '%s'", async (allowedMethod) => { + const defaultBranch = await getDefaultBranch(allowedMethod); + + expect(defaultBranch).toEqual("refs/heads/main"); + }); + + it("returns a context", async () => { + const context = await getGitContext(); + + expect(context).toBeDefined(); + }); +}); diff --git a/tests/tools/sync/git.spec.ts b/tests/tools/sync/git.spec.ts index a033fd3..a57acca 100644 --- a/tests/tools/sync/git.spec.ts +++ b/tests/tools/sync/git.spec.ts @@ -1,177 +1,178 @@ -import {simpleGit} from "simple-git"; -import {parseGitRemote, getGitContext, getDefaultBranch} from "../../../src/tools/sync/git"; -import {mock} from "jest-mock-extended"; +import { mock } from "jest-mock-extended"; +import { simpleGit } from "simple-git"; + +import { + getDefaultBranch, + getGitContext, + parseGitRemote, +} from "../../../src/tools/sync/git"; jest.mock("simple-git"); describe("git", () => { - let mockGit: jest.Mocked> - - beforeEach(() => { - mockGit = mock(); - jest.mocked(simpleGit).mockReturnValue(mockGit) - mockGit.raw.mockResolvedValue("refs/remotes/origin/main") - console.error = jest.fn(); - }) + let mockGit: jest.Mocked>; - describe("getDefaultBranch", () => { - it("should fallback if both origin and symbolic ref fail", async () => { - console.warn = jest.fn(); - mockGit.raw.mockResolvedValue("") - mockGit.remote.mockResolvedValue("") + beforeEach(() => { + mockGit = mock(); + jest.mocked(simpleGit).mockReturnValue(mockGit); + mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); + console.error = jest.fn(); + }); - const defaultBranch = await getDefaultBranch(); + describe("getDefaultBranch", () => { + it("should fallback if both origin and symbolic ref fail", async () => { + console.warn = jest.fn(); + mockGit.raw.mockResolvedValue(""); + mockGit.remote.mockResolvedValue(""); - expect(defaultBranch).toEqual("refs/heads/main") - expect(console.warn).toHaveBeenCalled() - }) + const defaultBranch = await getDefaultBranch(); - it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { - console.warn = jest.fn(); - mockGit.raw.mockResolvedValue("") - mockGit.remote.mockRejectedValue("") + expect(defaultBranch).toEqual("refs/heads/main"); + expect(console.warn).toHaveBeenCalled(); + }); - const defaultBranch = await getDefaultBranch(); + it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { + console.warn = jest.fn(); + mockGit.raw.mockResolvedValue(""); + mockGit.remote.mockRejectedValue(""); - expect(defaultBranch).toEqual("refs/heads/main") - expect(console.warn).toHaveBeenCalled() - }) + const defaultBranch = await getDefaultBranch(); + expect(defaultBranch).toEqual("refs/heads/main"); + expect(console.warn).toHaveBeenCalled(); + }); + }); - }) + describe("parseGitRemote", () => { + it("parses SSH git remote format (git@github.com:owner/repo.git)", async () => { + mockGit.remote.mockResolvedValue("git@github.com:my-org/my-repo.git"); - describe("parseGitRemote", () => { - it("parses SSH git remote format (git@github.com:owner/repo.git)", async () => { - mockGit.remote.mockResolvedValue("git@github.com:my-org/my-repo.git"); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({ owner: "my-org", repo: "my-repo" }); + }); - expect(result).toEqual({owner: "my-org", repo: "my-repo"}); - }); + it("parses SSH git remote format without .git suffix", async () => { + mockGit.remote.mockResolvedValue("git@github.com:owner/repo"); - it("parses SSH git remote format without .git suffix", async () => { - mockGit.remote.mockResolvedValue("git@github.com:owner/repo"); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({ owner: "owner", repo: "repo" }); + }); - expect(result).toEqual({owner: "owner", repo: "repo"}); - }); + it("parses HTTPS git remote format (https://github.com/owner/repo.git)", async () => { + mockGit.remote.mockResolvedValue("https://github.com/my-org/my-repo.git"); - it("parses HTTPS git remote format (https://github.com/owner/repo.git)", async () => { - mockGit.remote.mockResolvedValue("https://github.com/my-org/my-repo.git"); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({ owner: "my-org", repo: "my-repo" }); + }); - expect(result).toEqual({owner: "my-org", repo: "my-repo"}); - }); + it("parses HTTPS git remote format without .git suffix", async () => { + mockGit.remote.mockResolvedValue("https://github.com/owner/repo"); - it("parses HTTPS git remote format without .git suffix", async () => { - mockGit.remote.mockResolvedValue("https://github.com/owner/repo"); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({ owner: "owner", repo: "repo" }); + }); - expect(result).toEqual({owner: "owner", repo: "repo"}); - }); + it("parses HTTP git remote format", async () => { + mockGit.remote.mockResolvedValue("http://github.com/owner/repo.git"); - it("parses HTTP git remote format", async () => { - mockGit.remote.mockResolvedValue("http://github.com/owner/repo.git"); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({ owner: "owner", repo: "repo" }); + }); - expect(result).toEqual({owner: "owner", repo: "repo"}); - }); + it("returns empty object when remote is not a string", async () => { + mockGit.remote.mockResolvedValue(undefined); - it("returns empty object when remote is not a string", async () => { - mockGit.remote.mockResolvedValue(undefined); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({}); + }); - expect(result).toEqual({}); - }); + it("falls back to revparse when remote URL does not match expected format", async () => { + mockGit.remote.mockResolvedValue("some-unsupported-format"); + mockGit.revparse.mockResolvedValue("/home/user/my-project"); - it("falls back to revparse when remote URL does not match expected format", async () => { - mockGit.remote.mockResolvedValue("some-unsupported-format"); - mockGit.revparse.mockResolvedValue("/home/user/my-project"); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({ repo: "my-project" }); + expect(mockGit.revparse).toHaveBeenCalledWith(["--show-toplevel"]); + }); - expect(result).toEqual({repo: "my-project"}); - expect(mockGit.revparse).toHaveBeenCalledWith(["--show-toplevel"]); - }); + it("returns empty object when simpleGit throws an error", async () => { + mockGit.remote.mockRejectedValue(new Error("git not found")); - it("returns empty object when simpleGit throws an error", async () => { - mockGit.remote.mockRejectedValue(new Error("git not found")); + const result = await parseGitRemote(); - const result = await parseGitRemote(); + expect(result).toEqual({}); + }); + }); + + describe("getGitContext", () => { + it("returns full git context with branch, sha, owner and repo", async () => { + mockGit.revparse + .mockResolvedValueOnce("main") + .mockResolvedValueOnce("abc123def456"); + mockGit.remote.mockResolvedValue("git@github.com:my-org/my-repo.git"); + + const result = await getGitContext(); + + expect(result).toEqual({ + source: "github", + sha: "abc123def456", + ref: "refs/heads/main", + repo: "my-repo", + owner: "my-org", + defaultBranch: "refs/heads/main", + }); + }); - expect(result).toEqual({}); - }); + it("returns context without ref when branch is empty", async () => { + mockGit.revparse + .mockResolvedValueOnce("") + .mockResolvedValueOnce("abc123def456"); + mockGit.remote.mockResolvedValue("git@github.com:my-org/my-repo.git"); + + const result = await getGitContext(); + + expect(result).toEqual({ + source: "github", + sha: "abc123def456", + ref: undefined, + repo: "my-repo", + owner: "my-org", + defaultBranch: "refs/heads/main", + }); }); - describe("getGitContext", () => { - it("returns full git context with branch, sha, owner and repo", async () => { - mockGit.revparse - .mockResolvedValueOnce("main") - .mockResolvedValueOnce("abc123def456"); - mockGit.remote.mockResolvedValue("git@github.com:my-org/my-repo.git"); - - const result = await getGitContext(); - - expect(result).toEqual({ - source: "github", - sha: "abc123def456", - ref: "refs/heads/main", - repo: "my-repo", - owner: "my-org", - defaultBranch: "refs/heads/main" - }); - }); - - it("returns context without ref when branch is empty", async () => { - mockGit.revparse - .mockResolvedValueOnce("") - .mockResolvedValueOnce("abc123def456"); - mockGit.remote.mockResolvedValue("git@github.com:my-org/my-repo.git"); - - const result = await getGitContext(); - - expect(result).toEqual({ - source: "github", - sha: "abc123def456", - ref: undefined, - repo: "my-repo", - owner: "my-org", - defaultBranch: "refs/heads/main" - }); - }); - - it("returns context with only repo when remote parsing fails to match", async () => { - mockGit.revparse - .mockResolvedValueOnce("feature-branch") - .mockResolvedValueOnce("def789") - .mockResolvedValueOnce("/path/to/project-name"); - mockGit.remote.mockResolvedValue("unsupported-format"); - - const result = await getGitContext(); - - expect(result).toEqual({ - source: "github", - sha: "def789", - ref: "refs/heads/feature-branch", - repo: "project-name", - owner: undefined, - defaultBranch: "refs/heads/main" - }); - }); - - it("returns undefined when simpleGit throws an error", async () => { - mockGit.revparse.mockRejectedValue(new Error("not a git repository")); - - const result = await getGitContext(); - - expect(result).toBeUndefined(); - }); + it("returns context with only repo when remote parsing fails to match", async () => { + mockGit.revparse + .mockResolvedValueOnce("feature-branch") + .mockResolvedValueOnce("def789") + .mockResolvedValueOnce("/path/to/project-name"); + mockGit.remote.mockResolvedValue("unsupported-format"); + + const result = await getGitContext(); + + expect(result).toEqual({ + source: "github", + sha: "def789", + ref: "refs/heads/feature-branch", + repo: "project-name", + owner: undefined, + defaultBranch: "refs/heads/main", + }); }); -}); + it("returns undefined when simpleGit throws an error", async () => { + mockGit.revparse.mockRejectedValue(new Error("not a git repository")); + + const result = await getGitContext(); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/tests/tools/sync/push.spec.ts b/tests/tools/sync/push.spec.ts index 976c95c..b393ca9 100644 --- a/tests/tools/sync/push.spec.ts +++ b/tests/tools/sync/push.spec.ts @@ -1,100 +1,118 @@ -import {getGitContext} from "../../../src/tools/sync/git"; -import {readTestCasesFromDir} from "../../../src/tools/sync/yml"; -import {client} from "../../../src/tools/client"; -import {DeepMockProxy, mock, mockDeep} from "jest-mock-extended"; -import {push} from "../../../src/tools/sync/push"; +import { DeepMockProxy, mock, mockDeep } from "jest-mock-extended"; + +import { client } from "../../../src/tools/client"; +import { getGitContext } from "../../../src/tools/sync/git"; +import { push } from "../../../src/tools/sync/push"; +import { readTestCasesFromDir } from "../../../src/tools/sync/yml"; jest.mock("../../../src/tools/sync/git"); jest.mock("../../../src/tools/sync/yml"); describe("push", () => { - let mockedClient: DeepMockProxy; - - beforeEach(() => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/main", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - jest.mocked(readTestCasesFromDir).mockReturnValue([]) - console.log = jest.fn(); - mockedClient = mockDeep(); - mockedClient.POST.mockResolvedValue({ data: undefined, error: undefined, response: mock() }) - }) - - it("pushes to main if on default branch", async () => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/main", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - await push({ - testTargetId: "someId", - sourceDir: ".", - client: mockedClient, - onError: jest.fn(), - }) - - expect(mockedClient.POST).toHaveBeenCalledWith("/apiKey/beta/test-targets/{testTargetId}/push", expect.anything()) - }) - - it("pushes to draft if on other branch", async () => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/different", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - await push({ - testTargetId: "someId", - sourceDir: ".", - client: mockedClient, - onError: jest.fn(), - }) - - expect(mockedClient.POST).toHaveBeenCalledWith("/apiKey/beta/test-targets/{testTargetId}/draft/push", expect.anything()) - }) - - it("pushes to draft if no git context", async () => { - jest.mocked(getGitContext).mockResolvedValue(undefined) - - await push({ - testTargetId: "someId", - sourceDir: ".", - client: mockedClient, - onError: jest.fn(), - }) - - expect(mockedClient.POST).toHaveBeenCalledWith("/apiKey/beta/test-targets/{testTargetId}/draft/push", expect.anything()) - }) - - it("calls the handleError callback on error", async () => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/different", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - mockedClient.POST.mockResolvedValue({ data: undefined, error: [mock()], response: mock() }) - - let handleError = jest.fn(); - await push({ - testTargetId: "someId", - sourceDir: ".", - client: mockedClient, - onError: handleError, - }) - - expect(handleError).toHaveBeenCalled() - }) -}) + let mockedClient: DeepMockProxy; + + beforeEach(() => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/main", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + jest.mocked(readTestCasesFromDir).mockReturnValue([]); + console.log = jest.fn(); + mockedClient = mockDeep(); + mockedClient.POST.mockResolvedValue({ + data: undefined, + error: undefined, + response: mock(), + }); + }); + + it("pushes to main if on default branch", async () => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/main", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + await push({ + testTargetId: "someId", + sourceDir: ".", + client: mockedClient, + onError: jest.fn(), + }); + + expect(mockedClient.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/push", + expect.anything(), + ); + }); + + it("pushes to draft if on other branch", async () => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/different", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + await push({ + testTargetId: "someId", + sourceDir: ".", + client: mockedClient, + onError: jest.fn(), + }); + + expect(mockedClient.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/draft/push", + expect.anything(), + ); + }); + + it("pushes to draft if no git context", async () => { + jest.mocked(getGitContext).mockResolvedValue(undefined); + + await push({ + testTargetId: "someId", + sourceDir: ".", + client: mockedClient, + onError: jest.fn(), + }); + + expect(mockedClient.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/draft/push", + expect.anything(), + ); + }); + + it("calls the handleError callback on error", async () => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/different", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + mockedClient.POST.mockResolvedValue({ + data: undefined, + error: [mock()], + response: mock(), + }); + + const handleError = jest.fn(); + await push({ + testTargetId: "someId", + sourceDir: ".", + client: mockedClient, + onError: handleError, + }); + + expect(handleError).toHaveBeenCalled(); + }); +}); diff --git a/tests/tools/sync/yaml.spec.ts b/tests/tools/sync/yaml.spec.ts index 7cdad73..b0d6164 100644 --- a/tests/tools/sync/yaml.spec.ts +++ b/tests/tools/sync/yaml.spec.ts @@ -1,196 +1,274 @@ import fs from "fs"; -import path from "path"; import os from "os"; +import path from "path"; + import yaml from "yaml"; -import {createMockSyncTestCase} from "../../mocks"; -import {buildFilename, buildFolderName, readTestCasesFromDir, cleanupFilesystem} from "../../../src/tools/sync/yml"; + +import { + buildFilename, + buildFolderName, + cleanupFilesystem, + readTestCasesFromDir, +} from "../../../src/tools/sync/yml"; +import { createMockSyncTestCase } from "../../mocks"; describe("yml", () => { - let tmpDir: string; - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "octomind-cli-test-")); - }); - - afterEach(() => { - try { - fs.rmSync(tmpDir, { recursive: true, force: true }); - } catch { - } - }); - - describe("buildFilename", () => { - it("creates a camelCase filename from description with .yaml extension", () => { - const tc = createMockSyncTestCase({ description: "My test case: Login flow" }); - const name = buildFilename(tc, tmpDir); - expect(name).toBe("myTestCaseLoginFlow.yaml"); - }); - - it("removes diacritics", () => { - const tc = createMockSyncTestCase({ description: "Mein Testfall: Übermäßig viele Umlaute" }); - const name = buildFilename(tc, tmpDir); - expect(name).toBe("meinTestfallUbermaßigVieleUmlaute.yaml"); - }); - - it("keeps unicode characters", () => { - const tc = createMockSyncTestCase({ description: "统一码" }); - const name = buildFilename(tc, tmpDir); - expect(name).toBe("统一码.yaml"); - }); - - it("removes invalid characters", () => { - const tc = createMockSyncTestCase({ description: "some test\/:." }); - const name = buildFilename(tc, tmpDir); - expect(name).toBe("someTest.yaml"); - }); - - it("throws if there is ONLY invalid characters", () => { - const tc = createMockSyncTestCase({ description: "\/:." }); - expect(() => buildFilename(tc, tmpDir)).toThrow("Test case with title '/:.' has no valid characters for the file system, please rename it"); - }); - - it("appends -1 when a file with the same name exists for a different id", () => { - const tc1 = createMockSyncTestCase({ id: "AAA", description: "Duplicate name" }); - const baseline = buildFilename(tc1, tmpDir); - // simulate an existing file with different id content - fs.writeFileSync(path.join(tmpDir, baseline), yaml.stringify({ id: "DIFFERENT" })); - - const tc2 = createMockSyncTestCase({ id: "BBB", description: "Duplicate name" }); - const second = buildFilename(tc2, tmpDir); - expect(second).toBe(baseline.replace(/\.yaml$/, "-1.yaml")); - }); - }) - - describe("buildFolderName", () => { - it("returns '.' when there are no prerequisites", () => { - const tc = createMockSyncTestCase({ id: "1", description: "Root" }); - const folder = buildFolderName(tc, [tc]); - expect(folder).toBe("."); - }); - - it("builds a hierarchical path from prerequisite chain", () => { - const a = createMockSyncTestCase({ id: "A", description: "Set up data" }); - const b = createMockSyncTestCase({ id: "B", dependencyId: "A", description: "User logs in" }); - const c = createMockSyncTestCase({ id: "C", dependencyId: "B", description: "User navigates to dashboard" }); - - const folderForC = buildFolderName(c, [a, b, c]); - expect(folderForC).toBe("setUpData/userLogsIn"); - }); - - it("prefixes the path with destination when provided", () => { - const a = createMockSyncTestCase({ id: "A", description: "Set up data" }); - const b = createMockSyncTestCase({ id: "B", dependencyId: "A", description: "User logs in" }); - const c = createMockSyncTestCase({ id: "C", dependencyId: "B", description: "User navigates to dashboard" }); - - const folderForC = buildFolderName(c, [a, b, c], "my/dest"); - expect(folderForC).toBe("my/dest/setUpData/userLogsIn"); - }); - - it("throws when a dependency id is missing in the list", () => { - const a = createMockSyncTestCase({ id: "A", description: "First" }); - const b = createMockSyncTestCase({ id: "B", dependencyId: "Z", description: "Second" }); - expect(() => buildFolderName(b, [a, b] )).toThrow(/not found/i); - }); - - it("detects cycles in prerequisites", () => { - const a = createMockSyncTestCase({ id: "A", dependencyId: "B", description: "First" }); - const b = createMockSyncTestCase({ id: "B", dependencyId: "A", description: "Second" }); - expect(() => buildFolderName(a, [a, b])).toThrow(/cycle/i); - }); - }); - - describe("readTestCasesFromDir", () => { - it("should parse an empty dir", () => { - expect(readTestCasesFromDir(tmpDir)).toEqual([]); - }) - - it("should ignore non yaml files", () => { - fs.writeFileSync(path.join(tmpDir, "test.txt"), "some content"); - - expect(readTestCasesFromDir(tmpDir)).toEqual([]); - }) - - it("should ignore hidden directories", () => { - fs.mkdirSync(path.join(tmpDir, ".test")); - - fs.writeFileSync(path.join(tmpDir, ".test", "test.yaml"), yaml.stringify(createMockSyncTestCase())); - expect(readTestCasesFromDir(tmpDir)).toEqual([]); - }) - - it("should ignore node_modules", () => { - fs.mkdirSync(path.join(tmpDir, "node_modules")); - - fs.writeFileSync(path.join(tmpDir, "node_modules", "test.yaml"), yaml.stringify(createMockSyncTestCase())); - expect(readTestCasesFromDir(tmpDir)).toEqual([]); - }) - - it("should recursively find test cases in folders", () => { - fs.mkdirSync(path.join(tmpDir, "test1")); - - let testCase = createMockSyncTestCase(); - fs.writeFileSync(path.join(tmpDir, "test1", "test.yaml"), yaml.stringify(testCase)); - expect(readTestCasesFromDir(tmpDir)).toEqual([testCase]); - }) - - it("should throw on an invalid test case", () => { - fs.mkdirSync(path.join(tmpDir, "test1")); - - let testCase = createMockSyncTestCase({ - id: "invalidIdFormat" - }); - fs.writeFileSync(path.join(tmpDir, "test1", "test.yaml"), yaml.stringify(testCase)); - - expect(() => readTestCasesFromDir(tmpDir)).toThrow(/Invalid UUID/); - }) - }) - - describe("cleanupFilesystem", () => { - - it("should remove the old file if the test case description has changed", () => { - const id = crypto.randomUUID(); - const testCase = createMockSyncTestCase({ - id, - description: "Old test description" - }); - const folderName = tmpDir; - const oldFilename = buildFilename(testCase, folderName); - const oldFilePath = path.join(folderName, oldFilename); - - fs.writeFileSync(oldFilePath, yaml.stringify(testCase)); - expect(fs.existsSync(oldFilePath)).toBe(true); - - const updatedTestCase = { ...testCase, description: "New test description" }; - - cleanupFilesystem({ newTestCases: [updatedTestCase], destination: tmpDir }); - - expect(fs.existsSync(oldFilePath)).toBe(false); - }) - - it("should remove the old folder if the test case description has changed", () => { - const id = crypto.randomUUID(); - const testCase = createMockSyncTestCase({ - id, - description: "Old test description" - }); - const folderName = tmpDir; - const oldFilename = buildFilename(testCase, folderName); - const oldFilePath = path.join(folderName, oldFilename); - - fs.writeFileSync(oldFilePath, yaml.stringify(testCase)); - - const oldTestNameCamelCase = path.basename(oldFilePath).replace(/\.yaml$/, ""); - const oldFolderPath = path.join(folderName, oldTestNameCamelCase); - fs.mkdirSync(oldFolderPath, { recursive: true }); - fs.writeFileSync(path.join(oldFolderPath, "some-file.yaml"), yaml.stringify(createMockSyncTestCase())); - - expect(fs.existsSync(oldFolderPath)).toBe(true); - - const updatedTestCase = { ...testCase, description: "New test description" }; - - cleanupFilesystem({ newTestCases: [updatedTestCase], destination: tmpDir }); - - expect(fs.existsSync(oldFolderPath)).toBe(false); - }) - - }) + let tmpDir: string; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "octomind-cli-test-")); + }); + + afterEach(() => { + try { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } catch { + // ignore + } + }); + + describe("buildFilename", () => { + it("creates a camelCase filename from description with .yaml extension", () => { + const tc = createMockSyncTestCase({ + description: "My test case: Login flow", + }); + const name = buildFilename(tc, tmpDir); + expect(name).toBe("myTestCaseLoginFlow.yaml"); + }); + + it("removes diacritics", () => { + const tc = createMockSyncTestCase({ + description: "Mein Testfall: Übermäßig viele Umlaute", + }); + const name = buildFilename(tc, tmpDir); + expect(name).toBe("meinTestfallUbermaßigVieleUmlaute.yaml"); + }); + + it("keeps unicode characters", () => { + const tc = createMockSyncTestCase({ description: "统一码" }); + const name = buildFilename(tc, tmpDir); + expect(name).toBe("统一码.yaml"); + }); + + it("removes invalid characters", () => { + const tc = createMockSyncTestCase({ description: "some test\/:." }); + const name = buildFilename(tc, tmpDir); + expect(name).toBe("someTest.yaml"); + }); + + it("throws if there is ONLY invalid characters", () => { + const tc = createMockSyncTestCase({ description: "\/:." }); + expect(() => buildFilename(tc, tmpDir)).toThrow( + "Test case with title '/:.' has no valid characters for the file system, please rename it", + ); + }); + + it("appends -1 when a file with the same name exists for a different id", () => { + const tc1 = createMockSyncTestCase({ + id: "AAA", + description: "Duplicate name", + }); + const baseline = buildFilename(tc1, tmpDir); + // simulate an existing file with different id content + fs.writeFileSync( + path.join(tmpDir, baseline), + yaml.stringify({ id: "DIFFERENT" }), + ); + + const tc2 = createMockSyncTestCase({ + id: "BBB", + description: "Duplicate name", + }); + const second = buildFilename(tc2, tmpDir); + expect(second).toBe(baseline.replace(/\.yaml$/, "-1.yaml")); + }); + }); + + describe("buildFolderName", () => { + it("returns '.' when there are no prerequisites", () => { + const tc = createMockSyncTestCase({ id: "1", description: "Root" }); + const folder = buildFolderName(tc, [tc]); + expect(folder).toBe("."); + }); + + it("builds a hierarchical path from prerequisite chain", () => { + const a = createMockSyncTestCase({ id: "A", description: "Set up data" }); + const b = createMockSyncTestCase({ + id: "B", + dependencyId: "A", + description: "User logs in", + }); + const c = createMockSyncTestCase({ + id: "C", + dependencyId: "B", + description: "User navigates to dashboard", + }); + + const folderForC = buildFolderName(c, [a, b, c]); + expect(folderForC).toBe("setUpData/userLogsIn"); + }); + + it("prefixes the path with destination when provided", () => { + const a = createMockSyncTestCase({ id: "A", description: "Set up data" }); + const b = createMockSyncTestCase({ + id: "B", + dependencyId: "A", + description: "User logs in", + }); + const c = createMockSyncTestCase({ + id: "C", + dependencyId: "B", + description: "User navigates to dashboard", + }); + + const folderForC = buildFolderName(c, [a, b, c], "my/dest"); + expect(folderForC).toBe("my/dest/setUpData/userLogsIn"); + }); + + it("throws when a dependency id is missing in the list", () => { + const a = createMockSyncTestCase({ id: "A", description: "First" }); + const b = createMockSyncTestCase({ + id: "B", + dependencyId: "Z", + description: "Second", + }); + expect(() => buildFolderName(b, [a, b])).toThrow(/not found/i); + }); + + it("detects cycles in prerequisites", () => { + const a = createMockSyncTestCase({ + id: "A", + dependencyId: "B", + description: "First", + }); + const b = createMockSyncTestCase({ + id: "B", + dependencyId: "A", + description: "Second", + }); + expect(() => buildFolderName(a, [a, b])).toThrow(/cycle/i); + }); + }); + + describe("readTestCasesFromDir", () => { + it("should parse an empty dir", () => { + expect(readTestCasesFromDir(tmpDir)).toEqual([]); + }); + + it("should ignore non yaml files", () => { + fs.writeFileSync(path.join(tmpDir, "test.txt"), "some content"); + + expect(readTestCasesFromDir(tmpDir)).toEqual([]); + }); + + it("should ignore hidden directories", () => { + fs.mkdirSync(path.join(tmpDir, ".test")); + + fs.writeFileSync( + path.join(tmpDir, ".test", "test.yaml"), + yaml.stringify(createMockSyncTestCase()), + ); + expect(readTestCasesFromDir(tmpDir)).toEqual([]); + }); + + it("should ignore node_modules", () => { + fs.mkdirSync(path.join(tmpDir, "node_modules")); + + fs.writeFileSync( + path.join(tmpDir, "node_modules", "test.yaml"), + yaml.stringify(createMockSyncTestCase()), + ); + expect(readTestCasesFromDir(tmpDir)).toEqual([]); + }); + + it("should recursively find test cases in folders", () => { + fs.mkdirSync(path.join(tmpDir, "test1")); + + const testCase = createMockSyncTestCase(); + fs.writeFileSync( + path.join(tmpDir, "test1", "test.yaml"), + yaml.stringify(testCase), + ); + expect(readTestCasesFromDir(tmpDir)).toEqual([testCase]); + }); + + it("should throw on an invalid test case", () => { + fs.mkdirSync(path.join(tmpDir, "test1")); + + const testCase = createMockSyncTestCase({ + id: "invalidIdFormat", + }); + fs.writeFileSync( + path.join(tmpDir, "test1", "test.yaml"), + yaml.stringify(testCase), + ); + + expect(() => readTestCasesFromDir(tmpDir)).toThrow(/Invalid UUID/); + }); + }); + + describe("cleanupFilesystem", () => { + it("should remove the old file if the test case description has changed", () => { + const id = crypto.randomUUID(); + const testCase = createMockSyncTestCase({ + id, + description: "Old test description", + }); + const folderName = tmpDir; + const oldFilename = buildFilename(testCase, folderName); + const oldFilePath = path.join(folderName, oldFilename); + + fs.writeFileSync(oldFilePath, yaml.stringify(testCase)); + expect(fs.existsSync(oldFilePath)).toBe(true); + + const updatedTestCase = { + ...testCase, + description: "New test description", + }; + + cleanupFilesystem({ + newTestCases: [updatedTestCase], + destination: tmpDir, + }); + + expect(fs.existsSync(oldFilePath)).toBe(false); + }); + + it("should remove the old folder if the test case description has changed", () => { + const id = crypto.randomUUID(); + const testCase = createMockSyncTestCase({ + id, + description: "Old test description", + }); + const folderName = tmpDir; + const oldFilename = buildFilename(testCase, folderName); + const oldFilePath = path.join(folderName, oldFilename); + + fs.writeFileSync(oldFilePath, yaml.stringify(testCase)); + + const oldTestNameCamelCase = path + .basename(oldFilePath) + .replace(/\.yaml$/, ""); + const oldFolderPath = path.join(folderName, oldTestNameCamelCase); + fs.mkdirSync(oldFolderPath, { recursive: true }); + fs.writeFileSync( + path.join(oldFolderPath, "some-file.yaml"), + yaml.stringify(createMockSyncTestCase()), + ); + + expect(fs.existsSync(oldFolderPath)).toBe(true); + + const updatedTestCase = { + ...testCase, + description: "New test description", + }; + + cleanupFilesystem({ + newTestCases: [updatedTestCase], + destination: tmpDir, + }); + + expect(fs.existsSync(oldFolderPath)).toBe(false); + }); + }); }); diff --git a/tests/tools/test-cases.spec.ts b/tests/tools/test-cases.spec.ts index 2b333ed..3f1d147 100644 --- a/tests/tools/test-cases.spec.ts +++ b/tests/tools/test-cases.spec.ts @@ -1,5 +1,5 @@ +import { client, handleError } from "../../src/tools/client"; import { deleteTestCase } from "../../src/tools/test-cases"; -import { handleError, client } from "../../src/tools/client"; jest.mock("../../src/tools/client"); diff --git a/tests/tools/test-targets.spec.ts b/tests/tools/test-targets.spec.ts index 8612a3b..f3d8ade 100644 --- a/tests/tools/test-targets.spec.ts +++ b/tests/tools/test-targets.spec.ts @@ -1,58 +1,68 @@ -import {getGitContext} from "../../src/tools/sync/git"; -import {pushTestTarget} from "../../src/tools"; -import {readTestCasesFromDir} from "../../src/tools/sync/yml"; -import {client} from "../../src/tools/client"; -import {mock} from "jest-mock-extended"; +import { mock } from "jest-mock-extended"; + +import { pushTestTarget } from "../../src/tools"; +import { client } from "../../src/tools/client"; +import { getGitContext } from "../../src/tools/sync/git"; +import { readTestCasesFromDir } from "../../src/tools/sync/yml"; jest.mock("../../src/tools/sync/git"); jest.mock("../../src/tools/sync/yml"); jest.mock("../../src/tools/client"); describe("push", () => { + beforeEach(() => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/main", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + jest.mocked(readTestCasesFromDir).mockReturnValue([]); + jest.mocked(client).POST.mockResolvedValue({ + data: undefined, + error: undefined, + response: mock(), + }); + console.log = jest.fn(); + }); + + it("pushes to main if on default branch", async () => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/main", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + await pushTestTarget({ + testTargetId: "someId", + }); + + expect(client.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/push", + expect.anything(), + ); + }); + + it("pushes to draft if on other branch", async () => { + jest.mocked(getGitContext).mockResolvedValue({ + defaultBranch: "refs/heads/main", + ref: "refs/heads/different", + repo: "my-repo", + owner: "my-org", + sha: "sha256-12123as", + }); + + await pushTestTarget({ + testTargetId: "someId", + }); - beforeEach(() => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/main", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - jest.mocked(readTestCasesFromDir).mockReturnValue([]) - jest.mocked(client).POST.mockResolvedValue({ data: undefined, error: undefined, response: mock() }) - console.log = jest.fn(); - }) - - it("pushes to main if on default branch", async () => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/main", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - await pushTestTarget({ - testTargetId: "someId" - }) - - expect(client.POST).toHaveBeenCalledWith("/apiKey/beta/test-targets/{testTargetId}/push", expect.anything()) - }) - - it("pushes to draft if on other branch", async () => { - jest.mocked(getGitContext).mockResolvedValue({ - defaultBranch: "refs/heads/main", - ref: "refs/heads/different", - repo: "my-repo", - owner: "my-org", - sha: "sha256-12123as" - }) - - await pushTestTarget({ - testTargetId: "someId" - }) - - expect(client.POST).toHaveBeenCalledWith("/apiKey/beta/test-targets/{testTargetId}/draft/push", expect.anything()) - }) -}) \ No newline at end of file + expect(client.POST).toHaveBeenCalledWith( + "/apiKey/beta/test-targets/{testTargetId}/draft/push", + expect.anything(), + ); + }); +}); diff --git a/tests/tools/update.spec.ts b/tests/tools/update.spec.ts index a8d787c..4a5837f 100644 --- a/tests/tools/update.spec.ts +++ b/tests/tools/update.spec.ts @@ -1,48 +1,56 @@ import * as child_process from "node:child_process"; import fs from "fs"; +import { homedir } from "os"; import path from "path"; -import {homedir} from "os"; + import which from "which"; -import {update} from "../../src/tools/update"; + +import { update } from "../../src/tools/update"; jest.mock("node:child_process"); jest.mock("fs"); jest.mock("which"); describe("update", () => { - const mockPathToRoot = path.posix.normalize(path.join(homedir(), "/.local/packages")); - - beforeEach(() => { - jest.mocked(fs.existsSync).mockReturnValue(true); - jest.mocked(which).mockResolvedValue("/usr/bin/npm"); - jest.mocked(child_process.execSync).mockReturnValue(Buffer.from("installed @octomind/octomind@latest")); - console.log = jest.fn(); - console.error = jest.fn(); - process.exit = jest.fn(() => { throw new Error("Process exit") }); + const mockPathToRoot = path.posix.normalize( + path.join(homedir(), "/.local/packages"), + ); + + beforeEach(() => { + jest.mocked(fs.existsSync).mockReturnValue(true); + jest.mocked(which).mockResolvedValue("/usr/bin/npm"); + jest + .mocked(child_process.execSync) + .mockReturnValue(Buffer.from("installed @octomind/octomind@latest")); + console.log = jest.fn(); + console.error = jest.fn(); + process.exit = jest.fn(() => { + throw new Error("Process exit"); }); + }); - afterEach(() => { - jest.clearAllMocks(); - }); + afterEach(() => { + jest.clearAllMocks(); + }); - it("updates successfully when package.json exists and npm is available", async () => { - await update(); + it("updates successfully when package.json exists and npm is available", async () => { + await update(); - expect(child_process.execSync).toHaveBeenCalledWith( - "npm install @octomind/octomind@latest", - { cwd: path.dirname(mockPathToRoot) } - ); - }); + expect(child_process.execSync).toHaveBeenCalledWith( + "npm install @octomind/octomind@latest", + { cwd: path.dirname(mockPathToRoot) }, + ); + }); - it("exits with error if package.json does not exist", async () => { - jest.mocked(fs.existsSync).mockReturnValue(false); + it("exits with error if package.json does not exist", async () => { + jest.mocked(fs.existsSync).mockReturnValue(false); - await expect(update()).rejects.toThrow("Process exit"); - }); + await expect(update()).rejects.toThrow("Process exit"); + }); - it("exits with error if npm is not available", async () => { - jest.mocked(which).mockResolvedValue(""); + it("exits with error if npm is not available", async () => { + jest.mocked(which).mockResolvedValue(""); - await expect(update()).rejects.toThrow("Process exit"); - }); + await expect(update()).rejects.toThrow("Process exit"); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 66a72a3..3a9c7e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,5 +14,8 @@ "typeRoots": ["node_modules/@types"] }, "exclude": ["node_modules", "dist"], - "include": ["**/*.d.ts", "**/*.ts", "**/*.tsx"] + "include": ["**/*.d.ts", "**/*.ts", "**/*.tsx"], + "jest": { + "globals": true + } }