@apistudio/apim-cli
Version:
CLI for API Management Products
504 lines (473 loc) • 14.9 kB
text/typescript
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",
},
],
})
);
});
});
});