diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 6948d1bce2..3ebf43d7f0 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -129,6 +129,19 @@ jobs: MONGO_DB_URL: mongodb://localhost:27017/talawa-test-db REDIS_HOST: localhost REDIS_PORT: 6379 + RECAPTCHA_SITE_KEY: ${{secrets.RECAPTCHA_SITE_KEY}} + RECAPTCHA_SECRET_KEY: ${{secrets.RECAPTCHA_SECRET_KEY}} + MAIL_USERNAME: ${{secrets.MAIL_USERNAME}} + MAIL_PASSWORD: ${{secrets.MAIL_PASSWORD}} + IS_SMTP: "" + SMTP_HOST: "" + SMTP_PASSWORD: "" + SMTP_USERNAME: "" + LAST_RESORT_SUPERADMIN_EMAIL: "abc@gmail.com" + COLORIZE_LOGS: "true" + LOG_LEVEL: "info" + # ACCESS_TOKEN_SECRET: ${{ secrets.ACCESS_TOKEN_SECRET }} + # REFRESH_TOKEN_SECRET: ${{ secrets.REFRESH_TOKEN_SECRET }} steps: - name: Checkout repository @@ -153,6 +166,29 @@ jobs: - name: Run the tests run: npm run test + + - name: Start the development server + run: | + npm run dev & + echo "Development server started..." + + - name: Check server status + run: | + if curl -f 'http://localhost:4000' | jq -e '. == {"talawa-version":"v1","status":"healthy"}' > /dev/null; then + echo "Server is up and healthy" + else + echo "Server is down" + exit 1 + fi + - name: Check Apollo Server status + run: | + if curl -f 'http://localhost:4000/graphql?query=%7B__typename%7D' \ + -H 'Apollo-Require-Preflight: true' | jq -e '. == {"data":{"__typename":"Query"}}' > /dev/null; then + echo "Apollo Server is up" + else + echo "Server is down" + exit 1 + fi - name: Import Sample Data run: npm run import:sample-data @@ -170,7 +206,7 @@ jobs: with: path: './coverage/lcov.info' min_coverage: 95.0 - + JSDocs: name: 'JSDocs comments and pipeline' runs-on: ubuntu-latest diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index d7f3c69d79..d87f13334f 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -96,12 +96,13 @@ jobs: Generate-Documentation: name: Generate Documentation runs-on: ubuntu-latest - if: github.ref == 'refs/heads/develop' + if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/automated-docs' needs: Push-Workflow steps: - name: Checkout repository uses: actions/checkout@v3 - + # with: + # persist-credentials: false - name: Generate Documentation of HTML pages run: | npm install --global typedoc @@ -142,31 +143,50 @@ jobs: git push -f https://$GH_TOKEN@github.com/PalisadoesFoundation/talawa-api.git HEAD:automated-docs echo -e "🚀${Green} Hurrah! doc updated${NoColor}" env: - ACCESS_TOKEN: ${{ secrets.GH_TOKEN }} + ACCESS_TOKEN: ${{secrets.GH_TOKEN}} - name: Create Documentation Artifact uses: actions/upload-artifact@v2 with: name: documentation-api path: talawa-api-docs + + Empty-Commit: + name: Create Empty Commit + runs-on: ubuntu-latest + needs: Generate-Documentation + if: github.ref == 'refs/heads/develop' + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + persist-credentials: false + token: ${{ secrets.TALAWA_DOCS_SYNC }} + - name: Empty Commit + run: | + git config --global user.name "${{github.actor}}" + git config --global user.email "${{env.email}}" + git config --global url.https://${{ secrets.TALAWA_DOCS_SYNC }}@github.com/.insteadOf https://github.com/ + git commit --allow-empty -m "Trigger Documentation Workflow" + git push origin develop:automated-docs --force - # Copy-docs-to-talawa-docs: - # runs-on: ubuntu-latest - # if: github.ref == 'refs/heads/automated-docs' - # # needs: Generate-Documentation - # steps: - # - uses: actions/checkout@v3 - # - uses: dmnemec/copy_file_to_another_repo_action@v1.1.1 - # env: - # API_TOKEN_GITHUB: ${{secrets.TALAWA_DOCS_SYNC}} - # with: - # source_file: 'talawa-api-docs/' - # destination_repo: 'PalisadoesFoundation/talawa-docs' - # destination_branch: 'develop' - # destination_folder: 'docs/' - # user_email: '${{env.email}}' - # user_name: '${{github.actor}}' - # commit_message: 'Talawa API docs updated' + Copy-docs-to-talawa-docs: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/automated-docs' && contains(github.event.head_commit.message, 'Trigger Documentation Workflow') + needs : Generate-Documentation + steps: + - uses: actions/checkout@v2 + - uses: dmnemec/copy_file_to_another_repo_action@main + env: + API_TOKEN_GITHUB: ${{secrets.TALAWA_DOCS_SYNC}} + with: + source_file: 'talawa-api-docs/' + destination_repo: 'PalisadoesFoundation/talawa-docs' + destination_branch: 'develop' + destination_folder: 'docs/' + user_email: '${{env.email}}' + user_name: '${{github.actor}}' + commit_message: 'Talawa API docs updated' # You can find the deployment instructions in the scripts/cloud-api-demo/README.md file Deploy-Workflow: diff --git a/package-lock.json b/package-lock.json index 1b7c5f25a0..102a88de50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5734,9 +5734,9 @@ } }, "node_modules/@redis/client": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.13.tgz", - "integrity": "sha512-epkUM9D0Sdmt93/8Ozk43PNjLi36RZzG+d/T1Gdu5AI8jvghonTeLYV69WVWdilvFo+PYxbP0TZ0saMvr6nscQ==", + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", + "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -14873,12 +14873,12 @@ } }, "node_modules/redis": { - "version": "4.6.12", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.12.tgz", - "integrity": "sha512-41Xuuko6P4uH4VPe5nE3BqXHB7a9lkFL0J29AlxKaIfD6eWO8VO/5PDF9ad2oS+mswMsfFxaM5DlE3tnXT+P8Q==", + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz", + "integrity": "sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==", "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.13", + "@redis/client": "1.5.14", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", @@ -21446,9 +21446,9 @@ "requires": {} }, "@redis/client": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.13.tgz", - "integrity": "sha512-epkUM9D0Sdmt93/8Ozk43PNjLi36RZzG+d/T1Gdu5AI8jvghonTeLYV69WVWdilvFo+PYxbP0TZ0saMvr6nscQ==", + "version": "1.5.14", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.14.tgz", + "integrity": "sha512-YGn0GqsRBFUQxklhY7v562VMOP0DcmlrHHs3IV1mFE3cbxe31IITUkqhBcIhVSI/2JqtWAJXg5mjV4aU+zD0HA==", "requires": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -28178,12 +28178,12 @@ } }, "redis": { - "version": "4.6.12", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.12.tgz", - "integrity": "sha512-41Xuuko6P4uH4VPe5nE3BqXHB7a9lkFL0J29AlxKaIfD6eWO8VO/5PDF9ad2oS+mswMsfFxaM5DlE3tnXT+P8Q==", + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.13.tgz", + "integrity": "sha512-MHgkS4B+sPjCXpf+HfdetBwbRz6vCtsceTmw1pHNYJAsYxrfpOP6dz+piJWGos8wqG7qb3vj/Rrc5qOlmInUuA==", "requires": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.13", + "@redis/client": "1.5.14", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", diff --git a/src/models/ActionItemCategory.ts b/src/models/ActionItemCategory.ts index 3660085c9c..314d845511 100644 --- a/src/models/ActionItemCategory.ts +++ b/src/models/ActionItemCategory.ts @@ -4,7 +4,7 @@ import type { InterfaceUser } from "./User"; import type { InterfaceOrganization } from "./Organization"; /** - * This is an interface that represents a database(MongoDB) document for ActionItemCategory. + * This is an interface that represents a database(MongoDB) document for ActionItemCategory (~Test Check). */ export interface InterfaceActionItemCategory { diff --git a/src/models/AgendaCategory.ts b/src/models/AgendaCategory.ts index 1375141508..2ae6f9f37d 100644 --- a/src/models/AgendaCategory.ts +++ b/src/models/AgendaCategory.ts @@ -15,7 +15,7 @@ export interface InterfaceAgendaCategory { } /** - * This is the Mongoose schema for an agenda category. + * This is the Mongoose schema for an agenda category (test-change). * @param name - Name of the agenda category. * @param description - Optional description of the agenda category. * @param organization - Reference to the organization associated with the agenda category. diff --git a/src/resolvers/Mutation/forgotPassword.ts b/src/resolvers/Mutation/forgotPassword.ts index 513dc1f3bc..9b2891661f 100644 --- a/src/resolvers/Mutation/forgotPassword.ts +++ b/src/resolvers/Mutation/forgotPassword.ts @@ -1,8 +1,14 @@ import bcrypt from "bcryptjs"; import { jwtDecode } from "jwt-decode"; -import { INVALID_OTP } from "../../constants"; -import { User } from "../../models"; import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { User } from "../../models"; +import { + ACCESS_TOKEN_SECRET, + INVALID_OTP, + USER_NOT_FOUND_ERROR, +} from "../../constants"; +import jwt from "jsonwebtoken"; + /** * This function enables a user to restore password. * @param _parent - parent of current request @@ -20,7 +26,14 @@ export const forgotPassword: MutationResolvers["forgotPassword"] = async ( ) => { const { userOtp, newPassword, otpToken } = args.data; + try { + await jwt.verify(otpToken, ACCESS_TOKEN_SECRET as string); + } catch (error) { + throw new Error(INVALID_OTP); + } + // Extracts email and otp out of otpToken. + const { email, otp } = jwtDecode<{ email: string; otp: string; @@ -34,6 +47,23 @@ export const forgotPassword: MutationResolvers["forgotPassword"] = async ( throw new Error(INVALID_OTP); } + const user = await User.findOne({ email }).lean(); + + if (!user) { + throw new Error(USER_NOT_FOUND_ERROR.MESSAGE); + } + const oldHashedPassword: string = user.password; + + //Checks whether the old password is same as the new one + const isSameAsOldPassword = await bcrypt.compare( + newPassword, + oldHashedPassword + ); + + if (isSameAsOldPassword == true) { + throw new Error("New password cannot be same as old password"); + } + const hashedPassword = await bcrypt.hash(newPassword, 12); // Updates password field for user's document with email === email. diff --git a/src/resolvers/Mutation/updateUserProfile.ts b/src/resolvers/Mutation/updateUserProfile.ts index d6844af867..f6c6fceae2 100644 --- a/src/resolvers/Mutation/updateUserProfile.ts +++ b/src/resolvers/Mutation/updateUserProfile.ts @@ -57,7 +57,7 @@ export const updateUserProfile: MutationResolvers["updateUserProfile"] = async ( } // Update User - const updatedUser = await User.findOneAndUpdate( + let updatedUser = await User.findOneAndUpdate( { _id: context.userId, }, @@ -128,9 +128,13 @@ export const updateUserProfile: MutationResolvers["updateUserProfile"] = async ( runValidators: true, }, ).lean(); - updatedUser!.image = updatedUser?.image - ? `${context.apiRootUrl}${updatedUser?.image}` - : null; + + if (updatedUser != null) { + updatedUser.image = updatedUser?.image + ? `${context.apiRootUrl}${updatedUser?.image}` + : null; + } + if (args.data == undefined) updatedUser = null; return updatedUser ?? ({} as InterfaceUser); }; diff --git a/src/utilities/copyToClipboard.ts b/src/utilities/copyToClipboard.ts index 7b7c8c854d..647356d71a 100644 --- a/src/utilities/copyToClipboard.ts +++ b/src/utilities/copyToClipboard.ts @@ -1,7 +1,7 @@ import ncp from "copy-paste"; import { IN_PRODUCTION } from "../constants"; /** - * This utility function copy the text into the clipboard. + * This utility function copy the text into the clipboard (test change). * @remarks * This is a utility method. This works only in development or test mode. * @param text - The content that need to be copied. diff --git a/tests/resolvers/Mutation/forgotPassword.spec.ts b/tests/resolvers/Mutation/forgotPassword.spec.ts index 192a83a714..a2ede7eb17 100644 --- a/tests/resolvers/Mutation/forgotPassword.spec.ts +++ b/tests/resolvers/Mutation/forgotPassword.spec.ts @@ -1,9 +1,15 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import "dotenv/config"; import type { MutationForgotPasswordArgs } from "../../../src/types/generatedGraphQLTypes"; import { connect, disconnect } from "../../helpers/db"; import type mongoose from "mongoose"; import { forgotPassword as forgotPasswordResolver } from "../../../src/resolvers/Mutation/forgotPassword"; -import { INVALID_OTP } from "../../../src/constants"; +import { + INVALID_OTP, + ACCESS_TOKEN_SECRET, + USER_NOT_FOUND_ERROR, +} from "../../../src/constants"; import jwt from "jsonwebtoken"; import bcrypt from "bcryptjs"; import { beforeAll, afterAll, describe, it, expect } from "vitest"; @@ -31,7 +37,7 @@ describe("resolvers -> Mutation -> forgotPassword", () => { email: testUser?.email ?? "", otp: "otp", }, - process.env.NODE_ENV!, + ACCESS_TOKEN_SECRET as string, { expiresIn: 99999999, }, @@ -51,6 +57,101 @@ describe("resolvers -> Mutation -> forgotPassword", () => { } }); + //added ths test + it(`throws Error if newPassword is the same as the old password`, async () => { + const otp = "correctOtp"; + + const hashedOtp = await bcrypt.hash(otp, 1); + + const otpToken = jwt.sign( + { + email: testUser?.email ?? "", + otp: hashedOtp, + }, + ACCESS_TOKEN_SECRET as string, + { + expiresIn: 99999999, + } + ); + + const args: MutationForgotPasswordArgs = { + data: { + newPassword: testUser?.password ?? "", // Using optional chaining and nullish coalescing + otpToken, + userOtp: otp, + }, + }; + + try { + await forgotPasswordResolver?.({}, args, {}); + } catch (error: any) { + expect(error.message).toEqual( + "New password cannot be same as old password" + ); + } + }); + it("throws an error when the email is changed in the token", async () => { + const otp = "correctOtp"; + + const hashedOtp = await bcrypt.hash(otp, 1); + + // Sign the token + const otpToken = jwt.sign( + { + email: testUser?.email ?? "", + otp: hashedOtp, + }, + process.env.NODE_ENV ?? "", + { + expiresIn: 99999999, + } + ); + + const args: MutationForgotPasswordArgs = { + data: { + newPassword: testUser?.password ?? "", // Using optional chaining and nullish coalescing + otpToken, + userOtp: otp, + }, + }; + + try { + await forgotPasswordResolver?.({}, args, {}); + } catch (error: any) { + expect(error.message).toEqual(INVALID_OTP); + } + }); + it(`throws an error when the user is not found`, async () => { + const otp = "correctOtp"; + + const hashedOtp = await bcrypt.hash(otp, 1); + + // Sign the token with an email that doesn't exist + const otpToken = jwt.sign( + { + email: "nonexistent@example.com", // An email that doesn't exist + otp: hashedOtp, + }, + ACCESS_TOKEN_SECRET as string, + { + expiresIn: 99999999, + } + ); + + const args: MutationForgotPasswordArgs = { + data: { + newPassword: "newPassword", + otpToken, + userOtp: otp, + }, + }; + + try { + await forgotPasswordResolver?.({}, args, {}); + } catch (error: any) { + expect(error.message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); it(`changes the password if args.otp is correct`, async () => { const otp = "otp"; @@ -61,7 +162,7 @@ describe("resolvers -> Mutation -> forgotPassword", () => { email: testUser?.email ?? "", otp: hashedOtp, }, - process.env.NODE_ENV ?? "", + ACCESS_TOKEN_SECRET as string, { expiresIn: 99999999, }, @@ -79,6 +180,7 @@ describe("resolvers -> Mutation -> forgotPassword", () => { expect(forgotPasswordPayload).toEqual(true); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const updatedTestUser = await User! .findOne({ _id: testUser?._id ?? "", @@ -86,6 +188,6 @@ describe("resolvers -> Mutation -> forgotPassword", () => { .select(["password"]) .lean(); - expect(updatedTestUser?.password).not.toEqual(testUser!.password); + expect(updatedTestUser?.password).not.toEqual(testUser?.password); }); }); diff --git a/tests/resolvers/Mutation/updateUserProfile.spec.ts b/tests/resolvers/Mutation/updateUserProfile.spec.ts index 846026af4a..201c0c2729 100644 --- a/tests/resolvers/Mutation/updateUserProfile.spec.ts +++ b/tests/resolvers/Mutation/updateUserProfile.spec.ts @@ -26,13 +26,18 @@ import { } from "vitest"; let MONGOOSE_INSTANCE: typeof mongoose; -let testUser: InterfaceUser & Document; -let testUser2: InterfaceUser & Document; + +type UserDocument = InterfaceUser & + Document, InterfaceUser>; +let testUser: UserDocument; +let testUser2: UserDocument; vi.mock("../../utilities/uploadEncodedImage", () => ({ uploadEncodedImage: vi.fn(), })); const email = `email${nanoid().toLowerCase()}@gmail.com`; +const date = new Date("2002-03-04T18:30:00.000Z"); + beforeAll(async () => { MONGOOSE_INSTANCE = await connect(); @@ -43,6 +48,7 @@ beforeAll(async () => { lastName: "lastName", appLanguageCode: "en", gender: null, + image: null, birthDate: null, educationGrade: null, employmentStatus: null, @@ -107,11 +113,13 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { ); await updateUserProfileResolver?.({}, args, context); - } catch (error: any) { - expect(spy).toHaveBeenCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect(error.message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); + } catch (error) { + if (error instanceof Error) { + expect(spy).toHaveBeenCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + expect(error.message).toEqual( + `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + ); + } } }); @@ -135,11 +143,13 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { await import("../../../src/resolvers/Mutation/updateUserProfile"); await updateUserProfileResolverUserError?.({}, args, context); - } catch (error: any) { - expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); - expect(error.message).toEqual( - `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, - ); + } catch (error) { + if (error instanceof Error) { + expect(spy).toHaveBeenLastCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + expect(error.message).toEqual( + `Translated ${USER_NOT_FOUND_ERROR.MESSAGE}`, + ); + } } }); @@ -166,41 +176,15 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { ); await updateUserProfileResolver?.({}, args, context); - } catch (error: any) { - expect(spy).toHaveBeenLastCalledWith(EMAIL_ALREADY_EXISTS_ERROR.MESSAGE); - expect(error.message).toEqual( - `Translated ${EMAIL_ALREADY_EXISTS_ERROR.MESSAGE}`, - ); - } - }); - - it(`throws ConflictError if args.data.email is already registered for another user`, async () => { - const { requestContext } = await import("../../../src/libraries"); - - const spy = vi - .spyOn(requestContext, "translate") - .mockImplementationOnce((message) => `Translated ${message}`); - - try { - const args: MutationUpdateUserProfileArgs = { - data: { - email: testUser.email, - }, - }; - - const context = { - userId: testUser._id, - }; - - const { updateUserProfile: updateUserProfileResolverEmailError } = - await import("../../../src/resolvers/Mutation/updateUserProfile"); - - await updateUserProfileResolverEmailError?.({}, args, context); - } catch (error: any) { - expect(spy).toHaveBeenLastCalledWith(EMAIL_ALREADY_EXISTS_ERROR.MESSAGE); - expect(error.message).toEqual( - `Translated ${EMAIL_ALREADY_EXISTS_ERROR.MESSAGE}`, - ); + } catch (error) { + if (error instanceof Error) { + expect(spy).toHaveBeenLastCalledWith( + EMAIL_ALREADY_EXISTS_ERROR.MESSAGE, + ); + expect(error.message).toEqual( + `Translated ${EMAIL_ALREADY_EXISTS_ERROR.MESSAGE}`, + ); + } } }); @@ -317,17 +301,19 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { }); }); - it(`updates current user's user object and returns the object`, async () => { + it("When Image is give updates the current user's object with the uploaded image and returns it", async () => { const args: MutationUpdateUserProfileArgs = { - data: { - email: `email${nanoid().toLowerCase()}@gmail.com`, - firstName: "newFirstName", - lastName: "newLastName", - }, + data: {}, + file: "newImageFile.png", }; + vi.spyOn(uploadEncodedImage, "uploadEncodedImage").mockImplementation( + async (encodedImageURL: string) => encodedImageURL, + ); + const context = { userId: testUser._id, + apiRootUrl: BASE_URL, }; const updateUserProfilePayload = await updateUserProfileResolver?.( @@ -338,10 +324,10 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { expect(updateUserProfilePayload).toEqual({ ...testUser.toObject(), - email: args.data?.email, + email: updateUserProfilePayload?.email, firstName: "newFirstName", lastName: "newLastName", - image: null, + image: BASE_URL + "newImageFile.png", updatedAt: expect.anything(), createdAt: expect.anything(), }); @@ -377,14 +363,301 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { email: args.data?.email, firstName: "newFirstName", lastName: "newLastName", + image: BASE_URL + args.file, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object when any single argument(birthdate) is given w/0 changing other fields `, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + birthDate: date, + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, image: BASE_URL + "newImageFile.png", + birthDate: args.data?.birthDate, updatedAt: expect.anything(), createdAt: expect.anything(), }); }); - it("When Image is give updates the current user's object with the uploaded image and returns it", async () => { + + it(`updates current user's user object when any single argument(EducationGrade) is given w/0 changing other fields `, async () => { const args: MutationUpdateUserProfileArgs = { - data: {}, + data: { + educationGrade: "GRADUATE", + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, + image: BASE_URL + "newImageFile.png", + birthDate: date, + educationGrade: args.data?.educationGrade, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object when any single argument(EmployementStatus) is given w/0 changing other fields `, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + employmentStatus: "FULL_TIME", + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, + image: BASE_URL + "newImageFile.png", + birthDate: date, + educationGrade: "GRADUATE", + employmentStatus: args.data?.employmentStatus, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object when any single argument(Gender) is given w/0 changing other fields `, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + gender: "FEMALE", + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, + image: BASE_URL + "newImageFile.png", + birthDate: date, + educationGrade: "GRADUATE", + employmentStatus: "FULL_TIME", + gender: args.data?.gender, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object when any single argument(MaritalStatus) is given w/0 changing other fields `, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + maritalStatus: "SINGLE", + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, + image: BASE_URL + "newImageFile.png", + birthDate: date, + educationGrade: "GRADUATE", + employmentStatus: "FULL_TIME", + gender: "FEMALE", + maritalStatus: args.data?.maritalStatus, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object when any single argument(PhoneNumber) is given w/0 changing other fields `, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + phone: { + home: "020 89024004", + mobile: "+91 1234567890", + work: "+31 1234567890", + }, + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, + image: BASE_URL + "newImageFile.png", + birthDate: date, + educationGrade: "GRADUATE", + employmentStatus: "FULL_TIME", + gender: "FEMALE", + maritalStatus: "SINGLE", + phone: args.data?.phone, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object when any single argument(address) is given w/0 changing other fields `, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + address: { + city: "CityName", + countryCode: "123", + dependentLocality: "345", + line1: "Street ABC", + line2: "Near XYZ Park", + postalCode: "45672", + sortingCode: "234", + state: "StateName", + }, + }, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + const testUserobj = await User.findById({ _id: testUser.id }); + + expect(updateUserProfilePayload).toEqual({ + ...testUser.toObject(), + email: testUserobj?.email, + firstName: testUserobj?.firstName, + lastName: testUserobj?.lastName, + image: BASE_URL + "newImageFile.png", + birthDate: date, + educationGrade: "GRADUATE", + employmentStatus: "FULL_TIME", + gender: "FEMALE", + maritalStatus: "SINGLE", + address: args.data?.address, + phone: { + home: "020 89024004", + mobile: "+91 1234567890", + work: "+31 1234567890", + }, + updatedAt: expect.anything(), + createdAt: expect.anything(), + }); + }); + + it(`updates current user's user object and returns the object`, async () => { + const args: MutationUpdateUserProfileArgs = { + data: { + email: `email${nanoid().toLowerCase()}@gmail.com`, + firstName: "newFirstName", + lastName: "newLastName", + birthDate: date, + educationGrade: "GRADUATE", + employmentStatus: "FULL_TIME", + gender: "FEMALE", + maritalStatus: "SINGLE", + address: { + city: "CityName", + countryCode: "123", + dependentLocality: "345", + line1: "Street ABC", + line2: "Near XYZ Park", + postalCode: "45672", + sortingCode: "234", + state: "StateName", + }, + phone: { + home: "020 89024004", + mobile: "+91 1234567890", + work: "+31 1234567890", + }, + }, file: "newImageFile.png", }; @@ -405,12 +678,38 @@ describe("resolvers -> Mutation -> updateUserProfile", () => { expect(updateUserProfilePayload).toEqual({ ...testUser.toObject(), - email: updateUserProfilePayload?.email, - firstName: "newFirstName", - lastName: "newLastName", - image: BASE_URL + "newImageFile.png", + email: args.data?.email, + firstName: args.data?.firstName, + lastName: args.data?.lastName, + image: BASE_URL + args.file, + birthDate: date, + educationGrade: args.data?.educationGrade, + employmentStatus: args.data?.employmentStatus, + gender: args.data?.gender, + maritalStatus: args.data?.maritalStatus, + address: args.data?.address, + phone: args.data?.phone, updatedAt: expect.anything(), createdAt: expect.anything(), }); }); + + it(`if data is undefined dont update the profile`, async () => { + const args: MutationUpdateUserProfileArgs = { + data: undefined, + }; + + const context = { + userId: testUser._id, + apiRootUrl: BASE_URL, + }; + + const updateUserProfilePayload = await updateUserProfileResolver?.( + {}, + args, + context, + ); + + expect(updateUserProfilePayload).toEqual({}); + }); });