diff --git a/package.json b/package.json index 35813e4..ebe33d6 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "author": "octomind", "license": "MIT", "dependencies": { + "@logtape/logtape": "2.0.0", "@playwright/test": "1.57.0", "@types/shell-quote": "1.7.5", "commander": "14.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 30d64cb..ae57003 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@logtape/logtape': + specifier: 2.0.0 + version: 2.0.0 '@playwright/test': specifier: 1.57.0 version: 1.57.0 @@ -486,6 +489,9 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@logtape/logtape@2.0.0': + resolution: {integrity: sha512-z9Hp44mIRXAzgxSyQfFQiRuJ78EMnZa6g43UCxyGOO3RgHjn/7q+5OhdbhypkeHjiJRPxv6RmRsyF0S+OOYWnA==} + '@noble/hashes@2.0.1': resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} engines: {node: '>= 20.19.0'} @@ -2070,6 +2076,8 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@logtape/logtape@2.0.0': {} + '@noble/hashes@2.0.1': {} '@nodelib/fs.scandir@2.1.5': diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index c859a91..6ac4c9a 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -105,7 +105,7 @@ export function generateCommandDocs(command: Command, level = 1): string { */ async function main() { try { - const program = buildCmd(); + const program = await buildCmd(); const markdown = generateCommandDocs(program); diff --git a/src/cli.ts b/src/cli.ts index 1e8b77d..2577f10 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,6 +13,7 @@ import { } from "./completion"; import { executeLocalTestCases, runDebugtopus } from "./debugtopus"; import { resolveTestTargetId } from "./helpers"; +import { configureLogger } from "./logger"; import { startPrivateLocationWorker, stopPLW } from "./plw"; import { batchGeneration, @@ -77,7 +78,9 @@ const createCommandWithCommonOptions = ( .option("-j, --json", "Output raw JSON response") as CompletableCommand; }; -export const buildCmd = (): CompletableCommand => { +export const buildCmd = async (): Promise => { + await configureLogger(); + const program = new CompletableCommand(); program diff --git a/src/completion.ts b/src/completion.ts index f5a0c77..e0375bc 100644 --- a/src/completion.ts +++ b/src/completion.ts @@ -4,6 +4,7 @@ import { install, log, parseEnv, TabtabEnv, uninstall } from "tabtab"; import { BINARY_NAME } from "./cli"; import { loadConfig } from "./config"; +import { logger } from "./logger"; import { getTestReports } from "./tools"; import { getEnvironments } from "./tools/environments"; import { getTestCases } from "./tools/test-cases"; @@ -193,11 +194,11 @@ export const installCompletion = async () => { await install({ name: BINARY_NAME, completer: BINARY_NAME, - }).catch((err) => console.error("INSTALL ERROR", err)); + }).catch((err) => logger.error("INSTALL ERROR", { err })); }; export const uninstallCompletion = async () => { await uninstall({ name: BINARY_NAME, - }).catch((err) => console.error("UNINSTALL ERROR", err)); + }).catch((err) => logger.error("UNINSTALL ERROR", { err })); }; diff --git a/src/config.ts b/src/config.ts index 9268bd7..79ad407 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,6 +3,8 @@ import fs from "fs/promises"; import { homedir } from "os"; import { join } from "path"; +import { logger } from "./logger"; + const CONFIG_DIR = ".config"; export async function getConfigPath(ensureDir?: boolean): Promise { @@ -45,10 +47,7 @@ export async function loadConfig(force?: boolean): Promise { } catch (error) { // only exit on overwrite attempt if (force) { - console.error( - "❌ Error parsing configuration:", - (error as Error).message, - ); + logger.error("❌ Error parsing configuration", { error }); process.exit(1); } return {}; @@ -59,10 +58,10 @@ export async function saveConfig(newConfig: Config): Promise { try { const configPath = await getConfigPath(true); await fs.writeFile(configPath, JSON.stringify(newConfig, null, 2), "utf8"); - console.log(`✅ Configuration saved to ${configPath}`); + logger.info(`✅ Configuration saved to ${configPath}`); configLoaded = false; } catch (error) { - console.error("❌ Error saving configuration:", (error as Error).message); + logger.error("❌ Error saving configuration", { error }); process.exit(1); } } diff --git a/src/debugtopus/index.ts b/src/debugtopus/index.ts index 05ad042..32e7e63 100644 --- a/src/debugtopus/index.ts +++ b/src/debugtopus/index.ts @@ -16,6 +16,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../helpers"; +import { logger } from "../logger"; import { getEnvironments, getPlaywrightCode, @@ -91,7 +92,7 @@ const prepareDirectories = async ( const appDir = dirname(nodeModule.filename); packageRootDir = getPackageRootLevel(appDir); } else { - console.warn("package was not installed as valid nodeJS module"); + logger.warn("package was not installed as valid nodeJS module"); packageRootDir = process.cwd(); } } @@ -169,17 +170,17 @@ const runTests = async ({ command += " --ui"; } - console.log(`executing command : '${command}'`); + logger.info(`executing command : '${command}'`); const { stderr } = await promisify(exec)(command, { cwd: packageRootDir, }); if (stderr) { - console.error(stderr); + logger.error(stderr); process.exit(1); } else { - console.log(`success, you can find your artifacts at ${outputDir}`); + logger.info(`success, you can find your artifacts at ${outputDir}`); } }; diff --git a/src/debugtopus/installation.ts b/src/debugtopus/installation.ts index 6582abd..080346a 100644 --- a/src/debugtopus/installation.ts +++ b/src/debugtopus/installation.ts @@ -5,6 +5,8 @@ import { promisify } from "util"; import { chromium } from "@playwright/test"; +import { logger } from "../logger"; + export const ensureChromiumIsInstalled = async ( packageRootDir: string, ): Promise => { @@ -20,7 +22,7 @@ export const ensureChromiumIsInstalled = async ( reject(error); return; } - console.log( + logger.info( "Couldn't find any chromium binary, executing 'npx playwright install chromium'", ); @@ -32,7 +34,7 @@ export const ensureChromiumIsInstalled = async ( ); playwrightInstallExecution.child?.stdout?.on("data", (data) => - console.log(data), + logger.info(data), ); await playwrightInstallExecution; diff --git a/src/index.ts b/src/index.ts index 05df6f4..fa169c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ #!/usr/bin/env node import { buildCmd } from "./cli"; +import { logger } from "./logger"; -buildCmd().parse(); +void buildCmd() + .then((res) => res.parse()) + .catch(logger.error); diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..c2baaba --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,56 @@ +import { + configure, + FormattedValues, + getConsoleSink, + getLogger, + getTextFormatter, + LogLevel, +} from "@logtape/logtape"; + +export const logger = getLogger("octomind"); + +const ansiColors = { + red: "\x1B[31m", + green: "\x1B[32m", + yellow: "\x1B[33m", + blue: "\x1B[34m", + magenta: "\x1B[35m", + cyan: "\x1B[36m", + // use native terminal colors + none: "", +}; + +const levelColors: Record< + LogLevel | string, + (typeof ansiColors)[keyof typeof ansiColors] +> = { + trace: ansiColors.none, + debug: ansiColors.none, + info: ansiColors.none, + warning: ansiColors.yellow, + error: ansiColors.red, + fatal: ansiColors.red, +}; + +export const configureLogger = async (): Promise => + configure({ + sinks: { + console: getConsoleSink({ + formatter: getTextFormatter({ + timestamp: "disabled", + level: "full", + format: (values: FormattedValues): string => { + return levelColors[values.level] + values.message; + }, + }), + }), + }, + loggers: [ + { + category: ["logtape", "meta"], + sinks: ["console"], + lowestLevel: "warning", + }, + { category: "octomind", lowestLevel: "debug", sinks: ["console"] }, + ], + }); diff --git a/src/plw/index.ts b/src/plw/index.ts index b26e846..ea22afc 100644 --- a/src/plw/index.ts +++ b/src/plw/index.ts @@ -3,6 +3,7 @@ import { randomUUID } from "crypto"; import { createInterface } from "readline"; import { loadConfig } from "../config"; +import { logger } from "../logger"; interface StreamResult { lines: string[]; @@ -39,7 +40,7 @@ const spawnAndStreamLines = async ( rl.on("line", (line: string) => { lines.push(line); - console.log(`-- ${line}`); + logger.info(`-- ${line}`); lineCount++; if (lineCount >= maxLines) { @@ -58,7 +59,7 @@ const spawnAndStreamLines = async ( child.on("exit", (code, signal) => { if (lineCount < maxLines) { - console.log(`Process exited with code ${code}, signal ${signal}`); + logger.info(`Process exited with code ${code}, signal ${signal}`); } resolve({ lines, detached: false, code, signal }); }); @@ -111,19 +112,19 @@ export const startPrivateLocationWorker = async (options: { }); if (!(await checkDockerDaemon())) { - console.error( + logger.error( "Docker daemon is not running. Please start Docker and try again.", ); return; } - console.log( + logger.info( `executing command : '${command.replace(/APIKEY=[^\s&]*/g, "APIKEY=***")}'`, ); const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); - console.log(`Captured ${result.lines.length} lines`); - console.log(`Process detached: ${result.detached}`); + logger.info(`Captured ${result.lines.length} lines`); + logger.info(`Process detached: ${result.detached}`); }; export const stopPLW = async (): Promise => { @@ -131,9 +132,9 @@ export const stopPLW = async (): Promise => { const args = command.split(" "); const result = await spawnAndStreamLines(args[0], args.slice(1), 10); if (result.code === 0) { - console.log("Private Location Worker stopped successfully."); + logger.info("Private Location Worker stopped successfully."); } else { - console.error( + logger.error( `Failed to stop Private Location Worker. Exit code: ${result.code}`, ); } diff --git a/src/tools/client.ts b/src/tools/client.ts index 979aa2a..2c2ad33 100644 --- a/src/tools/client.ts +++ b/src/tools/client.ts @@ -2,6 +2,7 @@ import createClient, { Client, Middleware } from "openapi-fetch"; import type { components, paths } from "../api"; // generated by openapi-typescript import { loadConfig } from "../config"; +import { logger } from "../logger"; import { version } from "../version"; export const BASE_URL = @@ -65,7 +66,7 @@ const createAuthMiddleware = ({ return response; }, onError({ error }) { - console.error(error); + logger.error("error", { error }); process.exit(1); }, }; @@ -77,9 +78,9 @@ export { client, paths }; export const handleError = (error: ErrorResponse) => { if (error) { - console.error(error); + logger.error("error", { error }); if (typeof error === "string" && error.startsWith("403")) { - console.error( + logger.error( "You are not authorized. Check your API key or do a 'octomind init' to set it up.", ); } @@ -91,5 +92,5 @@ export type ListOptions = { json?: boolean; }; export const logJson = (result: unknown): void => { - console.log(JSON.stringify(result, null, 2)); + logger.info(JSON.stringify(result, null, 2)); }; diff --git a/src/tools/discoveries.ts b/src/tools/discoveries.ts index a8f2449..7aa3d14 100644 --- a/src/tools/discoveries.ts +++ b/src/tools/discoveries.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -47,9 +48,9 @@ export const batchGeneration = async ( throw new Error("Batch generation ID is missing"); } - console.log("Batch generation created successfully!"); - console.log(`Batch generation ID: ${response.batchGenerationId}`); - console.log( + logger.info("Batch generation created successfully!"); + logger.info(`Batch generation ID: ${response.batchGenerationId}`); + logger.info( `Batch generation URL: ${await getUrl({ batchGenerationId: response.batchGenerationId, entityType: "batch-generation", @@ -95,13 +96,13 @@ export const createDiscovery = async ( return; } - console.log("Discovery created successfully!"); - console.log( + logger.info("Discovery created successfully!"); + logger.info( `${await getUrl({ testCaseId: response.testCaseId ?? "", entityType: "discovery", })}`, ); - console.log(`Discovery ID: ${response.discoveryId}`); - console.log(`Test Case ID: ${response.testCaseId}`); + logger.info(`Discovery ID: ${response.discoveryId}`); + logger.info(`Test Case ID: ${response.testCaseId}`); }; diff --git a/src/tools/environments.ts b/src/tools/environments.ts index 6712707..c38925f 100644 --- a/src/tools/environments.ts +++ b/src/tools/environments.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type GetEnvironmentsOptions = @@ -26,12 +27,12 @@ export const listEnvironments = async ( return; } - console.log("Environments:"); + logger.info("Environments:"); environments.forEach((environment) => { - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }); }; @@ -99,11 +100,11 @@ export const createEnvironment = async ( return; } - console.log("Environment created successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment created successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const updateEnvironment = async ( @@ -152,11 +153,11 @@ export const updateEnvironment = async ( return; } - console.log("Environment updated successfully!"); - console.log(`- Name: ${response.name}`); - console.log(` ID: ${response.id}`); - console.log(` Discovery URL: ${response.discoveryUrl}`); - console.log(` Updated At: ${response.updatedAt}`); + logger.info("Environment updated successfully!"); + logger.info(`- Name: ${response.name}`); + logger.info(` ID: ${response.id}`); + logger.info(` Discovery URL: ${response.discoveryUrl}`); + logger.info(` Updated At: ${response.updatedAt}`); }; export const getEnvironment = async ( @@ -176,11 +177,11 @@ export const getEnvironment = async ( return; } - console.log("Environment:"); - console.log(`- Name: ${environment.name}`); - console.log(` ID: ${environment.id}`); - console.log(` Discovery URL: ${environment.discoveryUrl}`); - console.log(` Updated At: ${environment.updatedAt}`); + logger.info("Environment:"); + logger.info(`- Name: ${environment.name}`); + logger.info(` ID: ${environment.id}`); + logger.info(` Discovery URL: ${environment.discoveryUrl}`); + logger.info(` Updated At: ${environment.updatedAt}`); }; export const deleteEnvironment = async ( @@ -205,5 +206,5 @@ export const deleteEnvironment = async ( return; } - console.log("Environment deleted successfully!"); + logger.info("Environment deleted successfully!"); }; diff --git a/src/tools/init.ts b/src/tools/init.ts index a815fd2..6470e7c 100644 --- a/src/tools/init.ts +++ b/src/tools/init.ts @@ -1,5 +1,6 @@ import { Config, loadConfig, saveConfig } from "../config"; import { promptUser } from "../helpers"; +import { logger } from "../logger"; import { getTestTargets, listTestTargets } from "./test-targets"; const selectTestTarget = async (): Promise => { @@ -7,7 +8,7 @@ const selectTestTarget = async (): Promise => { await listTestTargets({}); if (testTargets.length === 1) { - console.log( + logger.info( `Only one test target found, using it: ${testTargets[0].app} (${testTargets[0].id})`, ); return testTargets[0].id; @@ -23,12 +24,12 @@ const selectTestTarget = async (): Promise => { testTargetIndexAsInt < 1 || testTargetIndexAsInt > testTargets.length ) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } const testTargetId = testTargets[testTargetIndexAsInt - 1].id; if (!testTargetId) { - console.log("❌ could not find a test target with the index you provided"); + logger.info("❌ could not find a test target with the index you provided"); process.exit(1); } @@ -43,7 +44,7 @@ export const switchTestTarget = async () => { testTargetId, }; await saveConfig(newConfig); - console.log(`✨ Switched to test target: ${testTargetId}`); + logger.info(`✨ Switched to test target: ${testTargetId}`); }; export const init = async (options: { @@ -52,12 +53,12 @@ export const init = async (options: { force?: boolean; }) => { try { - console.log("🚀 Initializing configuration...\n"); + logger.info("🚀 Initializing configuration...\n"); const existingConfig = await loadConfig(options.force); if (existingConfig.apiKey && !options.force) { - console.log("⚠️ Configuration already exists."); + logger.info("⚠️ Configuration already exists."); const overwrite = await promptUser( "Do you want to overwrite it? (y/N): ", ); @@ -66,7 +67,7 @@ export const init = async (options: { overwrite.toLowerCase() !== "y" && overwrite.toLowerCase() !== "yes" ) { - console.log("Configuration unchanged."); + logger.info("Configuration unchanged."); return; } } @@ -76,7 +77,7 @@ export const init = async (options: { "Enter your API key. Go to https://octomind.dev/docs/run-tests/execution-curl#create-an-api-key to learn how to generate one: ", ); if (!options.apiKey) { - console.log("❌ API key is required."); + logger.info("❌ API key is required."); process.exit(1); } } @@ -99,9 +100,9 @@ export const init = async (options: { await saveConfig(newConfig); - console.log("\n✨ Initialization complete!"); + logger.info("\n✨ Initialization complete!"); } catch (error) { - console.error("❌ Error during initialization:", (error as Error).message); + logger.error("❌ Error during initialization", { error }); process.exit(1); } }; diff --git a/src/tools/notifications.ts b/src/tools/notifications.ts index a1b9339..93f9319 100644 --- a/src/tools/notifications.ts +++ b/src/tools/notifications.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -27,28 +28,28 @@ export const listNotifications = async ( return; } - console.log("Notifications:"); + logger.info("Notifications:"); for (const notification of response) { - console.log(`\nID: ${notification.id}`); - console.log(`Type: ${notification.type}`); - console.log(`Created At: ${notification.createdAt}`); + logger.info(`\nID: ${notification.id}`); + logger.info(`Type: ${notification.type}`); + logger.info(`Created At: ${notification.createdAt}`); if (notification.payload?.testReportId) { - console.log(`Test Report ID: ${notification.payload.testReportId}`); - console.log( + logger.info(`Test Report ID: ${notification.payload.testReportId}`); + logger.info( `URL: ${await getUrl({ testReportId: notification.payload.testReportId, entityType: "test-report" })}`, ); } if (notification.payload?.testCaseId) { - console.log(`Test Case ID: ${notification.payload.testCaseId}`); - console.log( + logger.info(`Test Case ID: ${notification.payload.testCaseId}`); + logger.info( `URL: ${await getUrl({ testCaseId: notification.payload.testCaseId, entityType: "test-case" })}`, ); } if (notification.payload?.failed !== undefined) { - console.log(`Failed: ${notification.payload.failed}`); + logger.info(`Failed: ${notification.payload.failed}`); } if (notification.ack) { - console.log(`Acknowledged: ${notification.ack}`); + logger.info(`Acknowledged: ${notification.ack}`); } } }; diff --git a/src/tools/private-locations.ts b/src/tools/private-locations.ts index b91a414..a399778 100644 --- a/src/tools/private-locations.ts +++ b/src/tools/private-locations.ts @@ -1,4 +1,5 @@ import type { components } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { client, handleError, ListOptions, logJson } from "./client"; export type SuccessResponse = components["schemas"]["SuccessResponse"]; @@ -35,7 +36,9 @@ export const registerLocation = async ( return; } - console.log("Registration result:", response.success ? "Success" : "Failed"); + logger.info( + `Registration result: ${response.success ? "Success" : "Failed"}`, + ); }; export const unregisterLocation = async ( @@ -60,9 +63,8 @@ export const unregisterLocation = async ( return; } - console.log( - "Unregistration result:", - response.success ? "Success" : "Failed", + logger.info( + `Unregistration result: ${response.success ? "Success" : "Failed"}`, ); }; @@ -79,10 +81,10 @@ export const listPrivateLocations = async ( return; } - console.log("Private Locations:"); + logger.info("Private Locations:"); response.forEach((location) => { - console.log(`- Name: ${location.name}`); - console.log(` Status: ${location.status}`); - console.log(` Address: ${location.address}`); + logger.info(`- Name: ${location.name}`); + logger.info(` Status: ${location.status}`); + logger.info(` Address: ${location.address}`); }); }; diff --git a/src/tools/sync/git.ts b/src/tools/sync/git.ts index 765b3bd..e18dd43 100644 --- a/src/tools/sync/git.ts +++ b/src/tools/sync/git.ts @@ -2,6 +2,7 @@ import path from "path"; import { simpleGit } from "simple-git"; +import { logger } from "../../logger"; import { ExecutionContext } from "./types"; const FALLBACK_DEFAULT_BRANCH = "refs/heads/main"; @@ -33,7 +34,7 @@ export const parseGitRemote = async (): Promise<{ const revParse = await simpleGit().revparse(["--show-toplevel"]); return { repo: path.basename(revParse) }; } catch (error) { - console.error(error); + logger.error("Failed to parse git remote", { error }); return {}; } }; @@ -58,10 +59,10 @@ export const getDefaultBranch = async ( if (symbolicRefBranch) { return symbolicRefBranch; } - } catch (e) { - console.warn( + } catch (error) { + logger.warn( "could not identify symbolic ref, falling back to origin parsing", - e, + { error }, ); } } @@ -69,7 +70,7 @@ export const getDefaultBranch = async ( try { const origin = await simpleGit().remote(["show", "origin"]); if (!origin) { - console.warn("could not identify default branch, falling back to 'main'"); + logger.warn("could not identify default branch, falling back to 'main'"); return FALLBACK_DEFAULT_BRANCH; } @@ -78,11 +79,10 @@ export const getDefaultBranch = async ( return originDefaultBranch?.groups?.["branchName"] ? `refs/heads/${originDefaultBranch?.groups?.["branchName"]}` : FALLBACK_DEFAULT_BRANCH; - } catch (e) { - console.warn( - "could not identify default branch, falling back to 'main'", - e, - ); + } catch (error) { + logger.warn("could not identify default branch, falling back to 'main'", { + error, + }); } return FALLBACK_DEFAULT_BRANCH; @@ -107,11 +107,10 @@ export const getGitContext = async (): Promise => { defaultBranch, }; return ctx; - } catch (e) { - console.warn( - "could not identify git context, falling back to undefined", - e, - ); + } catch (error) { + logger.warn("could not identify git context, falling back to undefined", { + error, + }); return undefined; } }; diff --git a/src/tools/sync/yaml.ts b/src/tools/sync/yaml.ts index 7451476..edf2d28 100644 --- a/src/tools/sync/yaml.ts +++ b/src/tools/sync/yaml.ts @@ -4,6 +4,7 @@ import path from "path"; import yaml from "yaml"; +import { logger } from "../../logger"; import { PushTestTargetBody } from "../../schemas/octomindExternalAPI"; import { SyncTestCase, TestTargetSyncData } from "./types"; @@ -189,12 +190,12 @@ export const readTestCasesFromDir = (startDir: string): SyncTestCase[] => { if (result.success) { testCases.push(result.data); } else { - console.warn( + logger.warn( `Failed to read test case from ${file}: ${result.error.message}`, ); } } catch { - console.error(`Failed to read test case from ${file}`); + logger.error(`Failed to read test case from ${file}`); } } diff --git a/src/tools/test-cases.ts b/src/tools/test-cases.ts index 742fb06..54a3831 100644 --- a/src/tools/test-cases.ts +++ b/src/tools/test-cases.ts @@ -3,6 +3,7 @@ import path from "path"; import type { components, paths } from "../api"; // generated by openapi-typescript import { findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { getEnvironments } from "./environments"; @@ -37,7 +38,7 @@ export const deleteTestCase = async ( if (options.json) { logJson(data); } - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); return; } @@ -46,7 +47,7 @@ export const deleteTestCase = async ( const existingTestCase = testCasesById[options.testCaseId]; if (!existingTestCase) { - console.log( + logger.info( `No test case with id ${options.testCaseId} found in folder ${octomindRoot}`, ); return; @@ -54,7 +55,7 @@ export const deleteTestCase = async ( const existingTestCasePath = buildFilename(existingTestCase, octomindRoot); await fsPromises.unlink(path.join(octomindRoot, existingTestCasePath)); - console.log("Test Case deleted successfully"); + logger.info("Test Case deleted successfully"); }; export const listTestCase = async ( @@ -81,35 +82,35 @@ export const listTestCase = async ( return; } - console.log("Test Case Details:"); - console.log(`ID: ${response.id}`); - console.log(`Description: ${response.description}`); - console.log(`Status: ${response.status}`); - console.log(`Run Status: ${response.runStatus}`); - console.log(`Created At: ${response.createdAt}`); - console.log(`Updated At: ${response.updatedAt}`); + logger.info("Test Case Details:"); + logger.info(`ID: ${response.id}`); + logger.info(`Description: ${response.description}`); + logger.info(`Status: ${response.status}`); + logger.info(`Run Status: ${response.runStatus}`); + logger.info(`Created At: ${response.createdAt}`); + logger.info(`Updated At: ${response.updatedAt}`); if (response.elements && response.elements.length > 0) { - console.log("\nElements:"); + logger.info("\nElements:"); response.elements.forEach((element, index) => { - console.log(`\nElement ${index + 1}:`); + logger.info(`\nElement ${index + 1}:`); if (element.interaction) { - console.log(` Action: ${element.interaction.action}`); + logger.info(` Action: ${element.interaction.action}`); if (element.interaction.calledWith) { - console.log(` Called With: ${element.interaction.calledWith}`); + logger.info(` Called With: ${element.interaction.calledWith}`); } } if (element.assertion) { - console.log(` Expectation: ${element.assertion.expectation}`); + logger.info(` Expectation: ${element.assertion.expectation}`); if (element.assertion.calledWith) { - console.log(` Called With: ${element.assertion.calledWith}`); + logger.info(` Called With: ${element.assertion.calledWith}`); } } - console.log(" Selectors:"); + logger.info(" Selectors:"); element.selectors?.forEach((selector) => { - console.log(` - ${selector.selectorType}: ${selector.selector}`); + logger.info(` - ${selector.selectorType}: ${selector.selector}`); if (selector.options?.name) { - console.log(` Name: ${selector.options.name}`); + logger.info(` Name: ${selector.options.name}`); } }); }); @@ -193,7 +194,7 @@ export const getTestCaseCode = async ( if (!data) { throw new Error("no test code found"); } - console.log(data.testCode); + logger.info(data.testCode); }; export const listTestCases = async ( @@ -209,16 +210,16 @@ export const listTestCases = async ( return; } - console.log("Test Cases:"); + logger.info("Test Cases:"); for (let idx = 0; idx < testCases.length; idx++) { const testCase = testCases[idx]; const idxString = `${idx + 1}. `.padEnd( testCases.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}Description: ${testCase.description}`); - console.log(`${paddingString}ID: ${testCase.id}`); - console.log( + logger.info(`${idxString}Description: ${testCase.description}`); + logger.info(`${paddingString}ID: ${testCase.id}`); + logger.info( `${paddingString}${await getUrl({ testCaseId: testCase.id, entityType: "test-case", diff --git a/src/tools/test-reports.ts b/src/tools/test-reports.ts index 1e5e509..2571e8e 100644 --- a/src/tools/test-reports.ts +++ b/src/tools/test-reports.ts @@ -1,4 +1,5 @@ import type { components, paths } from "../api"; // generated by openapi-typescript +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; @@ -39,19 +40,19 @@ export const executeTests = async ( } if (data) { - console.log("Test execution started successfully!"); - console.log("Test Report URL:", data.testReportUrl); - console.log("Report Status:", data.testReport?.status); + logger.info("Test execution started successfully!"); + logger.info(`Test Report URL: ${data.testReportUrl}`); + logger.info(`Report Status: ${data.testReport?.status}`); const numberOfTestResults = data.testReport?.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); data.testReport?.testResults?.forEach((result) => { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } }); } @@ -100,15 +101,15 @@ export const listTestReport = async ( } const response = data as TestReport; - console.log("Test Report Details:"); - console.log("Status:", response.status); - console.log("Execution URL:", response.executionUrl); + logger.info("Test Report Details:"); + logger.info(`Status: ${response.status}`); + logger.info(`Execution URL: ${response.executionUrl}`); const numberOfTestResults = response.testResults?.length ?? 0; if (numberOfTestResults > 0) { - console.log("\nTest Results:"); + logger.info("\nTest Results:"); for (const result of response.testResults ?? []) { - console.log(`- Test ${result.testCaseVersionId}: ${result.status}`); - console.log( + logger.info(`- Test ${result.testCaseVersionId}: ${result.status}`); + logger.info( ` ${await getUrl({ testReportId: options.testReportId, testResultId: result.id ?? "", @@ -116,10 +117,10 @@ export const listTestReport = async ( })}`, ); if (result.errorMessage) { - console.log(` Error: ${result.errorMessage}`); + logger.info(` Error: ${result.errorMessage}`); } if (result.traceUrl) { - console.log(` Trace: ${result.traceUrl}`); + logger.info(` Trace: ${result.traceUrl}`); } } } diff --git a/src/tools/test-targets.ts b/src/tools/test-targets.ts index cf39a33..2cb64e4 100644 --- a/src/tools/test-targets.ts +++ b/src/tools/test-targets.ts @@ -4,6 +4,7 @@ import ora from "ora"; import { OCTOMIND_FOLDER_NAME } from "../constants"; import { confirmAction, findOctomindFolder } from "../helpers"; +import { logger } from "../logger"; import { getUrl } from "../url"; import { client, handleError, ListOptions, logJson } from "./client"; import { push } from "./sync/push"; @@ -49,7 +50,7 @@ export const listTestTargets = async (options: ListOptions): Promise => { return; } - console.log("Test Targets:"); + logger.info("Test Targets:"); for (let idx = 0; idx < testTargets.length; idx++) { const testTarget = testTargets[idx]; @@ -57,9 +58,9 @@ export const listTestTargets = async (options: ListOptions): Promise => { testTargets.length.toString().length + 2, ); const paddingString = " ".repeat(idxString.length); - console.log(`${idxString}ID: ${testTarget.id}`); - console.log(`${paddingString}App: ${testTarget.app}`); - console.log( + logger.info(`${idxString}ID: ${testTarget.id}`); + logger.info(`${paddingString}App: ${testTarget.app}`); + logger.info( `${paddingString}${await getUrl({ testTargetId: testTarget.id, entityType: "test-target", @@ -98,7 +99,7 @@ export const pullTestTarget = async ( path.join(process.cwd(), OCTOMIND_FOLDER_NAME); await writeYaml(data, destination); - console.log("Test Target pulled successfully"); + logger.info("Test Target pulled successfully"); }; export const pushTestTarget = async ( @@ -124,7 +125,7 @@ export const pushTestTarget = async ( `Push local changes to test target "${testTarget.app}" with id "${testTarget.id}"?`, ); if (!confirmed) { - console.log("Push cancelled."); + logger.info("Push cancelled."); return; } } diff --git a/src/tools/update.ts b/src/tools/update.ts index 3aff6dd..557bc7a 100644 --- a/src/tools/update.ts +++ b/src/tools/update.ts @@ -5,6 +5,8 @@ import path from "path"; import which from "which"; +import { logger } from "../logger"; + export const update = async (): Promise => { const pathToRoot = path.posix.normalize( path.join(homedir(), ".local", "packages"), @@ -12,14 +14,14 @@ export const update = async (): Promise => { const pathToPackageJson = path.join(pathToRoot, "package.json"); if (!fs.existsSync(pathToPackageJson)) { - console.error( + logger.error( "Cannot find package.json at ~/.local/packages. If you installed globally via npm, run: npm update -g @octomind/octomind", ); process.exit(1); } if (!(await which("npm"))) { - console.error( + logger.error( `cannot determine location of npm, cannot update, please update manually, package location: ${pathToPackageJson}`, ); process.exit(1); @@ -30,7 +32,7 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`version before update: ${oldVersion}`); + logger.info(`version before update: ${oldVersion}`); child_process.execSync("npm install @octomind/octomind@latest", { cwd: pathToRoot, @@ -42,5 +44,5 @@ export const update = async (): Promise => { stdio: ["ignore", "pipe", "ignore"], }); - console.log(`✔ update complete: ${newVersion}`); + logger.info(`✔ update complete: ${newVersion}`); }; diff --git a/src/tools/yamlMutations/create.ts b/src/tools/yamlMutations/create.ts index d819b50..946d268 100644 --- a/src/tools/yamlMutations/create.ts +++ b/src/tools/yamlMutations/create.ts @@ -9,6 +9,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -75,7 +76,7 @@ const openBrowser = async ({ await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); }; @@ -88,7 +89,7 @@ const writeOutput = async ({ createResult: SyncTestCase | "cancelled"; }): Promise => { if (createResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -101,8 +102,8 @@ const writeOutput = async ({ yaml.stringify(createResult), ); - console.log(`Created test case successfully`); - console.log(diff); + logger.info(`Created test case successfully`); + logger.info(diff); }; export const create = async (options: CreateOptions): Promise => { diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index b8fb58e..fe8a00c 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -7,6 +7,7 @@ import { findOctomindFolder, getAbsoluteFilePathInOctomindRoot, } from "../../helpers"; +import { logger } from "../../logger"; import { BASE_URL, client, handleError } from "../client"; import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; @@ -84,7 +85,7 @@ export const edit = async (options: EditOptions): Promise => { await open(localEditingUrl.href); - console.log( + logger.info( `Navigating to local editing url, open it manually if a browser didn't open already: ${localEditingUrl}`, ); @@ -95,7 +96,7 @@ export const edit = async (options: EditOptions): Promise => { ); if (editResult === "cancelled") { - console.log("Cancelled editing test case, exiting"); + logger.info("Cancelled editing test case, exiting"); return; } @@ -108,6 +109,6 @@ export const edit = async (options: EditOptions): Promise => { yaml.stringify(editResult), ); - console.log(`Edited test case successfully`); - console.log(diff); + logger.info(`Edited test case successfully`); + logger.info(diff); }; diff --git a/tests/cli.spec.ts b/tests/cli.spec.ts index 80d01f3..1007942 100644 --- a/tests/cli.spec.ts +++ b/tests/cli.spec.ts @@ -16,8 +16,8 @@ vi.mock("../src/config", () => ({ describe("CLI", () => { let program: Command; - beforeEach(() => { - program = buildCmd(); + beforeEach(async () => { + program = await buildCmd(); program.exitOverride((err) => { throw err; }); diff --git a/tests/config.spec.ts b/tests/config.spec.ts index c1c9b9c..82181cf 100644 --- a/tests/config.spec.ts +++ b/tests/config.spec.ts @@ -5,13 +5,13 @@ import path from "path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Config, loadConfig, resetConfig } from "../src/config"; +import { mockLogger } from "./setup"; vi.mock("fs/promises"); const mockedFs = vi.mocked(fs); describe("Config", () => { beforeEach(() => { - console.error = vi.fn(); resetConfig(); }); @@ -68,7 +68,7 @@ describe("Config", () => { const result = await loadConfig(); expect(result).toEqual({}); - expect(console.error).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); }); it("should error and exit when config file does not exist and force is true", async () => { @@ -89,7 +89,7 @@ describe("Config", () => { await expect(loadConfig(MOCK_FORCE_OPTION)).rejects.toThrow( "Process exit with code: 1", ); - expect(console.error).toHaveBeenCalled(); + expect(mockLogger.error).toHaveBeenCalled(); expect(mockExit).toHaveBeenCalledWith(1); mockExit.mockRestore(); diff --git a/tests/setup.ts b/tests/setup.ts index cd22023..64947ad 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -8,8 +8,18 @@ vi.mock("ora", () => ({ })), })); -beforeEach(() => { - console.log = vi.fn(); - console.warn = vi.fn(); - console.error = vi.fn(); -}); +export const mockLogger = { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + log: vi.fn(), + success: vi.fn(), +}; + +vi.mock("../src/logger", () => ({ + logger: mockLogger, + configureLogger: vi.fn(), +})); + +vi.mock("@logtape/logtape"); diff --git a/tests/tools/init.spec.ts b/tests/tools/init.spec.ts index a29a295..5da0f4a 100644 --- a/tests/tools/init.spec.ts +++ b/tests/tools/init.spec.ts @@ -4,6 +4,7 @@ import { loadConfig, saveConfig } from "../../src/config"; import { promptUser } from "../../src/helpers"; import { init } from "../../src/tools/init"; import { getTestTargets } from "../../src/tools/test-targets"; +import { mockLogger } from "../setup"; vi.mock("../../src/config"); vi.mock("../../src/helpers"); @@ -15,8 +16,6 @@ describe("init", () => { let getTestTargetsMock: Mock; beforeEach(() => { - console.log = vi.fn(); - loadConfigMock = vi.mocked(loadConfig); loadConfigMock.mockResolvedValue({ apiKey: "apiKey" }); @@ -36,10 +35,10 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Only one test target found, using it: testTargetApp (testTargetId)", ); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 3, "\n✨ Initialization complete!", ); @@ -56,7 +55,7 @@ describe("init", () => { apiKey: "newApiKey", testTargetId: "testTargetId1", }); - expect(console.log).toHaveBeenNthCalledWith( + expect(mockLogger.info).toHaveBeenNthCalledWith( 2, "\n✨ Initialization complete!", ); diff --git a/tests/tools/sync/git.spec.ts b/tests/tools/sync/git.spec.ts index 4dc35df..d4a6080 100644 --- a/tests/tools/sync/git.spec.ts +++ b/tests/tools/sync/git.spec.ts @@ -7,6 +7,7 @@ import { getGitContext, parseGitRemote, } from "../../../src/tools/sync/git"; +import { mockLogger } from "../../setup"; vi.mock("simple-git"); @@ -17,30 +18,27 @@ describe("git", () => { mockGit = mock(); vi.mocked(simpleGit).mockReturnValue(mockGit); mockGit.raw.mockResolvedValue("refs/remotes/origin/main"); - console.error = vi.fn(); }); describe("getDefaultBranch", () => { it("should fallback if both origin and symbolic ref fail", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockResolvedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); it("should fallback if both origin and symbolic ref do not return anything and origin throws", async () => { - console.warn = vi.fn(); mockGit.raw.mockResolvedValue(""); mockGit.remote.mockRejectedValue(""); const defaultBranch = await getDefaultBranch(); expect(defaultBranch).toEqual("refs/heads/main"); - expect(console.warn).toHaveBeenCalled(); + expect(mockLogger.warn).toHaveBeenCalled(); }); }); diff --git a/tests/tools/test-cases.spec.ts b/tests/tools/test-cases.spec.ts index 0731b48..98d4bc0 100644 --- a/tests/tools/test-cases.spec.ts +++ b/tests/tools/test-cases.spec.ts @@ -8,6 +8,7 @@ import { findOctomindFolder } from "../../src/helpers"; import { client, handleError } from "../../src/tools/client"; import { buildFilename, readTestCasesFromDir } from "../../src/tools/sync/yaml"; import { deleteTestCase } from "../../src/tools/test-cases"; +import { mockLogger } from "../setup"; vi.mock("../../src/tools/client"); vi.mock("../../src/helpers"); @@ -18,7 +19,6 @@ describe("test-cases", () => { beforeEach(() => { clientDELETE = vi.mocked(client.DELETE); - console.log = vi.fn(); }); describe("deleteTestCase via API", () => { @@ -36,7 +36,7 @@ describe("test-cases", () => { testCaseId: "test-case-id", }); expect(handleError).toHaveBeenCalledWith(undefined); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); }); @@ -84,7 +84,7 @@ describe("test-cases", () => { testCaseId, }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Test Case deleted successfully", ); await expect(fsPromises.access(filePath)).rejects.toThrow(); @@ -98,7 +98,7 @@ describe("test-cases", () => { testCaseId: "non-existent-id", }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( `No test case with id non-existent-id found in folder ${tmpDir}`, ); expect(clientDELETE).not.toHaveBeenCalled(); diff --git a/tests/tools/test-targets.spec.ts b/tests/tools/test-targets.spec.ts index 92e0bcb..07770b6 100644 --- a/tests/tools/test-targets.spec.ts +++ b/tests/tools/test-targets.spec.ts @@ -6,6 +6,7 @@ import { pushTestTarget } from "../../src/tools"; import { client } from "../../src/tools/client"; import { getGitContext } from "../../src/tools/sync/git"; import { readTestCasesFromDir } from "../../src/tools/sync/yaml"; +import { mockLogger } from "../setup"; vi.mock("../../src/helpers"); vi.mock("../../src/tools/sync/git"); @@ -35,7 +36,6 @@ describe("push", () => { error: undefined, response: mock(), }); - console.log = vi.fn(); }); it("pushes to main if on default branch", async () => { @@ -114,7 +114,7 @@ describe("push", () => { }); expect(client.POST).not.toHaveBeenCalled(); - expect(console.log).toHaveBeenCalledWith("Push cancelled."); + expect(mockLogger.info).toHaveBeenCalledWith("Push cancelled."); }); }); }); diff --git a/tests/tools/yamlMutations/create.spec.ts b/tests/tools/yamlMutations/create.spec.ts index eadbc73..f9fcc03 100644 --- a/tests/tools/yamlMutations/create.spec.ts +++ b/tests/tools/yamlMutations/create.spec.ts @@ -19,6 +19,7 @@ import { createMockDraftPushResponse, createMockSyncTestCase, } from "../../mocks"; +import { mockLogger } from "../../setup"; vi.mock("open"); vi.mock("../../../src/helpers"); @@ -32,8 +33,6 @@ describe("create", () => { const GENERATED_TEST_ID = "00000000-0000-0000-0000-000000000000"; beforeEach(() => { - console.log = vi.fn(); - vi.spyOn(crypto, "randomUUID").mockReturnValue(GENERATED_TEST_ID); vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(draftPush).mockResolvedValue( @@ -110,7 +109,7 @@ describe("create", () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -118,7 +117,9 @@ describe("create", () => { it("exits gracefully when creation is finished", async () => { await create({ testTargetId: "someId", name: "Test Name" }); - expect(console.log).toHaveBeenCalledWith("Created test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith( + "Created test case successfully", + ); expect(writeSingleTestCaseYaml).toHaveBeenCalledWith( "/mock/.octomind/new-test.yaml", expect.objectContaining({ id: GENERATED_TEST_ID }), diff --git a/tests/tools/yamlMutations/edit.spec.ts b/tests/tools/yamlMutations/edit.spec.ts index dcb928c..a70d9d5 100644 --- a/tests/tools/yamlMutations/edit.spec.ts +++ b/tests/tools/yamlMutations/edit.spec.ts @@ -16,6 +16,7 @@ import { createMockDraftPushResponse, createMockSyncTestCase, } from "../../mocks"; +import { mockLogger } from "../../setup"; vi.mock("open"); vi.mock("../../../src/helpers"); @@ -29,8 +30,6 @@ describe("edit", () => { const testCase = createMockSyncTestCase({ id: "test-id" }); beforeEach(() => { - console.log = vi.fn(); - vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( "/mock/.octomind/test.yaml", @@ -96,7 +95,7 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith( + expect(mockLogger.info).toHaveBeenCalledWith( "Cancelled editing test case, exiting", ); }); @@ -108,7 +107,9 @@ describe("edit", () => { await edit({ testTargetId: "someId", filePath: "test.yaml" }); - expect(console.log).toHaveBeenCalledWith("Edited test case successfully"); + expect(mockLogger.info).toHaveBeenCalledWith( + "Edited test case successfully", + ); }); it.each([