Skip to content

Commit

Permalink
Merge pull request #21 from ivanleekk/feat/add-test
Browse files Browse the repository at this point in the history
Feat/add test
  • Loading branch information
ivanleekk authored Sep 28, 2024
2 parents dbe7aa9 + 928c282 commit c3983c1
Show file tree
Hide file tree
Showing 16 changed files with 13,006 additions and 3,481 deletions.
15,842 changes: 12,426 additions & 3,416 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
"main": "index.ts",
"scripts": {
"build": "npx tsc",
"start": "node dist/index.js",
"dev": "nodemon src/index.ts",
"test": "vitest",
"deploy": "serverless deploy",
"offline": "serverless offline"
"deploy": "serverless deploy"
},
"repository": {
"type": "git",
Expand All @@ -23,21 +20,26 @@
"homepage": "https://github.com/ivanleekk/cashshare-telegram#readme",
"dependencies": {
"@prisma/client": "^5.19.1",
"@vitest/coverage-v8": "^2.1.1",
"aws-lambda": "^1.0.7",
"axios": "^1.7.7",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"telegraf": "^4.16.3"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.145",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.13",
"@types/node": "^22.5.5",
"jest": "^29.7.0",
"nodemon": "^3.1.5",
"prisma": "^5.19.1",
"serverless-dotenv-plugin": "^6.0.0",
"serverless-offline": "^14.3.2",
"serverless-plugin-typescript": "^2.1.5",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.6.2"
"typescript": "^5.6.2",
"vitest": "^2.1.1"
}
}
14 changes: 7 additions & 7 deletions src/handlers/BalanceHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {Response} from "express";

const prisma = new PrismaClient({});

