forked from PalisadoesFoundation/talawa-api
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement tagsAssignedWith connection on the User schema
- Loading branch information
Showing
3 changed files
with
285 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import type { UserResolvers } from "../../types/generatedGraphQLTypes"; | ||
// import { tagsAssignedWith } from "./tagsAssignedWith"; | ||
import { tagsAssignedWith } from "./tagsAssignedWith"; | ||
import { posts } from "./posts"; | ||
|
||
export const User: UserResolvers = { | ||
// tagsAssignedWith, | ||
tagsAssignedWith, | ||
posts, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import type { UserResolvers } from "../../types/generatedGraphQLTypes"; | ||
import type { | ||
InterfaceOrganizationTagUser, | ||
InterfaceTagUser, | ||
} from "../../models"; | ||
import { TagUser } from "../../models"; | ||
import { | ||
type DefaultGraphQLArgumentError, | ||
type ParseGraphQLConnectionCursorArguments, | ||
type ParseGraphQLConnectionCursorResult, | ||
getCommonGraphQLConnectionFilter, | ||
getCommonGraphQLConnectionSort, | ||
parseGraphQLConnectionArguments, | ||
transformToDefaultGraphQLConnection, | ||
} from "../../utilities/graphQLConnection"; | ||
import { GraphQLError } from "graphql"; | ||
import { MAXIMUM_FETCH_LIMIT } from "../../constants"; | ||
import type { Types } from "mongoose"; | ||
|
||
/** | ||
* Resolver function for the `tagsAssignedWith` field of a `User`. | ||
* | ||
* This resolver is used to resolve the `tagsAssignedWith` field of a `User` type. | ||
* | ||
* @param parent - The parent object representing the user. It contains information about the user, including the ID of the user. | ||
* @param args - The arguments provided to the field. These arguments are used to filter, sort, and paginate the tags assigned to the user. | ||
* @returns A promise that resolves to a connection object containing the tags assigned to the user. | ||
* | ||
* @see TagUser - The TagUser model used to interact with the tag users collection in the database. | ||
* @see parseGraphQLConnectionArguments - The function used to parse the GraphQL connection arguments (filter, sort, pagination). | ||
* @see transformToDefaultGraphQLConnection - The function used to transform the list of tags assigned to the user into a connection object. | ||
* @see getCommonGraphQLConnectionFilter - The function used to get the common filter object for the GraphQL connection. | ||
* @see getCommonGraphQLConnectionSort - The function used to get the common sort object for the GraphQL connection. | ||
* @see MAXIMUM_FETCH_LIMIT - The maximum number of users that can be fetched in a single request. | ||
* @see GraphQLError - The error class used to throw GraphQL errors. | ||
* @see UserTagResolvers - The type definition for the resolvers of the User fields. | ||
* | ||
*/ | ||
export const tagsAssignedWith: UserResolvers["tagsAssignedWith"] = async ( | ||
parent, | ||
args, | ||
) => { | ||
const parseGraphQLConnectionArgumentsResult = | ||
await parseGraphQLConnectionArguments({ | ||
args, | ||
parseCursor: (args) => | ||
parseCursor({ | ||
...args, | ||
userId: parent._id, | ||
}), | ||
maximumLimit: MAXIMUM_FETCH_LIMIT, | ||
}); | ||
|
||
if (!parseGraphQLConnectionArgumentsResult.isSuccessful) { | ||
throw new GraphQLError("Invalid arguments provided.", { | ||
extensions: { | ||
code: "INVALID_ARGUMENTS", | ||
errors: parseGraphQLConnectionArgumentsResult.errors, | ||
}, | ||
}); | ||
} | ||
|
||
const { parsedArgs } = parseGraphQLConnectionArgumentsResult; | ||
|
||
const filter = getCommonGraphQLConnectionFilter({ | ||
cursor: parsedArgs.cursor, | ||
direction: parsedArgs.direction, | ||
}); | ||
|
||
const sort = getCommonGraphQLConnectionSort({ | ||
direction: parsedArgs.direction, | ||
}); | ||
|
||
const [objectList, totalCount] = await Promise.all([ | ||
TagUser.find({ | ||
...filter, | ||
userId: parent._id, | ||
}) | ||
.sort(sort) | ||
.limit(parsedArgs.limit) | ||
.populate("tagId") | ||
.lean() | ||
.exec(), | ||
|
||
TagUser.find({ | ||
userId: parent._id, | ||
}) | ||
.countDocuments() | ||
.exec(), | ||
]); | ||
|
||
return transformToDefaultGraphQLConnection< | ||
ParsedCursor, | ||
InterfaceTagUser, | ||
InterfaceOrganizationTagUser | ||
>({ | ||
createNode: (object) => { | ||
return object.tagId as InterfaceOrganizationTagUser; | ||
}, | ||
objectList, | ||
parsedArgs, | ||
totalCount, | ||
}); | ||
}; | ||
|
||
/* | ||
This is typescript type of the parsed cursor for this connection resolver. | ||
*/ | ||
type ParsedCursor = string; | ||
|
||
/** | ||
* Parses the cursor value for the `tagsAssignedWith` connection resolver. | ||
* | ||
* This function is used to parse the cursor value provided to the `tagsAssignedWith` connection resolver. | ||
* | ||
* @param cursorValue - The cursor value to be parsed. | ||
* @param cursorName - The name of the cursor argument. | ||
* @param cursorPath - The path of the cursor argument in the GraphQL query. | ||
* @param tagId - The ID of the user to which the tags are assigned. | ||
* @returns An object containing the parsed cursor value or an array of errors if the cursor value is invalid. | ||
* | ||
* @see TagUser - The TagUser model used to interact with the tag users collection in the database. | ||
* @see DefaultGraphQLArgumentError - The type definition for the default GraphQL argument error. | ||
* @see ParseGraphQLConnectionCursorArguments - The type definition for the arguments provided to the parseCursor function. | ||
* @see ParseGraphQLConnectionCursorResult - The type definition for the result of the parseCursor function. | ||
* | ||
*/ | ||
export const parseCursor = async ({ | ||
cursorValue, | ||
cursorName, | ||
cursorPath, | ||
userId, | ||
}: ParseGraphQLConnectionCursorArguments & { | ||
userId: string | Types.ObjectId; | ||
}): ParseGraphQLConnectionCursorResult<ParsedCursor> => { | ||
const errors: DefaultGraphQLArgumentError[] = []; | ||
const tagUser = await TagUser.findOne({ | ||
_id: cursorValue, | ||
userId, | ||
}); | ||
|
||
if (!tagUser) { | ||
errors.push({ | ||
message: `Argument ${cursorName} is an invalid cursor.`, | ||
path: cursorPath, | ||
}); | ||
} | ||
|
||
if (errors.length !== 0) { | ||
return { | ||
errors, | ||
isSuccessful: false, | ||
}; | ||
} | ||
|
||
return { | ||
isSuccessful: true, | ||
parsedCursor: cursorValue, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
|
||
import "dotenv/config"; | ||
import { | ||
parseCursor, | ||
tagsAssignedWith as tagsAssignedWithResolver, | ||
} from "../../../src/resolvers/User/tagsAssignedWith"; | ||
import { connect, disconnect } from "../../helpers/db"; | ||
import type mongoose from "mongoose"; | ||
import { beforeAll, afterAll, describe, it, expect } from "vitest"; | ||
import type { TestUserTagType } from "../../helpers/tags"; | ||
import type { TestUserType } from "../../helpers/userAndOrg"; | ||
import { createTagsAndAssignToUser } from "../../helpers/tags"; | ||
import { GraphQLError } from "graphql"; | ||
import type { DefaultGraphQLArgumentError } from "../../../src/utilities/graphQLConnection"; | ||
import { | ||
type InterfaceUser, | ||
TagUser, | ||
OrganizationTagUser, | ||
} from "../../../src/models"; | ||
import { Types } from "mongoose"; | ||
|
||
let MONGOOSE_INSTANCE: typeof mongoose; | ||
let testTag: TestUserTagType, testUser: TestUserType; | ||
|
||
beforeAll(async () => { | ||
MONGOOSE_INSTANCE = await connect(); | ||
[testUser, , [testTag]] = await createTagsAndAssignToUser(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await disconnect(MONGOOSE_INSTANCE); | ||
}); | ||
|
||
describe("tagsAssignedWith resolver", () => { | ||
it(`throws GraphQLError if invalid arguments are provided to the resolver`, async () => { | ||
const parent = testUser as InterfaceUser; | ||
try { | ||
await tagsAssignedWithResolver?.(parent, {}, {}); | ||
} catch (error) { | ||
if (error instanceof GraphQLError) { | ||
expect(error.extensions.code).toEqual("INVALID_ARGUMENTS"); | ||
expect( | ||
(error.extensions.errors as DefaultGraphQLArgumentError[]).length, | ||
).toBeGreaterThan(0); | ||
} | ||
} | ||
}); | ||
|
||
it(`returns the expected connection object`, async () => { | ||
const parent = testUser as InterfaceUser; | ||
const connection = await tagsAssignedWithResolver?.( | ||
parent, | ||
{ | ||
first: 3, | ||
}, | ||
{}, | ||
); | ||
|
||
const tagUser = await TagUser.findOne({ | ||
tagId: testTag?._id, | ||
userId: testUser?._id, | ||
}); | ||
|
||
const tag = await OrganizationTagUser.findOne({ | ||
_id: testTag?._id, | ||
}); | ||
|
||
const totalCount = await TagUser.find({ | ||
userId: testUser?._id, | ||
}).countDocuments(); | ||
|
||
expect(connection).toEqual({ | ||
edges: [ | ||
{ | ||
cursor: tagUser?._id.toString(), | ||
node: tag?.toObject(), | ||
}, | ||
], | ||
pageInfo: { | ||
endCursor: tagUser?._id.toString(), | ||
hasNextPage: false, | ||
hasPreviousPage: false, | ||
startCursor: tagUser?._id.toString(), | ||
}, | ||
totalCount, | ||
}); | ||
}); | ||
}); | ||
|
||
describe("parseCursor function", () => { | ||
it("returns failure state if argument cursorValue is an invalid cursor", async () => { | ||
const result = await parseCursor({ | ||
cursorName: "after", | ||
cursorPath: ["after"], | ||
cursorValue: new Types.ObjectId().toString(), | ||
userId: testUser?._id.toString() as string, | ||
}); | ||
|
||
expect(result.isSuccessful).toEqual(false); | ||
if (result.isSuccessful === false) { | ||
expect(result.errors.length).toBeGreaterThan(0); | ||
} | ||
}); | ||
|
||
it("returns success state if argument cursorValue is a valid cursor", async () => { | ||
const tagUser = await TagUser.findOne({ | ||
tagId: testTag?._id, | ||
userId: testUser?._id, | ||
}); | ||
|
||
const result = await parseCursor({ | ||
cursorName: "after", | ||
cursorPath: ["after"], | ||
cursorValue: tagUser?._id.toString() as string, | ||
userId: testUser?._id.toString() as string, | ||
}); | ||
|
||
expect(result.isSuccessful).toEqual(true); | ||
if (result.isSuccessful === true) { | ||
expect(result.parsedCursor).toEqual(tagUser?._id.toString()); | ||
} | ||
}); | ||
}); |