UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

470 lines (424 loc) 15.3 kB
/** * Copyright IBM Corp. 2024, 2025 */ import { VCM } from '../../src/engine/variable-context-manager/context-manager.js'; import { ModelFactory } from '../../src/model-factories/model.factory.js'; import { Test } from '../../src/schemas/test.schema.js'; import { validAssertionData, validAssertionDataV2, } from '../__mocks__/test-data/assertion.data.js'; import { validEnvironmentData, validEnvironmentDataV2, } from '../__mocks__/test-data/environment.data.js'; import { gatewayEnv, multiGatewayAPIEnv, multiGatewayEnv, } from '../__mocks__/test-data/gateway.data.js'; import { invalidTestSchema, testWithEmptyEnvironment, testWithEmptyAssertion, unsupportedKind, validTest, validTests, validTestWithEnvironmentsAndAssertions, validTestWithMultiAPIAndEnvironment, } from '../__mocks__/test-data/test.data.js'; describe('ModelFactory', () => { beforeEach(() => { jest.clearAllMocks(); }); it('should validate session and undefined kind', () => { const env = { 'PaymentAPI:1.0.1': ['https://localhost:3000/'], }; const factory = new ModelFactory(); factory.create([env]); expect(factory.getGateway()).toEqual(env); expect(factory.getAllTests()).toHaveLength(0); factory.destroy(); expect(factory.getAllTests()).toHaveLength(0); }); it('should store and return valid test', () => { const factory = new ModelFactory(); factory.create([validTest]); const allTests = factory.getAllTests(); expect(allTests).toHaveLength(1); expect(allTests[0].metadata!.name).toBe('TestPayments'); expect(factory.getAllAssertions()).toHaveLength(0); expect(factory.getAllEnvironment()).toHaveLength(0); const { metadata: { namespace, name, version }, } = validTest; const result = factory.getTest(namespace, name, version); expect(result).toBeDefined(); expect(result?.metadata?.name).toBe(name); }); it('should store and return valid assertions', () => { const factory = new ModelFactory(); factory.create([validAssertionData]); const allTests = factory.getAllTests(); expect(allTests).toHaveLength(0); expect(factory.getAllAssertions()).toHaveLength(1); expect(factory.getAllEnvironment()).toHaveLength(0); const { metadata: { namespace, name, version }, } = validAssertionData; const result = factory.getAssertions(namespace, name, version); expect(result).toBeDefined(); expect(result?.metadata?.name).toBe(name); }); it('should store and return valid environment', () => { const factory = new ModelFactory(); factory.create([validEnvironmentData]); const allTests = factory.getAllTests(); expect(allTests).toHaveLength(0); expect(factory.getAllAssertions()).toHaveLength(0); expect(factory.getAllEnvironment()).toHaveLength(1); const { metadata: { namespace, name, version }, } = validEnvironmentData; const result = factory.getEnvironment(namespace, name, version); expect(result).toBeDefined(); expect(result?.metadata?.name).toBe(name); }); it('should throw for invalid schema', () => { const factory = new ModelFactory(); expect(() => factory.create([invalidTestSchema])).toThrow(); }); it('should ignore or throw error for unsupported kind', () => { const factory = new ModelFactory(); expect(() => factory.create([unsupportedKind])).toThrow(); // Assuming you silently ignore expect(factory.getAllTests()).toHaveLength(0); expect(factory.getAllAssertions()).toHaveLength(0); expect(factory.getAllEnvironment()).toHaveLength(0); }); }); describe('ModelFactory.resolveRefs', () => { let factory: ModelFactory; beforeEach(() => { factory = new ModelFactory(); }); it('should resolve API, environment, and assertions for each test ', () => { factory.create(gatewayEnv); factory.create([validAssertionData]); factory.create([validEnvironmentData]); factory.create([validTest]); factory.resolveRefs(); const getTest = factory.getAllTests(); expect(getTest).toHaveLength(1); // Get the environment data to check whether same data is modelled. const env = factory.getEnvironment( 'default', 'TestPaymentsEnvironment', '1.0.0', ); env?.spec?.variables!.forEach((variable) => { const resolvedEnvironment = getTest[0].spec.environment as { variables: Array<{ spec: { variables: any[] }; }>; }; expect(resolvedEnvironment.variables[0].spec.variables).toContainEqual( variable, ); expect( VCM.createContext(getTest[0].vcmId!).get(variable.key!), ).toMatchObject({ value: variable.value, }); }); // Get the assertion data to check whether same data is modelled. const assertions = factory.getAssertions( 'default', 'TestPaymentAssertion', '1.0.0', ); assertions?.spec!.forEach((assert) => { const requestAssertions = getTest[0].spec.request[0].assertions; const resolvedAssertions = requestAssertions && 'assertions' in requestAssertions ? requestAssertions.assertions : []; expect(resolvedAssertions).toBeDefined(); if (resolvedAssertions) { const allAssertionSpecs = resolvedAssertions.flatMap((a) => a.spec); expect(allAssertionSpecs).toContainEqual(assert); } }); }); it('should resolve API, environment, and assertions for each multi gateway and multiple environment test ', () => { factory.create(gatewayEnv); factory.create([validAssertionData, validAssertionDataV2]); factory.create([validEnvironmentData, validEnvironmentDataV2]); factory.create([validTestWithEnvironmentsAndAssertions]); factory.resolveRefs(); const getTests = factory.getAllTests(); expect(getTests).toHaveLength(2); expect(getTests[0].vcmId).not.toEqual(getTests[1].vcmId); }); it('should resolve 2 endpoints with 1 environment ', () => { factory.create(multiGatewayEnv); factory.create([validAssertionData]); factory.create([validEnvironmentData]); factory.create([validTest]); factory.create([validEnvironmentDataV2]); factory.resolveRefs(); const getTests = factory.getAllTests(); expect(getTests).toHaveLength(2); }); it('should resolve 2 gateway endpoints with 2 test suite', () => { factory.create(multiGatewayEnv); factory.create([validAssertionData]); factory.create([validEnvironmentData]); const patchedValidTests = validTests.map((t) => ({ ...t, spec: { ...t.spec, environment: { $ref: 'default:TestPaymentsEnvironment:1.0.0' }, }, })); factory.create(patchedValidTests); factory.create([validEnvironmentDataV2]); factory.resolveRefs(); const getTests = factory.getAllTests(); expect(getTests).toHaveLength(4); }); it('should resolve 1 endpoint and 2 environments ', () => { factory.create([validAssertionData, validAssertionDataV2]); factory.create([validEnvironmentData, validEnvironmentDataV2]); factory.create([validTestWithEnvironmentsAndAssertions]); factory.create([validEnvironmentDataV2]); factory.resolveRefs(); const getTests = factory.getAllTests(); expect(getTests).toHaveLength(2); }); it('should resolve 2x2 api gateway, 2 environments ', () => { factory.create(multiGatewayAPIEnv); factory.create([validAssertionData]); factory.create([validEnvironmentData]); factory.create([validTestWithMultiAPIAndEnvironment]); factory.create([validEnvironmentDataV2]); factory.resolveRefs(); const getTests = factory.getAllTests(); expect(getTests).toHaveLength(8); // All test should have unique VCM to store test results const vcmIds = getTests.map((test) => test.vcmId); const uniqueIds = new Set(vcmIds); expect(uniqueIds.size).toBe(vcmIds.length); }); it('should correctly resolve array of $ref objects in assertions', () => { factory.create([validAssertionData, validAssertionDataV2]); factory.create([validEnvironmentData]); // Create a test with array of assertion ref objects const testWithArrayOfRefObjects = { kind: 'test', metadata: { name: 'TestWithArrayOfRefObjects', version: '1.0.0', namespace: 'default', }, spec: { api: { $endpoint: 'www.test.com', }, environment: { $ref: 'default:TestPaymentsEnvironment:1.0.0', }, request: [ { method: 'POST', resource: '/api/resource', assertions: [ { $ref: 'default:TestPaymentAssertion:1.0.0' }, { $ref: 'default:TestPaymentAssertion:2.0.0' }, ], }, ], }, }; factory.create([testWithArrayOfRefObjects]); factory.resolveRefs(); const tests = factory.getAllTests(); expect(tests).toHaveLength(1); // Verify that both assertion references were resolved correctly const assertions1 = factory.getAssertions( 'default', 'TestPaymentAssertion', '1.0.0', )?.spec; const assertions2 = factory.getAssertions( 'default', 'TestPaymentAssertion', '2.0.0', )?.spec; // Check that all assertions from both references are included in the expressions const assertions = tests[0].spec.request[0].assertions; const expressions = assertions && 'assertions' in assertions ? assertions.assertions : []; assertions1?.forEach((assertion) => { expect(expressions?.[0].spec).toContainEqual( expect.objectContaining({ name: assertion.name, key: assertion.key, value: assertion.value, action: assertion.action, }), ); }); assertions2?.forEach((assertion) => { expect(expressions?.[0].spec).toContainEqual( expect.objectContaining({ name: assertion.name, key: assertion.key, value: assertion.value, action: assertion.action, }), ); }); }); it('should resolve API, environment, and assertions for test collection ', () => { factory.create(gatewayEnv); factory.create([validAssertionData]); factory.create([validEnvironmentData]); // ensure environment is a valid $ref const patchedValidTests = validTests.map((t) => ({ ...t, spec: { ...t.spec, environment: { $ref: 'default:TestPaymentsEnvironment:1.0.0' }, }, })); factory.create(patchedValidTests); factory.resolveRefs(); const getTests = factory.getAllTests(); expect(getTests).toHaveLength(2); if ('variables' in patchedValidTests[1].spec.environment) { expect( (getTests[0].spec.environment as { variables: any[] }).variables, ).toContainEqual( (patchedValidTests[1].spec.environment as { variables: any[] }) .variables[0], ); } const assertions = factory.getAssertions( 'default', 'TestPaymentAssertion', '1.0.0', ); assertions?.spec!.forEach((assert) => { const requestAssertions0 = getTests[0].spec.request[0].assertions; const resolvedAssertions1 = requestAssertions0 && 'assertions' in requestAssertions0 ? requestAssertions0.assertions : []; expect(resolvedAssertions1).toBeDefined(); if (resolvedAssertions1) { const allAssertionSpecs1 = resolvedAssertions1.flatMap((a) => a.spec); expect(allAssertionSpecs1).toContainEqual(assert); } const requestAssertions2 = getTests[1].spec.request[0].assertions; const resolvedAssertions2 = requestAssertions2 && 'assertions' in requestAssertions2 ? requestAssertions2.assertions : []; expect(resolvedAssertions2).toBeDefined(); if (resolvedAssertions2) { const allAssertionSpecs2 = resolvedAssertions2.flatMap((a) => a.spec); expect(allAssertionSpecs2).toContainEqual(assert); } }); }); it('should handle missing environment', () => { expect(() => factory.create([testWithEmptyEnvironment])).toThrow( `Validation error at spec.environment.$ref: $ref cannot be an empty string`, ); }); it('should handle missing assertion', () => { expect(() => factory.create([testWithEmptyAssertion])).toThrow( `Validation error at spec.request.[0].assertions.$ref: $ref cannot be an empty string`, ); }); it('should handle error in resolving values', () => { factory.create([ { ...validTest, spec: { ...validTest.spec, api: { $ref: 'PaymentAPI:3.0.1', }, }, }, ]); expect(() => factory.resolveRefs()).toThrow( 'default:TestPaymentAssertion:1.0.0 is not available in assertions', ); factory.create([validAssertionData]); expect(() => factory.resolveRefs()).toThrow( `Reference variable 'PaymentAPI:3.0.1' not defined`, ); factory.create([ { 'PaymentAPI:3.0.1': [], }, ]); expect(() => factory.resolveRefs()).not.toThrow(); }); it('should handle invalid environment resolution', () => { factory.create([ { ...validTest, spec: { ...validTest.spec, environment: { $ref: 'default:TestPaymentsEnvironment:4.0.0', }, }, }, ]); factory.create(gatewayEnv); factory.create([validAssertionData]); expect(() => factory.resolveRefs()).toThrow( 'default:TestPaymentsEnvironment:4.0.0 is not available in environment', ); }); it('should do nothing if no test s are present', () => { jest.spyOn(factory, 'getAllTests').mockReturnValue([]); jest.spyOn(factory as any, 'resolveAPI').mockImplementation(jest.fn()); jest .spyOn(factory as any, 'resolveEnvironment') .mockImplementation(jest.fn()); jest .spyOn(factory as any, 'resolveAssertions') .mockImplementation(jest.fn()); factory.resolveRefs(); expect((factory as any).resolveAPI).not.toHaveBeenCalled(); expect((factory as any).resolveEnvironment).not.toHaveBeenCalled(); expect((factory as any).resolveAssertions).not.toHaveBeenCalled(); }); it('should handle a single test gracefully', () => { jest .spyOn(factory, 'getAllTestsWithKey') .mockReturnValue([{ key: 'key1', test: validTests[0] as Test }]); jest.spyOn(factory as any, 'resolveAPI').mockImplementation(jest.fn()); jest .spyOn(factory as any, 'resolveEnvironment') .mockImplementation(jest.fn()); jest .spyOn(factory as any, 'resolveAssertions') .mockImplementation(jest.fn()); factory.resolveRefs(); expect((factory as any).resolveAPI).toHaveBeenCalledTimes(1); expect((factory as any).resolveEnvironment).toHaveBeenCalledTimes(1); expect((factory as any).resolveAssertions).toHaveBeenCalledTimes(1); }); it('should handle clearing the factory, when destroy is called', () => { factory.create([validTest]); factory.destroy(); expect(factory.getAllTests()).toHaveLength(0); }); });