export async function individualBalanceHandler(chatId: String, res: Response<any, any>, messageSender: String) {
export async function individualBalanceHandler(chatId: string, messageSender: String) {
// check if user exists in user group balance table
const user = await findUser_byUsername(`@${messageSender}`);
if (!user) {
return sendMessage(chatId, res, "You are not part of this group!");
return sendMessage(chatId, "You are not part of this group!");
}
const userBalance = await prisma.userGroupBalance.findUnique({
where: {
Expand All @@ -20,15 +20,15 @@ export async function individualBalanceHandler(chatId: String, res: Response<any
});

if (!userBalance) {
return sendMessage(chatId, res, "You have no balance in this group!");
return sendMessage(chatId, "You have no balance in this group!");
}

// get the user's balance
const balance = userBalance.balance;
return sendMessage(chatId, res, `Your balance in this group is \$${balance.toFixed(2)}`);
return sendMessage(chatId, `Your balance in this group is \$${balance.toFixed(2)}`);
}

export async function groupBalanceHandler(chatId: String, res: Response<any, any>) {
export async function groupBalanceHandler(chatId: string) {
const group = await prisma.group.findUnique({
where: {
id: chatId.toString()
Expand All @@ -39,7 +39,7 @@ export async function groupBalanceHandler(chatId: String, res: Response<any, any
});

if (!group) {
return sendMessage(chatId, res, "Group not found!");
return sendMessage(chatId, "Group not found!");
}

// get the balance per user in the group
Expand All @@ -60,5 +60,5 @@ export async function groupBalanceHandler(chatId: String, res: Response<any, any
return { username: member.username, balance: userBalance.balance };
}));

return sendMessage(chatId, res, `The group balance is \n${groupBalance.map((user) => `${user.username}: \$${user.balance.toFixed(2)}`).join("\n")}`);
return sendMessage(chatId, `The group balance is \n${groupBalance.map((user) => `${user.username}: \$${user.balance.toFixed(2)}`).join("\n")}`);
}
107 changes: 107 additions & 0 deletions src/handlers/addHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { addHandler } from './addHandler';
import { sendMessage, findUser_byUsername } from '../utils/utils';
import { PrismaClient } from '@prisma/client';
import resetAllMocks = jest.resetAllMocks;

vi.mock('../utils/utils', () => ({
sendMessage: vi.fn(),
findUser_byUsername: vi.fn(),
}));
vi.mock('@prisma/client', () => {
const mPrismaClient = {
group: {
findUnique: vi.fn(),
update: vi.fn(),
},
user: {
findUnique: vi.fn(),
create: vi.fn(),
},
userGroupBalance: {
findFirst: vi.fn(),
create: vi.fn(),
update: vi.fn(),
findMany: vi.fn(),
},
transaction: {
create: vi.fn(),
},
};
return { PrismaClient: vi.fn(() => mPrismaClient) };
});

describe('addHandler', () => {
let prisma: any;
let chatId: string;
let messageSender: string;

beforeEach(() => {
prisma = new PrismaClient();
chatId = '1';
messageSender = 'testuser';
vi.clearAllMocks();
});

it('should return an error message if the format is invalid', async () => {
const messageArray = ['/add', '10'];
await addHandler(messageArray, chatId, messageSender);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'Invalid format! Please use /add [amount] [description] [people]');
});

it('should return an error message if the group does not exist', async () => {
prisma.group.findUnique.mockResolvedValue(null);
const messageArray = ['/add', '10', 'lunch', '@user1'];
await addHandler(messageArray, chatId, messageSender);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'Cashshare Bot is not initialized for this group! Use /start to initialize.');
});

it('should create a new user if a user is not found', async () => {
prisma.group.findUnique.mockResolvedValue({ id: chatId, members: [] });
prisma.user.findUnique.mockResolvedValue(null);
findUser_byUsername.mockResolvedValue(null);
prisma.user.create.mockResolvedValue({ id: 'newUser' });

const messageArray = ['/add', '10', 'lunch', '@newUser'];
await addHandler(messageArray, chatId, messageSender);
expect(prisma.user.create).toHaveBeenCalled();
});

it('should add the expense and update balances correctly', async () => {
prisma.group.findUnique.mockResolvedValue({ id: chatId, members: [] });
findUser_byUsername.mockResolvedValue({ id: 'user1' });
prisma.user.findUnique.mockResolvedValue({ id: 'user1' });
prisma.userGroupBalance.findFirst.mockResolvedValue(null);
prisma.userGroupBalance.findMany.mockResolvedValue([{ userId: 'user1', balance: 0 }]);

const messageArray = ['/add', '10', 'lunch', '@user1'];
await addHandler(messageArray, chatId, messageSender);

expect(prisma.transaction.create).toHaveBeenCalled();
expect(prisma.userGroupBalance.update).toHaveBeenCalled();
expect(sendMessage).toHaveBeenCalledWith(chatId, 'Added expense of $10 for lunch for @testuser, @user1!');
});


it('should handle multiple users correctly', async () => {
prisma.group.findUnique.mockResolvedValue({ id: chatId, members: [] });
findUser_byUsername.mockResolvedValueOnce({ id: 'user1' }).mockResolvedValueOnce({ id: 'user2' });
prisma.user.findUnique.mockResolvedValueOnce({ id: 'user1' }).mockResolvedValueOnce({ id: 'user2' });
prisma.userGroupBalance.findFirst.mockResolvedValue(null);
prisma.userGroupBalance.findMany.mockResolvedValue([{ userId: 'user1', balance: 0 }, { userId: 'user2', balance: 0 }]);

const messageArray = ['/add', '20', 'dinner', '@user1', '@user2'];
await addHandler(messageArray, chatId, messageSender);

expect(prisma.transaction.create).toHaveBeenCalled();
expect(prisma.userGroupBalance.update).toHaveBeenCalledTimes(3);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'Added expense of $20 for dinner for @testuser, @user1, @user2!');
});

// it('should handle database errors gracefully', async () => {
// prisma.group.findUnique.mockRejectedValue(new Error('Database error'));
// const messageArray = ['/add', '10', 'lunch', '@user1'];
// await addHandler(messageArray, chatId, messageSender);
// expect(sendMessage).toHaveBeenCalledWith(chatId, 'Error adding expense! Please try again.');
// });
});
15 changes: 8 additions & 7 deletions src/handlers/addHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {PrismaClient} from "@prisma/client";

const prisma = new PrismaClient({});

export async function addHandler(messageArray: string[], chatId: String, res: Response<any, any>, messageSender: String) {
export async function addHandler(messageArray: string[], chatId: string, messageSender: String) {
// format: /add <amount> <description> <people>
if (messageArray.length < 4) {
return sendMessage(chatId, res, "Invalid format! Please use /add [amount] [description] [people]");
return sendMessage(chatId, "Invalid format! Please use /add [amount] [description] [people]");
}
const amount = parseFloat(messageArray[1]);
// find the index with @ prefix
Expand All @@ -22,7 +22,7 @@ export async function addHandler(messageArray: string[], chatId: String, res: Re

// if not all people are valid, return an error message
if (payerList.length !== payers.length + 1) {
return sendMessage(chatId, res, "Invalid format! Please use @username for all users involved.");
return sendMessage(chatId, "Invalid format! Please use @username for all users involved.");

Check warning on line 25 in src/handlers/addHandler.ts

View check run for this annotation

Codecov / codecov/patch

src/handlers/addHandler.ts#L25

Added line #L25 was not covered by tests
}

// remove duplicates from the payer list
Expand All @@ -40,7 +40,7 @@ export async function addHandler(messageArray: string[], chatId: String, res: Re

// if group does not exist, return an error message
if (!group) {
return sendMessage(chatId, res, "Cashshare Bot is not initialized for this group! Use /start to initialize.");
return sendMessage(chatId, "Cashshare Bot is not initialized for this group! Use /start to initialize.");
}
// check if all users are part of the group
const users = [];
Expand Down Expand Up @@ -104,7 +104,7 @@ export async function addHandler(messageArray: string[], chatId: String, res: Re
// by default, the payee is the user who added the expense
const payee = await findUser_byUsername(`@${messageSender}`);
if (!payee) {
return sendMessage(chatId, res, "Error adding expense! Please try again.");
return sendMessage(chatId, "Error adding expense! Please try again.");
}
await prisma.transaction.create({
data: {
Expand All @@ -131,8 +131,9 @@ export async function addHandler(messageArray: string[], chatId: String, res: Re
const amountPerPerson = amount / payerList.length;
for (const person of payerList) {
const user = await findUser_byUsername(person);
console.log(user);
if (!user) {
return sendMessage(chatId, res, "Error adding expense! Please try again.");
return sendMessage(chatId, "Error adding expense! Please try again.");
}

Check warning on line 137 in src/handlers/addHandler.ts

View check run for this annotation

Codecov / codecov/patch

src/handlers/addHandler.ts#L136-L137

Added lines #L136 - L137 were not covered by tests
if (user.id === payee.id) {
await prisma.userGroupBalance.update({
Expand Down Expand Up @@ -173,5 +174,5 @@ export async function addHandler(messageArray: string[], chatId: String, res: Re
});


return sendMessage(chatId, res, `Added expense of \$${amount} for ${description} for ${payerList.join(", ")}!`);
return sendMessage(chatId, `Added expense of \$${amount} for ${description} for ${payerList.join(", ")}!`);
}
90 changes: 90 additions & 0 deletions src/handlers/balanceHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { individualBalanceHandler, groupBalanceHandler } from './BalanceHandler';
import { sendMessage, findUser_byUsername } from '../utils/utils';
import { PrismaClient } from '@prisma/client';

vi.mock('../utils/utils', () => ({
sendMessage: vi.fn(),
findUser_byUsername: vi.fn(),
}));

vi.mock('@prisma/client', () => {
const mPrismaClient = {
group: {
findUnique: vi.fn(),
},
userGroupBalance: {
findUnique: vi.fn(),
},
};
return { PrismaClient: vi.fn(() => mPrismaClient) };
});

describe('BalanceHandler', () => {
let prisma: any;
let chatId: string;
let messageSender: string;

beforeEach(() => {
prisma = new PrismaClient();
chatId = '1';
messageSender = 'testuser';
vi.clearAllMocks();
});

describe('individualBalanceHandler', () => {
it('should return an error message if the user is not part of the group', async () => {
findUser_byUsername.mockResolvedValue(null);
await individualBalanceHandler(chatId, messageSender);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'You are not part of this group!');
});

it('should return an error message if the user has no balance in the group', async () => {
findUser_byUsername.mockResolvedValue({ id: 'user1' });
prisma.userGroupBalance.findUnique.mockResolvedValue(null);
await individualBalanceHandler(chatId, messageSender);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'You have no balance in this group!');
});

it('should return the user\'s balance in the group', async () => {
findUser_byUsername.mockResolvedValue({ id: 'user1' });
prisma.userGroupBalance.findUnique.mockResolvedValue({ balance: 100 });
await individualBalanceHandler(chatId, messageSender);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'Your balance in this group is $100.00');
});
});

describe('groupBalanceHandler', () => {
it('should return an error message if the group is not found', async () => {
prisma.group.findUnique.mockResolvedValue(null);
await groupBalanceHandler(chatId);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'Group not found!');
});

it('should return the group balance', async () => {
prisma.group.findUnique.mockResolvedValue({
id: chatId,
members: [{ id: 'user1', username: 'user1' }, { id: 'user2', username: 'user2' }],
});
prisma.userGroupBalance.findUnique
.mockResolvedValueOnce({ balance: 50 })
.mockResolvedValueOnce({ balance: 75 });

await groupBalanceHandler(chatId);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'The group balance is \nuser1: $50.00\nuser2: $75.00');
});

it('should handle members with no balance', async () => {
prisma.group.findUnique.mockResolvedValue({
id: chatId,
members: [{ id: 'user1', username: 'user1' }, { id: 'user2', username: 'user2' }],
});
prisma.userGroupBalance.findUnique
.mockResolvedValueOnce(null)
.mockResolvedValueOnce({ balance: 75 });

await groupBalanceHandler(chatId);
expect(sendMessage).toHaveBeenCalledWith(chatId, 'The group balance is \nuser1: $0.00\nuser2: $75.00');
});
});
});
Loading

0 comments on commit c3983c1

Please sign in to comment.