Skip to content

Commit

Permalink
Merge pull request #72 from HackRU/update-buy-ins
Browse files Browse the repository at this point in the history
Update buy ins
  • Loading branch information
ethxng authored Oct 14, 2024
2 parents 06c206f + 74c45e3 commit 9793f07
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 2 deletions.
3 changes: 2 additions & 1 deletion serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import resetPassword from '@functions/reset-password';
import forgotPassword from '@functions/forgot-password';
import leaderboard from '@functions/leaderboard';
import points from '@functions/points';
import updateBuyIns from '@functions/update-buy-ins';
import getBuyIns from '@functions/get-buy-ins';

import * as path from 'path';
import * as dotenv from 'dotenv';
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
Expand Down Expand Up @@ -52,6 +52,7 @@ const serverlessConfiguration: AWS = {
resetPassword,
leaderboard,
points,
updateBuyIns,
getBuyIns,
},
package: { individually: true, patterns: ['!.env*', '.env.vault'] },
Expand Down
1 change: 1 addition & 0 deletions src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export { default as resetPassword } from './reset-password';
export { default as forgotPassword } from './forgot-password';
export { default as leaderboard } from './leaderboard';
export { default as points } from './points';
export { default as updateBuyIns } from './update-buy-ins';
export { default as getBuyIns } from './get-buy-ins';
104 changes: 104 additions & 0 deletions src/functions/update-buy-ins/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { ValidatedEventAPIGatewayProxyEvent } from '@libs/api-gateway';
import { middyfy } from '@libs/lambda';

import schema from './schema';

import { MongoDB, validateToken } from '../../util';
import * as path from 'path';
import * as dotenv from 'dotenv';

dotenv.config({ path: path.resolve(process.cwd(), '.env') });

const updateBuyIns: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
try {
// validate auth token
const validToken = validateToken(event.body.auth_token, process.env.JWT_SECRET, event.body.email);
if (!validToken) {
return {
statusCode: 401,
body: JSON.stringify({
statusCode: 401,
message: 'Unauthorized',
}),
};
}

// connect to DB
const db = MongoDB.getInstance(process.env.MONGO_URI);
await db.connect();
const pointCollection = db.getCollection('f24-points-syst');
const userPoints = await pointCollection.findOne({ email: event.body.email });

if (!userPoints) {
return {
statusCode: 404,
body: JSON.stringify({
statusCode: 404,
message: 'User point balance information not found',
}),
};
}

//sort the request buy_ins array and the buy_ins array from the db
const userBuyInsSorted = userPoints.buy_ins.sort((a, b) => b.prize_id.localeCompare(a.prize_id));
const requestBuyInsSorted = event.body.buy_ins.sort((a, b) => b.prize_id.localeCompare(a.prize_id));

//check if the length of both arrays are equal
if (userBuyInsSorted.length !== requestBuyInsSorted.length) {
return {
statusCode: 400,
body: JSON.stringify({
statusCode: 400,
message: 'Request body prizes do not match',
}),
};
}

let pointsUsed = 0;
for (let i = 0; i < userBuyInsSorted.length; i++) {
if (requestBuyInsSorted[i].prize_id !== userBuyInsSorted[i].prize_id) {
return {
statusCode: 400,
body: JSON.stringify({
statusCode: 400,
message: 'Request body prizes do not match',
}),
};
}
pointsUsed += event.body.buy_ins[i].buy_in;
}

//check that the points used are within the total_points
if (pointsUsed > userPoints.total_points) {
return {
statusCode: 403,
body: JSON.stringify({
statusCode: 403,
message: 'Points distributed exceed user point total.',
}),
};
}

//update the buy_ins array
await pointCollection.updateOne({ email: event.body.email }, { $set: { buy_ins: event.body.buy_ins } });
return {
statusCode: 200,
body: JSON.stringify({
statusCode: 200,
message: 'Updated user point balance successfully',
}),
};
} catch (error) {
console.error('Error updating', error);
return {
statusCode: 500,
body: JSON.stringify({
statusCode: 500,
message: 'Internal server error',
error,
}),
};
}
};

export const main = middyfy(updateBuyIns);
20 changes: 20 additions & 0 deletions src/functions/update-buy-ins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { handlerPath } from '@libs/handler-resolver';
import schema from './schema';

