import { jest } from '@jest/globals' import { APIGatewayEvent, Context } from 'aws-lambda' import { expect as c_expect } from 'chai' import Response from '../../src/API/Response.js' import Transaction from '../../src/BaseEvent/Transaction.js' import Globals from '../../src/Globals.js' import { observableTransaction, defaultHeaders } from '../Test.utils.js' async function testResponse(r: Response, body: any, optCode?: number, optHeaders?: any) { const t = observableTransaction() const context = t['context'] const buildR = await r.build(context, t, false) c_expect(buildR).to.be.undefined expect(t.responseProxy).toBeCalledTimes(1) expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toHaveBeenCalledWith({ statusCode: optCode || 400, headers: { ...defaultHeaders, ...(optHeaders || {}), }, ...(body ? { body: JSON.stringify({ ...body, transactionID: 'unknown', }), } : {}), }) } describe('Response basics', () => { test('Null body', () => { const r = new Response(200, null) c_expect(r.getCode()).to.be.equals(200) c_expect(r.getBody()).to.be.null }) test('String body', () => { const r = new Response(200, 'abc') c_expect(r.getCode()).to.be.equals(200) c_expect(r.getBody()).to.be.equals('abc') }) test('Generic body', () => { const r = new Response(200, { name: 'abc' }) r.appendIntoBody('name2', '123') c_expect(r.getCode()).to.be.equals(200) c_expect(r.getBody().name).to.be.equals('abc') c_expect(r.getBody().name2).to.be.equals('123') }) test('Append/validate header', () => { const r = new Response(200, {}) r.appendHeader('name2', '123') c_expect(r['headers']['name2']).to.be.equals('123') }) }) describe('Response build', () => { test('Succeeds to transaction context', async () => { const b = { name: 'abc' } const r = new Response(200, b) r.appendHeader('abc', '123') // do not use default, so we dont use proxy response for once const t = new Transaction( ({}), ({ done: jest.fn(), fail: jest.fn(), succeed: jest.fn(), }) ) const context = t['context'] const buildR = await r.build(context, t, false) c_expect(buildR).to.be.undefined expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toHaveBeenCalledWith({ statusCode: 200, headers: { ...defaultHeaders, abc: '123', }, body: JSON.stringify({ ...b, transactionID: 'unknown', }), }) }) test('Succeeds with no context call', async () => { const b = { name: 'abc' } const r = new Response(200, b) const t = observableTransaction() const context = t['context'] const buildR = await r.build(context, t, true) c_expect(buildR).to.be.undefined expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() }) test('Succeeds to transaction context as RAW', async () => { const b = { name: 'abc' } const r = new Response(200, b, { rawBody: true, }) const t = observableTransaction() const context = t['context'] const buildR = await r.build(context, t, false) c_expect(buildR).to.be.undefined expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toHaveBeenCalledWith({ ...b, transactionID: 'unknown', }) }) test('Succeeds to transaction piping', async () => { const b = { name: 'abc' } const r = new Response(200, b, { shouldStream: true, }) const t = observableTransaction() const context = t['context'] const buildR = await r.build(context, t, false) await r.build(context, t, false) //pipe twice, should not affect c_expect(buildR).to.be.undefined expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toHaveBeenCalledTimes(1) expect(context.succeed).toHaveBeenCalledWith({ statusCode: 200, headers: { ...defaultHeaders, }, body: { ...b, }, }) }) test('Failure to transaction context as RAW', async () => { const b = { name: 'abc' } const r = new Response(400, b, { rawBody: true, }) const t = observableTransaction() const context = t['context'] const buildR = await r.build(context, t, false) c_expect(buildR).to.be.undefined expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() expect(context.fail).toHaveBeenCalledTimes(1) }) test('Failure exception to transaction context as RAW', async () => { const b = { name: 'abc' } const r = new Response(400, b, { rawBody: true, throwOnErrors: true, }) const t = observableTransaction() const context = t['context'] let buildR: any = null, exception = null try { buildR = await r.build(context, t, false) } catch (e) { exception = e } c_expect(buildR).to.be.null c_expect(exception).to.not.be.null expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() expect(context.fail).not.toBeCalled() }) test('Failure exception to transaction context as RAW and null body', async () => { const b = null const r = new Response(400, b, { rawBody: true, throwOnErrors: true, }) const t = observableTransaction() const context = t['context'] let buildR: any = null, exception = null try { buildR = await r.build(context, t, false) } catch (e) { exception = e } c_expect(buildR).to.be.null c_expect(exception).to.not.be.null expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() expect(context.fail).not.toBeCalled() }) test('Failure exception to transaction context as RAW and custom ero', async () => { const b = { message: 'abc', error: 'error 123' } const r = new Response(400, b, { rawBody: true, throwOnErrors: true, }) const t = observableTransaction() const context = t['context'] let buildR: any = null, exception = null try { buildR = await r.build(context, t, false) } catch (e) { exception = e } c_expect(buildR).to.be.null c_expect(exception).to.not.be.null expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() expect(context.fail).not.toBeCalled() }) }) describe('Response shortcuts', () => { test('MissingParamResponse', async () => { const pName = 'abc' const r = Response.MissingParamResponse(pName) await testResponse(r, { err: `Invalid request. Path parameter ${pName} is missing.`, errCode: Globals.ErrorCode_MissingParam, }) }) test('MissingQueryResponse', async () => { const pName = 'abc' const r = Response.MissingQueryResponse(pName) await testResponse(r, { err: `Invalid request. Query parameter ${pName} is missing.`, errCode: Globals.ErrorCode_MissingParam, }) }) test('BadRequestResponse', async () => { const message = 'abc' const code = 'CODE' const optBody = { additional: 123 } const r = Response.BadRequestResponse(message, code, optBody) await testResponse(r, { err: message, errCode: code, ...optBody, }) }) test('BadRequestResponse no opts', async () => { const message = 'abc' const r = Response.BadRequestResponse(message) await testResponse(r, { err: message, }) }) test('BadRequestResponseWithRollback', async () => { const message = 'abc' const code = 'CODE' const optBody = { additional: 123 } const r = Response.BadRequestResponseWithRollback(message, code, optBody) await testResponse(r, { err: message, rollback: true, errCode: code, ...optBody, }) }) test('BadRequestResponseWithRollback no opts', async () => { const message = 'abc' const r = Response.BadRequestResponseWithRollback(message) await testResponse(r, { err: message, rollback: true, }) }) test('UnauthorizedResponse', async () => { const message = 'abc' const code = 'CODE' const r = Response.UnauthorizedResponse(message, code) await testResponse( r, { err: message, errCode: code, }, 401 ) }) test('UnauthorizedResponse no code', async () => { const message = 'abc' const r = Response.UnauthorizedResponse(message) await testResponse(r, { err: message }, 401) }) test('SuccessResponse', async () => { const body = { additional: 123 } const r = Response.SuccessResponse(body) await testResponse(r, body, 200) }) test('SuccessResponse null body', async () => { const r = Response.SuccessResponse(null) await testResponse(r, {}, 200) }) test('SuccessNoContentResponse', async () => { const r = Response.SuccessNoContentResponse() await testResponse(r, null, 204) }) test('SimpleResponse', async () => { const r = Response.SimpleResponse(null) await testResponse(r, null, 200) }) test('SimpleResponse override statuscode', async () => { const r = Response.SimpleResponse(null, 203) await testResponse(r, null, 203) }) test('RedirectResponse', async () => { const url = 'https://google.com' const r = Response.RedirectResponse(url) await testResponse(r, null, 302, { Location: url }) }) test('SuccessStreamResponse', async () => { const b = { name: 'abc' } const r = Response.SuccessStreamResponse(b, 'test-stream') const t = observableTransaction() const context = t['context'] const buildR = await r.build(context, t, false) await r.build(context, t, false) //pipe twice, should not affect c_expect(buildR).to.be.undefined expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toHaveBeenCalledTimes(1) expect(context.succeed).toHaveBeenCalledWith({ statusCode: 200, headers: { ...defaultHeaders, 'Content-Type': 'test-stream', Connection: 'keep-alive', }, body: { ...b, }, }) }) }) export {}