imported resources and added readme with assignment description

main
Inga 🏳‍🌈 6 days ago
parent 59c10e48d8
commit 985c2a72d1
  1. 153
      README.md
  2. 6
      database/.dockerignore
  3. 16
      database/Dockerfile
  4. 38
      database/init.sql
  5. 3740
      test-app/package-lock.json
  6. 18
      test-app/package.json
  7. 110
      test-app/test.js

@ -1,2 +1,153 @@
# test-assignment-enpal
---
gitea: none
include_toc: true
---
## Appointment Booking Challenge
### Overview
We want to build an appointment booking system that allows customers to schedule appointments with our sales managers to discuss
one or more of our products. For the MVP, we will have a website that displays available appointment slots that a customer can choose
from.
The goal of this project is to implement the backend for this system. We need an endpoint that returns the available appointment slots
for a customer.
There are a few rules we need to consider when checking for available appointment slots for a customer:
* Each slot corresponds to a one-hour appointment
* Slots can have overlapping time ranges. For example, it is possible to have the following three slots:
* 10:30 - 11:30
* 11:00 - 12:00
* 11:30 - 12:30
* A sales manager CANNOT be booked for two overlapping slots at the same time. For example, if a sales manger has a slot booked
at 10:30 - 11:30, then the 11:00 - 12:00 cannot be booked anymore.
* Customers are matched to sales managers based on specific criteria. A slot CANNOT be booked by a customer if the sales manager
does not match any of these three criteria:
* Language. Currently we have 2 possible languages: German, English
* Product(s) to discuss. Currently we have 2 possible products: SolarPanels, Heatpumps
* Internal customer rating. Currently we have 3 possible ratings: Gold, Silver, Bronze.
* Customers can book one appointment to discuss multiple products
### Requirements
Design and implement a REST endpoint in any language of your choice that:
* Listens for POST requests on this route: http://localhost:3000/calendar/query
* Connects to the provided Postgres database instance
* Receives a request body in this format:
```
{
"date": "2024-05-03",
"products": ["SolarPanels", "Heatpumps"],
"language": "German",
"rating": "Gold"
}
```
* Returns a response with an array of available slots that can be booked by the customer in this format
```
[
{
"available_count": 1,
"start_date": "2024-05-03T10:30:00.00Z"
},
{
"available_count": 2,
"start_date": "2024-05-03T12:00:00.00Z"
}
]
```
#### NOTES:
* You can use any language, framework, library of your choice for this challenge.
* The system should not book appointments in this challenge; your focus is returning available slots.
* We provide you with a docker database already populated with data. You are not allowed to modify the database structure of the
database in any way but you can create indexes or views if you think it is necessary. The docker database can be downloaded
here: https://[redacted]/Take_Home_Challenge_Resources.zip
#### Database Schema
The provided database has the following schema:
##### Table: sales_managers
| Column Name | Column Type | Comment |
| ----------- | ----------- | ------- |
| id (PK) | serial | ID of the sales manager |
| name | varchar(250) | Full name of sales manager |
| languages | array(varchar(100)) | List of languages spoken by sales manager |
| products | array(varchar(100)) | List of products the sales manager can work with |
| customer_ratings | array(varchar(100)) | List of customer ratings the sales manager can work with |
##### Table: slots
| Column Name | Column Type | Comment |
| ----------- | ----------- | ------- |
| id (PK) | serial | ID of the slot |
| start_date | timestampz | Start date and time of the slot |
| end_date | timestampz | End date and time of the slot |
| booked | bool | Value indicating whether the slot has already been booked |
| sales_manager_id (FK) | integer | ID of the sales manager the slot belongs to |
### Getting Started
Download the resources for this challenge here https://[redacted]/Take_Home_Challenge_Resources.zip. This is a zip file that contains two folders:
* database: This folder contains a Dockerfile that can be used to start a Postgres database server and an init.sql file that
initializes the database and preloads it with data.
* test-app: This folder contains a node application that can be used to run test scenarios to verify your application.
#### Setup the database
Extract the `Take_Home_Challenge_Resources.zip` file and run the following commands in the database folder. This requires
that you have docker installed on your local environment
```
docker build -t enpal-coding-challenge-db .
docker run --name enpal-coding-challenge-db -p 5432:5432 -d enpal-coding-challenge-db
```
Once the docker container is up and running, ensure you can connect to it using your favourite DB query tool (e.g.: DBeaver or pgAdmin).
The default connection string is `postgres://postgres:mypassword123!@localhost:5432/coding-challenge`
If you want to use a local database installation instead, you also can get the `init.sql` file and run it in your local database.
#### Setup tests
Extract the `Take_Home_Challenge_Resources.zip` file and run the following commands in the `test-app` folder. This requires
that you have node installed on your local environment
```
npm install
npm run test
```
The tests try to connect to an endpoint running on http://localhost:3000/calendar/query and run several test scenarios. Since that is not
probably running yet the tests will fail.
You can inspect the `test.js` file and see some example requests and the expected responses.
#### Start coding
You can now create an api in your language of choice that fulfils the requirements.
### How to submit the solution
The solution should contain:
* your application code
* docker setup that starts the database
* instructions on how to run your application.
You can send this to us as a zip file or push this to a github repository and send us the link.
Your solution must connect to the database in the docker container and then we’ll run the same tests provided to you. We might ALSO
run additional tests, such as loading thousands of records in the database to assert the application is performant enough.
### Evaluation Criteria
Please note that we will be evaluating your solution not just based on correctness but also on the following criteria. We place a high
importance on these criteria, and we strongly encourage you to carefully consider them as you develop your solution:
* Accuracy in adhering to the specified rules.
* Efficiency and performance of the api endpoint.
* Clarity, readability and testability of the code.
* Handling of edge cases and error conditions.

