Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions solution/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
*.d.ts
*.js
node_modules
cdktf.out
cdktf.log
*terraform.*.tfstate*
.gen
.terraform
tsconfig.tsbuildinfo
!jest.config.js
!setup.js
13 changes: 13 additions & 0 deletions solution/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Prerequisitos
- Terraform
- Node
- Setup aws configure in AWS CLI
- Git Bash

Deploy de Infraestructura
- npm run build
- cdktf get
- cdktf deploy

Eliminar Infraestructura
- cdktf destroy
81 changes: 81 additions & 0 deletions solution/__tests__/lambdas/execute-payment/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { handler } from '../../../src/lambdas/update-account/handler';
import { ExecutePaymemtDependencyInjectionContainer } from '../../../src/lambdas/execute-payment/execute-payment-di';
import { ExecutePaymentUseCase } from '../../../src/lambdas/execute-payment/execute-payment-use-case';
import { DynamoDBStreamEvent } from 'aws-lambda';


jest.mock('../../../src/lambdas/execute-payment/execute-payment-di');

class MockExecutePaymentUseCase extends ExecutePaymentUseCase {
constructor() {
super({} as any);
}

executePayment = jest.fn();
}

describe('execute-payment handler', () => {
let mockExecutePaymentUseCase: MockExecutePaymentUseCase;

beforeEach(() => {
mockExecutePaymentUseCase = new MockExecutePaymentUseCase();

jest.spyOn(ExecutePaymemtDependencyInjectionContainer.prototype, 'executePaymentUseCase', 'get').mockReturnValue(mockExecutePaymentUseCase);
});

afterEach(() => {
jest.clearAllMocks();
});

it('should return transactionId and generatedId on successful payment', async () => {
const mockEvent: DynamoDBStreamEvent = {
Records: [
{
eventName: 'INSERT',
dynamodb: {
NewImage: {
accountId: { S: '12345' },
amount: { N: '100.50' },
},
},
},
],
};

const mockTransactionId = '12345';

mockExecutePaymentUseCase.executePayment.mockResolvedValue(mockTransactionId);

const response = await handler(mockEvent);

expect(mockExecutePaymentUseCase.executePayment).toHaveBeenCalledWith('12345', 100.50);
expect(mockExecutePaymentUseCase.executePayment).toHaveBeenCalledTimes(1);

expect(response).toEqual({
transactionId: mockTransactionId,
id: expect.any(Number)
});
});

it('should throw an error when payment fails', async () => {
const mockEvent: DynamoDBStreamEvent = {
Records: [
{
eventName: 'INSERT',
dynamodb: {
NewImage: {
accountId: { S: '12345' },
amount: { N: '100.50' },
},
},
},
],
};
mockExecutePaymentUseCase.executePayment.mockRejectedValue(new Error('Could not process payment'));

await expect(handler(mockEvent)).rejects.toThrow('Could not process payment');

expect(mockExecutePaymentUseCase.executePayment).toHaveBeenCalledWith('12345', 100.50);
expect(mockExecutePaymentUseCase.executePayment).toHaveBeenCalledTimes(1);
});
});
67 changes: 67 additions & 0 deletions solution/__tests__/lambdas/get-account/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { handler } from '../../../src/lambdas/get-account/handler';

jest.mock('@aws-sdk/lib-dynamodb');

describe('Lambda handler', () => {
let mockSend: jest.Mock;

beforeEach(() => {
mockSend = jest.fn();
DynamoDBDocumentClient.prototype.send = mockSend;
});

afterEach(() => {
jest.clearAllMocks();
});

it('should return 200 and the item when the item is found', async () => {
const mockItem = { id: '123', name: 'Test Item' };
mockSend.mockResolvedValue({ Item: mockItem });

const event: Partial<APIGatewayProxyEvent> = {
pathParameters: {
id: '123',
},
};

const response = await handler(event as APIGatewayProxyEvent) as APIGatewayProxyResult;

expect(response.statusCode).toBe(200);
expect(JSON.parse(response.body)).toEqual(mockItem);
expect(mockSend).toHaveBeenCalledWith(expect.any(GetCommand));
});

it('should return 404 when the item is not found', async () => {
mockSend.mockResolvedValue({});

const event: Partial<APIGatewayProxyEvent> = {
pathParameters: {
id: '123',
},
};

const response = await handler(event as APIGatewayProxyEvent) as APIGatewayProxyResult;

expect(response.statusCode).toBe(404);
expect(JSON.parse(response.body)).toEqual({ message: 'Item not found' });
expect(mockSend).toHaveBeenCalledWith(expect.any(GetCommand));
});

it('should return 500 when there is an error retrieving the item', async () => {
mockSend.mockRejectedValue(new Error('Something went wrong'));

const event: Partial<APIGatewayProxyEvent> = {
pathParameters: {
id: '123',
},
};

const response = await handler(event as APIGatewayProxyEvent) as APIGatewayProxyResult;

expect(response.statusCode).toBe(500);
expect(JSON.parse(response.body)).toEqual({ message: 'Could not retrieve item', error: 'Something went wrong' });
expect(mockSend).toHaveBeenCalledWith(expect.any(GetCommand));
});
});
132 changes: 132 additions & 0 deletions solution/__tests__/lambdas/update-account/handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { handler } from '../../../src/lambdas/update-account/handler';
import { UpdateAccountDependencyInjectionContainer } from '../../../src/lambdas/update-account/update-account-di';
import { UpdateAccountUseCase } from '../../../src/lambdas/update-account/update-account-use-case';
import { AccountRepository } from '../../../src/layer/nodejs/repositories/account/account-repository'

