diff --git a/README.md b/README.md index 5e79d05..e1129ff 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ This way even entityIds like environmentIds or testCaseIds will be autocompleted # octomind -Octomind cli tool. Version: 3.3.2. Additional documentation see https://octomind.dev/docs/api-reference/ +Octomind cli tool. Version: 3.4.0. Additional documentation see https://octomind.dev/docs/api-reference/ **Usage:** `octomind [options] [command]` diff --git a/package.json b/package.json index 0b5f744..57039e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@octomind/octomind", - "version": "3.3.2", + "version": "3.4.0", "description": "a command line client for octomind apis", "main": "./dist/index.js", "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", @@ -15,6 +15,10 @@ "./client": { "node": "./dist/tools/client.js", "types": "./dist/tools/client.d.ts" + }, + "./push": { + "node": "./dist/tools/sync/push.js", + "types": "./dist/tools/sync/push.d.ts" } }, "scripts": { @@ -23,7 +27,7 @@ "tsc": "tsc --project tsconfig.build.json", "dev:staging": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://preview.octomind.dev/api tsx src/index.ts", "dev:prod": "pnpm apigen && OCTOMIND_CONFIG_FILE=octomind.dev.staging.json OCTOMIND_API_URL=https://app.octomind.dev/api tsx src/index.ts", - "apigen": "openapi-typescript https://app.octomind.dev/openapi.yaml --output src/api.ts && orval", + "apigen": "openapi-typescript https://preview.octomind.dev/openapi.yaml --output src/api.ts && orval", "build": "pnpm apigen && npx genversion -des src/version.ts && pnpm gendoc && tsc --project tsconfig.build.json", "octomind": "pnpm apigen && tsx src/index.ts", "test": "pnpm apigen && npx genversion -des src/version.ts && jest", diff --git a/src/tools/sync/push.ts b/src/tools/sync/push.ts new file mode 100644 index 0000000..379b15d --- /dev/null +++ b/src/tools/sync/push.ts @@ -0,0 +1,85 @@ +import { Client } from "openapi-fetch"; + +import { components, paths } from "../../api"; +import { ListOptions } from "../client"; +import { checkForConsistency } from "./consistency"; +import { getGitContext } from "./git"; +import { TestTargetSyncData } from "./types"; +import { readTestCasesFromDir } from "./yml"; + +type ErrorResponse = + | components["schemas"]["ZodResponse"] + | string + | { + status: "error"; + error: string; + } + | undefined; + +type PushOptions = { + testTargetId: string; + sourceDir: string; + client: Client; + onError: (error: ErrorResponse) => void; +}; + +export const push = async ( + options: PushOptions, +): Promise<{ success: boolean; versionIds: string[] } | undefined> => { + const testCases = readTestCasesFromDir(options.sourceDir); + checkForConsistency(testCases); + const context = await getGitContext(); + const isDefaultBranch = context?.defaultBranch === context?.ref; + + const body: TestTargetSyncData = { + testCases, + }; + + if (isDefaultBranch) { + return defaultPush(body, options); + } else { + return draftPush(body, options); + } +}; + +const defaultPush = async ( + body: TestTargetSyncData, + options: PushOptions, +): Promise<{ success: boolean; versionIds: string[] } | undefined> => { + const { data, error } = await options.client.POST( + "/apiKey/beta/test-targets/{testTargetId}/push", + { + params: { + path: { + testTargetId: options.testTargetId, + }, + }, + body, + }, + ); + + options.onError(error); + + return data; +}; + +const draftPush = async ( + body: TestTargetSyncData, + options: PushOptions & ListOptions, +): Promise<{ success: boolean; versionIds: string[] } | undefined> => { + const { data, error } = await options.client.POST( + "/apiKey/beta/test-targets/{testTargetId}/draft/push", + { + params: { + path: { + testTargetId: options.testTargetId, + }, + }, + body, + }, + ); + + options.onError(error); + + return data; +}; diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index db256a6..112a2d2 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -2,10 +2,8 @@ import path from "path"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; -import { checkForConsistency } from "./sync/consistency"; -import { getGitContext } from "./sync/git"; -import { TestTargetSyncData } from "./sync/types"; -import { readTestCasesFromDir, writeYaml } from "./sync/yml"; +import { push } from "./sync/push"; +import { writeYaml } from "./sync/yml"; export const getTestTargets = async () => { const { data, error } = await client.GET("/apiKey/v3/test-targets"); @@ -81,68 +79,16 @@ export const pushTestTarget = async ( const sourceDir = options.source ? path.resolve(options.source) : process.cwd(); - const testCases = readTestCasesFromDir(sourceDir); - checkForConsistency(testCases); - const context = await getGitContext(); - const isDefaultBranch = context?.defaultBranch === context?.ref; - - const body: TestTargetSyncData = { - testCases, - }; - - if (isDefaultBranch) { - await defaultPush(body, options); - } else { - await draftPush(body, options); - } -}; - -const defaultPush = async ( - body: TestTargetSyncData, - options: { testTargetId: string; json?: boolean }, -): Promise => { - const { data, error } = await client.POST( - "/apiKey/beta/test-targets/{testTargetId}/push", - { - params: { - path: { - testTargetId: options.testTargetId, - }, - }, - body, - }, - ); - - handleError(error); - - if (options.json) { - logJson(data); - } else { - console.log("Test Target pushed successfully"); - } -}; -const draftPush = async ( - body: TestTargetSyncData, - options: { testTargetId: string; json?: boolean }, -): Promise => { - const { data, error } = await client.POST( - "/apiKey/beta/test-targets/{testTargetId}/draft/push", - { - params: { - path: { - testTargetId: options.testTargetId, - }, - }, - body, - }, - ); - - handleError(error); + const data = await push({ + ...options, + sourceDir, + testTargetId: options.testTargetId, + onError: handleError, + client, + }); if (options.json) { logJson(data); - } else { - console.log("Test Target draft pushed successfully"); } }; diff --git a/tests/tools/sync/push.spec.ts b/tests/tools/sync/push.spec.ts new file mode 100644 index 0000000..f43fa19 --- /dev/null +++ b/tests/tools/sync/push.spec.ts @@ -0,0 +1,87 @@ +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"; + +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("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() + }) +})