Skip to content

Commit

Permalink
Merge pull request #61 from HackRU/points
Browse files Browse the repository at this point in the history
Feat: /points Endpoint
  • Loading branch information
ethxng authored Sep 20, 2024
2 parents 4a53d3e + 38ce76d commit f5dfb39
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 0 deletions.
2 changes: 2 additions & 0 deletions serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import waiver from '@functions/waiver';
import resume from '@functions/resume';
import resetPassword from '@functions/reset-password';
import forgotPassword from '@functions/forgot-password';
import points from '@functions/points';

import * as path from 'path';
import * as dotenv from 'dotenv';
Expand Down Expand Up @@ -47,6 +48,7 @@ const serverlessConfiguration: AWS = {
discord,
forgotPassword,
resetPassword,
points,
},
package: { individually: true, patterns: ['!.env*', '.env.vault'] },
custom: {
Expand Down
1 change: 1 addition & 0 deletions src/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { default as waiver } from './waiver';
export { default as resume } from './resume';
export { default as resetPassword } from './reset-password';
export { default as forgotPassword } from './forgot-password';
export { default as points } from './points';
75 changes: 75 additions & 0 deletions src/functions/points/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 points: ValidatedEventAPIGatewayProxyEvent<typeof schema> = async (event) => {
const email = event.body.email.toLowerCase();

try {
// check token
const isValidToken = validateToken(event.body.auth_token, process.env.JWT_SECRET, email);
if (!isValidToken) {
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 users = db.getCollection('users');
const pointsCollection = db.getCollection('f24-points-syst');

// Make sure user exists
const user = await users.findOne({ email: email });
if (!user) {
return {
statusCode: 404,
body: JSON.stringify({
statusCode: 404,
message: 'User not found.',
}),
};
}

// get users points
const pointUser = await pointsCollection.findOne({ email: email });
if (!pointUser) {
return {
statusCode: 404,
body: JSON.stringify({
statusCode: 404,
message: 'Points not found for this user.',
}),
};
}

return {
statusCode: 200,
body: JSON.stringify({
statusCode: 200,
balance: pointUser.balance,
total_points: pointUser.total_points,
}),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({
statusCode: 500,
message: 'Internal server error.',
error,
}),
};
}
};

export const main = middyfy(points);
20 changes: 20 additions & 0 deletions src/functions/points/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: 'points',
cors: true,
request: {
schemas: {
'application/json': schema,
},
},
},
},
],
};
8 changes: 8 additions & 0 deletions src/functions/points/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
type: 'object',
properties: {
auth_token: { type: 'string' },
email: { type: 'string', format: 'email' },
},
required: ['auth_token', 'email'],
} as const;
111 changes: 111 additions & 0 deletions tests/points.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { main } from '../src/functions/points/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(),
}),
}),
},
validateToken: jest.fn(),
}));

describe('Points endpoint', () => {
beforeEach(() => {
jest.clearAllMocks();
process.env.JWT_SECRET = 'test-secret';
});

const path = '/points';
const httpMethod = 'POST';

it('should return 401 for invalid auth token', async () => {
const userData = {
email: '[email protected]',
auth_token: 'invalidToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(false);

const result = await main(mockEvent, mockContext, jest.fn());

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

it('should return 404 if user is not found', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;
findOneMock.mockResolvedValue(null);

const result = await main(mockEvent, mockContext, jest.fn());

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

it('should return 404 if points not found for user', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('').findOne as jest.Mock;
findOneMock.mockResolvedValueOnce({ email: '[email protected]' }); // user found
findOneMock.mockResolvedValueOnce(null); // points not found

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(404);
expect(JSON.parse(result.body).message).toBe('Points not found for this user.');
});

it('should return 200 with balance and total_points for valid user', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};

const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;

findOneMock.mockResolvedValueOnce({ email: '[email protected]' }); // User exists
findOneMock.mockResolvedValueOnce({ user_email: '[email protected]', balance: 100, total_points: 150 }); // Points found

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(200);
const body = JSON.parse(result.body);
expect(body.balance).toBe(100);
expect(body.total_points).toBe(150);
});

it('should return 500 for internal server error', async () => {
const userData = {
email: '[email protected]',
auth_token: 'validToken',
};
const mockEvent = createEvent(userData, path, httpMethod);
(util.validateToken as jest.Mock).mockReturnValue(true);
const findOneMock = util.MongoDB.getInstance('uri').getCollection('users').findOne as jest.Mock;
findOneMock.mockRejectedValue(new Error('Database error'));

const result = await main(mockEvent, mockContext, jest.fn());

expect(result.statusCode).toBe(500);
expect(JSON.parse(result.body).message).toBe('Internal server error.');
});
});

0 comments on commit f5dfb39

Please sign in to comment.