jest.mock('../../../src/lambdas/update-account/update-account-di');

class MockUpdateAccountUseCase extends UpdateAccountUseCase {
constructor() {
super({} as AccountRepository);
}

updateAccount = jest.fn();
}

describe('update-account handler', () => {
let mockUpdateAccountUseCase: MockUpdateAccountUseCase;

beforeEach(() => {
mockUpdateAccountUseCase = new MockUpdateAccountUseCase();

jest.spyOn(UpdateAccountDependencyInjectionContainer.prototype, 'updateAccountUseCase', 'get').mockReturnValue(mockUpdateAccountUseCase);

jest.spyOn(UpdateAccountDependencyInjectionContainer.prototype, 'accountRepository', 'get').mockReturnValue({
getAccount: jest.fn(),
updateAccountBalance: jest.fn(),
});
});

afterEach(() => {
jest.clearAllMocks();
});

it('should process INSERT records and update account', async () => {
const mockEvent = {
Records: [
{
eventName: 'INSERT',
dynamodb: {
NewImage: {
data: {
M: {
accountId: { S: '12345' },
amount: { N: '100.50' },
},
},
},
},
},
],
} as any;

await handler(mockEvent);

expect(mockUpdateAccountUseCase.updateAccount).toHaveBeenCalledWith('12345', 100.50);
expect(mockUpdateAccountUseCase.updateAccount).toHaveBeenCalledTimes(1);
});

it('should log an error if updateAccount fails', async () => {
const mockEvent = {
Records: [
{
eventName: 'INSERT',
dynamodb: {
NewImage: {
data: {
M: {
accountId: { S: '12345' },
amount: { N: '100.50' },
},
},
},
},
},
],
} as any;

const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
mockUpdateAccountUseCase.updateAccount.mockRejectedValue(new Error('Update failed'));

await handler(mockEvent);

expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('updateAccount failed with error'));
expect(mockUpdateAccountUseCase.updateAccount).toHaveBeenCalledTimes(1);

consoleSpy.mockRestore();
});

it('should skip records that do not have a NewImage', async () => {
const mockEvent = {
Records: [
{
eventName: 'INSERT',
dynamodb: {},
},
],
} as any;

const consoleSpy = jest.spyOn(console, 'log').mockImplementation();

await handler(mockEvent);

expect(mockUpdateAccountUseCase.updateAccount).not.toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith('Could not read record adata');

consoleSpy.mockRestore();
});

it('should skip records that are not INSERT', async () => {
const mockEvent = {
Records: [
{
eventName: 'MODIFY',
dynamodb: {
NewImage: {
data: {
M: {
accountId: { S: '12345' },
amount: { N: '100.50' },
},
},
},
},
},
],
} as any;

await handler(mockEvent);

expect(mockUpdateAccountUseCase.updateAccount).not.toHaveBeenCalled();
});
});
11 changes: 11 additions & 0 deletions solution/cdktf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"language": "typescript",
"app": "npx ts-node src/infrastructure/main.ts",
"projectId": "f2855776-c01d-4e55-8c30-36ca5af89ecd",
"sendCrashReports": "false",
"terraformProviders": [
"hashicorp/aws@~> 5.65"
],
"terraformModules": [],
"context": {}
}
51 changes: 51 additions & 0 deletions solution/help
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
========================================================================================================

Your CDKTF TypeScript project is ready!

cat help Print this message

Compile:
npm run get Import/update Terraform providers and modules (you should check-in this directory)
npm run compile Compile typescript code to javascript (or "npm run watch")
npm run watch Watch for changes and compile typescript in the background
npm run build Compile typescript

Synthesize:
cdktf synth [stack] Synthesize Terraform resources from stacks to cdktf.out/ (ready for 'terraform apply')

Diff:
cdktf diff [stack] Perform a diff (terraform plan) for the given stack

Deploy:
cdktf deploy [stack] Deploy the given stack

Destroy:
cdktf destroy [stack] Destroy the stack

Test:
npm run test Runs unit tests (edit __tests__/main-test.ts to add your own tests)
npm run test:watch Watches the tests and reruns them on change

Upgrades:
npm run upgrade Upgrade cdktf modules to latest version
npm run upgrade:next Upgrade cdktf modules to latest "@next" version (last commit)

Use Providers:

You can add prebuilt providers (if available) or locally generated ones using the add command:

cdktf provider add "aws@~>3.0" null kreuzwerker/docker

You can find all prebuilt providers on npm: https://www.npmjs.com/search?q=keywords:cdktf
You can also install these providers directly through npm:

npm install @cdktf/provider-aws
npm install @cdktf/provider-google
npm install @cdktf/provider-azurerm
npm install @cdktf/provider-docker
npm install @cdktf/provider-github
npm install @cdktf/provider-null

You can also build any module or provider locally. Learn more https://cdk.tf/modules-and-providers

========================================================================================================
19 changes: 19 additions & 0 deletions solution/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json',
},
},
moduleNameMapper: {
"^/opt/nodejs/(.*)$": "<rootDir>/src/layer/nodejs/$1",
},
// moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
testMatch: ['**/__tests__/**/*.test.ts', '**/__tests__/**/*.spec.ts'],
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
transformIgnorePatterns: ['/node_modules/'],
};
Loading