Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test 9 30 vector #14

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
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
50 changes: 50 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore

# Node artifact files
node_modules/
dist/

# Compiled Java class files
*.class

# Compiled Python bytecode
*.py[cod]

# Log files
*.log

# Package files
*.jar

# Maven
target/
dist/

# JetBrains IDE
.idea/

# Unit test reports
TEST*.xml

# Generated by MacOS
.DS_Store

# Generated by Windows
Thumbs.db

# Applications
*.app
*.exe
*.war

# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv

1 change: 1 addition & 0 deletions AboutProject.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
What does this project do?
93 changes: 9 additions & 84 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,99 +1,24 @@
# Bunk Dev Test - Holiday Expenses Calculator

A code challenge for new developers applying to work at Bunk.

## Challenge

The Bunk team are all going on a work holiday but they need an app to keep a track of expenses and who owes money to who at the end of the trip.

## Technologies To Use

- TypeScript preferably, (JavaScript if not familiar with TypeScript)
- Angular 14+
- Node 14+ + Web Framework, i.e. Express
- Backend tests (ideally with Jest)
- E2E tests (ideally with Cypress)
- Git + Github for version control

## Challenge Requirements

- The client-side should be built using Angular
- The client-side should contain a table showing a expenses with the following fields (per traveller):
- Name
- Expense
- You should be able to add an expense to the table (not persisted).
- The page should then have a "Settle Up" button.
- When the "Settle Up" button is clicked, it will make a request to the server to calculate how much each traveller needs to pay out to any other traveller
- The server-side should be a NodeJS Web API.
- The NodeJS server-side logic should be arranged into a service.
- There is no need for a database.

### Bonus Requirements

- If you have time, you should write tests.
- Linting is appreciated, ideally with prettier.
- Tidy, well documented functions are great.

## Installation

Run `npm run install-all` in the project root.

This script will kick off `npm install` in both the Express server (`api`) and the Angular application (`web`).

## Quick Start

Run `npm run start` from the project root. This script will start both the Express server, and the Angular application.
Run `npm run start` from the project root.

## Running Tests

Run server-side tests with `npm test` from the project root.
Run E2E tests with the `npm run e2e` command from project root.

## API Reference

### Calculate Payouts (`POST /payouts`)

**Endpoint URL**
`http://localhost:3000/payouts`

**JSON Body Parameters**

`expenses` - array : Specifies the expenses you want to calculate a payout on.

`expenses.name` - string : The name of the person who incurred the expense.

`expenses.amount` - number : The amount of the expense incurred.

**Example Request**

```shell script
curl --location --request POST 'localhost:3000/payouts' \
--header 'Content-Type: application/json' \
--data-raw '{
"expenses": [
{ "name": "Adriana", "amount": 5.75 },
{ "name": "Adriana", "amount": 5.75 },
{ "name": "Bao", "amount": 12 }
]
}'
```

**Example Response**
## Usage of Project

```json
{
"total": 23.5,
"equalShare": 11.75,
"payouts": [
{
"owes": "Adriana",
"owed": "Bao",
"amount": 0.25
}
]
}
```
- Open browser and go to "http://localhost:4200/".
- You can add traveller name and expense of him/her. If you click add button, traveller detail will be shown in table. If you click traveller detail row in table, that detail will be removed from table.
- If you click "Settle Up" button, you will be navigated to other page and you can see payouts details.
- You can also can be navigated to list of expense page and add or remove traveller details using header's navigation buttons.

## Submitting
## Functionality of Project

When you have completed the test, please email a link to your public Github Repository to [email protected] and [email protected].
If you send traveller's details to server, payouts details will be created with time complexity(n log n) and sent to cient side.
(n: number of travellers)
50 changes: 50 additions & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore

# Node artifact files
node_modules/
dist/

# Compiled Java class files
*.class

# Compiled Python bytecode
*.py[cod]

# Log files
*.log

