From 014b9c504b3f89cd19423f8c41056372474f18c4 Mon Sep 17 00:00:00 2001 From: luandro Date: Fri, 7 Feb 2025 21:03:14 -0300 Subject: [PATCH] feat: added route for unregistering coord and doing correct checks with appropriate tests --- src/db.js | 17 ++++++++++ src/errors.js | 4 +++ src/routes/auth.js | 56 +++++++++++++++++++++++++++++- test/test_auth_script.sh | 73 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/db.js b/src/db.js index 8b80fe2..85a4739 100644 --- a/src/db.js +++ b/src/db.js @@ -26,8 +26,10 @@ const DB_FILE = 'users.json' * @typedef {Object} DBMethods * @property {() => Coordinator[]} getCoordinators * @property {(coordinator: Coordinator) => void} saveCoordinator + * @property {(phoneNumber: string) => void} deleteCoordinatorByPhone * @property {(phoneNumber: string) => Coordinator|null} findCoordinatorByPhone * @property {(phoneNumber: string) => string|null} findProjectByCoordinatorPhone + * @property {(projectName: string) => Coordinator|null} findCoordinatorByProject * @property {() => Member[]} getMembers * @property {(member: Member) => void} saveMember * @property {(phoneNumber: string) => Member|null} findMemberByPhone @@ -106,6 +108,14 @@ async function dbPlugin(fastify, { dbFolder }) { writeDb(data) }, + deleteCoordinatorByPhone(phoneNumber) { + const data = readDb() + data.coordinators = data.coordinators.filter( + (c) => c.phoneNumber !== phoneNumber, + ) + writeDb(data) + }, + findCoordinatorByPhone(phoneNumber) { const coordinator = readDb().coordinators.find( (c) => c.phoneNumber === phoneNumber, @@ -120,6 +130,13 @@ async function dbPlugin(fastify, { dbFolder }) { return coordinator ? coordinator.projectName : null }, + findCoordinatorByProject(projectName) { + const coordinator = readDb().coordinators.find( + (c) => c.projectName === projectName, + ) + return coordinator || null + }, + getMembers() { return readDb().members }, diff --git a/src/errors.js b/src/errors.js index 5b3dfb9..4dc5f28 100644 --- a/src/errors.js +++ b/src/errors.js @@ -43,6 +43,10 @@ export const tooManyProjects = () => export const projectNotFoundError = () => new HttpError(404, 'PROJECT_NOT_FOUND', 'Project not found') +/** @param {string} message */ +export const notFoundError = (message) => + new HttpError(404, 'NOT_FOUND', message) + /** @param {never} value */ export const shouldBeImpossibleError = (value) => new Error(`${value} should be impossible`) diff --git a/src/routes/auth.js b/src/routes/auth.js index 9bc8fab..6eced95 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -60,12 +60,19 @@ export default async function authRoutes(fastify, opts) { // Check if coordinator already exists const existingCoordinator = fastify.db.findCoordinatorByPhone(phoneNumber) - if (existingCoordinator) { + if (existingCoordinator?.token) { fastify.log.warn(`Coordinator already exists for phone: ${phoneNumber}`) throw errors.conflictError('Phone number already registered') } // Check if project name already exists + const existingProjectCoordinator = + fastify.db.findCoordinatorByProject(projectName) + if (existingProjectCoordinator) { + fastify.log.warn(`Project name already exists: ${projectName}`) + throw errors.conflictError('Project name already exists') + } + const projects = await fastify.comapeo.listProjects() const existingProject = projects.find((p) => p.name === projectName) if (existingProject) { @@ -91,6 +98,53 @@ export default async function authRoutes(fastify, opts) { } }, ) + // DELETE /auth/unregister + fastify.delete( + '/auth/unregister', + { + schema: { + body: Type.Object({ + phoneNumber: Type.String(), + }), + response: { + 200: Type.Object({ + data: Type.Object({ + message: Type.String(), + }), + }), + '4xx': schemas.errorResponse, + }, + }, + async preHandler(req) { + verifyBearerAuth(req, serverBearerToken) + }, + }, + async (req) => { + const { phoneNumber } = + /** @type {import('fastify').FastifyRequest<{Body: {phoneNumber: string}}>} */ ( + req + ).body + + fastify.log.info(`Attempting to unregister coordinator: ${phoneNumber}`) + + // Check if coordinator exists + const coordinator = fastify.db.findCoordinatorByPhone(phoneNumber) + if (!coordinator) { + fastify.log.warn(`No coordinator found for phone: ${phoneNumber}`) + throw errors.notFoundError('Coordinator not found') + } + + // Delete coordinator + fastify.db.deleteCoordinatorByPhone(phoneNumber) + fastify.log.info(`Successfully unregistered coordinator: ${phoneNumber}`) + + return { + data: { + message: 'Coordinator successfully unregistered', + }, + } + }, + ) // POST /auth/coordinator fastify.post( '/auth/coordinator', diff --git a/test/test_auth_script.sh b/test/test_auth_script.sh index 3b7a514..2f16f33 100755 --- a/test/test_auth_script.sh +++ b/test/test_auth_script.sh @@ -27,8 +27,39 @@ echo "๐Ÿงช Starting AUTH API Tests..." echo "========================" echo -# Test POST /auth/register with existing number (should fail) -echo "POST /auth/register with existing number" +# Test POST /auth/register with new random number +echo "POST /auth/register with new number" +echo "----------------------------------------" +RESPONSE=$(curl -s -f -X POST \ + "${HOST}/auth/register" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${BEARER_TOKEN}" \ + -d '{ + "phoneNumber": "'"${RANDOM_PHONE}"'", + "projectName": "'"${PROJECT_NAME} 1"'" + }') +echo "Response: ${RESPONSE}" +echo "โœ… Passed" +echo + +# Test POST /auth/register with same random number +echo "POST /auth/register with same number" +echo "----------------------------------------" +RESPONSE=$(curl -s -f -X POST \ + "${HOST}/auth/register" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${BEARER_TOKEN}" \ + -d '{ + "phoneNumber": "'"${RANDOM_PHONE}"'", + "projectName": "'"${PROJECT_NAME}"'" + }') +echo "Response: ${RESPONSE}" +echo "โœ… Passed" +echo + + +# Test POST /auth/register with existing project name (should fail) +echo "POST /auth/register with existing project name" echo "----------------------------------------" if curl -s -f -X POST \ "${HOST}/auth/register" \ @@ -45,8 +76,39 @@ else fi echo -# Test POST /auth/register with new random number -echo "POST /auth/register with new number" +# Test DELETE /auth/unregister with invalid phone number +echo "DELETE /auth/unregister with invalid phone" +echo "----------------------------------------" +if curl -s -f -X DELETE \ + "${HOST}/auth/unregister" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${BEARER_TOKEN}" \ + -d '{ + "phoneNumber": "+999999999" + }' 2>/dev/null; then + echo "โŒ Failed: Request should have failed for non-existent number" + exit 1 +else + echo "โœ… Passed: Request failed as expected for non-existent number" +fi +echo + +# Test DELETE /auth/unregister with valid phone number +echo "DELETE /auth/unregister with valid phone" +echo "----------------------------------------" +RESPONSE=$(curl -s -f -X DELETE \ + "${HOST}/auth/unregister" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${BEARER_TOKEN}" \ + -d '{ + "phoneNumber": "'"${RANDOM_PHONE}"'" + }') +echo "Response: ${RESPONSE}" +echo "โœ… Passed" +echo + +# Verify coordinator was deleted by trying to register with same number again +echo "POST /auth/register to verify coordinator was deleted" echo "----------------------------------------" RESPONSE=$(curl -s -f -X POST \ "${HOST}/auth/register" \ @@ -57,7 +119,7 @@ RESPONSE=$(curl -s -f -X POST \ "projectName": "'"${PROJECT_NAME}"'" }') echo "Response: ${RESPONSE}" -echo "โœ… Passed" +echo "โœ… Passed: Successfully re-registered deleted coordinator" echo # Create project for the registered coordinator @@ -94,6 +156,7 @@ if ! echo "${RESPONSE}" | jq -e ".data[] | select(.name == \"${PROJECT_NAME}\")" fi echo "โœ… Passed: Project exists" echo + # Test POST /auth/coordinator with new number echo "POST /auth/coordinator" echo "------------------------------------------"