UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

539 lines (463 loc) 15.7 kB
/** * Copyright IBM Corp. 2024, 2025 */ import { addErrorToResponse, errorsArray, constructErrorResponse, filterSensitiveData, generateCSV, generatePDF, sanitizeAxiosResponse, isSuccessStatus, parseSetCookies, } from '../../src/helpers/helper.js'; import { Buffer } from 'buffer'; jest.mock('../../src/service/log-wrapper.ts'); describe('addErrorToResponse', () => { beforeEach(() => { // Clear errorsArray before each test errorsArray.length = 0; }); test('should add error object to errorsArray', () => { addErrorToResponse('ERR001', 'field1', 'Error description 1'); expect(errorsArray).toHaveLength(1); expect(errorsArray[0]).toEqual({ code: 'ERR001', field: 'field1', description: 'Error description 1', }); }); test('should add multiple error objects to errorsArray', () => { addErrorToResponse('ERR001', 'field1', 'Error description 1'); addErrorToResponse('ERR002', 'field2', 'Error description 2'); expect(errorsArray).toHaveLength(2); expect(errorsArray[0]).toEqual({ code: 'ERR001', field: 'field1', description: 'Error description 1', }); expect(errorsArray[1]).toEqual({ code: 'ERR002', field: 'field2', description: 'Error description 2', }); }); }); describe('constructErrorResponse', () => { beforeEach(() => { // Clear errorsArray before each test errorsArray.length = 0; }); test('should construct error response object with correct properties', () => { addErrorToResponse('ERR001', 'field1', 'Error description 1'); addErrorToResponse('ERR002', 'field2', 'Error description 2'); const response = constructErrorResponse(); expect(response.respCode).toBe(400); expect(response.message).toBe('Invalid Assets or Reference in the Zip'); expect(response.Endpoints).toEqual([]); expect(response.errors).toHaveLength(2); expect(response.errors[0]).toEqual({ code: 'ERR001', field: 'field1', description: 'Error description 1', }); expect(response.errors[1]).toEqual({ code: 'ERR002', field: 'field2', description: 'Error description 2', }); }); test('should clear errorsArray after constructing error response', () => { addErrorToResponse('ERR001', 'field1', 'Error description 1'); constructErrorResponse(); expect(errorsArray).toHaveLength(0); }); }); describe('filterSensitiveData', () => { it('should remove exact sensitive keys like password and token', () => { const input = { username: 'user1', contact: { username: 'user3', password: 'secret123', // pragma: allowlist secret }, token: 'abcd1234', }; const result = filterSensitiveData(input); expect(result).toEqual({ username: 'user1', contact: { username: 'user3' }, }); }); it('should remove keys that match sensitive patterns like SECRET and sensitive', () => { const input = { API_SECRET: 'hidden', // pragma: allowlist secret sensitive_info: 'classified', userId: 42, }; const result = filterSensitiveData(input); expect(result).toEqual({ userId: 42 }); }); it('should remove case-insensitive matches', () => { const input = { PASSWORD: 'pass', Token: '123', SecretKey: 'topsecret', EnvVar: 'do-not-export', }; const result = filterSensitiveData(input); expect(result).toEqual({}); }); it('should retain non-sensitive fields', () => { const input = { name: 'Alice', email: 'alice@example.com', }; const result = filterSensitiveData(input); expect(result).toEqual(input); }); it('should handle an empty input object', () => { const result = filterSensitiveData({}); expect(result).toEqual({}); }); }); describe('generateCSV', () => { const testData = [ { id: '1', name: 'John Doe', age: 30 }, { id: '2', name: 'Jane Doe', age: 28 }, ]; it('should generate a CSV buffer with the correct header and data', () => { const csvBuffer = generateCSV(testData); expect(csvBuffer.toString()).toBe( 'id,name,age\n1,John Doe,30\n2,Jane Doe,28\n', ); }); it('should handle empty data array', () => { const csvBuffer = generateCSV([]); expect(csvBuffer.toString('utf-8')).toBe(''); }); it('should handle objects with varying field order by using the first object’s keys as headers', () => { const data = [ { firstName: 'Carol', lastName: 'Danvers', score: 95 }, // Second object has keys shuffled Object.assign({}, { score: 82, lastName: 'Frost', firstName: 'Dave' }), ]; const buffer: Buffer = generateCSV(data); const csv = buffer.toString('utf-8'); const lines = csv.trim().split('\n'); expect(lines[0]).toBe('firstName,lastName,score'); expect(lines[1]).toBe('Carol,Danvers,95'); expect(lines[2]).toBe('Dave,Frost,82'); }); }); describe('generatePDF', () => { it('should return a non-empty Buffer', async () => { const data = [{ name: 'Test', value: 123 }]; const buffer = await generatePDF(data); expect(buffer.length).toBeGreaterThan(0); }); }); describe('sanitizeAxiosResponse', () => { it('should handle successful response', () => { const mockResponse = { status: 200, statusText: 'OK', headers: [ { key: 'content-type', value: 'application/json' }, { key: 'set-cookie', value: ['session=123; Path=/; HttpOnly'] }, ], config: { method: 'get', url: 'https://api.example.com/data', timeout: 5000, }, data: { id: 1, name: 'Test' }, responseTime: 250, }; const result = sanitizeAxiosResponse(mockResponse); expect(result).toEqual({ success: true, status: 200, statusText: 'OK', headers: mockResponse.headers, cookies: [{ key: 'session', value: '123' }], config: { method: 'get', url: 'https://api.example.com/data', timeout: 5000, }, data: { id: 1, name: 'Test' }, responseTime: 250, }); }); it('should handle error response', () => { const mockError = { message: 'Request failed with status code 404', code: 'ERR_BAD_REQUEST', response: { status: 404, statusText: 'Not Found', headers: [ { key: 'content-type', value: 'application/json' }, { key: 'set-cookie', value: ['session=xyz; Path=/; HttpOnly'] }, ], data: { error: 'Resource not found' }, }, config: { method: 'get', url: 'https://api.example.com/missing', timeout: 3000, }, }; const result = sanitizeAxiosResponse(null, mockError); expect(result).toEqual({ success: false, message: 'Request failed with status code 404', code: 'ERR_BAD_REQUEST', status: 404, statusText: 'Not Found', headers: mockError.response.headers, cookies: [{ key: 'session', value: 'xyz' }], config: { method: 'get', url: 'https://api.example.com/missing', timeout: 3000, }, data: { error: 'Resource not found' }, }); }); it('should handle error response with missing response object', () => { const mockError = { message: 'Network Error', code: 'ERR_NETWORK', config: { method: 'get', url: 'https://api.example.com/data', timeout: 3000, }, }; const result = sanitizeAxiosResponse(null, mockError); expect(result).toEqual({ success: false, message: 'Network Error', code: 'ERR_NETWORK', status: null, statusText: null, headers: {}, cookies: [], config: { method: 'get', url: 'https://api.example.com/data', timeout: 3000, }, data: null, }); }); it('should handle error response with missing headers', () => { const mockError = { message: 'Request failed', code: 'ERR_BAD_REQUEST', response: { status: 500, statusText: 'Internal Server Error', headers: [], data: { message: 'Server error' }, }, config: { method: 'post', url: 'https://api.example.com/data', }, }; const result = sanitizeAxiosResponse(null, mockError); expect(result).toEqual({ success: false, message: 'Request failed', code: 'ERR_BAD_REQUEST', status: 500, statusText: 'Internal Server Error', headers: [], cookies: [], config: { method: 'post', url: 'https://api.example.com/data', timeout: undefined, }, data: { message: 'Server error' }, }); }); it('should handle error response with missing config', () => { const mockError = { message: 'Request failed', code: 'ERR_BAD_REQUEST', response: { status: 400, statusText: 'Bad Request', headers: [{ key: 'content-type', value: 'application/json' }], data: { message: 'Invalid input' }, }, }; const result = sanitizeAxiosResponse(null, mockError); expect(result).toEqual({ success: false, message: 'Request failed', code: 'ERR_BAD_REQUEST', status: 400, statusText: 'Bad Request', headers: mockError.response.headers, cookies: [], config: { method: undefined, url: undefined, timeout: undefined, }, data: { message: 'Invalid input' }, }); }); }); describe('isSuccessStatus', () => { it('should return true for status codes in the 2xx range', () => { expect(isSuccessStatus(200)).toBe(true); expect(isSuccessStatus(201)).toBe(true); }); it('should return false for status codes outside the 2xx range', () => { // 4xx status codes expect(isSuccessStatus(400)).toBe(false); expect(isSuccessStatus(404)).toBe(false); // 5xx status codes expect(isSuccessStatus(500)).toBe(false); expect(isSuccessStatus(503)).toBe(false); }); }); describe('parseSetCookies', () => { // Test case 1: Handling empty, null, or undefined input describe('handling empty or invalid inputs', () => { it('should return an empty array for empty string', () => { expect(parseSetCookies('')).toEqual([]); }); it('should handle null-like values correctly', () => { // @ts-ignore - Testing runtime behavior with null expect(parseSetCookies(null)).toEqual([]); // @ts-ignore - Testing runtime behavior with undefined expect(parseSetCookies(undefined)).toEqual([]); }); it('should return an empty array for whitespace string', () => { expect(parseSetCookies(' ')).toEqual([]); }); it('should return an empty array for empty array', () => { expect(parseSetCookies([])).toEqual([]); }); }); // Test case 2: Parsing a single Set-Cookie header describe('parsing a single Set-Cookie header', () => { it('should parse a simple name=value cookie', () => { const result = parseSetCookies('session=abc123'); expect(result).toEqual([{ key: 'session', value: 'abc123' }]); }); it('should parse a cookie with attributes', () => { const result = parseSetCookies('session=abc123; Path=/; HttpOnly'); expect(result).toEqual([{ key: 'session', value: 'abc123' }]); }); it('should handle cookies with equals sign in the value', () => { const result = parseSetCookies('data=key1=value1&key2=value2; Path=/'); expect(result).toEqual([ { key: 'data', value: 'key1=value1&key2=value2' }, ]); }); it('should trim whitespace from cookie name and value', () => { const result = parseSetCookies(' session = abc123 ; Path=/'); expect(result).toEqual([{ key: 'session', value: 'abc123' }]); }); }); // Test case 3: Parsing multiple Set-Cookie headers describe('parsing multiple Set-Cookie headers', () => { it('should parse an array of cookie strings', () => { const cookies = [ 'session=abc123; Path=/; HttpOnly', 'user=john; Path=/account; Secure', 'theme=dark', ]; const result = parseSetCookies(cookies); expect(result).toEqual([ { key: 'session', value: 'abc123' }, { key: 'user', value: 'john' }, { key: 'theme', value: 'dark' }, ]); }); it('should handle empty strings in the array', () => { const cookies = ['session=abc123', '', 'theme=dark']; const result = parseSetCookies(cookies); expect(result).toEqual([ { key: 'session', value: 'abc123' }, { key: '', value: '' }, { key: 'theme', value: 'dark' }, ]); }); }); // Test case 4: Handling various cookie attributes describe('handling various cookie attributes', () => { it('should extract only the cookie name-value pair, ignoring attributes', () => { const cookie = 'session=abc123; Expires=Wed, 21 Oct 2025 07:28:00 GMT; Max-Age=3600; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict'; const result = parseSetCookies(cookie); expect(result).toEqual([{ key: 'session', value: 'abc123' }]); }); it('should handle cookies with multiple attributes in different orders', () => { const cookies = [ 'session=abc123; HttpOnly; Path=/; Secure', 'user=john; SameSite=Lax; Domain=example.com', ]; const result = parseSetCookies(cookies); expect(result).toEqual([ { key: 'session', value: 'abc123' }, { key: 'user', value: 'john' }, ]); }); }); // Test case 5: Edge cases and special characters describe('handling edge cases and special characters', () => { it('should handle cookies with special characters in values', () => { const cookies = [ 'complex=value@with+special&chars?', 'json={"key":"value"}', 'encoded=hello%20world', ]; const result = parseSetCookies(cookies); expect(result).toEqual([ { key: 'complex', value: 'value@with+special&chars?' }, { key: 'json', value: '{"key":"value"}' }, { key: 'encoded', value: 'hello%20world' }, ]); }); it('should handle malformed cookies missing values', () => { const cookies = ['session=', 'user', '=value']; const result = parseSetCookies(cookies); expect(result).toEqual([ { key: 'session', value: '' }, { key: 'user', value: '' }, { key: '', value: 'value' }, ]); }); it('should handle cookies with multiple equal signs', () => { const result = parseSetCookies('token=abc=123=xyz; Path=/'); expect(result).toEqual([{ key: 'token', value: 'abc=123=xyz' }]); }); }); // Test case 6: Verifying correct object structure describe('verifying correct object structure', () => { it('should always return an array of objects with key and value properties', () => { const result = parseSetCookies('session=abc123'); expect(Array.isArray(result)).toBe(true); expect(result[0]).toHaveProperty('key'); expect(result[0]).toHaveProperty('value'); expect(Object.keys(result[0]).length).toBe(2); // Only key and value properties }); it('should maintain the order of cookies as provided in the input', () => { const cookies = ['first=cookie', 'second=cookie', 'third=cookie']; const result = parseSetCookies(cookies); expect(result[0].key).toBe('first'); expect(result[1].key).toBe('second'); expect(result[2].key).toBe('third'); }); }); });