Skip to content
Merged
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
3 changes: 0 additions & 3 deletions config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
import { Runtime } from 'aws-cdk-lib/aws-lambda'

export const MAIN_USER_NAME: string = 'troy'
export const HALFSIES_MAX_LOGS: number = 300
export const HALFSIES_NODE_VERSION = Runtime.NODEJS_20_X
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
testEnvironment: 'node',
rootDir: './',
roots: ['<rootDir>/src'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
transform: {
'^.+\\.ts?$': 'ts-jest',
},
Expand Down
3 changes: 3 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
afterEach(() => {
jest.clearAllMocks()
})
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "troyblank-api",
"version": "1.0.0",
"packageManager": "yarn@1.22.21",
"packageManager": "yarn@1.22.22",
"bin": {
"halfsies": "bin/halfsies.js"
},
Expand All @@ -15,20 +15,21 @@
"deploy": "cdk deploy --all"
},
"dependencies": {
"aws-sdk": "2.1606.0",
"constructs": "10.3.0",
"@aws-sdk/client-dynamodb": "3.797.0",
"@aws-sdk/lib-dynamodb": "3.797.0",
"constructs": "10.4.2",
"source-map-support": "0.5.21"
},
"devDependencies": {
"@troyblank/eslint-config-troyblank": "2.4.0",
"@types/chance": "1.1.6",
"@types/aws-lambda": "8.10.137",
"@types/jest": "29.5.12",
"@types/node": "20.12.7",
"aws-cdk": "2.139.0",
"aws-cdk-lib": "2.139.0",
"@types/aws-lambda": "8.10.149",
"@types/jest": "29.5.14",
"@types/node": "22.14.1",
"aws-cdk": "2.1012.0",
"aws-cdk-lib": "2.192.0",
"aws-lambda": "1.0.7",
"chance": "1.1.11",
"chance": "1.1.12",
"esbuild": "0.20.2",
"jest": "29.7.0",
"ts-jest": "29.1.2",
Expand Down
21 changes: 11 additions & 10 deletions src/lambdas/halfsies/createHalfsie.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MAIN_USER_NAME } from '../../../config'
import { HalfsieLog } from '../../types'
import { mockApiGatewayProxyEvent, mockHalfsieLogs, mockNewLog } from '../../mocks'
import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants'
import * as utils from '../../utils'
import { pruneLogs } from '../../utils/halfsies/log'
import {
getBalance,
getUserName,
Expand All @@ -12,12 +12,10 @@ import {
} from './utils'
import { handler } from './createHalfsie'

jest.mock('../../utils', () => {
return {
__esModule: true,
...jest.requireActual('../../utils'),
}
})
jest.mock('../../utils/halfsies/log', () => ({
...jest.requireActual('../../utils/halfsies/log'),
pruneLogs: jest.fn(),
}))
jest.mock('./utils')

describe('Lambda - Create Halfsie', () => {
Expand All @@ -26,6 +24,9 @@ describe('Lambda - Create Halfsie', () => {
const newLogs: HalfsieLog[] = mockHalfsieLogs()

beforeEach(() => {
process.env.balanceTableName = chance.word({ syllables: 4 })
process.env.halfsiesLogTableName = chance.word({ syllables: 4 })

jest.mocked(getBalance).mockResolvedValue({
data: currentBalance,
isError: false,
Expand All @@ -43,7 +44,7 @@ describe('Lambda - Create Halfsie', () => {
errorMessage: undefined,
})

jest.spyOn(utils, 'pruneLogs').mockResolvedValue({
jest.mocked(pruneLogs).mockResolvedValue({
data: newLogs,
isError: false,
errorMessage: undefined,
Expand Down Expand Up @@ -244,7 +245,7 @@ describe('Lambda - Create Halfsie', () => {
it('should return an error if there is a problem using the prune logs util', async () => {
const errorMessage = chance.sentence()

jest.spyOn(utils, 'pruneLogs').mockRejectedValue(errorMessage)
jest.mocked(pruneLogs).mockRejectedValue(errorMessage)

const result = await handler(mockApiGatewayProxyEvent(
mockNewLog(),
Expand All @@ -263,7 +264,7 @@ describe('Lambda - Create Halfsie', () => {
it('should return an error if there is a problem pruning the logs', async () => {
const errorMessage = chance.sentence()

jest.spyOn(utils, 'pruneLogs').mockResolvedValue({
jest.mocked(pruneLogs).mockResolvedValue({
errorMessage,
isError: true,
})
Expand Down
10 changes: 5 additions & 5 deletions src/lambdas/halfsies/createHalfsie.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AWSError } from 'aws-sdk'
import { type APIGatewayProxyEvent, type APIGatewayProxyResult } from 'aws-lambda'
import { type DatabaseResponse, type NewLog } from '../../types'
import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants'
import { isALog, isUserNameTheMainUserName, pruneLogs } from '../../utils'
import { isUserNameTheMainUserName } from '../../utils/halfsies/auth'
import { isALog, pruneLogs } from '../../utils/halfsies/log'
import {
getBalance,
getUserName,
Expand Down Expand Up @@ -53,20 +53,20 @@ export const handler = ({ body, headers }: APIGatewayProxyEvent): Promise<APIGat
} else {
result.body = JSON.stringify({ newBalance, newLog, newLogs })
}
}).catch((error: AWSError) => {
}).catch((error) => {
result.statusCode = RESPONSE_CODE_SERVER_ERROR
result.body = JSON.stringify({ message: error.toString() })
}).finally(() => {
resolve(result)
})
}
}).catch((error: AWSError) => {
}).catch((error) => {
result.statusCode = RESPONSE_CODE_SERVER_ERROR
result.body = JSON.stringify({ message: error.toString() })

resolve(result)
})
}).catch((error: AWSError) => {
}).catch((error) => {
result.statusCode = RESPONSE_CODE_SERVER_ERROR
result.body = JSON.stringify({ message: error.toString() })

Expand Down
5 changes: 2 additions & 3 deletions src/lambdas/halfsies/getBalance.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AWSError } from 'aws-sdk'
import { APIGatewayProxyResult } from 'aws-lambda'
import { type APIGatewayProxyResult } from 'aws-lambda'
import { type DatabaseResponse } from '../../types'
import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants'
import { getBalance } from './utils'
Expand All @@ -22,7 +21,7 @@ export const handler = (): Promise<APIGatewayProxyResult> => new Promise((resolv
}

result.body = JSON.stringify({ message: errorMessage, balance })
}).catch((error: AWSError) => {
}).catch((error) => {
result.statusCode = RESPONSE_CODE_SERVER_ERROR
result.body = JSON.stringify({ message: error.toString() })
}).finally(() => {
Expand Down
10 changes: 7 additions & 3 deletions src/lambdas/halfsies/getLog.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Chance } from 'chance'
import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants'
import { mockHalfsieLog } from '../../mocks'
import { sortLogs } from '../../utils'
import { getLog } from './utils'
import { sortLogs } from '../../utils/halfsies/log'
import { getLog } from './utils/log'
import { handler } from './getLog'

jest.mock('./utils')
jest.mock('./utils/log')

describe('Lambda - Get Log', () => {
const chance = new Chance()

beforeEach(() => {
process.env.halfsiesLogTableName = chance.word({ syllables: 4 })
})

it('should return a log', async () => {
const log = [
mockHalfsieLog(),
Expand Down
9 changes: 4 additions & 5 deletions src/lambdas/halfsies/getLog.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { AWSError } from 'aws-sdk'
import { APIGatewayProxyResult } from 'aws-lambda'
import { type APIGatewayProxyResult } from 'aws-lambda'
import { type DatabaseResponse } from '../../types'
import { RESPONSE_CODE_OK, RESPONSE_CODE_SERVER_ERROR } from '../../constants'
import { sortLogs } from '../../utils'
import { getLog } from './utils'
import { sortLogs } from '../../utils/halfsies/log'
import { getLog } from './utils/log'

export const handler = (): Promise<APIGatewayProxyResult> => new Promise((resolve) => {
const { accessControlAllowOrigin = '' } = process.env
Expand All @@ -23,7 +22,7 @@ export const handler = (): Promise<APIGatewayProxyResult> => new Promise((resolv
}

result.body = JSON.stringify({ message: errorMessage, log: log && sortLogs(log) })
}).catch((error: AWSError) => {
}).catch((error) => {
result.statusCode = RESPONSE_CODE_SERVER_ERROR
result.body = JSON.stringify({ message: error.toString() })
}).finally(() => {
Expand Down
93 changes: 93 additions & 0 deletions src/lambdas/halfsies/initializeHalfsiesDatabase.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Chance } from 'chance'
import { ConditionalCheckFailedException, DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { sendCustomResourceLambdaResponse } from '../../utils/lambdas'
import { handler } from './initializeHalfsiesDatabase'
import { generateNewBalanceDbItem} from '../../utils/halfsies'

jest.mock('@aws-sdk/client-dynamodb', () => ({
...jest.requireActual('@aws-sdk/client-dynamodb'),
DynamoDBClient: jest.fn(() => ({
send: jest.fn(),
})),
}))

jest.mock('../../utils/lambdas', () => ({
...jest.requireActual('../../utils/lambdas'),
sendCustomResourceLambdaResponse: jest.fn(),
}))

describe('Lambda - Initialize Halfsies Database', () => {
const chance = new Chance()

it('Should not initialize the Halfises database anytime when we are not creating the lambda.', async () => {
const putItemMock = jest.fn().mockReturnValue({
promise: jest.fn().mockResolvedValue({}),
})

await handler({ RequestType: chance.word({ syllables: 4 }) } as any)

expect(putItemMock).not.toHaveBeenCalled()
})

it('Should not initialize the Halfises database anytime when there is already data in the database.', async () => {
const tableName = chance.word()
const send = jest.fn().mockRejectedValue(new ConditionalCheckFailedException({} as any))

jest.mocked(DynamoDBClient).mockImplementation(() => ({
send,
}) as any)

jest.spyOn(console, 'warn').mockImplementation(() => {})

process.env.balanceTableName = tableName

await handler({ RequestType: 'Create' } as any)

// eslint-disable-next-line no-console
expect(console.warn).toHaveBeenCalledWith(
'Halfsies balance amount already exists, skipping initialization.',
)
})

it('Should throw an error if there are any issues putting the value in the database.', async () => {
const tableName = chance.word()
const error = chance.sentence()
const send = jest.fn().mockRejectedValue(new Error(error))

jest.mocked(DynamoDBClient).mockImplementation(() => ({
send,
}) as any)

process.env.balanceTableName = tableName

await expect(handler({ RequestType: 'Create' } as any))
.rejects.toThrow(error)
})

it('Should initialize the Halfises database with a balance of zero.', async () => {
const tableName = chance.word()
const send = jest.fn()

const event = {
RequestType: 'Create',
}

jest.mocked(DynamoDBClient).mockImplementation(() => ({
send,
}) as any)

process.env.balanceTableName = tableName
await handler({
...event,
} as any)

expect(send).toHaveBeenCalledWith(expect.objectContaining({
input: expect.objectContaining({
TableName: tableName,
Item: generateNewBalanceDbItem(0),
ConditionExpression: 'attribute_not_exists(id)',
}),
}))
expect(sendCustomResourceLambdaResponse).toHaveBeenCalledWith(event, true)
})
})
35 changes: 35 additions & 0 deletions src/lambdas/halfsies/initializeHalfsiesDatabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type CloudFormationCustomResourceEvent } from 'aws-lambda'
import {
DynamoDBClient,
PutItemCommand,
PutItemCommandInput,
ConditionalCheckFailedException,
} from '@aws-sdk/client-dynamodb'
import { sendCustomResourceLambdaResponse } from '../../utils/lambdas'
import { generateNewBalanceDbItem} from '../../utils/halfsies'

export const handler = async (event: CloudFormationCustomResourceEvent) => {
const dynamoDbClient = new DynamoDBClient()
const { balanceTableName } = process.env

if (event.RequestType === 'Create') {
const params: PutItemCommandInput = {
TableName: balanceTableName,
Item: generateNewBalanceDbItem(0),
ConditionExpression: 'attribute_not_exists(id)',
}

try {
await dynamoDbClient.send(new PutItemCommand(params))
} catch (error) {
if (error instanceof ConditionalCheckFailedException) {
// eslint-disable-next-line no-console
console.warn('Halfsies balance amount already exists, skipping initialization.')
} else {
throw new Error((error as Error).message)
}
}

await sendCustomResourceLambdaResponse(event, true)
}
}
Loading