export default {
handler: `${handlerPath(__dirname)}/handler.main`,
events: [
{
http: {
method: 'post',
path: 'update-buy-ins',
cors: true,
request: {
schemas: {
'application/json': schema,
},
},
},
},
],
};
19 changes: 19 additions & 0 deletions src/functions/update-buy-ins/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
auth_token: { type: 'string ' },
buy_ins: {
type: 'array',
items: {
type: 'object',
properties: {
prize_id: { type: 'string' },
buy_in: { type: 'number' },
},
required: ['prize_id', 'buy_in'],
},
},
},
required: ['email', 'auth_token', 'buy_ins'],
} as const;
2 changes: 1 addition & 1 deletion tests/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface Updates {
}

export function createEvent(
userData: Record<string, string | boolean | number | Updates>,
userData: Record<string, string | boolean | number | Updates | unknown[]>,
path: string,
httpMethod: string
): MockEvent {
Expand Down
116 changes: 116 additions & 0 deletions tests/update-buy-ins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { main } from '../src/functions/update-buy-ins/handler';
import { createEvent, mockContext } from './helper';
import * as util from '../src/util';

jest.mock('../src/util', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
MongoDB: {
getInstance: jest.fn().mockReturnValue({
connect: jest.fn(),
disconnect: jest.fn(),
getCollection: jest.fn().mockReturnValue({
findOne: jest.fn(),
updateOne: jest.fn(),
}),
}),
},
validateToken: jest.fn().mockReturnValueOnce(false).mockReturnValue(true),
}));

describe('Update-Buy-Ins tests', () => {
beforeEach(() => {
jest.clearAllMocks(); // Clear mocks before each test to avoid interference
});

const userData = {
email: 'mockEmail@mock.org',
buy_ins: [
{ prize_id: 'prize1', buy_in: 10 },
{ prize_id: 'prize2', buy_in: 20 },
],
};

const path = '/update-buy-ins';
const httpMethod = 'POST';

const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;
const mockCallback = jest.fn();

// case 1
it('auth token is not valid', async () => {
const mockEvent = createEvent(userData, path, httpMethod);

const result = await main(mockEvent, mockContext, mockCallback);

expect(result.statusCode).toBe(401);
expect(JSON.parse(result.body).message).toBe('Unauthorized');
});

// case 2
it('user not found', async () => {
findOneMock.mockReturnValueOnce(null);
const mockEvent = createEvent(userData, path, httpMethod);

const result = await main(mockEvent, mockContext, mockCallback);

expect(result.statusCode).toBe(404);
expect(JSON.parse(result.body).message).toBe('User point balance information not found');
});

// case 3
it('prize IDs do not match', async () => {
findOneMock.mockReturnValueOnce({
email: userData.email,
total_points: 100,
buy_ins: [
{ prize_id: 'prize2', buy_in: 10 },
{ prize_id: 'prize2', buy_in: 20 },
],
});

const mockEvent = createEvent(userData, path, httpMethod);

const result = await main(mockEvent, mockContext, mockCallback);

expect(result.statusCode).toBe(400);
expect(JSON.parse(result.body).message).toBe('Request body prizes do not match');
});

// case 4
it('points distributed exceed user point total', async () => {
findOneMock.mockReturnValueOnce({
email: userData.email,
total_points: 15,
buy_ins: [
{ prize_id: 'prize1', buy_in: 5 },
{ prize_id: 'prize2', buy_in: 11 },
],
});

const mockEvent = createEvent(userData, path, httpMethod);

const result = await main(mockEvent, mockContext, mockCallback);

expect(result.statusCode).toBe(403);
expect(JSON.parse(result.body).message).toBe('Points distributed exceed user point total.');
});

// case 5
it('successfully update user point balance', async () => {
findOneMock.mockReturnValueOnce({
email: userData.email,
total_points: 30,
buy_ins: [
{ prize_id: 'prize1', buy_in: 10 },
{ prize_id: 'prize2', buy_in: 20 },
],
});

const mockEvent = createEvent(userData, path, httpMethod);

const result = await main(mockEvent, mockContext, mockCallback);

expect(result.statusCode).toBe(200);
expect(JSON.parse(result.body).message).toBe('Updated user point balance successfully');
});
});

0 comments on commit 9793f07

Please sign in to comment.