# Package files
*.jar

# Maven
target/
dist/

# JetBrains IDE
.idea/

# Unit test reports
TEST*.xml

# Generated by MacOS
.DS_Store

# Generated by Windows
Thumbs.db

# Applications
*.app
*.exe
*.war

# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv

67 changes: 67 additions & 0 deletions api/__tests__/payoutService.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const { mapPayouts } = require('../services/payoutService');

describe('Payouts Reliability', () => {
test('Should calculate correct payouts', () => {
const expenses = [
{ name: "Adriana", amount: 5.75 },
{ name: "Adriana", amount: 5.75 },
{ name: "Bao", amount: 12 }
];
const result = mapPayouts(expenses);
expect(result).toEqual({
total: 23.5,
equalShare: 11.75,
payouts: [
{
owes: "Adriana",
owed: "Bao",
amount: 0.25
}
]
});
});

test('Should calculate correct payouts with more complex input', () => {
const expenses = [
{ "name": "AAA", "amount": 200 },
{ "name": "BBB", "amount": 200 },
{ "name": "CCC", "amount": 150 },
{ "name": "DDD", "amount": 270 },
{ "name": "EEE", "amount": 250 },
{ "name": "FFF", "amount": 150 },
{ "name": "GGG", "amount": 180 }
];
const result = mapPayouts(expenses);
expect(result).toEqual({
total: 1400,
equalShare: 200,
payouts: [
{
owes: "CCC",
owed: "DDD",
amount: 50
},
{
owes: "FFF",
owed: "DDD",
amount: 20
},
{
owes: "FFF",
owed: "EEE",
amount: 30
},
{
owes: "GGG",
owed: "EEE",
amount: 20
}
]
});
});

test('Should throw error for invalid expenses', () => {
const expenses = 'invalid';
expect(() => mapPayouts(expenses)).toThrow('Invalid expenses');
});
});
37 changes: 37 additions & 0 deletions api/__tests__/payouts.route.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const request = require('supertest');
const app = require('../index');

describe('Payouts Route', () => {
test('POST /payouts should return a valid response', async () => {
const expenses = [
{ name: "Adriana", amount: 5.75 },
{ name: "Adriana", amount: 5.75 },
{ name: "Bao", amount: 12 }
];

const response = await request(app)
.post('/payouts')
.send({ expenses })
.expect(200);

expect(response.body).toEqual({
total: 23.5,
equalShare: 11.75,
payouts: [
{
owes: "Adriana",
owed: "Bao",
amount: 0.25
}
]
});
});

test('Should return 400 for invalid expenses', async () => {
const expenses = 'invalid';
await request(app)
.post('/payouts')
.send({ expenses })
.expect(400);
});
});
35 changes: 35 additions & 0 deletions api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Request, Response, NextFunction } from 'express';

const express = require('express');
const router = require('./routes/api/payouts.route');

let cors = require('cors');
let bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(cors());

app.use('/payouts', router);

// PORT
const port = process.env.PORT || 3000;
const server = app.listen(port, () => {
console.log('Connected to port ' + port)
})

// 404 Error
app.use((req: Request, res: Response, next: NextFunction) => {
res.status(404).send('Error 404!')
});

app.use(function (err: any, req: Request, res: Response, next: NextFunction) {
console.error(err.message);
if (!err.statusCode) err.statusCode = 500;
res.status(err.statusCode).send(err.message);
});

module.exports = app;
10 changes: 10 additions & 0 deletions api/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
testMatch: [
'<rootDir>/__tests__/**/*.spec.ts',
],
};
19 changes: 19 additions & 0 deletions api/models/payout.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface Payout {
owes: string;
owed: string;
amount: number;
}

export interface Expense {
name: string;
amount: number;
status: string;
}

export interface PayoutResponse {
total: number;
equalShare: number;
payouts: Payout[];
}

export type Traveller = Record<string, number>;
Loading