diff --git a/codegen.ts b/codegen.ts index 6777dbd3ade..26a01aafd4a 100644 --- a/codegen.ts +++ b/codegen.ts @@ -42,6 +42,8 @@ const config: CodegenConfig = { EventAttendee: "../models/EventAttendee#InterfaceEventAttendee", + Family: "../models/Family#InterfaceFamily", + Feedback: "../models/Feedback#InterfaceFeedback", // File: '../models/File#InterfaceFile', diff --git a/sample_data/family.json b/sample_data/family.json new file mode 100644 index 00000000000..b936f133662 --- /dev/null +++ b/sample_data/family.json @@ -0,0 +1,20 @@ +[ + { + "_id": "60f18f31b7e5c4a2a4c3f905", + "title": "Smith Family", + "users": [ + "64378abd85008f171cf2990d", + "65378abd85008f171cf2990d", + "66378abd85008f171cf2990d" + ] + }, + { + "_id": "60f18f31b7e5c4a2a4c3f906", + "title": "Johnson Family", + "users": [ + "66378abd85008f171cf2990d", + "65378abd85008f171cf2990d", + "64378abd85008f171cf2990d" + ] + } +] diff --git a/schema.graphql b/schema.graphql index d1aa9bb3123..b056bb3c9b4 100644 --- a/schema.graphql +++ b/schema.graphql @@ -315,6 +315,12 @@ type ExtendSession { refreshToken: String! } +type Family { + _id: ID! + title: String! + users: [User!]! +} + type Feedback { _id: ID! createdAt: DateTime! @@ -486,6 +492,7 @@ type Mutation { addUserCustomData(dataName: String!, dataValue: Any!, organizationId: ID!): UserCustomData! addUserImage(file: String!): User! addUserToGroupChat(chatId: ID!, userId: ID!): GroupChat! + addUserToFamily(familyId: ID!, userId: ID!): Family! adminRemoveEvent(eventId: ID!): Event! adminRemoveGroup(groupId: ID!): GroupChat! assignUserTag(input: ToggleUserTagAssignInput!): User @@ -500,6 +507,7 @@ type Mutation { createDonation(amount: Float!, nameOfOrg: String!, nameOfUser: String!, orgId: ID!, payPalId: ID!, userId: ID!): Donation! createEvent(data: EventInput): Event! createGroupChat(data: createGroupChatInput!): GroupChat! + createFamilyGroup(data: createFamilyGroupInput): Family! createMember(input: UserAndOrganizationInput!): Organization! createMessageChat(data: MessageChatInput!): MessageChat! createOrganization(data: OrganizationInput, file: String): Organization! @@ -528,6 +536,8 @@ type Mutation { removeDirectChat(chatId: ID!, organizationId: ID!): DirectChat! removeEvent(id: ID!): Event! removeEventAttendee(data: EventAttendeeInput!): User! + removeEventProject(id: ID!): EventProject! + removeFamily(familyId: ID!): Family! removeGroupChat(chatId: ID!): GroupChat! removeMember(data: UserAndOrganizationInput!): Organization! removeOrganization(id: ID!): User! @@ -537,6 +547,7 @@ type Mutation { removeSampleOrganization: Boolean! removeUserCustomData(organizationId: ID!): UserCustomData! removeUserFromGroupChat(chatId: ID!, userId: ID!): GroupChat! + removeUserFromFamily(familyId: ID!, userId: ID!): Family! removeUserImage: User! removeUserTag(id: ID!): UserTag revokeRefreshTokenForUser: Boolean! @@ -810,6 +821,7 @@ type Query { event(id: ID!): Event eventsByOrganization(id: ID, orderBy: EventOrderByInput): [Event] eventsByOrganizationConnection(first: Int, orderBy: EventOrderByInput, skip: Int, where: EventWhereInput): [Event!]! + family(id: ID!): [Family]! getAdvertisements: [Advertisement] getDonationById(id: ID!): Donation! getDonationByOrgId(orgId: ID!): [Donation] @@ -973,6 +985,7 @@ type User { email: EmailAddress! employmentStatus: EmploymentStatus eventAdmin: [Event] + Family: [Family] firstName: String! gender: Gender image: String @@ -1144,4 +1157,9 @@ input createGroupChatInput { organizationId: ID! title: String! userIds: [ID!]! +} + +input createFamilyGroupInput { + title: String! + userIds: [ID!]! } \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index d09d839acc0..703a5e5f37e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -150,12 +150,24 @@ export const LENGTH_VALIDATION_ERROR = { PARAM: "stringValidation", }; +export const FAMILY_MIN_MEMBERS_ERROR_CODE = { + MESSAGE: "Error: Members in the family must be more than one", + CODE: "membersInFamilyLessThanOne", + PARAM: "membersInFamilyLessThanOne", +}; + export const REGEX_VALIDATION_ERROR = { MESSAGE: "Error: Entered value must be a valid string", CODE: "string.notValid", PARAM: "stringValidation", }; +export const FAMILY_NOT_FOUND_ERROR = { + MESSAGE: "Error: Family Not Found", + CODE: "familyNotFound", + PARAM: "familyNotFound", +}; + export const USER_NOT_AUTHORIZED_SUPERADMIN = { MESSAGE: "Error: Current user must be a SUPERADMIN", CODE: "role.notValid.superadmin", diff --git a/src/middleware/isAuth.ts b/src/middleware/isAuth.ts index 410d90a3fb3..1ec68175b17 100644 --- a/src/middleware/isAuth.ts +++ b/src/middleware/isAuth.ts @@ -81,4 +81,4 @@ export const isAuth = (request: Request): InterfaceAuthData => { authData.userId = decodedToken.userId; return authData; -}; +}; \ No newline at end of file diff --git a/src/models/Family.ts b/src/models/Family.ts new file mode 100644 index 00000000000..8b959dcfd01 --- /dev/null +++ b/src/models/Family.ts @@ -0,0 +1,47 @@ +import type { PopulatedDoc, Types, Document, Model } from "mongoose"; +import { Schema, model, models } from "mongoose"; +import type { InterfaceUser } from "./User"; +/** + * This is an interface that represents a database(MongoDB) document for Family. + */ + +export interface InterfaceFamily { + _id: Types.ObjectId; + title: string; + users: PopulatedDoc[]; +} + +/** + * @param title - Name of the Family (type: String) + * Description: Name of the Family. + */ + +/** + * @param users - Members associated with the Family (type: String) + * Description: Members associated with the Family. + */ +const FamilySchema = new Schema({ + title: { + type: String, + required: true, + }, + users: [ + { + type: Schema.Types.ObjectId, + ref: "User", + required: true, + }, + ], +}); + +// $2a$12$usOiKueW0FsZfpUuGNrAQuxvM.AB/nCOsHLdpE0liAcUr5PHOuc2K + +//token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblZlcnNpb24iOjcsInVzZXJJZ + +const familyModel = (): Model => + model("Family", FamilySchema); + +// This syntax is needed to prevent Mongoose OverwriteModelError while running tests. +export const Family = (models.Family || familyModel()) as ReturnType< + typeof familyModel +>; diff --git a/src/models/User.ts b/src/models/User.ts index d94f755ec40..fc1331d6437 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -5,6 +5,7 @@ import validator from "validator"; import type { InterfaceEvent } from "./Event"; import type { InterfaceMembershipRequest } from "./MembershipRequest"; import type { InterfaceOrganization } from "./Organization"; +import type { InterfaceFamily } from "./Family"; /** * This is an interface that represents a database(MongoDB) document for User. */ @@ -35,6 +36,7 @@ export interface InterfaceUser { gender: string; image: string | undefined | null; joinedOrganizations: PopulatedDoc[]; + Family: PopulatedDoc | null; lastName: string; maritalStatus: string; membershipRequests: PopulatedDoc[]; @@ -131,6 +133,10 @@ const userSchema = new Schema( birthDate: { type: Date, }, + createdAt: { + type: Date, + default: Date.now, + }, createdOrganizations: [ { type: Schema.Types.ObjectId, @@ -197,6 +203,10 @@ const userSchema = new Schema( ref: "Organization", }, ], + Family: { + type: Schema.Types.ObjectId, + ref: "Family", + }, lastName: { type: String, required: true, @@ -225,6 +235,10 @@ const userSchema = new Schema( ref: "Organization", }, ], + organizationUserBelongsTo: { + type: Schema.Types.ObjectId, + ref: "Organization", + }, password: { type: String, required: true, @@ -263,7 +277,6 @@ const userSchema = new Schema( }, tokenVersion: { type: Number, - required: true, default: 0, }, userType: { diff --git a/src/resolvers/Family/index.ts b/src/resolvers/Family/index.ts new file mode 100644 index 00000000000..9126c24da7f --- /dev/null +++ b/src/resolvers/Family/index.ts @@ -0,0 +1,6 @@ +import type { FamilyResolvers } from "../../types/generatedGraphQLTypes"; +import { users } from "./users"; + +export const Family: FamilyResolvers = { + users, +}; diff --git a/src/resolvers/Family/users.ts b/src/resolvers/Family/users.ts new file mode 100644 index 00000000000..8097afdc13c --- /dev/null +++ b/src/resolvers/Family/users.ts @@ -0,0 +1,14 @@ +import type { FamilyResolvers } from "../../types/generatedGraphQLTypes"; +import { User } from "../../models"; +/** + * This resolver function will fetch and return the list of all Members of the Group Chat from database. + * @param parent - An object that is the return value of the resolver for this field's parent. + * @returns An `object` that contains the Member data. + */ +export const users: FamilyResolvers["users"] = async (parent) => { + return await User.find({ + _id: { + $in: parent.users, + }, + }).lean(); +}; diff --git a/src/resolvers/Mutation/adminAddFamilyMember.ts b/src/resolvers/Mutation/adminAddFamilyMember.ts new file mode 100644 index 00000000000..a25f48c7d9f --- /dev/null +++ b/src/resolvers/Mutation/adminAddFamilyMember.ts @@ -0,0 +1,86 @@ +import "dotenv/config"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { errors, requestContext } from "../../libraries"; +import { superAdminCheck } from "../../utilities"; +import { User } from "../../models"; +import { Family } from "../../models/Family"; +import { + FAMILY_NOT_FOUND_ERROR, + USER_ALREADY_MEMBER_ERROR, + USER_NOT_FOUND_ERROR, +} from "../../constants"; +/** + * This function adds user to the family. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of the entire application + * @remarks The following checks are done: + * 1. If the family exists + * 2. If the user exists + * 3. If the user is already member of the family + * @returns Updated family + */ +export const addUserToFamily: MutationResolvers["addUserToFamily"] = async ( + _parent, + args, + context +) => { + const family = await Family.findOne({ + _id: args.familyId, + }).lean(); + + const currentUser = await User.findById({ + _id: context.userId, + }); + + //check whether user is superadmin + if (currentUser) { + superAdminCheck(currentUser); + } + + //check wheather family exists + if (!family) { + throw new errors.NotFoundError( + requestContext.translate(FAMILY_NOT_FOUND_ERROR.MESSAGE), + FAMILY_NOT_FOUND_ERROR.CODE, + FAMILY_NOT_FOUND_ERROR.PARAM + ); + } + + // Checks whether user with _id === args.userId exists. + if (currentUser === null) { + throw new errors.NotFoundError( + requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), + USER_NOT_FOUND_ERROR.CODE, + USER_NOT_FOUND_ERROR.PARAM + ); + } + + const isUserFamilyMember = family.users.some((user) => { + user.equals(args.userId); + }); + + // Checks whether user with _id === args.userId is already a member of Family. + if (isUserFamilyMember === true) { + throw new errors.ConflictError( + requestContext.translate(USER_ALREADY_MEMBER_ERROR.MESSAGE), + USER_ALREADY_MEMBER_ERROR.CODE, + USER_ALREADY_MEMBER_ERROR.PARAM + ); + } + + // Adds args.userId to users lists on family group and return the updated family. + return await Family.findOneAndUpdate( + { + _id: args.familyId, + }, + { + $push: { + users: args.userId, + }, + }, + { + new: true, + } + ).lean(); +}; diff --git a/src/resolvers/Mutation/adminRemoveFamilyMember.ts b/src/resolvers/Mutation/adminRemoveFamilyMember.ts new file mode 100644 index 00000000000..3f5211f647a --- /dev/null +++ b/src/resolvers/Mutation/adminRemoveFamilyMember.ts @@ -0,0 +1,60 @@ +import { + FAMILY_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, +} from "../../constants"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { errors, requestContext } from "../../libraries"; +import { User } from "../../models"; +import { Family } from "../../models/Family"; +import { superAdminCheck } from "../../utilities"; +/** + * This function enables to remove a user from group chat. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire publication + * @remarks The following checks are done: + * 1. If the family exists. + * 2. If the user to be removed is member of the organisation. + * @returns Updated group chat. + */ +export const removeUserFromFamily: MutationResolvers["removeUserFromFamily"] = + async (_parent, args, context) => { + const family = await Family.findOne({ + _id: args.familyId, + }).lean(); + + const currentUser = await User.findById({ + _id: context.userId, + }); + + //check whether user is superadmin + if (currentUser) { + superAdminCheck(currentUser); + } + + //Check whether family exists + if (!family) { + throw new errors.NotFoundError( + requestContext.translate(FAMILY_NOT_FOUND_ERROR.MESSAGE), + FAMILY_NOT_FOUND_ERROR.CODE, + FAMILY_NOT_FOUND_ERROR.PARAM + ); + } + + //Removes args.userId from users list of family ans return the updated family. + return await Family.findOneAndUpdate( + { + _id: args.familyId, + }, + { + $set: { + users: family.users.filter( + (user) => user.toString() !== args.userId.toString() + ), + }, + }, + { + new: true, + } + ).lean(); + }; diff --git a/src/resolvers/Mutation/createFamilyGroup.ts b/src/resolvers/Mutation/createFamilyGroup.ts new file mode 100644 index 00000000000..123516c3f26 --- /dev/null +++ b/src/resolvers/Mutation/createFamilyGroup.ts @@ -0,0 +1,67 @@ +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { User } from "../../models"; +import { errors, requestContext } from "../../libraries"; +import { + LENGTH_VALIDATION_ERROR, + FAMILY_MIN_MEMBERS_ERROR_CODE, +} from "../../constants"; +import { superAdminCheck } from "../../utilities"; +import { isValidString } from "../../libraries/validators/validateString"; +import { Family } from "../../models/Family"; +/** + * This Function enables to create Family Groups + * @param _parent - parent of current request + * @param args - payload provided with the request + * @param context - context of entire application + * @remarks - The following checks are done: + * 1. If the user exists + * @returns Created Family Group + */ +export const createFamilyGroup: MutationResolvers["createFamilyGroup"] = async ( + _parent, + args, + context +) => { + const currentUser = await User.findById({ + _id: context.userId, + }); + + if (currentUser) { + superAdminCheck(currentUser); + } + + let ValidationResultName = { + isLessThanMaxLength: false, + }; + + if (args && args.data && typeof args.data.title === 'string') { + ValidationResultName = isValidString(args.data.title, 256); + } + + if (!ValidationResultName.isLessThanMaxLength) { + throw new errors.InputValidationError( + requestContext.translate( + `${LENGTH_VALIDATION_ERROR.MESSAGE} 256 characters in name` + ), + LENGTH_VALIDATION_ERROR.CODE + ); + } + + // Check if there are at least 2 members + if (args.data?.userIds && args.data?.userIds.length < 2) { + throw new errors.InputValidationError( + requestContext.translate("Family must have at least 2 members."), + FAMILY_MIN_MEMBERS_ERROR_CODE.CODE + ); + } + + const familyTitle = args.data?.title; + + const createdFamily = await Family.create({ + ...args.data, + title: familyTitle, + users: [context.userId], + }); + + return createdFamily.toObject(); +}; diff --git a/src/resolvers/Mutation/index.ts b/src/resolvers/Mutation/index.ts index 5d3333da9aa..b0ee69f64ab 100644 --- a/src/resolvers/Mutation/index.ts +++ b/src/resolvers/Mutation/index.ts @@ -9,6 +9,7 @@ import { addOrganizationImage } from "./addOrganizationImage"; import { addUserCustomData } from "./addUserCustomData"; import { addUserImage } from "./addUserImage"; import { addUserToGroupChat } from "./addUserToGroupChat"; +import { addUserToFamily } from "./adminAddFamilyMember"; import { adminRemoveEvent } from "./adminRemoveEvent"; import { adminRemoveGroup } from "./adminRemoveGroup"; import { assignUserTag } from "./assignUserTag"; @@ -24,6 +25,7 @@ import { createDirectChat } from "./createDirectChat"; import { createDonation } from "./createDonation"; import { createEvent } from "./createEvent"; import { createGroupChat } from "./createGroupChat"; +import { createFamilyGroup } from "./createFamilyGroup"; import { createMessageChat } from "./createMessageChat"; import { createOrganization } from "./createOrganization"; import { createPlugin } from "./createPlugin"; @@ -51,6 +53,7 @@ import { removeDirectChat } from "./removeDirectChat"; import { removeEvent } from "./removeEvent"; import { removeEventAttendee } from "./removeEventAttendee"; import { removeGroupChat } from "./removeGroupChat"; +import { removeFamily } from "./removeFamilyGroup"; import { removeAdvertisement } from "./removeAdvertisement"; import { removeMember } from "./removeMember"; import { removeOrganization } from "./removeOrganization"; @@ -60,6 +63,7 @@ import { removePost } from "./removePost"; import { removeSampleOrganization } from "./removeSampleOrganization"; import { removeUserCustomData } from "./removeUserCustomData"; import { removeUserFromGroupChat } from "./removeUserFromGroupChat"; +import { removeUserFromFamily } from "./adminRemoveFamilyMember"; import { removeUserImage } from "./removeUserImage"; import { removeUserTag } from "./removeUserTag"; import { revokeRefreshTokenForUser } from "./revokeRefreshTokenForUser"; @@ -97,6 +101,7 @@ export const Mutation: MutationResolvers = { addUserCustomData, addUserImage, addUserToGroupChat, + addUserToFamily, adminRemoveEvent, adminRemoveGroup, assignUserTag, @@ -113,6 +118,7 @@ export const Mutation: MutationResolvers = { createDonation, createEvent, createGroupChat, + createFamilyGroup, createMessageChat, createOrganization, createPlugin, @@ -140,6 +146,7 @@ export const Mutation: MutationResolvers = { removeEvent, removeEventAttendee, removeAdvertisement, + removeFamily, removeGroupChat, removeMember, removeOrganization, @@ -149,6 +156,7 @@ export const Mutation: MutationResolvers = { removePost, removeUserCustomData, removeUserFromGroupChat, + removeUserFromFamily, removeUserImage, removeUserTag, revokeRefreshTokenForUser, diff --git a/src/resolvers/Mutation/removeFamilyGroup.ts b/src/resolvers/Mutation/removeFamilyGroup.ts new file mode 100644 index 00000000000..b7bac012592 --- /dev/null +++ b/src/resolvers/Mutation/removeFamilyGroup.ts @@ -0,0 +1,47 @@ +import { superAdminCheck } from "../../utilities"; +import { FAMILY_NOT_FOUND_ERROR } from "../../constants"; +import type { MutationResolvers } from "../../types/generatedGraphQLTypes"; +import { errors, requestContext } from "../../libraries"; +import { Family } from "../../models/Family"; +import { User } from "../../models"; +/** + * This function enables to remove a family group. + * @param _parent - parent of current request + * @param args - payload provided with the request + * @context The following checks are done: + * 1. If the family group exists + * 2. If the user is superAdmin + * @returns Deleted family group. + */ +export const removeFamily: MutationResolvers["removeFamily"] = async ( + _parent, + args, + context +) => { + const family = await Family.findOne({ + _id: args.familyId, + }).lean(); + + const currentUser = await User.findById({ + _id: context.userId, + }); + + if (currentUser) { + superAdminCheck(currentUser); + } + + // Checks if a family with _id === args.familyId exists + if (!family) { + throw new errors.NotFoundError( + requestContext.translate(FAMILY_NOT_FOUND_ERROR.MESSAGE), + FAMILY_NOT_FOUND_ERROR.CODE, + FAMILY_NOT_FOUND_ERROR.PARAM + ); + } + + await Family.deleteOne({ + _id: family._id, + }); + + return family; +}; diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index b746c0c75b2..1cc6eeb1db4 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -70,6 +70,7 @@ const resolversComposition = { "Mutation.createComment": [currentUserExists()], "Mutation.createDirectChat": [currentUserExists()], "Mutation.createGroupChat": [currentUserExists()], + "Mutation.createFamilyGroup": [currentUserExists()], "Mutation.createOrganization": [currentUserExists()], "Mutation.likeComment": [currentUserExists()], "Mutation.likePost": [currentUserExists()], diff --git a/src/typeDefs/inputs.ts b/src/typeDefs/inputs.ts index 45f43eaff66..5b2d46af38c 100644 --- a/src/typeDefs/inputs.ts +++ b/src/typeDefs/inputs.ts @@ -29,6 +29,11 @@ export const inputs = gql` title: String! } + input createFamilyGroupInput { + title: String! + userIds: [ID!]! + } + input CreateUserTagInput { name: String! parentTagId: ID diff --git a/src/typeDefs/mutations.ts b/src/typeDefs/mutations.ts index 6e3ee8aa5da..a0236eee43d 100644 --- a/src/typeDefs/mutations.ts +++ b/src/typeDefs/mutations.ts @@ -34,6 +34,8 @@ export const mutations = gql` addUserToGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth + addUserToFamily(userId: ID!, familyId: ID!): Family! @auth + adminRemoveEvent(eventId: ID!): Event! @auth adminRemoveGroup(groupId: ID!): GroupChat! @auth @@ -73,6 +75,8 @@ export const mutations = gql` createGroupChat(data: createGroupChatInput!): GroupChat! @auth + createFamilyGroup(data: createFamilyGroupInput!): Family! @auth + createMessageChat(data: MessageChatInput!): MessageChat! @auth createOrganization(data: OrganizationInput, file: String): Organization! @@ -148,6 +152,10 @@ export const mutations = gql` removeEventAttendee(data: EventAttendeeInput!): User! @auth + removeEventProject(id: ID!): EventProject! @auth + + removeFamily(familyId: ID!): Family! @auth + removeGroupChat(chatId: ID!): GroupChat! @auth removeMember(data: UserAndOrganizationInput!): Organization! @auth @@ -168,6 +176,8 @@ export const mutations = gql` removeUserFromGroupChat(userId: ID!, chatId: ID!): GroupChat! @auth + removeUserFromFamily(userId: ID!, familyId: ID!): Family! @auth + removeUserImage: User! @auth revokeRefreshTokenForUser: Boolean! @auth diff --git a/src/typeDefs/types.ts b/src/typeDefs/types.ts index c4a46988f92..5ba78c94807 100644 --- a/src/typeDefs/types.ts +++ b/src/typeDefs/types.ts @@ -170,6 +170,12 @@ export const types = gql` organization: Organization! } + type Family { + _id: ID! + title: String + users: [User!]! + } + type GroupChatMessage { _id: ID! groupChatMessageBelongsTo: GroupChat! diff --git a/src/types/generatedGraphQLTypes.ts b/src/types/generatedGraphQLTypes.ts index 8ac08c6f75e..87455d09768 100644 --- a/src/types/generatedGraphQLTypes.ts +++ b/src/types/generatedGraphQLTypes.ts @@ -7,6 +7,7 @@ import type { InterfaceDirectChatMessage as InterfaceDirectChatMessageModel } fr import type { InterfaceDonation as InterfaceDonationModel } from '../models/Donation'; import type { InterfaceEvent as InterfaceEventModel } from '../models/Event'; import type { InterfaceEventAttendee as InterfaceEventAttendeeModel } from '../models/EventAttendee'; +import type { InterfaceFamily as InterfaceFamilyModel } from '../models/Family'; import type { InterfaceFeedback as InterfaceFeedbackModel } from '../models/Feedback'; import type { InterfaceGroup as InterfaceGroupModel } from '../models/Group'; import type { InterfaceGroupChat as InterfaceGroupChatModel } from '../models/GroupChat'; @@ -366,8 +367,15 @@ export type EventWhereInput = { export type ExtendSession = { __typename?: 'ExtendSession'; - accessToken: Scalars['String']['output']; - refreshToken: Scalars['String']['output']; + accessToken: Scalars['String']; + refreshToken: Scalars['String']; +}; + +export type Family = { + __typename?: 'Family'; + _id: Scalars['ID']; + title?: Maybe; + users: Array; }; export type Feedback = { @@ -547,6 +555,7 @@ export type Mutation = { addOrganizationImage: Organization; addUserCustomData: UserCustomData; addUserImage: User; + addUserToFamily: Family; addUserToGroupChat: GroupChat; adminRemoveEvent: Event; adminRemoveGroup: GroupChat; @@ -561,6 +570,7 @@ export type Mutation = { createDirectChat: DirectChat; createDonation: Donation; createEvent: Event; + createFamilyGroup: Family; createGroupChat: GroupChat; createMember: Organization; createMessageChat: MessageChat; @@ -590,6 +600,7 @@ export type Mutation = { removeDirectChat: DirectChat; removeEvent: Event; removeEventAttendee: User; + removeFamily: Family; removeGroupChat: GroupChat; removeMember: Organization; removeOrganization: User; @@ -598,6 +609,7 @@ export type Mutation = { removePost?: Maybe; removeSampleOrganization: Scalars['Boolean']['output']; removeUserCustomData: UserCustomData; + removeUserFromFamily: Family; removeUserFromGroupChat: GroupChat; removeUserImage: User; removeUserTag?: Maybe; @@ -677,6 +689,12 @@ export type MutationAddUserImageArgs = { }; +export type MutationAddUserToFamilyArgs = { + familyId: Scalars['ID']; + userId: Scalars['ID']; +}; + + export type MutationAddUserToGroupChatArgs = { chatId: Scalars['ID']['input']; userId: Scalars['ID']['input']; @@ -761,6 +779,11 @@ export type MutationCreateEventArgs = { }; +export type MutationCreateFamilyGroupArgs = { + data: CreateFamilyGroupInput; +}; + + export type MutationCreateGroupChatArgs = { data: CreateGroupChatInput; }; @@ -902,6 +925,16 @@ export type MutationRemoveEventAttendeeArgs = { }; +export type MutationRemoveEventProjectArgs = { + id: Scalars['ID']; +}; + + +export type MutationRemoveFamilyArgs = { + familyId: Scalars['ID']; +}; + + export type MutationRemoveGroupChatArgs = { chatId: Scalars['ID']['input']; }; @@ -929,12 +962,23 @@ export type MutationRemoveOrganizationImageArgs = { export type MutationRemovePostArgs = { - id: Scalars['ID']['input']; + id: Scalars['ID']; +}; + + +export type MutationRemoveTaskArgs = { + id: Scalars['ID']; }; export type MutationRemoveUserCustomDataArgs = { - organizationId: Scalars['ID']['input']; + organizationId: Scalars['ID']; +}; + + +export type MutationRemoveUserFromFamilyArgs = { + familyId: Scalars['ID']; + userId: Scalars['ID']; }; @@ -1852,6 +1896,11 @@ export type CreateChatInput = { userIds: Array; }; +export type CreateFamilyGroupInput = { + title: Scalars['String']; + userIds: Array; +}; + export type CreateGroupChatInput = { organizationId: Scalars['ID']['input']; title: Scalars['String']['input']; @@ -1970,6 +2019,7 @@ export type ResolversTypes = { EventOrderByInput: EventOrderByInput; EventWhereInput: EventWhereInput; ExtendSession: ResolverTypeWrapper; + Family: ResolverTypeWrapper; Feedback: ResolverTypeWrapper; FeedbackInput: FeedbackInput; FieldError: ResolverTypeWrapper['FieldError']>; @@ -2063,6 +2113,7 @@ export type ResolversTypes = { UsersConnectionInput: UsersConnectionInput; UsersConnectionResult: ResolverTypeWrapper & { data?: Maybe, errors: Array }>; createChatInput: CreateChatInput; + createFamilyGroupInput: CreateFamilyGroupInput; createGroupChatInput: CreateGroupChatInput; }; @@ -2100,6 +2151,7 @@ export type ResolversParentTypes = { EventInput: EventInput; EventWhereInput: EventWhereInput; ExtendSession: ExtendSession; + Family: InterfaceFamilyModel; Feedback: InterfaceFeedbackModel; FeedbackInput: FeedbackInput; FieldError: ResolversInterfaceTypes['FieldError']; @@ -2183,6 +2235,7 @@ export type ResolversParentTypes = { UsersConnectionInput: UsersConnectionInput; UsersConnectionResult: Omit & { data?: Maybe, errors: Array }; createChatInput: CreateChatInput; + createFamilyGroupInput: CreateFamilyGroupInput; createGroupChatInput: CreateGroupChatInput; }; @@ -2383,6 +2436,13 @@ export type ExtendSessionResolvers; }; +export type FamilyResolvers = { + _id?: Resolver; + title?: Resolver, ParentType, ContextType>; + users?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type FeedbackResolvers = { _id?: Resolver; createdAt?: Resolver; @@ -2531,6 +2591,7 @@ export type MutationResolvers>; addUserCustomData?: Resolver>; addUserImage?: Resolver>; + addUserToFamily?: Resolver>; addUserToGroupChat?: Resolver>; adminRemoveEvent?: Resolver>; adminRemoveGroup?: Resolver>; @@ -2545,6 +2606,7 @@ export type MutationResolvers>; createDonation?: Resolver>; createEvent?: Resolver>; + createFamilyGroup?: Resolver>; createGroupChat?: Resolver>; createMember?: Resolver>; createMessageChat?: Resolver>; @@ -2574,6 +2636,7 @@ export type MutationResolvers>; removeEvent?: Resolver>; removeEventAttendee?: Resolver>; + removeFamily?: Resolver>; removeGroupChat?: Resolver>; removeMember?: Resolver>; removeOrganization?: Resolver>; @@ -2582,6 +2645,7 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; removeSampleOrganization?: Resolver; removeUserCustomData?: Resolver>; + removeUserFromFamily?: Resolver>; removeUserFromGroupChat?: Resolver>; removeUserImage?: Resolver; removeUserTag?: Resolver, ParentType, ContextType, RequireFields>; @@ -2918,6 +2982,7 @@ export type Resolvers = { Error?: ErrorResolvers; Event?: EventResolvers; ExtendSession?: ExtendSessionResolvers; + Family?: FamilyResolvers; Feedback?: FeedbackResolvers; FieldError?: FieldErrorResolvers; Group?: GroupResolvers; @@ -2973,4 +3038,4 @@ export type Resolvers = { export type DirectiveResolvers = { auth?: AuthDirectiveResolver; role?: RoleDirectiveResolver; -}; +}; \ No newline at end of file diff --git a/tests/helpers/family.ts b/tests/helpers/family.ts new file mode 100644 index 00000000000..67cfba78f20 --- /dev/null +++ b/tests/helpers/family.ts @@ -0,0 +1,27 @@ +import { nanoid } from "nanoid"; +import type { InterfaceFamily } from "../../src/models/Family"; +import { Family } from "../../src/models/Family"; +import { createTestUser } from "./user"; +import type { TestUserType } from "./user"; + +import type { Document } from "mongoose"; + +export type TestFamilyType = + | (InterfaceFamily & Document) + | null; + +export const createTestFamily = async (): Promise< + [TestUserType, TestFamilyType] +> => { + const testUser = await createTestUser(); + if (testUser) { + const testFamily = await Family.create({ + title: `name${nanoid().toLocaleLowerCase()}`, + users: [testUser._id], + }); + + return [testUser, testFamily]; + } else { + return [testUser, null]; + } +}; diff --git a/tests/resolvers/Family/users.spec.ts b/tests/resolvers/Family/users.spec.ts new file mode 100644 index 00000000000..50822c623f4 --- /dev/null +++ b/tests/resolvers/Family/users.spec.ts @@ -0,0 +1,37 @@ +import "dotenv/config"; +import { users as usersResolver } from "../../../src/resolvers/Family/users"; +import { connect, disconnect } from "../../helpers/db"; +import type mongoose from "mongoose"; +import { User } from "../../../src/models"; +import { beforeAll, afterAll, describe, it, expect } from "vitest"; +import type { TestFamilyType } from "../../helpers/family"; +import { createTestFamily } from "../../helpers/family"; + +let testFamily: TestFamilyType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestFamily(); + testFamily = resultArray[1]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> GroupChat -> users", () => { + it(`returns user objects for parent.users`, async () => { + const parent = testFamily!.toObject(); + + const usersPayload = await usersResolver?.(parent, {}, {}); + + const users = await User.find({ + _id: { + $in: testFamily?.users, + }, + }).lean(); + + expect(usersPayload).toEqual(users); + }); +}); diff --git a/tests/resolvers/Mutation/adminAddFamilyMember.spec.ts b/tests/resolvers/Mutation/adminAddFamilyMember.spec.ts new file mode 100644 index 00000000000..aef3d35d942 --- /dev/null +++ b/tests/resolvers/Mutation/adminAddFamilyMember.spec.ts @@ -0,0 +1,172 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { Family } from "../../../src/models/Family"; +import type { MutationAddUserToFamilyArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { + USER_ALREADY_MEMBER_ERROR, + USER_NOT_FOUND_ERROR, + FAMILY_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_SUPERADMIN, +} from "../../../src/constants"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { TestUserType } from "../../helpers/user"; +import type { TestFamilyType } from "../../helpers/family"; +import { createTestFamily } from "../../helpers/family"; + +let testUser: TestUserType; +let testFamily: TestFamilyType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultsArray = await createTestFamily(); + testUser = resultsArray[0]; + testFamily = resultsArray[1]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolver -> mutation -> addUserToFamily", () => { + afterAll(() => { + vi.doUnmock("../../../src/constants"); + vi.resetModules(); + }); + + it(`throws user is not SUPERADMIN error if current user is with _id === context.userId is not a SUPERADMIN`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + + try { + const args: MutationAddUserToFamilyArgs = { + userId: testUser?.id, + familyId: testFamily?.id, + }; + + const context = { + userId: testUser?.id + } + + const { addUserToFamily } = await import( + "../../../src/resolvers/Mutation/adminAddFamilyMember" + ); + await addUserToFamily?.({}, args, context); + } catch (error: any) { + expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE); + expect(error.message).toEqual( + `${USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE}` + ); + } + }) + + it(`throws NotFoundError if no Family exists with _id === args.familyId`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + try { + const args: MutationAddUserToFamilyArgs = { + familyId: { input: Types.ObjectId().toString(), output: "" }, + userId: testUser?.id, + }; + + const context = { + userId: testUser?.id, + }; + + const { addUserToFamily } = await import( + "../../../src/resolvers/Mutation/adminAddFamilyMember" + ); + await addUserToFamily?.({}, args, context); + } catch (error: any) { + expect(spy).toBeCalledWith(FAMILY_NOT_FOUND_ERROR.MESSAGE); + expect(error.message).toEqual(FAMILY_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws NotFoundError if no user exists with _id === args.userId`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + try { + const args: MutationAddUserToFamilyArgs = { + familyId: testFamily?._id, + userId: { input: Types.ObjectId().toString(), output: "" }, + }; + + const context = { + userId: testUser?._id, + }; + + const { addUserToFamily } = await import( + "../../../src/resolvers/Mutation/adminAddFamilyMember" + ); + await addUserToFamily?.({}, args, context); + } catch (error: any) { + expect(spy).toBeCalledWith(USER_NOT_FOUND_ERROR.MESSAGE); + expect(error.message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws ConflictError if user with _id === args.userId is already a member + of family group with _id === args.familyId`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + try { + const args: MutationAddUserToFamilyArgs = { + familyId: testFamily?._id, + userId: testUser?.id, + }; + + const context = { + userId: testUser?._id, + }; + + const { addUserToFamily } = await import( + "../../../src/resolvers/Mutation/adminAddFamilyMember" + ); + await addUserToFamily?.({}, args, context); + } catch (error: any) { + expect(spy).toBeCalledWith(USER_ALREADY_MEMBER_ERROR.MESSAGE); + expect(error.message).toEqual(USER_ALREADY_MEMBER_ERROR.MESSAGE); + } + }); + + it(`add the family group with _id === args.familyId and returns it`, async () => { + await Family.updateOne( + { + _id: testFamily?._id, + }, + { + $set: { + users: [], + }, + } + ); + + const args: MutationAddUserToFamilyArgs = { + familyId: testFamily?.id, + userId: testUser?.id, + }; + + const context = { + userId: testUser?.id, + }; + + const { addUserToFamily } = await import( + "../../../src/resolvers/Mutation/adminAddFamilyMember" + ); + const addUserToFamilyPayload = await addUserToFamily?.({}, args, context); + expect(addUserToFamilyPayload?._id).toEqual(testFamily?._id); + expect(addUserToFamilyPayload?.users).toEqual([testUser?._id]); + }); +}); \ No newline at end of file diff --git a/tests/resolvers/Mutation/adminRemoveFamilyMember.spec.ts b/tests/resolvers/Mutation/adminRemoveFamilyMember.spec.ts new file mode 100644 index 00000000000..40d62faaab4 --- /dev/null +++ b/tests/resolvers/Mutation/adminRemoveFamilyMember.spec.ts @@ -0,0 +1,161 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { User } from "../../../src/models"; +import { Family } from "../../../src/models/Family"; +import type { MutationRemoveUserFromFamilyArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { removeUserFromFamily as removeUserFromFamilyResolver } from "../../../src/resolvers/Mutation/adminRemoveFamilyMember"; +import { + FAMILY_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_ERROR, + USER_NOT_AUTHORIZED_SUPERADMIN, +} from "../../../src/constants"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { TestUserType } from "../../helpers/user"; +import type { TestFamilyType } from "../../helpers/family"; +import { createTestFamily } from "../../helpers/family"; + +let MONGOOSE_INSTANCE: typeof mongoose; +let testUser: TestUserType; +let testFamily: TestFamilyType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultArray = await createTestFamily(); + testUser = resultArray[0]; + testFamily = resultArray[1]; +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolver -> Mutation -> removerUserFromFamily", () => { + it(`throws user is not SUPERADMIN error if current user is with _id === context.userId is not SUPERADMIN`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + + try { + const args: MutationRemoveUserFromFamilyArgs = { + userId: testUser?.id, + familyId: testFamily?.id + } + + const context = { + userId: testUser?.id + } + + const { removeUserFromFamily } = await import( + "../../../src/resolvers/Mutation/adminRemoveFamilyMember" + ); + + await removeUserFromFamily?.({}, args, context); + } catch (error: any) { + expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE); + expect(error.message).toEqual( + `Translated ${USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE}` + ); + } + }) + + it(`throws NotFoundError if no family exists with _id === args.familyId`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => message); + try { + const args: MutationRemoveUserFromFamilyArgs = { + familyId: Types.ObjectId().toString(), + userId: "", + }; + + const context = { + userId: testUser?._id, + }; + + const { removeUserFromFamily: removeUserFromFamilyResolver } = + await import("../../../src/resolvers/Mutation/adminRemoveFamilyMember"); + + await removeUserFromFamilyResolver?.({}, args, context); + } catch (error: any) { + expect(spy).toBeCalledWith(FAMILY_NOT_FOUND_ERROR.MESSAGE); + expect(error.message).toEqual(FAMILY_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`throws UnauthorizedError if users field of family with _id === args.familyId + does not contain user with _id === args.userId`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementationOnce((message) => message); + try { + await User.updateOne({ + _id: testUser?._id, + }); + + const args: MutationRemoveUserFromFamilyArgs = { + familyId: testFamily?.id, + userId: "", + }; + + const context = { + userId: testUser?.id, + }; + + const { removeUserFromFamily: removeUserFromFamilyResolver } = + await import("../../../src/resolvers/Mutation/adminRemoveFamilyMember"); + + await removeUserFromFamilyResolver?.({}, args, context); + } catch (error: any) { + expect(spy).toBeCalledWith(USER_NOT_AUTHORIZED_ERROR.MESSAGE); + expect(error.message).toEqual(USER_NOT_AUTHORIZED_ERROR.MESSAGE); + } + }); + + it(`removes user with _id === args.userId from users list field of family + with _id === args.familyId and returns the updated family`, async () => { + const { requestContext } = await import("../../../src/libraries"); + vi.spyOn(requestContext, "translate").mockImplementationOnce( + (message) => `Translated${message}` + ); + + await Family.updateOne( + { + _id: testFamily?.id, + }, + { + $push: { + users: testUser?.id, + }, + } + ); + + const args: MutationRemoveUserFromFamilyArgs = { + familyId: testFamily?.id, + userId: testUser?.id, + }; + + const context = { + userId: testUser?.id, + }; + + const removerUserFromFamilyPayload = await removeUserFromFamilyResolver?.( + {}, + args, + context + ); + + const testRemoveUserFromFamilyPayload = await Family.findOne({ + _id: testFamily?._id, + }).lean(); + + expect(removerUserFromFamilyPayload).toEqual( + testRemoveUserFromFamilyPayload + ); + }); +}); diff --git a/tests/resolvers/Mutation/createFamilyGroup.spec.ts b/tests/resolvers/Mutation/createFamilyGroup.spec.ts new file mode 100644 index 00000000000..e0ca12a6557 --- /dev/null +++ b/tests/resolvers/Mutation/createFamilyGroup.spec.ts @@ -0,0 +1,76 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import type { MutationCreateFamilyGroupArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { createFamilyGroup as createFamilyGroupResolver } from "../../../src/resolvers/Mutation/createFamilyGroup"; +import { USER_NOT_FOUND_ERROR } from "../../../src/constants"; +import { beforeAll, afterAll, describe, it, expect, vi } from "vitest"; +import type { TestUserType } from "../../helpers/userAndOrg"; +import { createTestUser } from "../../helpers/user"; + +let testUser: TestUserType; +let MONGOOSE_INSTANCE: typeof mongoose; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const resultsArray = await createTestUser(); + + testUser = resultsArray; + const { requestContext } = await import("../../../src/libraries"); + vi.spyOn(requestContext, "translate").mockImplementation( + (message) => message + ); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> createFamilyGroup", () => { + it(`throws NotFoundError if no user exists for any one of the ids in args.data.userIds`, async () => { + try { + const args: MutationCreateFamilyGroupArgs = { + data: { + title: "", + userIds: [Types.ObjectId().toString()], + }, + }; + + const context = { + userIds: testUser?.id, + }; + + await createFamilyGroupResolver?.({}, args, context); + } catch (error: any) { + expect(error.message).toEqual(USER_NOT_FOUND_ERROR.MESSAGE); + } + }); + + it(`creates the Family and returns it`, async () => { + const args: MutationCreateFamilyGroupArgs = { + data: { + title: "title", + userIds: [testUser?.id], + }, + }; + + const context = { + userId: testUser?.id, + }; + + const createFamilyGroupPayload = await createFamilyGroupResolver?.( + {}, + args, + context + ); + + expect(createFamilyGroupPayload).toEqual( + expect.objectContaining({ + title: "title", + users: [testUser?.id], + }) + ); + }); +}); diff --git a/tests/resolvers/Mutation/removeFamilyGroup.spec.ts b/tests/resolvers/Mutation/removeFamilyGroup.spec.ts new file mode 100644 index 00000000000..dd2fb922f3c --- /dev/null +++ b/tests/resolvers/Mutation/removeFamilyGroup.spec.ts @@ -0,0 +1,132 @@ +import "dotenv/config"; +import type mongoose from "mongoose"; +import { Types } from "mongoose"; +import { Family } from "../../../src/models/Family"; +import { removeFamily as removeFamilyResolver } from "../../../src/resolvers/Mutation/removeFamilyGroup"; +import type { MutationRemoveFamilyArgs } from "../../../src/types/generatedGraphQLTypes"; +import { connect, disconnect } from "../../helpers/db"; + +import { + FAMILY_NOT_FOUND_ERROR, + USER_NOT_AUTHORIZED_SUPERADMIN, +} from "../../../src/constants"; +import { + beforeAll, + afterAll, + describe, + it, + expect, + afterEach, + vi, +} from "vitest"; +import type { TestUserType } from "../../helpers/user"; +import type { TestFamilyType } from "../../helpers/family"; +import { createTestFamily } from "../../helpers/family"; + + +let MONGOOSE_INSTANCE: typeof mongoose; +let testUser: TestUserType; +let testFamily: TestFamilyType; + +beforeAll(async () => { + MONGOOSE_INSTANCE = await connect(); + const temp = await createTestFamily(); + testUser = temp[0]; + testFamily = temp[1]; + testFamily = await Family.findOneAndUpdate( + { + _id: testFamily?._id, + }, + { + new: true, + } + ); +}); + +afterAll(async () => { + await disconnect(MONGOOSE_INSTANCE); +}); + +describe("resolvers -> Mutation -> removeFamily", () => { + afterEach(() => { + vi.resetAllMocks(); + vi.doMock("../../src/constants"); + vi.resetModules(); + }); + + it(`throws User is not SUPERADMIN error if current user is with _id === context.userId is not a SUPERADMIN`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => message); + + try { + const args: MutationRemoveFamilyArgs = { + familyId: testFamily?.id, + }; + + const context = { + userId: testUser?.id, + }; + + const { removeFamily: removeFamilyResolver } = await import( + "../../../src/resolvers/Mutation/removeFamilyGroup" + ); + + await removeFamilyResolver?.({}, args, context); + } catch (error: any) { + expect(spy).toHaveBeenCalledWith(USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE); + expect(error.message).toEqual( + `Translated ${USER_NOT_AUTHORIZED_SUPERADMIN.MESSAGE}` + ); + } + }); + + it(`throws NotFoundError if no family exists with _id === args.familyId`, async () => { + const { requestContext } = await import("../../../src/libraries"); + const spy = vi + .spyOn(requestContext, "translate") + .mockImplementation((message) => `Translated ${message}`); + + try { + const args: MutationRemoveFamilyArgs = { + familyId: Types.ObjectId().toString(), + }; + + const context = { + userId: testUser?._id, + }; + + const { removeFamily: removeFamilyResolver } = await import( + "../../../src/resolvers/Mutation/removeFamilyGroup" + ); + + await removeFamilyResolver?.({}, args, context); + } catch (error: any) { + expect(spy).toHaveBeenCalledWith(FAMILY_NOT_FOUND_ERROR.MESSAGE); + expect(error.message).toEqual( + `Translated ${FAMILY_NOT_FOUND_ERROR.MESSAGE}` + ); + } + }); + + it(`removes the family with _id === args.familyId and returns it`, async () => { + const args: MutationRemoveFamilyArgs = { + familyId: testFamily!._id, + }; + + const context = { + userId: testUser!._id, + }; + + const removeFamilyPayload = await removeFamilyResolver?.({}, args, context); + + expect(removeFamilyPayload).toEqual(testFamily!.toObject()); + + const testRemovedFamily = await Family.findOne({ + _id: testFamily!._id, + }); + + expect(testRemovedFamily).toEqual(null); + }); +});