@ -0,0 +1,6 @@
**/node_modules
*.log
.DS_Store
.git
.gitignore
.env

@ -0,0 +1,16 @@
# Use the official PostgreSQL image from the Docker Hub
FROM postgres:16
# Add the init.sql script to the Docker image
COPY init.sql /docker-entrypoint-initdb.d/
# Set environment variables for PostgreSQL
ENV POSTGRES_DB=coding-challenge
ENV POSTGRES_USER=postgres
ENV POSTGRES_PASSWORD=mypassword123!
# To build and run this container run the following commands
# docker build -t enpal-coding-challenge-db .
# docker run --name enpal-coding-challenge-db -p 5432:5432 -d enpal-coding-challenge-db

@ -0,0 +1,38 @@
create table if not exists sales_managers (
id serial primary key not null,
name varchar(250) not null,
languages varchar(100)[],
products varchar(100)[],
customer_ratings varchar(100)[]
);
create table if not exists slots (
id serial primary key not null,
start_date timestamptz not null,
end_date timestamptz not null,
booked boolean not null default false,
sales_manager_id int not null references sales_managers(Id)
);
insert into sales_managers (name, languages, products, customer_ratings) values ('Seller 1', '{"German"}', '{"SolarPanels"}', '{"Bronze"}');
insert into sales_managers (name, languages, products, customer_ratings) values ('Seller 2', '{"German", "English"}', '{"SolarPanels", "Heatpumps"}', '{"Gold","Silver","Bronze"}');
insert into sales_managers (name, languages, products, customer_ratings) values ('Seller 3', '{"German", "English"}', '{"Heatpumps"}', '{"Gold","Silver","Bronze"}');
insert into slots (sales_manager_id, booked, start_date, end_date) values (1, false, '2024-05-03T10:30Z', '2024-05-03T11:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (1, true, '2024-05-03T11:00Z', '2024-05-03T12:00Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (1, false, '2024-05-03T11:30Z', '2024-05-03T12:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (2, false, '2024-05-03T10:30Z', '2024-05-03T11:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (2, false, '2024-05-03T11:00Z', '2024-05-03T12:00Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (2, false, '2024-05-03T11:30Z', '2024-05-03T12:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (3, true, '2024-05-03T10:30Z', '2024-05-03T11:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (3, false, '2024-05-03T11:00Z', '2024-05-03T12:00Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (3, false, '2024-05-03T11:30Z', '2024-05-03T12:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (1, false, '2024-05-04T10:30Z', '2024-05-04T11:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (1, false, '2024-05-04T11:00Z', '2024-05-04T12:00Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (1, true, '2024-05-04T11:30Z', '2024-05-04T12:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (2, true, '2024-05-04T10:30Z', '2024-05-04T11:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (2, false, '2024-05-04T11:00Z', '2024-05-04T12:00Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (2, true, '2024-05-04T11:30Z', '2024-05-04T12:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (3, true, '2024-05-04T10:30Z', '2024-05-04T11:30Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (3, false, '2024-05-04T11:00Z', '2024-05-04T12:00Z');
insert into slots (sales_manager_id, booked, start_date, end_date) values (3, false, '2024-05-04T11:30Z', '2024-05-04T12:30Z');

File diff suppressed because it is too large Load Diff

@ -0,0 +1,18 @@
{
"name": "coding_challenge_tests",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"axios": "^1.7.2",
"jest": "^29.7.0",
"pg": "^8.12.0"
}
}

@ -0,0 +1,110 @@
const { default: axios } = require("axios");
jest.setTimeout(600000);
const tests = [
[
"Monday 2024-05-03, Solar Panels and Heatpumps, German and Gold customer. Only Seller 2 is selectable.",
{
input: {
"date": "2024-05-03",
"products": ["SolarPanels", "Heatpumps"],
"language": "German",
"rating": "Gold"
},
expectedResult: [
{ "start_date": "2024-05-03T10:30:00.000Z", "available_count": 1 },
{ "start_date": "2024-05-03T11:00:00.000Z", "available_count": 1 },
{ "start_date": "2024-05-03T11:30:00.000Z", "available_count": 1 },
]
}
],
[
"Monday 2024-05-03, Heatpumps, English and Silver customer. Both Seller 2 and Seller 3 are selectable.",
{
input: {
"date": "2024-05-03",
"products": ["Heatpumps"],
"language": "English",
"rating": "Silver"
},
expectedResult: [
{ "start_date": "2024-05-03T10:30:00.000Z", "available_count": 1 },
{ "start_date": "2024-05-03T11:00:00.000Z", "available_count": 1 },
{ "start_date": "2024-05-03T11:30:00.000Z", "available_count": 2 },
]
}
],
[
"Monday 2024-05-03, SolarPanels, German and Bronze customer. All Seller 1 and 2 are selectable, but Seller 1 does not have available slots.",
{
input: {
"date": "2024-05-03",
"products": ["SolarPanels"],
"language": "German",
"rating": "Bronze"
},
expectedResult: [
{ "start_date": "2024-05-03T10:30:00.000Z", "available_count": 1 },
{ "start_date": "2024-05-03T11:00:00.000Z", "available_count": 1 },
{ "start_date": "2024-05-03T11:30:00.000Z", "available_count": 1 },
]
}
],
[
"Tuesday 2024-05-04, Solar Panels and Heatpumps, German and Gold customer. Only Seller 2 is selectable, but it is fully booked",
{
input: {
"date": "2024-05-04",
"products": ["SolarPanels", "Heatpumps"],
"language": "German",
"rating": "Gold"
},
expectedResult: []
}
],
[
"Tuesday 2024-05-04, Heatpumps, English and Silver customer. Both Seller 2 and Seller 3 are selectable, but Seller 2 is fully booked.",
{
input: {
"date": "2024-05-04",
"products": ["Heatpumps"],
"language": "English",
"rating": "Silver"
},
expectedResult: [
{ "start_date": "2024-05-04T11:30:00.000Z", "available_count": 1 },
]
}
],
[
"Monday 2024-05-03, SolarPanels, German and Bronze customer. Seller 1 and 2 are selectable, but Seller 2 is fully booked",
{
input: {
"date": "2024-05-04",
"products": ["SolarPanels"],
"language": "German",
"rating": "Bronze"
},
expectedResult: [
{ "start_date": "2024-05-04T10:30:00.000Z", "available_count": 1 },
]
}
],
];
describe("Coding challenge calendar tests", () => {
test.each(tests)(
"%s",
async (_, test_data) => {
const response = await axios.post("http://localhost:3000/calendar/query", test_data.input);
expect(response.status).toBe(200);
expect(response.data.length).toBe(test_data.expectedResult.length);
for (let i = 0; i < test_data.expectedResult.length; i++) {
expect(response.data[i].available_count).toBe(test_data.expectedResult[i].available_count);
expect(response.data[i].start_date).toBe(test_data.expectedResult[i].start_date);
}
}
);
});
Loading…
Cancel
Save