UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

504 lines (473 loc) 14.9 kB
import fs, { readdirSync } from "fs"; import path from "path"; import { migrateTestAction } from "./migrate-test-action.js"; import { showError, showInfo } from "../helpers/common/message-helper.js"; import { parse } from "yaml"; import { Test } from "@apic/api-model/test/Test.js"; import { MIGRATION_COMPLETED, MIGRATION_STARTED, } from "../constants/message-constants.js"; beforeEach(() => { // @ts-ignore mockExit = jest.spyOn(process, "exit").mockImplementation(() => {}); }); jest.mock("../helpers/common/message-helper.js", () => ({ showError: jest.fn(), showInfo: jest.fn(), })); describe("MigrateTestAction", () => { const namespace = "default"; describe("read files from localDir", () => { const fixtures = path.join(__dirname, "fixtures"); const outputFixtures = path.join(__dirname, "out-fixtures"); beforeAll(() => { fs.mkdirSync(fixtures, { recursive: true }); // Valid YAML files fs.writeFileSync(path.join(fixtures, "valid.yaml"), "key: value"); fs.writeFileSync(path.join(fixtures, "valid.yml"), "a: 1"); // Invalid YAML file fs.writeFileSync( path.join(fixtures, "bad.yaml"), "foo: [this is not valid yaml" ); // Non-YAML file fs.writeFileSync(path.join(fixtures, "ignore.txt"), "should be ignored"); }); afterAll(() => { fs.rmSync(fixtures, { recursive: true, force: true }); fs.rmSync(outputFixtures, { recursive: true, force: true }); }); it("reads a valid YAML file path, but invalid ATM test", async () => { const file = path.join(fixtures, "valid.yaml"); await migrateTestAction({ localDir: file, output: outputFixtures, debug: true, }); expect(showInfo).toHaveBeenCalledWith( expect.stringContaining("Successfully read: 0 Atm test files") ); expect(showError).toHaveBeenCalledWith( expect.stringContaining("Failed to read YAML file:") ); }); it("returns empty for non-YAML file path", async () => { const file = path.join(fixtures, "ignore.txt"); await migrateTestAction({ localDir: file, output: outputFixtures, debug: false, }); expect(showError).toHaveBeenCalledWith( expect.stringContaining("Unsupported path type:") ); }); it("reads all YAML files in a directory, skipping bad and non-ATM files", async () => { await migrateTestAction({ localDir: fixtures, output: outputFixtures, debug: true, }); expect(showInfo).toHaveBeenCalledWith( "Successfully read: 0 Atm test files" ); }); it("returns 0 files for unreadable YAML file", async () => { const bad = path.join(fixtures, "bad.yaml"); await migrateTestAction({ localDir: bad, output: outputFixtures, debug: true, }); expect(showInfo).toHaveBeenCalledWith( expect.stringContaining("Successfully read: 0 Atm test files") ); }); it("warns for unsupported path type", async () => { const socketPath = path.join(fixtures, "nonexistent.sock"); // Create a broken symlink to simulate non-regular file try { fs.symlinkSync("/dev/null", socketPath); } catch {} await migrateTestAction({ localDir: socketPath, output: outputFixtures, debug: true, }); expect(showError).toHaveBeenCalledWith( expect.stringContaining("Unsupported path type:") ); }); }); describe("debug mode", () => { beforeEach(() => { (showInfo as jest.Mock).mockClear(); }); it("should print debug messages when debug is true", async () => { await migrateTestAction({ localDir: "", output: "", debug: true, }); expect(showInfo).toHaveBeenCalledTimes(3); expect(showInfo).toHaveBeenCalledWith( expect.stringContaining(MIGRATION_STARTED) ); expect(showInfo).toHaveBeenCalledWith( expect.stringContaining("Started reading ATM test files from: ") ); expect(showError).toHaveBeenCalledWith( expect.stringContaining("Error: ENOENT: no such file or directory,") ); }); it("should not print debug messages when debug is false", async () => { await migrateTestAction({ localDir: "", output: "", debug: false, }); expect(showInfo).toHaveBeenCalledTimes(2); expect(showInfo).toHaveBeenCalledWith( expect.stringContaining(MIGRATION_STARTED) ); expect(showInfo).toHaveBeenCalledWith( expect.stringContaining(MIGRATION_COMPLETED) ); expect(showError).toHaveBeenCalledWith( expect.stringContaining("Error: ENOENT: no such file or directory,") ); }); }); describe("migrateTestAction", () => { const output = "test/resources/atm/test-output"; afterEach(() => { fs.rmSync(output, { recursive: true, force: true }); }); it("should write valid yaml files for version 2, each for input variables", async () => { const inputFileName = "atm-test-v2.yaml"; const localDir = `test/resources/atm/${inputFileName}`; await migrateTestAction({ localDir, output, debug: false, }); const files = readdirSync(output, { withFileTypes: true }).filter((f) => f.isFile() ); expect(files.length).toBe(5); const test = parse(fs.readFileSync(`${output}/${inputFileName}`, "utf8")); expect(test).toEqual( expect.objectContaining({ apiVersion: "api.webmethods.io/beta", kind: "test", metadata: { name: "Test for atm-test-v2", namespace: "default", tags: [], version: "1.0.0", }, spec: { api: { $endpoint: "// Replace with a valid endpoint", }, environment: { $ref: "default:environment_default_atm-test-v2:1.0.0", }, request: [ { assertions: { $ref: "default:assertion_atm-test-v2:1.0.0", }, endpoint: "${base_url}/bank", headers: [ { key: "Authorization", value: "Bearer ${api_key}", }, ], method: "get", resource: "/${map1.path}", var: "branches", }, { assertions: { $ref: "default:assertion_1_atm-test-v2:1.0.0", }, endpoint: "https://sample-api.us-east-a.apiconnect.automation.ibm.com/orders/order", method: "get", resource: "/AB1234", var: "payload", }, ], }, }) ); expect( parse( fs.readFileSync( `${output}/environment_default_atm-test-v2.yaml`, "utf-8" ) ) ).toEqual( expect.objectContaining({ apiVersion: "api.webmethods.io/beta", kind: "environment", metadata: { name: "environment_default_atm-test-v2", namespace: "default", tags: [], version: "1.0.0", }, spec: { variables: [ { isSecret: false, key: "X-IBM-Client-Id", value: "X-IBM-Client-Id", }, { isSecret: false, key: "orderNumber", value: "3", }, { isSecret: false, key: "base_url", value: "url.com", }, { isSecret: false, key: "api_key", value: "abcd", }, { isSecret: false, key: "value", value: 2, }, { isSecret: false, key: "map1", value: { path: "branches2", path1: "branches1", }, }, { isSecret: false, key: "boolean", value: true, }, { isSecret: false, key: "array", value: ["array1", "array4", "array3"], }, ], }, }) ); expect( fs.existsSync(`${output}/environment_second_atm-test-v2.yaml`) ).toBeTruthy(); expect( fs.existsSync(`${output}/assertion_atm-test-v2.yaml`) ).toBeTruthy(); expect( parse( fs.readFileSync(`${output}/assertion_1_atm-test-v2.yaml`, "utf-8") ) ).toEqual( expect.objectContaining({ apiVersion: "api.webmethods.io/beta", kind: "assertion", metadata: { name: "assertion_1_atm-test-v2", namespace: "default", tags: [], version: "1.0.0", }, spec: [ { action: "greaterThan", key: "${code}", name: "assert-greater => greaterThan", value: "${value}", }, { action: "lessThan", key: "${code}", name: "assert-less => lessThan", value: "${value}", }, { action: "equals", key: "headers().content-type", name: "assert-equals => equals", value: "application/json", }, { action: "include", key: ["application/json", "${array.2}"], name: "assert-in => include", value: "headers().content-type", }, { action: "include", key: "headers().content-type", name: "assert-contains => include", value: "app", }, { action: "matches", key: "headers().content-type", name: "assert-matches => matches", value: "^[a-z]+/json", }, { action: "haveProperty", key: "${payload}", name: "assert-exists => haveProperty", }, { action: "type", key: "${payload.tracking_reference}", name: "assert-is => type", value: "string", }, { action: "haveProperty", key: "${payload.shipped_at}", name: "assert-exists => haveProperty", }, ], }) ); }); it("should write valid yaml file for version 1", async () => { const inputFileName = "atm-test-v1.yaml"; const localDir = `test/resources/atm/${inputFileName}`; await migrateTestAction({ localDir, output, debug: false, }); const files = readdirSync(output, { withFileTypes: true }) .filter((f) => f.isFile()) .map(({ name }) => name); expect(files.length).toBe(4); expect(files).toEqual( expect.arrayContaining([ "assertion_1_atm-test-v1.yaml", "assertion_atm-test-v1.yaml", "atm-test-v1.yaml", "environment_default_atm-test-v1.yaml", ]) ); }); it("should write valid yaml for if condition in atm yaml's", async () => { const inputFileName = "atm-test-if-v2.yaml"; const localDir = `test/resources/atm/${inputFileName}`; await migrateTestAction({ localDir, output, debug: false, }); expect( parse(fs.readFileSync(`${output}/assertion_${inputFileName}`, "utf8")) ).toEqual( expect.objectContaining({ spec: [ { action: "haveProperty", if: "${branches}", key: "${branches.1}", name: "assert-exists => haveProperty", }, { action: "equals", if: "${branches}", key: "${branches.0.type}", name: "assert-equals => equals", stopOnFail: true, value: "string", }, { action: "equals", key: "${branches.0.type}", name: "assert-compares => equals", stopOnFail: true, value: "${branches.1.type}", }, ], }) ); }); it("should read and write valid petStore yaml", async () => { const inputFileName = "atm-pet-store.yaml"; const localDir = `test/resources/atm/${inputFileName}`; const outputV1 = "test/resources/atm/out"; fs.rmSync(outputV1, { recursive: true, force: true }); await migrateTestAction({ localDir, output: outputV1, debug: true, }); expect(fs.existsSync(`${outputV1}/${inputFileName}`)).toBeTruthy(); }); it("should read and output all atm tests", async () => { const localDir = "test/resources/atm/"; await migrateTestAction({ localDir, output, debug: false, }); const files = readdirSync(output, { withFileTypes: true }) .filter((f) => f.isFile()) .map(({ name }) => name); expect(files.length).toBe(20); expect(files).toEqual( expect.arrayContaining([ "assertion_1_atm-pet-store.yaml", "assertion_1_atm-test-if-v2.yaml", "assertion_1_atm-test-v1.yaml", "assertion_1_atm-test-v2.yaml", "assertion_atm-pet-store.yaml", "assertion_atm-test-if-v2.yaml", "assertion_atm-test-v1.yaml", "assertion_atm-test-v2.yaml", "atm-pet-store.yaml", "atm-test-if-v2.yaml", ]) ); }); it("should process atm files with invalid type also", async () => { const inputFileName = "atm-test-invalid-type"; const localDir = `test/resources/atm/${inputFileName}.yml`; await migrateTestAction({ localDir, output, debug: false, }); expect( parse( fs.readFileSync(`${output}/assertion_${inputFileName}.yaml`, "utf8") ) ).toEqual( expect.objectContaining({ spec: [ { action: "type", key: "${branches}", name: "assert-is => type", value: "array", }, ], }) ); }); }); });