aded k6 and script and updated code with better logic and indexing on schema
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
.env
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
|
dist
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
FROM node:22-bookworm-slim AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json ./
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:22-bookworm-slim AS runtime
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json ./
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo:7.0.41
|
||||||
|
container_name: node-ass1-mongo
|
||||||
|
restart: unless-stopped
|
||||||
|
command: ["mongod", "--bind_ip_all"]
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_DATABASE: node_ass1
|
||||||
|
volumes:
|
||||||
|
- mongo_data:/data/db
|
||||||
|
networks:
|
||||||
|
- testnet
|
||||||
|
mem_limit: 2g
|
||||||
|
cpus: 1.0
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"mongosh",
|
||||||
|
"--quiet",
|
||||||
|
"mongodb://127.0.0.1:27017/node_ass1",
|
||||||
|
"--eval",
|
||||||
|
"db.runCommand({ ping: 1 }).ok"
|
||||||
|
]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 15
|
||||||
|
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: node-ass1-app
|
||||||
|
init: true
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: 3000
|
||||||
|
MONGO_URI: mongodb://mongo:27017/node_ass1
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:3000:3000"
|
||||||
|
depends_on:
|
||||||
|
mongo:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- testnet
|
||||||
|
mem_limit: 1g
|
||||||
|
cpus: 1.5
|
||||||
|
|
||||||
|
k6:
|
||||||
|
image: grafana/k6:latest
|
||||||
|
container_name: node-ass1-k6
|
||||||
|
profiles: ["testing"]
|
||||||
|
working_dir: /scripts
|
||||||
|
volumes:
|
||||||
|
- ./k6:/scripts:ro
|
||||||
|
- ./results:/results
|
||||||
|
networks:
|
||||||
|
- testnet
|
||||||
|
mem_limit: 2g
|
||||||
|
cpus: 1.0
|
||||||
|
entrypoint: ["k6"]
|
||||||
|
|
||||||
|
networks:
|
||||||
|
testnet:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mongo_data:
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check } from 'k6';
|
||||||
|
import { Counter } from 'k6/metrics';
|
||||||
|
|
||||||
|
const seed = JSON.parse(open('./seed-data.json'));
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://app:3000';
|
||||||
|
|
||||||
|
const successfulBookings = new Counter('successful_bookings');
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
scenarios: {
|
||||||
|
concurrent_booking: {
|
||||||
|
executor: 'per-vu-iterations',
|
||||||
|
vus: 10,
|
||||||
|
iterations: 1,
|
||||||
|
maxDuration: '30s',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
thresholds: {
|
||||||
|
successful_bookings: ['count==1'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const session = seed.sessions[0];
|
||||||
|
const user = seed.users[__VU - 1];
|
||||||
|
|
||||||
|
const response = http.post(
|
||||||
|
`${BASE_URL}/sessions/${session.id}/book`,
|
||||||
|
JSON.stringify({ userId: user.id }),
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
responseCallback: http.expectedStatuses(200, 400),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
successfulBookings.add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
check(response, {
|
||||||
|
'booking returned expected status': (r) =>
|
||||||
|
r.status === 200 || r.status === 400,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`VU ${__VU}: status=${response.status}, session=${session.id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://app:3000';
|
||||||
|
const seed = JSON.parse(open('./seed-data.json'));
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '15s', target: 1 },
|
||||||
|
{ duration: '20s', target: 3 },
|
||||||
|
{ duration: '20s', target: 5 },
|
||||||
|
{ duration: '10s', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.01'],
|
||||||
|
http_req_duration: ['p(95)<500'],
|
||||||
|
checks: ['rate>0.99'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const user = seed.users[(__VU + __ITER) % seed.users.length];
|
||||||
|
const session =
|
||||||
|
seed.sessions[(__VU + __ITER) % seed.sessions.length];
|
||||||
|
|
||||||
|
const requests = [
|
||||||
|
['users', `${BASE_URL}/users/`],
|
||||||
|
['teachers', `${BASE_URL}/teachers/`],
|
||||||
|
[
|
||||||
|
'available sessions',
|
||||||
|
`${BASE_URL}/sessions/available?dateTimestamp=${encodeURIComponent(session.dateTimestamp)}`,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'user session history',
|
||||||
|
`${BASE_URL}/sessions/${user.id}/sessions`,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [name, url] of requests) {
|
||||||
|
const response = http.get(url);
|
||||||
|
|
||||||
|
check(response, {
|
||||||
|
[`${name}: HTTP 200`]: (r) => r.status === 200,
|
||||||
|
[`${name}: JSON response`]: (r) =>
|
||||||
|
r.headers['Content-Type']?.includes('application/json'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { Counter } from 'k6/metrics';
|
||||||
|
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://app:3000';
|
||||||
|
const seed = JSON.parse(open('./seed-data.json'));
|
||||||
|
|
||||||
|
const writeFailures = new Counter('write_failures');
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '20s', target: 50 },
|
||||||
|
{ duration: '20s', target: 100 },
|
||||||
|
{ duration: '30s', target: 150 },
|
||||||
|
{ duration: '30s', target: 200 },
|
||||||
|
{ duration: '30s', target: 250 },
|
||||||
|
{ duration: '30s', target: 300 },
|
||||||
|
{ duration: '30s', target: 350 },
|
||||||
|
{ duration: '30s', target: 400 },
|
||||||
|
{ duration: '20s', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.02'],
|
||||||
|
http_req_duration: ['p(95)<500'],
|
||||||
|
write_failures: ['count==0'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const unique = `${__VU}-${__ITER}-${Date.now()}`;
|
||||||
|
const startTime = new Date(Date.now() + 60 * 60 * 1000);
|
||||||
|
const endTime = new Date(startTime.getTime() + 30 * 60 * 1000);
|
||||||
|
|
||||||
|
// 1. Insert a new user
|
||||||
|
const userResponse = http.post(
|
||||||
|
`${BASE_URL}/users/`,
|
||||||
|
JSON.stringify({
|
||||||
|
fullName: `Load Test ${unique}`,
|
||||||
|
email: `load-${unique}@example.invalid`,
|
||||||
|
phone: `9${String(__VU).padStart(1, '0')}${String(__ITER).padStart(8, '0')}`.slice(0, 10),
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /users' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const userOk = check(userResponse, {
|
||||||
|
'user inserted (201)': (r) => r.status === 201,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = userResponse.json('result._id');
|
||||||
|
|
||||||
|
// 2. Insert a session for the existing seeded teacher
|
||||||
|
const sessionResponse = http.post(
|
||||||
|
`${BASE_URL}/sessions/`,
|
||||||
|
JSON.stringify({
|
||||||
|
teacherId: seed.teacher.id,
|
||||||
|
startTime: startTime.toISOString(),
|
||||||
|
endTime: endTime.toISOString(),
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /sessions' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessionOk = check(sessionResponse, {
|
||||||
|
'session inserted (201)': (r) => r.status === 201,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sessionOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = sessionResponse.json('result._id');
|
||||||
|
|
||||||
|
// 3. Book the newly created session
|
||||||
|
const bookingResponse = http.post(
|
||||||
|
`${BASE_URL}/sessions/${sessionId}/book`,
|
||||||
|
JSON.stringify({ userId }),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /sessions/:id/book' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const bookingOk = check(bookingResponse, {
|
||||||
|
'session booked (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!bookingOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Complete the booked session
|
||||||
|
const completeResponse = http.patch(
|
||||||
|
`${BASE_URL}/sessions/${sessionId}/complete`,
|
||||||
|
null,
|
||||||
|
{ tags: { name: 'PATCH /sessions/:id/complete' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const completeOk = check(completeResponse, {
|
||||||
|
'session completed (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!completeOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Read user session history
|
||||||
|
const historyResponse = http.get(
|
||||||
|
`${BASE_URL}/sessions/${userId}/sessions`,
|
||||||
|
{ tags: { name: 'GET /sessions/:userId/sessions' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
check(historyResponse, {
|
||||||
|
'user history returned (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleSummary(data) {
|
||||||
|
return {
|
||||||
|
'/results/endpoint-summary.json': JSON.stringify(data, null, 2),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { Counter } from 'k6/metrics';
|
||||||
|
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://app:3000';
|
||||||
|
const seed = JSON.parse(open('./seed-data.json'));
|
||||||
|
|
||||||
|
const writeFailures = new Counter('write_failures');
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '20s', target: 50 },
|
||||||
|
{ duration: '20s', target: 100 },
|
||||||
|
{ duration: '30s', target: 150 },
|
||||||
|
{ duration: '30s', target: 200 },
|
||||||
|
{ duration: '30s', target: 250 },
|
||||||
|
{ duration: '30s', target: 300 },
|
||||||
|
{ duration: '30s', target: 350 },
|
||||||
|
{ duration: '30s', target: 400 },
|
||||||
|
{ duration: '20s', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.02'],
|
||||||
|
http_req_duration: ['p(95)<500'],
|
||||||
|
write_failures: ['count==0'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const unique = `${__VU}-${__ITER}-${Date.now()}`;
|
||||||
|
const startTime = new Date(Date.now() + 60 * 60 * 1000);
|
||||||
|
const endTime = new Date(startTime.getTime() + 30 * 60 * 1000);
|
||||||
|
|
||||||
|
// 1. Insert a new user
|
||||||
|
const userResponse = http.post(
|
||||||
|
`${BASE_URL}/users/`,
|
||||||
|
JSON.stringify({
|
||||||
|
fullName: `Load Test ${unique}`,
|
||||||
|
email: `load-${unique}@example.invalid`,
|
||||||
|
phone: `9${String(__VU).padStart(1, '0')}${String(__ITER).padStart(8, '0')}`.slice(0, 10),
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /users' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const userOk = check(userResponse, {
|
||||||
|
'user inserted (201)': (r) => r.status === 201,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = userResponse.json('result._id');
|
||||||
|
|
||||||
|
// 2. Insert a session for the existing seeded teacher
|
||||||
|
const sessionResponse = http.post(
|
||||||
|
`${BASE_URL}/sessions/`,
|
||||||
|
JSON.stringify({
|
||||||
|
teacherId: seed.teacher.id,
|
||||||
|
startTime: startTime.toISOString(),
|
||||||
|
endTime: endTime.toISOString(),
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /sessions' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessionOk = check(sessionResponse, {
|
||||||
|
'session inserted (201)': (r) => r.status === 201,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sessionOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = sessionResponse.json('result._id');
|
||||||
|
|
||||||
|
// 3. Book the newly created session
|
||||||
|
const bookingResponse = http.post(
|
||||||
|
`${BASE_URL}/sessions/${sessionId}/book`,
|
||||||
|
JSON.stringify({ userId }),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /sessions/:id/book' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const bookingOk = check(bookingResponse, {
|
||||||
|
'session booked (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!bookingOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Complete the booked session
|
||||||
|
const completeResponse = http.patch(
|
||||||
|
`${BASE_URL}/sessions/${sessionId}/complete`,
|
||||||
|
null,
|
||||||
|
{ tags: { name: 'PATCH /sessions/:id/complete' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const completeOk = check(completeResponse, {
|
||||||
|
'session completed (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!completeOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Read user session history
|
||||||
|
const historyResponse = http.get(
|
||||||
|
`${BASE_URL}/sessions/${userId}/sessions`,
|
||||||
|
{ tags: { name: 'GET /sessions/:userId/sessions' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
check(historyResponse, {
|
||||||
|
'user history returned (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleSummary(data) {
|
||||||
|
return {
|
||||||
|
'/results/endpoint-summary.json': JSON.stringify(data, null, 2),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
|
||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
import { Counter } from 'k6/metrics';
|
||||||
|
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.1/index.js';
|
||||||
|
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://app:3000';
|
||||||
|
const seed = JSON.parse(open('./seed-data.json'));
|
||||||
|
|
||||||
|
const writeFailures = new Counter('write_failures');
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '20s', target: 100 },
|
||||||
|
{ duration: '20s', target: 200 },
|
||||||
|
{ duration: '30s', target: 300 },
|
||||||
|
{ duration: '30s', target: 400 },
|
||||||
|
{ duration: '30s', target: 450 },
|
||||||
|
{ duration: '30s', target: 500 },
|
||||||
|
{ duration: '30s', target: 550 },
|
||||||
|
{ duration: '30s', target: 600 },
|
||||||
|
{ duration: '20s', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.02'],
|
||||||
|
http_req_duration: ['p(95)<500'],
|
||||||
|
write_failures: ['count==0'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const unique = `${__VU}-${__ITER}-${Date.now()}`;
|
||||||
|
const startTime = new Date(Date.now() + 60 * 60 * 1000);
|
||||||
|
const endTime = new Date(startTime.getTime() + 30 * 60 * 1000);
|
||||||
|
|
||||||
|
// 1. Insert a new user
|
||||||
|
const userResponse = http.post(
|
||||||
|
`${BASE_URL}/users/`,
|
||||||
|
JSON.stringify({
|
||||||
|
fullName: `Load Test ${unique}`,
|
||||||
|
email: `load-${unique}@example.invalid`,
|
||||||
|
phone: `9${String(__VU).padStart(1, '0')}${String(__ITER).padStart(8, '0')}`.slice(0, 10),
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /users' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const userOk = check(userResponse, {
|
||||||
|
'user inserted (201)': (r) => r.status === 201,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = userResponse.json('result._id');
|
||||||
|
|
||||||
|
// 2. Insert a session for the existing seeded teacher
|
||||||
|
const sessionResponse = http.post(
|
||||||
|
`${BASE_URL}/sessions/`,
|
||||||
|
JSON.stringify({
|
||||||
|
teacherId: seed.teacher.id,
|
||||||
|
startTime: startTime.toISOString(),
|
||||||
|
endTime: endTime.toISOString(),
|
||||||
|
}),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /sessions' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessionOk = check(sessionResponse, {
|
||||||
|
'session inserted (201)': (r) => r.status === 201,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!sessionOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = sessionResponse.json('result._id');
|
||||||
|
|
||||||
|
// 3. Book the newly created session
|
||||||
|
const bookingResponse = http.post(
|
||||||
|
`${BASE_URL}/sessions/${sessionId}/book`,
|
||||||
|
JSON.stringify({ userId }),
|
||||||
|
{ headers: { 'Content-Type': 'application/json' }, tags: { name: 'POST /sessions/:id/book' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const bookingOk = check(bookingResponse, {
|
||||||
|
'session booked (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!bookingOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Complete the booked session
|
||||||
|
const completeResponse = http.patch(
|
||||||
|
`${BASE_URL}/sessions/${sessionId}/complete`,
|
||||||
|
null,
|
||||||
|
{ tags: { name: 'PATCH /sessions/:id/complete' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const completeOk = check(completeResponse, {
|
||||||
|
'session completed (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!completeOk) {
|
||||||
|
writeFailures.add(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Read user session history
|
||||||
|
const historyResponse = http.get(
|
||||||
|
`${BASE_URL}/sessions/${userId}/sessions`,
|
||||||
|
{ tags: { name: 'GET /sessions/:userId/sessions' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
check(historyResponse, {
|
||||||
|
'user history returned (200)': (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleSummary(data) {
|
||||||
|
return {
|
||||||
|
stdout: textSummary(data, { indent: ' ', enableColors: true }),
|
||||||
|
'/results/endpoint-summary.json': JSON.stringify(data, null, 2),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,392 @@
|
|||||||
|
{
|
||||||
|
"generatedAt": "2026-10-21T18:53:24.974Z",
|
||||||
|
"runId": "1792608804974",
|
||||||
|
"teacher": {
|
||||||
|
"id": "6ad90a253d51b696cdc51357",
|
||||||
|
"email": "teacher-1792608804974@example.invalid"
|
||||||
|
},
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51358",
|
||||||
|
"email": "user-1792608804974-1@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51359",
|
||||||
|
"email": "user-1792608804974-2@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5135a",
|
||||||
|
"email": "user-1792608804974-3@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5135b",
|
||||||
|
"email": "user-1792608804974-4@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5135c",
|
||||||
|
"email": "user-1792608804974-5@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5135d",
|
||||||
|
"email": "user-1792608804974-6@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5135e",
|
||||||
|
"email": "user-1792608804974-7@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5135f",
|
||||||
|
"email": "user-1792608804974-8@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51360",
|
||||||
|
"email": "user-1792608804974-9@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51361",
|
||||||
|
"email": "user-1792608804974-10@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51362",
|
||||||
|
"email": "user-1792608804974-11@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51363",
|
||||||
|
"email": "user-1792608804974-12@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51364",
|
||||||
|
"email": "user-1792608804974-13@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51365",
|
||||||
|
"email": "user-1792608804974-14@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51366",
|
||||||
|
"email": "user-1792608804974-15@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51367",
|
||||||
|
"email": "user-1792608804974-16@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51368",
|
||||||
|
"email": "user-1792608804974-17@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51369",
|
||||||
|
"email": "user-1792608804974-18@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5136a",
|
||||||
|
"email": "user-1792608804974-19@example.invalid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5136b",
|
||||||
|
"email": "user-1792608804974-20@example.invalid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5136c",
|
||||||
|
"startTime": "2026-10-22T18:53:25.235Z",
|
||||||
|
"endTime": "2026-10-22T19:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-22T18:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5136d",
|
||||||
|
"startTime": "2026-10-22T19:53:25.235Z",
|
||||||
|
"endTime": "2026-10-22T20:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-22T19:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5136e",
|
||||||
|
"startTime": "2026-10-22T20:53:25.235Z",
|
||||||
|
"endTime": "2026-10-22T21:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-22T20:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5136f",
|
||||||
|
"startTime": "2026-10-22T21:53:25.235Z",
|
||||||
|
"endTime": "2026-10-22T22:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-22T21:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51370",
|
||||||
|
"startTime": "2026-10-22T22:53:25.235Z",
|
||||||
|
"endTime": "2026-10-22T23:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-22T22:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51371",
|
||||||
|
"startTime": "2026-10-22T23:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T00:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-22T23:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51372",
|
||||||
|
"startTime": "2026-10-23T00:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T01:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T00:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51373",
|
||||||
|
"startTime": "2026-10-23T01:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T02:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T01:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51374",
|
||||||
|
"startTime": "2026-10-23T02:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T03:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T02:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51375",
|
||||||
|
"startTime": "2026-10-23T03:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T04:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T03:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51376",
|
||||||
|
"startTime": "2026-10-23T04:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T05:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T04:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51377",
|
||||||
|
"startTime": "2026-10-23T05:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T06:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T05:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51378",
|
||||||
|
"startTime": "2026-10-23T06:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T07:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T06:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51379",
|
||||||
|
"startTime": "2026-10-23T07:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T08:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T07:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5137a",
|
||||||
|
"startTime": "2026-10-23T08:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T09:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T08:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5137b",
|
||||||
|
"startTime": "2026-10-23T09:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T10:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T09:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5137c",
|
||||||
|
"startTime": "2026-10-23T10:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T11:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T10:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5137d",
|
||||||
|
"startTime": "2026-10-23T11:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T12:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T11:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5137e",
|
||||||
|
"startTime": "2026-10-23T12:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T13:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T12:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5137f",
|
||||||
|
"startTime": "2026-10-23T13:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T14:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T13:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51380",
|
||||||
|
"startTime": "2026-10-23T14:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T15:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T14:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51381",
|
||||||
|
"startTime": "2026-10-23T15:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T16:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T15:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51382",
|
||||||
|
"startTime": "2026-10-23T16:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T17:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T16:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51383",
|
||||||
|
"startTime": "2026-10-23T17:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T18:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T17:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51384",
|
||||||
|
"startTime": "2026-10-23T18:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T19:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T18:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51385",
|
||||||
|
"startTime": "2026-10-23T19:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T20:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T19:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51386",
|
||||||
|
"startTime": "2026-10-23T20:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T21:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T20:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51387",
|
||||||
|
"startTime": "2026-10-23T21:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T22:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T21:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51388",
|
||||||
|
"startTime": "2026-10-23T22:53:25.235Z",
|
||||||
|
"endTime": "2026-10-23T23:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T22:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51389",
|
||||||
|
"startTime": "2026-10-23T23:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T00:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-23T23:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5138a",
|
||||||
|
"startTime": "2026-10-24T00:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T01:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T00:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5138b",
|
||||||
|
"startTime": "2026-10-24T01:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T02:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T01:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5138c",
|
||||||
|
"startTime": "2026-10-24T02:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T03:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T02:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5138d",
|
||||||
|
"startTime": "2026-10-24T03:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T04:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T03:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5138e",
|
||||||
|
"startTime": "2026-10-24T04:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T05:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T04:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5138f",
|
||||||
|
"startTime": "2026-10-24T05:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T06:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T05:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51390",
|
||||||
|
"startTime": "2026-10-24T06:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T07:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T06:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51391",
|
||||||
|
"startTime": "2026-10-24T07:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T08:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T07:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51392",
|
||||||
|
"startTime": "2026-10-24T08:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T09:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T08:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51393",
|
||||||
|
"startTime": "2026-10-24T09:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T10:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T09:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51394",
|
||||||
|
"startTime": "2026-10-24T10:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T11:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T10:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51395",
|
||||||
|
"startTime": "2026-10-24T11:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T12:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T11:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51396",
|
||||||
|
"startTime": "2026-10-24T12:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T13:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T12:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51397",
|
||||||
|
"startTime": "2026-10-24T13:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T14:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T13:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51398",
|
||||||
|
"startTime": "2026-10-24T14:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T15:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T14:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc51399",
|
||||||
|
"startTime": "2026-10-24T15:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T16:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T15:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5139a",
|
||||||
|
"startTime": "2026-10-24T16:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T17:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T16:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5139b",
|
||||||
|
"startTime": "2026-10-24T17:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T18:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T17:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5139c",
|
||||||
|
"startTime": "2026-10-24T18:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T19:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T18:53:25.235Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ad90a253d51b696cdc5139d",
|
||||||
|
"startTime": "2026-10-24T19:53:25.235Z",
|
||||||
|
"endTime": "2026-10-24T20:23:25.235Z",
|
||||||
|
"dateTimestamp": "2026-10-24T19:53:25.235Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check, sleep } from 'k6';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
vus: 1,
|
||||||
|
iterations: 10,
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.01'],
|
||||||
|
http_req_duration: ['p(95)<500'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const response = http.get('http://app:3000/users/');
|
||||||
|
|
||||||
|
check(response, {
|
||||||
|
'status is 200': (r) => r.status === 200,
|
||||||
|
'response is JSON': (r) =>
|
||||||
|
r.headers['Content-Type']?.includes('application/json'),
|
||||||
|
});
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import http from 'k6/http';
|
||||||
|
import { check } from 'k6';
|
||||||
|
|
||||||
|
const BASE_URL = __ENV.BASE_URL || 'http://app:3000';
|
||||||
|
|
||||||
|
export const options = {
|
||||||
|
stages: [
|
||||||
|
{ duration: '20s', target: 5 },
|
||||||
|
{ duration: '20s', target: 10 },
|
||||||
|
{ duration: '20s', target: 20 },
|
||||||
|
{ duration: '20s', target: 30 },
|
||||||
|
{ duration: '20s', target: 40 },
|
||||||
|
{ duration: '20s', target: 50 },
|
||||||
|
{ duration: '15s', target: 0 },
|
||||||
|
],
|
||||||
|
thresholds: {
|
||||||
|
http_req_failed: ['rate<0.01'],
|
||||||
|
http_req_duration: ['p(95)<500'],
|
||||||
|
checks: ['rate>0.99'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const endpoints = [
|
||||||
|
'/users/',
|
||||||
|
'/teachers/',
|
||||||
|
'/sessions/available?dateTimestamp=2026-10-25T10%3A00%3A00.000Z',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const endpoint of endpoints) {
|
||||||
|
const response = http.get(`${BASE_URL}${endpoint}`);
|
||||||
|
|
||||||
|
check(response, {
|
||||||
|
[`${endpoint}: status 200`]: (r) => r.status === 200,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { writeFile } from 'node:fs/promises';
|
||||||
|
|
||||||
|
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000';
|
||||||
|
const USER_COUNT = Number(process.env.SEED_USERS || 20);
|
||||||
|
const SESSION_COUNT = Number(process.env.SEED_SESSIONS || 50);
|
||||||
|
|
||||||
|
const runId = `${Date.now()}`;
|
||||||
|
const generated = {
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
runId,
|
||||||
|
teacher: null,
|
||||||
|
users: [],
|
||||||
|
sessions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function request(path, method = 'GET', body) {
|
||||||
|
const response = await fetch(`${BASE_URL}${path}`, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
...(body ? { body: JSON.stringify(body) } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
let data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
throw new Error(`${method} ${path}: Invalid JSON response: ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`${method} ${path} failed (${response.status}): ${JSON.stringify(data)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function entityId(data) {
|
||||||
|
const entity = data.result ?? data.data ?? data;
|
||||||
|
const id = entity._id ?? entity.id;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
throw new Error(`Could not find created record ID: ${JSON.stringify(data)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log(`Seeding ${BASE_URL}`);
|
||||||
|
|
||||||
|
const teacherResponse = await request('/teachers/', 'POST', {
|
||||||
|
fullName: `Load Test Teacher ${runId}`,
|
||||||
|
email: `teacher-${runId}@example.invalid`,
|
||||||
|
specialization: 'Mathematics',
|
||||||
|
experience: '5 years',
|
||||||
|
});
|
||||||
|
|
||||||
|
generated.teacher = {
|
||||||
|
id: entityId(teacherResponse),
|
||||||
|
email: `teacher-${runId}@example.invalid`,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 1; i <= USER_COUNT; i++) {
|
||||||
|
const email = `user-${runId}-${i}@example.invalid`;
|
||||||
|
|
||||||
|
const response = await request('/users/', 'POST', {
|
||||||
|
fullName: `Load Test User ${i}`,
|
||||||
|
email,
|
||||||
|
phone: String(9000000000 + i),
|
||||||
|
});
|
||||||
|
|
||||||
|
generated.users.push({
|
||||||
|
id: entityId(response),
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (i % 10 === 0 || i === USER_COUNT) {
|
||||||
|
console.log(`Created ${i}/${USER_COUNT} users`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start tomorrow and spread sessions one hour apart.
|
||||||
|
const firstStart = Date.now() + 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
for (let i = 0; i < SESSION_COUNT; i++) {
|
||||||
|
const start = new Date(firstStart + i * 60 * 60 * 1000);
|
||||||
|
const end = new Date(start.getTime() + 30 * 60 * 1000);
|
||||||
|
|
||||||
|
const response = await request('/sessions/', 'POST', {
|
||||||
|
teacherId: generated.teacher.id,
|
||||||
|
startTime: start.toISOString(),
|
||||||
|
endTime: end.toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
generated.sessions.push({
|
||||||
|
id: entityId(response),
|
||||||
|
startTime: start.toISOString(),
|
||||||
|
endTime: end.toISOString(),
|
||||||
|
dateTimestamp: start.toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((i + 1) % 10 === 0 || i + 1 === SESSION_COUNT) {
|
||||||
|
console.log(`Created ${i + 1}/${SESSION_COUNT} sessions`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
new URL('../k6/seed-data.json', import.meta.url),
|
||||||
|
JSON.stringify(generated, null, 2) + '\n'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('\nSeeding completed successfully.');
|
||||||
|
console.log(`Teacher: 1`);
|
||||||
|
console.log(`Users: ${generated.users.length}`);
|
||||||
|
console.log(`Sessions: ${generated.sessions.length}`);
|
||||||
|
console.log('Saved IDs to k6/seed-data.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('\nSeeding failed:', error.message);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -1,31 +1,34 @@
|
|||||||
import mongoose, { Types } from "mongoose";
|
import mongoose, { Types } from "mongoose";
|
||||||
const {Schema} = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const SessionsSchema = new Schema({
|
const SessionsSchema = new Schema({
|
||||||
teacherId: {
|
teacherId: {
|
||||||
type: Types.ObjectId,
|
type: Types.ObjectId,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
userId: {
|
userId: {
|
||||||
type: Types.ObjectId,
|
type: Types.ObjectId,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
startTime: {
|
startTime: {
|
||||||
type: Date,
|
type: Date,
|
||||||
},
|
},
|
||||||
endTime: {
|
endTime: {
|
||||||
type: Date,
|
type: Date,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ['BOOKED', 'AVAILABLE', 'COMPLETED'],
|
enum: ['BOOKED', 'AVAILABLE', 'COMPLETED'],
|
||||||
default: 'AVAILABLE',
|
default: 'AVAILABLE',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
completedAt: {
|
completedAt: {
|
||||||
type: Date
|
type: Date
|
||||||
}
|
}
|
||||||
}, {timestamps: true});
|
}, { timestamps: true });
|
||||||
|
|
||||||
|
SessionsSchema.index({ userId: 1 });
|
||||||
|
SessionsSchema.index({ status: 1, startTime: 1 });
|
||||||
|
|
||||||
const Sessions = mongoose.model('sessions', SessionsSchema);
|
const Sessions = mongoose.model('sessions', SessionsSchema);
|
||||||
export default Sessions;
|
export default Sessions;
|
||||||
+145
-105
@@ -7,116 +7,156 @@ const SessionsRoutes = express.Router();
|
|||||||
|
|
||||||
|
|
||||||
// Add New Session for teacher
|
// Add New Session for teacher
|
||||||
SessionsRoutes.post('/', async(req, res) => {
|
SessionsRoutes.post('/', async (req, res) => {
|
||||||
const {teacherId, startTime, endTime } = req.body;
|
const { teacherId, startTime, endTime } = req.body;
|
||||||
|
|
||||||
//Check for teacher existance
|
//Check for teacher existance
|
||||||
const existingTeacher = await Teachers.findOne({_id: teacherId});
|
const existingTeacher = await Teachers.findOne({ _id: teacherId });
|
||||||
|
|
||||||
console.log('existingTeacher', existingTeacher)
|
console.log('existingTeacher', existingTeacher)
|
||||||
if(!existingTeacher){
|
if (!existingTeacher) {
|
||||||
return res.status(400).send({message: 'Teacher Does not exist please provide valid teacherId.'})
|
return res.status(400).send({ message: 'Teacher Does not exist please provide valid teacherId.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check endTime must be greater then startTime
|
// Check endTime must be greater then startTime
|
||||||
if(new Date(endTime) <= new Date(startTime)) {
|
if (new Date(endTime) <= new Date(startTime)) {
|
||||||
return res.status(400).send({message: 'End time must be greater then start time.'})
|
return res.status(400).send({ message: 'End time must be greater then start time.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSession = new Sessions({teacherId, startTime, endTime});
|
const newSession = new Sessions({ teacherId, startTime, endTime });
|
||||||
const result = await newSession.save();
|
const result = await newSession.save();
|
||||||
|
|
||||||
if(result) {
|
if (result) {
|
||||||
return res.status(201).send({message: 'Session Added Successfully', result})
|
return res.status(201).send({ message: 'Session Added Successfully', result })
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Get Available Sessions
|
// Get Available Sessions
|
||||||
SessionsRoutes.get('/available', async(req: any, res) => {
|
SessionsRoutes.get('/available', async (req: any, res) => {
|
||||||
const dateTimestamp = req.query.dateTimestamp;
|
const dateTimestamp = req.query.dateTimestamp;
|
||||||
|
|
||||||
const dateString = dateTimestamp.slice(0, 10);
|
const dateString = dateTimestamp.slice(0, 10);
|
||||||
|
|
||||||
// us standard dates we used here for simplicity
|
// us standard dates we used here for simplicity
|
||||||
const startOfDay = new Date(`${dateString}T00:00:00.000Z`);
|
const startOfDay = new Date(`${dateString}T00:00:00.000Z`);
|
||||||
const endOfDay = new Date(`${dateString}T23:59:59.999Z`);
|
const endOfDay = new Date(`${dateString}T23:59:59.999Z`);
|
||||||
|
|
||||||
const availableSessions = await Sessions.aggregate([
|
const availableSessions = await Sessions.aggregate([
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
startTime: { $lte: endOfDay },
|
startTime: { $lte: endOfDay },
|
||||||
endTime: { $gte: startOfDay },
|
endTime: { $gte: startOfDay },
|
||||||
status: "AVAILABLE",
|
status: "AVAILABLE",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return res.status(200).send({message: 'Available Session', availableSessions})
|
return res.status(200).send({ message: 'Available Session', availableSessions })
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// Book Available Sessions
|
// Book Available Sessions
|
||||||
SessionsRoutes.post('/:id/book', async(req: any, res) => {
|
// SessionsRoutes.post('/:id/book', async(req: any, res) => {
|
||||||
|
//
|
||||||
|
// const {id} = req.params;
|
||||||
|
// const {userId} = req.body;
|
||||||
|
//
|
||||||
|
// const existingSession = await Sessions.findOne({_id: id});
|
||||||
|
//
|
||||||
|
// if(!existingSession) {
|
||||||
|
// return res.status(400).send({message: 'Session does not exist.'})
|
||||||
|
// }
|
||||||
|
// if(existingSession.status !== 'AVAILABLE') {
|
||||||
|
// return res.status(400).send({message: 'This session is not Available'});
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const result = await Sessions.findByIdAndUpdate(
|
||||||
|
// id,
|
||||||
|
// { status: "BOOKED", userId: userId },
|
||||||
|
// { new: true }
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// // console.log('re-------', result)
|
||||||
|
//
|
||||||
|
// if(result?.status === 'BOOKED') {
|
||||||
|
// return res.status(200).send({message: 'Session Booked', result})
|
||||||
|
// } else {
|
||||||
|
// return res.status(400).send({message: 'Error during Booking session.'})
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// })
|
||||||
|
|
||||||
const {id} = req.params;
|
// Book Available Sessions atomically
|
||||||
const {userId} = req.body;
|
SessionsRoutes.post('/:id/book', async (req: any, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { userId } = req.body;
|
||||||
|
|
||||||
const existingSession = await Sessions.findOne({_id: id});
|
// Atomically book only if the session is still AVAILABLE
|
||||||
|
const result = await Sessions.findOneAndUpdate(
|
||||||
if(!existingSession) {
|
{ _id: id, status: 'AVAILABLE' },
|
||||||
return res.status(400).send({message: 'Session does not exist.'})
|
{
|
||||||
}
|
$set: {
|
||||||
if(existingSession.status !== 'AVAILABLE') {
|
status: 'BOOKED',
|
||||||
return res.status(400).send({message: 'This session is not Available'});
|
userId: userId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true,
|
||||||
|
runValidators: true
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const result = await Sessions.findByIdAndUpdate(
|
if (result) {
|
||||||
id,
|
return res.status(200).send({
|
||||||
{ status: "BOOKED", userId: userId },
|
message: 'Session Booked',
|
||||||
{ new: true }
|
result
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// console.log('re-------', result)
|
// Determine why the booking failed
|
||||||
|
const existingSession = await Sessions.findById(id);
|
||||||
|
|
||||||
if(result?.status === 'BOOKED') {
|
if (!existingSession) {
|
||||||
return res.status(200).send({message: 'Session Booked', result})
|
return res.status(400).send({
|
||||||
} else {
|
message: 'Session does not exist.'
|
||||||
return res.status(400).send({message: 'Error during Booking session.'})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'This session is not Available'
|
||||||
|
});
|
||||||
|
});
|
||||||
// Update Available Sessions To Complete : User Id also needed to update
|
// Update Available Sessions To Complete : User Id also needed to update
|
||||||
SessionsRoutes.patch('/:id/complete', async(req: any, res) => {
|
SessionsRoutes.patch('/:id/complete', async (req: any, res) => {
|
||||||
|
|
||||||
const {id} = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const existingSession = await Sessions.findOne({_id: id, });
|
const existingSession = await Sessions.findOne({ _id: id, });
|
||||||
|
|
||||||
if(!existingSession) {
|
if (!existingSession) {
|
||||||
return res.status(400).send({message: 'Session does not exist.'})
|
return res.status(400).send({ message: 'Session does not exist.' })
|
||||||
}
|
}
|
||||||
if(existingSession.status !== 'BOOKED') {
|
if (existingSession.status !== 'BOOKED') {
|
||||||
return res.status(400).send({message: 'This session is not BOOKED'});
|
return res.status(400).send({ message: 'This session is not BOOKED' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await Sessions.findByIdAndUpdate(
|
const result = await Sessions.findByIdAndUpdate(
|
||||||
id,
|
id,
|
||||||
{ status: "COMPLETED", completedAt: Date.now() },
|
{ status: "COMPLETED", completedAt: Date.now() },
|
||||||
{ new: true }
|
{ new: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// console.log('re-------', result)
|
// console.log('re-------', result)
|
||||||
|
|
||||||
if(result?.status === 'COMPLETED') {
|
if (result?.status === 'COMPLETED') {
|
||||||
return res.status(200).send({message: 'Session Marked Completed', result})
|
return res.status(200).send({ message: 'Session Marked Completed', result })
|
||||||
} else {
|
} else {
|
||||||
return res.status(400).send({message: 'Error during updating session.'})
|
return res.status(400).send({ message: 'Error during updating session.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -124,44 +164,44 @@ SessionsRoutes.patch('/:id/complete', async(req: any, res) => {
|
|||||||
|
|
||||||
|
|
||||||
// Get Sessions for User : With Seperation
|
// Get Sessions for User : With Seperation
|
||||||
SessionsRoutes.get('/:id/sessions', async(req,res) => {
|
SessionsRoutes.get('/:id/sessions', async (req, res) => {
|
||||||
|
|
||||||
let userId = new Types.ObjectId(req.params.id);
|
let userId = new Types.ObjectId(req.params.id);
|
||||||
console.log('userId', userId)
|
console.log('userId', userId)
|
||||||
|
|
||||||
const result = await Sessions.aggregate([
|
const result = await Sessions.aggregate([
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
userId: userId
|
userId: userId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$facet: {
|
$facet: {
|
||||||
upcomingSessions: [
|
upcomingSessions: [
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
status: 'BOOKED'
|
status: 'BOOKED'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
completedSessions: [
|
completedSessions: [
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
status: 'COMPLETED'
|
status: 'COMPLETED'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: result[0] ?? {
|
data: result[0] ?? {
|
||||||
upcomingSessions: [],
|
upcomingSessions: [],
|
||||||
completedSessions: []
|
completedSessions: []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
const {Schema} = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const UsersSchema = new Schema({
|
const UsersSchema = new Schema({
|
||||||
fullName: {
|
fullName: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
phone: {
|
phone: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
}, {timestamps: true});
|
}, { timestamps: true });
|
||||||
|
|
||||||
const Users = mongoose.model('users', UsersSchema);
|
const Users = mongoose.model('users', UsersSchema);
|
||||||
export default Users;
|
export default Users;
|
||||||
+34
-23
@@ -1,34 +1,45 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import Users from './Schema/users.schema.js'
|
import Users from './Schema/users.schema.js';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
const UsersRoutes = express.Router();
|
const UsersRoutes = express.Router();
|
||||||
|
|
||||||
// Get All Users
|
// Get all users
|
||||||
UsersRoutes.get('/', async(req, res) => {
|
UsersRoutes.get('/', async (_req, res, next) => {
|
||||||
|
try {
|
||||||
const users = await Users.find();
|
const users = await Users.find();
|
||||||
return res.status(200).send({'messesage': 'success', data: {users}})
|
return res.status(200).send({
|
||||||
})
|
message: 'success',
|
||||||
|
data: { users }
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add New User
|
// Add a new user
|
||||||
UsersRoutes.post('/', async(req, res) => {
|
UsersRoutes.post('/', async (req, res, next) => {
|
||||||
const {fullName, email, phone} = req.body;
|
try {
|
||||||
|
const { fullName, email, phone } = req.body;
|
||||||
|
|
||||||
//Check for email uniqeness
|
const result = await Users.create({ fullName, email, phone });
|
||||||
const existingUser = await Users.find({email});
|
|
||||||
console.log('existingUser', existingUser)
|
return res.status(201).send({
|
||||||
if(existingUser.length){
|
message: 'User added successfully',
|
||||||
return res.status(400).send({message: 'User already exist with this email. Please Use Correct Email Address.'})
|
result
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (
|
||||||
|
error instanceof mongoose.mongo.MongoServerError &&
|
||||||
|
error.code === 11000
|
||||||
|
) {
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'A user already exists with this email.'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTeacher = new Users({fullName, email, phone });
|
next(error);
|
||||||
const result = await newTeacher.save();
|
}
|
||||||
|
});
|
||||||
if(result) {
|
|
||||||
return res.status(201).send({message: 'Teacher Added Successfully', result})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
export default UsersRoutes;
|
export default UsersRoutes;
|
||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
// Visit https://aka.ms/tsconfig to read more about this file
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// File Layout
|
// File Layout
|
||||||
// "rootDir": "./src",
|
"rootDir": "./src",
|
||||||
// "outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
|
||||||
// Environment Settings
|
// Environment Settings
|
||||||
// See also https://aka.ms/tsconfig/module
|
// See also https://aka.ms/tsconfig/module
|
||||||
|
|||||||
Reference in New Issue
Block a user