import { jest } from '@jest/globals' import { expect as c_expect } from 'chai' import Response from '../../src/API/Response.js' import Transaction from '../../src/BaseEvent/Transaction.js' import type { DbConfig } from '../../src/Database/types.js' import Globals from '../../src/Globals.js' import { defaultHeaders, emptyEvent, observableContext } from '../Test.utils.js' describe('Transaction success invocation path', () => { test('Simple success', async () => { const b = { name: '123' } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) const handlerResp = await transaction.execute(async () => { return Response.SuccessResponse(b) }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 200, body: JSON.stringify(b), headers: defaultHeaders, }) }) test('Simple success - sync return', async () => { const b = { name: '123' } const context = observableContext() const transaction = new Transaction(emptyEvent(), context, { syncReturn: true, }) const handlerResp = await transaction.execute(async () => { return Response.SuccessResponse(b) }) // check resp c_expect(handlerResp).to.be.an.instanceof(Response) c_expect(handlerResp?.getBody()).to.be.deep.equals(b) c_expect(handlerResp?.getCode()).to.be.equals(200) // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() }) }) describe('Transaction error invocation path without response', () => { test('Not valid response returned success', async () => { const b = { err: Globals.ErrorResponseInvalidServerResponse, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: '123', } const context = observableContext({ awsRequestId: '123' }) const transaction = new Transaction(emptyEvent(), context) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return null }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 400, body: JSON.stringify(b), headers: defaultHeaders, }) }) test('Not valid response returned success - sync return', async () => { const b = { err: Globals.ErrorResponseInvalidServerResponse, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: '123', } const context = observableContext() const transaction = new Transaction( emptyEvent({ requestContext: { requestId: '123' }, }), context, { syncReturn: true, } ) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return null }) // check resp c_expect(handlerResp).to.be.an.instanceof(Response) c_expect(handlerResp?.getBody()).to.be.deep.equals(b) c_expect(handlerResp?.getCode()).to.be.equals(400) // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() }) }) describe('Transaction error invocation path with non-Response class response', () => { test('Not a response-class response returned success', async () => { const b = { err: Globals.ErrorResponseInvalidServerResponse, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: 'unknown', } const b2 = { name: 'abc' } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) const handlerResp = await transaction.execute(async () => { return b2 }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 400, body: JSON.stringify(b), headers: defaultHeaders, }) }) test('Not a response-class response returned success - sync return', async () => { const b2 = { name: 'abc' } const context = observableContext() const transaction = new Transaction(emptyEvent(), context, { syncReturn: true, }) const handlerResp = await transaction.execute(async () => { return b2 }) // check resp c_expect(handlerResp).to.be.deep.equals(b2) // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() }) }) describe('Transaction error invocation path with exception', () => { test('Transaction exception', async () => { const b = { err: Globals.ErrorResponseUnhandledError, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: 'unknown', } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) const handlerResp = await transaction.execute(async () => { throw new Error('Failed!') }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 400, body: JSON.stringify(b), headers: defaultHeaders, }) }) test('Transaction exception - sync return', async () => { const b = { err: Globals.ErrorResponseUnhandledError, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: 'unknown', } const context = observableContext() const transaction = new Transaction(emptyEvent(), context, { syncReturn: true, }) const handlerResp = await transaction.execute(async () => { throw new Error('Failed!') }) // check resp c_expect(handlerResp?.getBody()).to.be.deep.equals(b) c_expect(handlerResp?.getCode()).to.be.equals(400) // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() }) test('Transaction exception - sync return', async () => { const context = observableContext() const transaction = new Transaction(emptyEvent(), context, { throwOnErrors: true, }) const error = new Error('Failed!') let handlerResp: any = null, err: any = null try { handlerResp = await transaction.execute(async () => { throw error }) } catch (e) { err = e } // check resp c_expect(handlerResp).to.be.null c_expect(err).to.be.deep.equals(error) // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).not.toBeCalled() }) }) describe('Transaction - db integration', () => { const testResources = () => { const mockTrans = { select: jest.fn(), closeSuccess: jest.fn(), closeFailure: jest.fn(), } const mockCreate = jest.fn( () => ({ transaction: () => new Promise(resolve => resolve(mockTrans)) }) as any ) const transaction = new Transaction(emptyEvent(), observableContext()) transaction['databaseManager'] = { create: mockCreate, } as any return { transaction, mockCreate, mockTrans, } } const config = { username: 'test' } as DbConfig<'knex'> test('Transaction provides dbTransaction and calls closeSuccess', async () => { const { mockCreate, mockTrans, transaction } = testResources() await transaction.execute(async () => { const knexTransaction = await transaction.getDbTransaction(config) knexTransaction.select('name') return Response.SuccessResponse(null) }) expect(mockCreate).toBeCalledWith(config) expect(mockTrans.select).toBeCalledWith('name') expect(mockTrans.closeSuccess).toBeCalled() }) test('Errored transaction calls closeFailure', async () => { const { mockCreate, transaction, mockTrans } = testResources() const ss = await transaction.execute(async () => { const _ii = await transaction.getDbTransaction(config) console.log('ii', _ii) throw new Error('Something went wrong!') return Response.SuccessResponse(null) }) console.log(ss) expect(mockCreate).toBeCalledWith(config) expect(mockTrans.closeFailure).toBeCalled() }) }) describe('Transaction response types', () => { test('Non-response return', async () => { const b = { name: '123' } const b2 = { err: Globals.ErrorResponseInvalidServerResponse, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: 'unknown', } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return { d: 1 } }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 400, body: JSON.stringify(b2), headers: defaultHeaders, }) }) test('Null return', async () => { const b = { name: '123' } const b2 = { err: Globals.ErrorResponseInvalidServerResponse, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: 'unknown', } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return null }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 400, body: JSON.stringify(b2), headers: defaultHeaders, }) }) test('Undefined return', async () => { const b = { name: '123' } const b2 = { err: Globals.ErrorResponseInvalidServerResponse, rollback: true, errCode: Globals.ErrorCode_APIError, transactionID: 'unknown', } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 400, body: JSON.stringify(b2), headers: defaultHeaders, }) }) test('Null inner-response return', async () => { const b = { name: '123' } const b2 = { transactionID: 'unknown' } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return Response.SuccessResponse(null) }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 200, body: JSON.stringify(b2), headers: defaultHeaders, }) }) test('Wrong keys inner-response return', async () => { const b = { name: '123' } const b2 = { notName: '123', transactionID: 'unknown', } const context = observableContext() const transaction = new Transaction(emptyEvent(), context) // @ts-expect-error const handlerResp = await transaction.execute(async () => { return Response.SuccessResponse({ notName: '123' }) }) // check resp c_expect(handlerResp).to.be.null // ctx expect(context.fail).not.toBeCalled() expect(context.done).not.toBeCalled() expect(context.succeed).toBeCalledWith({ statusCode: 200, body: JSON.stringify(b2), headers: defaultHeaders, }) }) }) export {}