From 747ef9a4e7b0fae2648fbec4f6a59c9ff3b91924 Mon Sep 17 00:00:00 2001 From: Tay Yi Hsuen Date: Thu, 19 Oct 2023 22:26:14 +0800 Subject: [PATCH] Fix OR operator typo and increase number of pods (#148) Co-authored-by: Ong Jun Xiong Co-authored-by: Charisma Kausar Co-authored-by: Charisma Kausar <68203159+ckcherry23@users.noreply.github.com> Co-authored-by: Gabriel Goh <77230723+gycgabriel@users.noreply.github.com> Co-authored-by: chunweii <47494777+chunweii@users.noreply.github.com> --- .../Dockerfile.collaboration-service | 1 + .../dev-dockerfiles/Dockerfile.frontend | 1 + .../admin-service-deployment.yaml | 2 +- .../collaboration-service-deployment.yaml | 2 +- .../frontend-deployment.yaml | 2 +- .../gateway-deployment.yaml | 2 +- .../matching-service-deployment.yaml | 2 +- .../question-service-deployment.yaml | 2 +- .../user-service-deployment.yaml | 2 +- .../Dockerfile.collaboration-service-prod | 1 + .../prod-dockerfiles/Dockerfile.frontend-prod | 1 + docker-compose.yml | 3 + frontend/package.json | 1 + frontend/src/components/room/description.tsx | 28 +-- frontend/src/components/room/video-room.tsx | 161 ++++++++++++++++++ .../src/firebase-client/gateway-address.ts | 2 +- frontend/src/hooks/useCollaboration.tsx | 27 ++- frontend/src/pages/interviews/index.tsx | 2 +- frontend/src/pages/room/[id].tsx | 12 +- services/collaboration-service/README.md | 2 +- services/collaboration-service/package.json | 1 + .../collaboration-service/src/routes/room.ts | 22 +++ services/user-service/src/routes/index.ts | 2 +- services/user-service/systemtest/app.test.ts | 6 +- .../user-service/test/routes/index.test.ts | 2 +- yarn.lock | 81 ++++++++- 26 files changed, 316 insertions(+), 54 deletions(-) create mode 100644 frontend/src/components/room/video-room.tsx diff --git a/deployment/dev-dockerfiles/Dockerfile.collaboration-service b/deployment/dev-dockerfiles/Dockerfile.collaboration-service index 84a1643f..828e7f04 100644 --- a/deployment/dev-dockerfiles/Dockerfile.collaboration-service +++ b/deployment/dev-dockerfiles/Dockerfile.collaboration-service @@ -10,6 +10,7 @@ WORKDIR /app/services/collaboration-service # Copy the entire services directory and prisma COPY services/collaboration-service /app/services/collaboration-service COPY prisma ./prisma/ +COPY utils /app/utils/ # Install all dependencies using Yarn Workspaces RUN yarn install --frozen-lockfile --cwd /app diff --git a/deployment/dev-dockerfiles/Dockerfile.frontend b/deployment/dev-dockerfiles/Dockerfile.frontend index 4110297e..38eb9aeb 100644 --- a/deployment/dev-dockerfiles/Dockerfile.frontend +++ b/deployment/dev-dockerfiles/Dockerfile.frontend @@ -10,6 +10,7 @@ WORKDIR /app/frontend # Copy the entire frontend directory and prisma COPY frontend /app/frontend COPY prisma ./prisma/ +COPY utils /app/utils/ # Install all dependencies using Yarn Workspaces RUN yarn install --frozen-lockfile --cwd /app diff --git a/deployment/gke-prod-manifests/admin-service-deployment.yaml b/deployment/gke-prod-manifests/admin-service-deployment.yaml index bf09dd8c..01abb115 100644 --- a/deployment/gke-prod-manifests/admin-service-deployment.yaml +++ b/deployment/gke-prod-manifests/admin-service-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: admin-service namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: admin-service diff --git a/deployment/gke-prod-manifests/collaboration-service-deployment.yaml b/deployment/gke-prod-manifests/collaboration-service-deployment.yaml index 622950fa..a7873219 100644 --- a/deployment/gke-prod-manifests/collaboration-service-deployment.yaml +++ b/deployment/gke-prod-manifests/collaboration-service-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: collaboration-service namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: collaboration-service diff --git a/deployment/gke-prod-manifests/frontend-deployment.yaml b/deployment/gke-prod-manifests/frontend-deployment.yaml index 323aa8d1..9f975bc7 100644 --- a/deployment/gke-prod-manifests/frontend-deployment.yaml +++ b/deployment/gke-prod-manifests/frontend-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: frontend namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: frontend diff --git a/deployment/gke-prod-manifests/gateway-deployment.yaml b/deployment/gke-prod-manifests/gateway-deployment.yaml index 176e0e84..198f3bb0 100644 --- a/deployment/gke-prod-manifests/gateway-deployment.yaml +++ b/deployment/gke-prod-manifests/gateway-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: gateway namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: gateway diff --git a/deployment/gke-prod-manifests/matching-service-deployment.yaml b/deployment/gke-prod-manifests/matching-service-deployment.yaml index 6e03fc6c..b8645b4b 100644 --- a/deployment/gke-prod-manifests/matching-service-deployment.yaml +++ b/deployment/gke-prod-manifests/matching-service-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: matching-service namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: matching-service diff --git a/deployment/gke-prod-manifests/question-service-deployment.yaml b/deployment/gke-prod-manifests/question-service-deployment.yaml index cf50d672..610cf612 100644 --- a/deployment/gke-prod-manifests/question-service-deployment.yaml +++ b/deployment/gke-prod-manifests/question-service-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: question-service namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: question-service diff --git a/deployment/gke-prod-manifests/user-service-deployment.yaml b/deployment/gke-prod-manifests/user-service-deployment.yaml index b7687f6c..3bfb041a 100644 --- a/deployment/gke-prod-manifests/user-service-deployment.yaml +++ b/deployment/gke-prod-manifests/user-service-deployment.yaml @@ -6,7 +6,7 @@ metadata: name: user-service namespace: default spec: - replicas: 1 + replicas: 3 selector: matchLabels: io.kompose.service: user-service diff --git a/deployment/prod-dockerfiles/Dockerfile.collaboration-service-prod b/deployment/prod-dockerfiles/Dockerfile.collaboration-service-prod index 4c3a5a71..85457b6e 100644 --- a/deployment/prod-dockerfiles/Dockerfile.collaboration-service-prod +++ b/deployment/prod-dockerfiles/Dockerfile.collaboration-service-prod @@ -10,6 +10,7 @@ WORKDIR /app/services/collaboration-service # Copy the entire services directory and prisma COPY services/collaboration-service /app/services/collaboration-service COPY prisma ./prisma/ +COPY utils /app/utils/ # Install all dependencies using Yarn Workspaces RUN yarn install --frozen-lockfile --cwd /app diff --git a/deployment/prod-dockerfiles/Dockerfile.frontend-prod b/deployment/prod-dockerfiles/Dockerfile.frontend-prod index c117eb25..8bea719f 100644 --- a/deployment/prod-dockerfiles/Dockerfile.frontend-prod +++ b/deployment/prod-dockerfiles/Dockerfile.frontend-prod @@ -10,6 +10,7 @@ WORKDIR /app/frontend # Copy the entire frontend directory and prisma COPY frontend /app/frontend COPY prisma ./prisma/ +COPY utils /app/utils/ # Install all dependencies using Yarn Workspaces RUN yarn install --frozen-lockfile --cwd /app diff --git a/docker-compose.yml b/docker-compose.yml index e36044db..31cbd11e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,9 @@ services: - "5003:5003" environment: PORT: 5003 + TWILIO_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID} + TWILIO_API_KEY: ${TWILIO_API_KEY} + TWILIO_API_SECRET: ${TWILIO_API_SECRET} PRISMA_DATABASE_URL: ${PRISMA_DATABASE_URL} question-service: diff --git a/frontend/package.json b/frontend/package.json index b7074c4f..2b10715b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,6 +52,7 @@ "tailwind-merge": "^1.14.0", "tailwindcss": "3.3.3", "tailwindcss-animate": "^1.0.7", + "twilio-video": "^2.28.1", "typescript": "5.2.2", "zod": "^3.22.4" }, diff --git a/frontend/src/components/room/description.tsx b/frontend/src/components/room/description.tsx index 2e45629b..6f5f625f 100644 --- a/frontend/src/components/room/description.tsx +++ b/frontend/src/components/room/description.tsx @@ -1,9 +1,10 @@ +import { Room } from "twilio-video"; import { Question } from "../../types/QuestionTypes"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; import { Card } from "../ui/card"; import { TypographyH2, TypographySmall } from "../ui/typography"; -import { Video, Mic } from "lucide-react"; +import VideoRoom from "./video-room"; // todo change this @@ -11,12 +12,12 @@ type DescriptionProps = { question: Question; className?: string; participants?: string[]; + room: Room | null; }; export default function Description({ question, className, - participants, }: DescriptionProps) { return ( @@ -43,29 +44,6 @@ export default function Description({
- {/*
- {participants?.map((participant) => ( -
-
-
-
-

{participant}

-
- - -
-
-
-
- ))} -
*/}
); } diff --git a/frontend/src/components/room/video-room.tsx b/frontend/src/components/room/video-room.tsx new file mode 100644 index 00000000..ca42997a --- /dev/null +++ b/frontend/src/components/room/video-room.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { LocalParticipant, LocalVideoTrack, Participant, RemoteParticipant, RemoteAudioTrack, RemoteVideoTrack, Room, Track } from 'twilio-video'; +import { Button } from '../ui/button'; +import { Mic, MicOff, Video, VideoOff } from 'lucide-react'; + +interface VideoRoomProps { + room: Room | null; + className?: string; +} + +function SingleVideoTrack({ track, userId, isLocal, isMute, toggleMute, isCameraOn, toggleCamera }: + { + track: RemoteVideoTrack | LocalVideoTrack, userId: string, isLocal: boolean, + isMute: boolean, toggleMute: () => void, + isCameraOn: boolean, toggleCamera: () => void + }) { + const videoContainer = useRef(null); + useEffect(() => { + const videoElement = track.attach(); + videoElement.classList.add("w-full", "h-full", "items-center", "justify-center", "flex"); + videoContainer.current?.appendChild(videoElement); + return () => { + track.detach().forEach(element => element.remove()); + videoElement.remove(); + }; + }, [isLocal, track]); + return (
+
+
+
+

{userId}

+ {isLocal ?
+ + +
: null} +
+
+
); +} + +function SingleAudioTrack({track}: {track: RemoteAudioTrack}) { + const audioContainer = useRef(null); + useEffect(() => { + const audioElement = track.attach(); + audioContainer.current?.appendChild(audioElement); + return () => { + track.detach().forEach(element => element.remove()); + audioElement.remove(); + }; + }, [track]); + return (
); +} + +const VideoRoom: React.FC = ({ room, className }) => { + const [isCameraOn, setIsCameraOn] = useState(true); + const [isMute, setIsMute] = useState(false); + const [participants, setParticipants] = useState([]); + const [localParticipant, setLocalParticipant] = useState(null); + + + const handleNewParticipant = (participant: RemoteParticipant) => { + + participant.on('trackSubscribed', track => { + setParticipants(p => [...p]) + }); + + participant.on('trackUnsubscribed', track => { + setParticipants(p => [...p]) + }); + }; + + const participantConnected = (participant: RemoteParticipant) => { + console.log('Participant "%s" connected,', participant.identity); + + setParticipants(participants => [...participants, participant]); + + handleNewParticipant(participant); + }; + + const participantDisconnected = (participant: RemoteParticipant) => { + console.log('Participant "%s" disconnected', participant.identity); + participant.removeAllListeners(); + setParticipants(participants.filter(p => p.identity !== participant.identity)); + }; + + const toggleCamera = () => { + room?.localParticipant.videoTracks.forEach(publication => { + if (publication.track) { + publication.track.enable(!isCameraOn); + setIsCameraOn(!isCameraOn); + } + }); + }; + + const toggleMute = () => { + room?.localParticipant.audioTracks.forEach(publication => { + if (publication.track) { + publication.track.enable(isMute); + setIsMute(!isMute); + } + }); + }; + + useEffect(() => { + if (!room) return; + + room.on('participantConnected', participantConnected); + room.on('participantDisconnected', participantDisconnected); + room.once('disconnected', error => room.participants.forEach(participantDisconnected)); + + setLocalParticipant(room.localParticipant); + + room.participants.forEach(handleNewParticipant); + + setParticipants(Array.from(room.participants.values())); + + return () => { + room.disconnect(); + }; + }, [room]); + + return ( +
+
+ {localParticipant ? Array.from(localParticipant.videoTracks.values()).map(publication => { + if (publication.track.kind === 'video') { + return ; + } else { return null; } + }) : null} + {participants.flatMap(participant => { + return Array.from(participant.videoTracks.values()).map(publication => { + if (publication.track?.kind === 'video') { + return ; + } else { + return null; + } + }); + })} + {participants.flatMap(participant => { + return Array.from(participant.audioTracks.values()).map(audioPublication => { + if (audioPublication.track?.kind === 'audio') { + return ; + } else { + return null; + } + }); + })} +
+ ); +}; + +export default VideoRoom; diff --git a/frontend/src/firebase-client/gateway-address.ts b/frontend/src/firebase-client/gateway-address.ts index 7a614192..381bc240 100644 --- a/frontend/src/firebase-client/gateway-address.ts +++ b/frontend/src/firebase-client/gateway-address.ts @@ -5,7 +5,7 @@ * - Leave GATEWAY_ADDRESS empty for dev environments * - For prod, pass in a separate address to GATEWAY_ADDRESS */ -const gatewayAddress = process.env.GATEWAY_ADDRESS ?? "http://localhost:4000/" +const gatewayAddress = process.env.GATEWAY_ADDRESS || "http://localhost:4000/" export const userApiPathAddress = gatewayAddress + "api/user-service/"; export const questionApiPathAddress = gatewayAddress + "api/question-service/"; diff --git a/frontend/src/hooks/useCollaboration.tsx b/frontend/src/hooks/useCollaboration.tsx index 4955c474..eca5cead 100644 --- a/frontend/src/hooks/useCollaboration.tsx +++ b/frontend/src/hooks/useCollaboration.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useRef } from "react"; +import { useEffect, useState, useRef, use } from "react"; import { io, Socket } from "socket.io-client"; import { debounce } from "lodash"; import { @@ -6,10 +6,12 @@ import { createTextOpFromTexts, } from "../../../utils/shared-ot"; import { TextOp } from "ot-text-unicode"; +import { Room, connect } from "twilio-video"; type UseCollaborationProps = { roomId: string; userId: string; + disableVideo?: boolean; }; enum SocketEvents { @@ -22,18 +24,20 @@ enum SocketEvents { var vers = 0; -const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => { +const useCollaboration = ({ roomId, userId, disableVideo }: UseCollaborationProps) => { const [socket, setSocket] = useState(null); const [text, setText] = useState("#Write your solution here"); const [cursor, setCursor] = useState( "#Write your solution here".length ); + const [room, setRoom] = useState(null); // twilio room const textRef = useRef(text); const cursorRef = useRef(cursor); const prevCursorRef = useRef(cursor); const prevTextRef = useRef(text); const awaitingAck = useRef(false); // ack from sending update const awaitingSync = useRef(false); // synced with server + const twilioTokenRef = useRef(""); const questionId = "1"; useEffect(() => { @@ -43,6 +47,20 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => { socketConnection.emit(SocketEvents.ROOM_JOIN, roomId, userId); socketConnection.emit(SocketEvents.QUESTION_SET, questionId); + socketConnection.on("twilio-token", (token: string) => { + twilioTokenRef.current = token; + if (disableVideo) return; + connect(token, { + name: roomId, audio: true, + video: { width: 640, height: 480, frameRate: 24 } + }).then((room) => { + console.log("Connected to Room"); + setRoom(room); + }).catch(err => { + console.log(err, token, userId, roomId); + }); + }); + socketConnection.on( SocketEvents.ROOM_UPDATE, ({ @@ -83,6 +101,9 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => { return () => { socketConnection.disconnect(); + if (room) { + room.disconnect(); + } }; }, [roomId, userId]); @@ -126,7 +147,7 @@ const useCollaboration = ({ roomId, userId }: UseCollaborationProps) => { }); }, [text, socket]); - return { text, setText, cursor, setCursor }; + return { text, setText, cursor, setCursor, room }; }; export default useCollaboration; diff --git a/frontend/src/pages/interviews/index.tsx b/frontend/src/pages/interviews/index.tsx index 7e12f6bd..a9cbdea0 100644 --- a/frontend/src/pages/interviews/index.tsx +++ b/frontend/src/pages/interviews/index.tsx @@ -59,7 +59,7 @@ export default function Interviews() { const onClickSearch = () => { try { - joinQueue([difficulty], "python"); + joinQueue([difficulty], "python"); // TODO: update with actual language console.log("Joined queue"); router.push("/interviews/find-match"); } catch (error) { diff --git a/frontend/src/pages/room/[id].tsx b/frontend/src/pages/room/[id].tsx index 3db0d4be..724fb55a 100644 --- a/frontend/src/pages/room/[id].tsx +++ b/frontend/src/pages/room/[id].tsx @@ -4,17 +4,20 @@ import useCollaboration from "@/hooks/useCollaboration"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { TypographyBody } from "@/components/ui/typography"; import { useRouter } from "next/router"; +import VideoRoom from "../../components/room/video-room"; import { Question } from "../../types/QuestionTypes"; export default function Room() { const router = useRouter(); const roomId = router.query.id as string; - const userId = "user1"; + const userId = router.query.userId as string || "user1"; + const disableVideo = (router.query.disableVideo as string)?.toLowerCase() === "true"; - const { text, setText, cursor, setCursor } = useCollaboration({ - roomId: roomId, + const { text, setText, cursor, setCursor, room } = useCollaboration({ + roomId: roomId as string, userId, + disableVideo, }); const question: Question = { @@ -46,7 +49,7 @@ export default function Room() { {question.solution} @@ -60,6 +63,7 @@ export default function Room() { /> + ); } diff --git a/services/collaboration-service/README.md b/services/collaboration-service/README.md index 837ba69a..2cd1725e 100644 --- a/services/collaboration-service/README.md +++ b/services/collaboration-service/README.md @@ -18,7 +18,7 @@ WebSocket Events (refer to `routes/room.ts#initSocketListeners`): socket.disconnect() ``` -- /room/join - Join a room, same room_id gets connected together. user_id if get details of room +- /room/join - Join a room and get twilio video access token, same room_id gets connected together. user_id if get details of room. - /room/update - Update the room after text change - /room/save - Save current text - /room/load - Load previously saved text (calls /room/update after retrieving text from db) diff --git a/services/collaboration-service/package.json b/services/collaboration-service/package.json index a80827a7..6a839625 100644 --- a/services/collaboration-service/package.json +++ b/services/collaboration-service/package.json @@ -26,6 +26,7 @@ "swagger-autogen": "^2.23.5", "swagger-express-ts": "^1.1.0", "swagger-ui-express": "^5.0.0", + "twilio": "^4.18.1", "typescript": "^5.2.2", "uuid": "^9.0.1" }, diff --git a/services/collaboration-service/src/routes/room.ts b/services/collaboration-service/src/routes/room.ts index e9c4fc74..015956a8 100644 --- a/services/collaboration-service/src/routes/room.ts +++ b/services/collaboration-service/src/routes/room.ts @@ -1,6 +1,7 @@ import express, { Request, Response } from "express"; import { type } from "ot-text-unicode"; import { Socket, Server } from "socket.io"; + import { Room } from "@prisma/client"; import { createOrUpdateRoomWithUser, @@ -40,6 +41,14 @@ enum SocketEvents { const socketMap: Record = {}; const opMap: OpHistoryMap = new OpHistoryMap(); + +const AccessToken = require("twilio").jwt.AccessToken; +const VideoGrant = AccessToken.VideoGrant; + +const TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID; +const TWILIO_API_KEY = process.env.TWILIO_API_KEY; +const TWILIO_API_SECRET = process.env.TWILIO_API_SECRET; + // Data Access Layer function mapSocketToRoomAndUser( socket_id: string, @@ -205,6 +214,18 @@ function initSocketListeners(io: Server, socket: Socket, room_id: string) { }); } +function getTwilioAccessToken(room_id: string, user_id: string): string { + const videoGrant = new VideoGrant({ room: room_id }); + const token = new AccessToken( + TWILIO_ACCOUNT_SID, + TWILIO_API_KEY, + TWILIO_API_SECRET, + { identity: user_id, ttl: 60*60*12 } + ); + token.addGrant(videoGrant); + return token.toJwt(); +} + export const roomRouter = (io: Server) => { const router = express.Router(); @@ -255,6 +276,7 @@ export const roomRouter = (io: Server) => { createOrUpdateRoomWithUser(room_id, user_id); mapSocketToRoomAndUser(socket.id, room_id, user_id); roomUpdateWithTextFromDb(io, socket, room_id); + socket.emit("twilio-token", getTwilioAccessToken(room_id, user_id)) initSocketListeners(io, socket, room_id); }); diff --git a/services/user-service/src/routes/index.ts b/services/user-service/src/routes/index.ts index f649e1c6..360b2644 100644 --- a/services/user-service/src/routes/index.ts +++ b/services/user-service/src/routes/index.ts @@ -8,7 +8,7 @@ indexRouter.post("/", function (req: express.Request, res: express.Response) { .createUser(req.body) .then((result) => { if (result === null) { - res.status(400).append("Is-User-Already-Found", "true").end(); + res.status(200).append("Is-User-Already-Found", "true").end(); } else { res.status(201).json(result); } diff --git a/services/user-service/systemtest/app.test.ts b/services/user-service/systemtest/app.test.ts index 8f97b7e6..3c5069d0 100644 --- a/services/user-service/systemtest/app.test.ts +++ b/services/user-service/systemtest/app.test.ts @@ -14,7 +14,7 @@ const updatePayload = { matchDifficulty: 1 }; describe('/index', () => { describe('Sample App Workflow', () => { - it('Step 1: Add user 1 to database should pass', async () => { + it('Step 1: Add user 1 to database should pass with status 201', async () => { // The function being tested const response = await request(app).post('/api/user-service').send(fullNewUser); expect(response.status).toStrictEqual(201); @@ -42,9 +42,9 @@ describe('/index', () => { expect(response.body).toStrictEqual(updatedNewUser); }) - it('Step 5: Attempt to add duplicate user 1 to database should fail with error', async () => { + it('Step 5: Attempt to add duplicate user 1 to database should give status 200', async () => { const response = await request(app).post('/api/user-service').send(fullNewUser); - expect(response.status).toStrictEqual(400); + expect(response.status).toStrictEqual(200); }) it('Step 6: Delete user 1 from database', async () => { diff --git a/services/user-service/test/routes/index.test.ts b/services/user-service/test/routes/index.test.ts index a34e7017..8b3bfe06 100644 --- a/services/user-service/test/routes/index.test.ts +++ b/services/user-service/test/routes/index.test.ts @@ -42,7 +42,7 @@ describe('/index', () => { // The function being tested const response = await request(app).post('/').send(fullNewUser); - expect(response.status).toStrictEqual(400); + expect(response.status).toStrictEqual(200); }) it('[POST] to / when database is unavailable', async () => { diff --git a/yarn.lock b/yarn.lock index a4181822..1e5db615 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3825,6 +3825,13 @@ axe-core@^4.6.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae" integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== +axios@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + axobject-query@^3.1.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -4807,6 +4814,11 @@ data-uri-to-buffer@^6.0.0: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz#540bd4c8753a25ee129035aebdedf63b078703c7" integrity sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg== +dayjs@^1.11.9: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@~2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -5806,6 +5818,11 @@ events-listener@^1.1.0: resolved "https://registry.yarnpkg.com/events-listener/-/events-listener-1.1.0.tgz#dd49b4628480eba58fde31b870ee346b3990b349" integrity sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + exegesis-express@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/exegesis-express/-/exegesis-express-4.0.0.tgz#f5f8486f6f0d81739e8e27ce75ce0f61ba3f3578" @@ -6309,7 +6326,7 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.14.8: version "1.15.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== @@ -7395,7 +7412,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-generator-function@^1.0.10: +is-generator-function@^1.0.10, is-generator-function@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== @@ -7532,7 +7549,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -9837,7 +9854,7 @@ qs@6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -qs@^6.11.0, qs@^6.6.0: +qs@^6.11.0, qs@^6.6.0, qs@^6.9.4: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== @@ -10434,6 +10451,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +scmp@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.1.0.tgz#37b8e197c425bdeb570ab91cc356b311a11f9c9a" + integrity sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q== + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -11571,6 +11593,30 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== +twilio-video@^2.28.1: + version "2.28.1" + resolved "https://registry.yarnpkg.com/twilio-video/-/twilio-video-2.28.1.tgz#e955c4d077c147bb59d3eb88cecca3518489ff21" + integrity sha512-q87gLqwPfpfAchLa1KIL58MBMxOZwUFPfZUYBoMH9P2kSG0+42Y+eKCJ192u7eFit7iDY32nzF/TkYBxXAcvIA== + dependencies: + events "^3.3.0" + util "^0.12.4" + ws "^7.4.6" + xmlhttprequest "^1.8.0" + +twilio@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/twilio/-/twilio-4.18.1.tgz#465bdde32127fe0040659b0ab4049bf6b52b4f47" + integrity sha512-cz9jfz7uGCYBIkJ+WYrLhffQnOk+oluLBTdCTHQEG6TI4agSWWA4WaYVBV5ImoICrTbY0vNmThw8pmgWfYdAiw== + dependencies: + axios "^0.26.1" + dayjs "^1.11.9" + https-proxy-agent "^5.0.0" + jsonwebtoken "^9.0.0" + qs "^6.9.4" + scmp "^2.1.0" + url-parse "^1.5.9" + xmlbuilder "^13.0.2" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -11864,7 +11910,7 @@ url-join@0.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8" integrity sha512-H6dnQ/yPAAVzMQRvEvyz01hhfQL5qRWSEt7BX8t9DqnPw9BjMb64fjIRq76Uvf1hkHp+mTZvEVJ5guXOT0Xqaw== -url-parse@^1.4.7: +url-parse@^1.4.7, url-parse@^1.5.9: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== @@ -11897,6 +11943,17 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +util@^0.12.4: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -12110,7 +12167,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.11, which-typed-array@^1.1.9: +which-typed-array@^1.1.11, which-typed-array@^1.1.2, which-typed-array@^1.1.9: version "1.1.11" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== @@ -12248,7 +12305,7 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.2.3: +ws@^7.2.3, ws@^7.4.6: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== @@ -12263,6 +12320,11 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xmlbuilder@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7" + integrity sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ== + xmlcreate@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be" @@ -12273,6 +12335,11 @@ xmlhttprequest-ssl@~2.0.0: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== +xmlhttprequest@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"