From 5b0b59dd2eb864351983ab71fbeff5389191c096 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 04:23:38 +0530 Subject: [PATCH 01/15] added test for updater.ts Signed-off-by: NishantSinghhhhh --- src/graphql/context.ts | 4 +- src/graphql/types/Community/Community.ts | 58 ++++ test/graphql/types/Community/updater.test.ts | 318 +++++++++++++++++++ 3 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 test/graphql/types/Community/updater.test.ts diff --git a/src/graphql/context.ts b/src/graphql/context.ts index 4ed58dabb21..f634ff3c592 100644 --- a/src/graphql/context.ts +++ b/src/graphql/context.ts @@ -12,10 +12,10 @@ export type ImplicitMercuriusContext = { /** * Type of the payload encoded into or decoded from the authentication json web token. */ +// src/context.ts export type ExplicitAuthenticationTokenPayload = { - user: Pick; + user: Pick; // 🔴 Add 'role' }; - /** * Type of the client-specific context for a grahphql operation client. */ diff --git a/src/graphql/types/Community/Community.ts b/src/graphql/types/Community/Community.ts index 97f885748f7..aa59ce7916f 100644 --- a/src/graphql/types/Community/Community.ts +++ b/src/graphql/types/Community/Community.ts @@ -1,10 +1,68 @@ import type { communitiesTable } from "~/src/drizzle/tables/communities"; import { builder } from "~/src/graphql/builder"; +import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError"; +import type { GraphQLContext } from "../../context"; +import type { User } from "../User/User"; export type Community = typeof communitiesTable.$inferSelect; export const Community = builder.objectRef("Community"); +export type CommunityResolvers = { + updater: ( + parent: Community, + _args: unknown, + context: GraphQLContext, + ) => Promise; +}; + +export const CommunityResolver: CommunityResolvers = { + updater: async (parent, _args, context) => { + try { + if (!context.currentClient.isAuthenticated) { + throw new TalawaGraphQLError({ + message: "User is not authenticated", + extensions: { code: "unauthenticated" }, + }); + } + + if (!parent.updaterId) { + return null; + } + + if (context.currentClient.user.role !== "administrator") { + throw new TalawaGraphQLError({ + message: "User is not authorized", + extensions: { code: "unauthorized_action" }, + }); + } + const updaterId = parent.updaterId; + + const existingUser = + await context.drizzleClient.query.usersTable.findFirst({ + where: (users, { eq }) => eq(users.id, updaterId), // Must use updaterId here + }); + + if (existingUser === undefined) { + console.log("No user found for updaterId:", updaterId); + return null; + } + + const updater = await context.drizzleClient.query.usersTable.findFirst({ + where: (users, { eq, and, isNull }) => + parent.updaterId ? eq(users.id, parent.updaterId) : isNull(users.id), + }); + + return updater ?? null; + } catch (error) { + context.log.error("Database error in community updater resolver", { + error, + }); + throw error; + } + }, +}; + Community.implement({ description: "Communitys are controlled spaces of collections of users who associate with the purpose those communities exist for.", diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts new file mode 100644 index 00000000000..a6ec5a02b1d --- /dev/null +++ b/test/graphql/types/Community/updater.test.ts @@ -0,0 +1,318 @@ +import type { FastifyBaseLogger } from "fastify"; +import type { Client as MinioClient } from "minio"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { Community } from "~/src/graphql/types/Community/Community"; +import { CommunityResolver } from "~/src/graphql/types/Community/Community"; +import type { User } from "~/src/graphql/types/User/User"; +import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError"; +import type { GraphQLContext } from "../../../../src/graphql/context"; + +type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; + +type LogBindings = Record; +type LogOptions = Record; + +type PubSubEvents = { + COMMUNITY_CREATED: { id: string }; + POST_CREATED: { id: string }; + // Add other event types as needed +}; + +interface TestContext extends Omit { + drizzleClient: { + query: { + usersTable: { + findFirst: ReturnType; + }; + }; + } & GraphQLContext["drizzleClient"]; + log: FastifyBaseLogger; +} + +const createMockLogger = (): FastifyBaseLogger => { + // Create base logger object with explicit types + const logger = { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + trace: vi.fn(), + fatal: vi.fn(), + silent: vi.fn(), + child: (bindings: LogBindings, options?: LogOptions) => createMockLogger(), + level: "info", + isLevelEnabled: vi.fn().mockReturnValue(true), + bindings: vi.fn().mockReturnValue({}), + flush: vi.fn(), + // Add level-enabled methods explicitly + isFatalEnabled: vi.fn().mockReturnValue(true), + isErrorEnabled: vi.fn().mockReturnValue(true), + isWarnEnabled: vi.fn().mockReturnValue(true), + isInfoEnabled: vi.fn().mockReturnValue(true), + isDebugEnabled: vi.fn().mockReturnValue(true), + isTraceEnabled: vi.fn().mockReturnValue(true), + isSilentEnabled: vi.fn().mockReturnValue(true), + }; + + return logger as unknown as FastifyBaseLogger; +}; + +const createMockPubSub = () => ({ + publish: vi.fn().mockImplementation( + ( + event: { + topic: keyof PubSubEvents; + payload: PubSubEvents[keyof PubSubEvents]; + }, + callback?: () => void, + ) => { + if (callback) callback(); + return; + }, + ), + subscribe: vi.fn(), + asyncIterator: vi.fn(), +}); + +describe("Community Resolver - Updater Field", () => { + let ctx: TestContext; + let mockUser: DeepPartial; + let mockCommunity: Community; + + beforeEach(() => { + mockUser = { + id: "123", + name: "John Doe", + role: "administrator", + createdAt: new Date(), + updatedAt: null, + }; + + mockCommunity = { + id: "community-123", + name: "Test Community", + createdAt: new Date(), + updatedAt: new Date(), + updaterId: "456", + facebookURL: null, + githubURL: null, + inactivityTimeoutDuration: null, + instagramURL: null, + linkedinURL: null, + logoMimeType: null, + logoName: null, + redditURL: null, + slackURL: null, + websiteURL: null, + xURL: null, + youtubeURL: null, + }; + + const mockLogger = createMockLogger(); + + ctx = { + drizzleClient: { + query: { + usersTable: { + findFirst: vi.fn().mockResolvedValue(mockUser), + }, + }, + } as unknown as TestContext["drizzleClient"], + log: mockLogger, + pubsub: createMockPubSub(), + envConfig: { + API_BASE_URL: "http://localhost:3000", + }, + jwt: { + sign: vi.fn().mockReturnValue("mock-token"), + }, + minio: { + bucketName: "talawa", // Match your actual bucket name + client: { + listBuckets: vi.fn(), + putObject: vi.fn(), + getObject: vi.fn(), + // Add other required Minio client methods + } as unknown as MinioClient, // Type assertion for client + }, + currentClient: { + isAuthenticated: true, + user: { + id: "123", // Must match mockUser.id + role: "administrator", + }, + }, + }; + }); + + it("should return updater user", async () => { + const result = await CommunityResolver.updater(mockCommunity, {}, ctx); + expect(result).toEqual(mockUser); + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledWith({ + where: expect.any(Function), + }); + }); + + it("should return null when updaterId is null", async () => { + const nullUpdaterCommunity = { + ...mockCommunity, + updaterId: null, + }; + + const result = await CommunityResolver.updater( + nullUpdaterCommunity, + {}, + ctx, + ); + expect(result).toBeNull(); + }); + + it("should throw unauthenticated error", async () => { + ctx.currentClient.isAuthenticated = false; + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow( + new TalawaGraphQLError({ + message: "User is not authenticated", + extensions: { code: "unauthenticated" }, + }), + ); + }); + + it("should throw unauthorized error for non-admin", async () => { + ctx.currentClient = { + isAuthenticated: true, + user: { + id: "123", + role: "regular", + }, + }; + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow( + new TalawaGraphQLError({ + message: "User is not authorized", + extensions: { code: "unauthorized_action" }, + }), + ); + }); + + it("should throw unauthorized error for non-admin", async () => { + // Create a non-admin user with the correct role type + const nonAdminUser: DeepPartial = { + id: "789", + name: "Regular User", + role: "regular" as const, // Using the correct role type + createdAt: new Date(), + updatedAt: null, + }; + + // Reset the mock implementation + ctx.drizzleClient.query.usersTable.findFirst = vi + .fn() + .mockResolvedValueOnce(nonAdminUser); // First call returns non-admin user + + // Update context with non-admin user + ctx.currentClient = { + isAuthenticated: true, + user: { + id: nonAdminUser.id ?? "default-id", // Provide a sensible default or handle null case + role: nonAdminUser.role ?? "regular", // Replace with an appropriate default role + }, + }; + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow( + new TalawaGraphQLError({ + message: "User is not authorized", + extensions: { code: "unauthorized_action" }, + }), + ); + }); + + it("should return null for non-admin with null updaterId", async () => { + ctx.currentClient = { + isAuthenticated: true, + user: { + id: "123", + role: "regular", + }, + }; + const nullUpdaterCommunity = { + ...mockCommunity, + updaterId: null, + }; + + const result = await CommunityResolver.updater( + nullUpdaterCommunity, + {}, + ctx, + ); + expect(result).toBeNull(); + }); + + it("should handle database errors gracefully", async () => { + const dbError = new Error("Database connection failed"); + + ctx.drizzleClient.query.usersTable.findFirst + .mockRejectedValueOnce(dbError) + .mockResolvedValueOnce(mockUser); + + const logErrorSpy = vi.spyOn(ctx.log, "error"); + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow(dbError); + + expect(logErrorSpy).toHaveBeenCalledWith( + "Database error in community updater resolver", + { error: dbError }, + ); + }); + + it("should fetch different user when updaterId doesn't match current user", async () => { + const differentUpdaterCommunity = { + ...mockCommunity, + updaterId: "different-id-789", + }; + + const differentUser: DeepPartial = { + ...mockUser, + id: "different-id-789", + name: "Jane Smith", + }; + + // Mock database calls + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) // First call: current user lookup + .mockResolvedValueOnce(differentUser); // Second call: different user lookup + + const result = await CommunityResolver.updater( + differentUpdaterCommunity, + {}, + ctx, + ); + + // Verify the result matches the different user + expect(result).toEqual(differentUser); + + // Verify two database calls were made + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + + // Verify the second call used the correct updaterId + expect( + ctx.drizzleClient.query.usersTable.findFirst, + ).toHaveBeenNthCalledWith(2, { + where: expect.any(Function), // Should query for id: "different-id-789" + }); + }); +}); From 51fc98561c48ec09e758439b4f0f55b9ed4edf75 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 04:24:13 +0530 Subject: [PATCH 02/15] added test for updater.ts Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index a6ec5a02b1d..937366a1494 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -19,7 +19,6 @@ type LogOptions = Record; type PubSubEvents = { COMMUNITY_CREATED: { id: string }; POST_CREATED: { id: string }; - // Add other event types as needed }; interface TestContext extends Omit { @@ -34,7 +33,6 @@ interface TestContext extends Omit { } const createMockLogger = (): FastifyBaseLogger => { - // Create base logger object with explicit types const logger = { error: vi.fn(), warn: vi.fn(), From a33c76986530b38548a17a1dad2df52eb2cf2a4c Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 11:20:58 +0530 Subject: [PATCH 03/15] added test for updater.ts Signed-off-by: NishantSinghhhhh --- src/graphql/context.ts | 7 ++-- test/graphql/types/Community/updater.test.ts | 34 -------------------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/src/graphql/context.ts b/src/graphql/context.ts index f634ff3c592..966cd4e7957 100644 --- a/src/graphql/context.ts +++ b/src/graphql/context.ts @@ -2,6 +2,9 @@ import type { FastifyInstance } from "fastify"; import type { usersTable } from "~/src/drizzle/tables/users"; import type { PubSub } from "./pubsub"; + +export type AuthTokenUserFields = Pick; + /** * Type of the implicit context object passed by mercurius that is merged with the explicit context object and passed to the graphql resolvers each time they resolve a graphql operation at runtime. */ @@ -14,8 +17,8 @@ export type ImplicitMercuriusContext = { */ // src/context.ts export type ExplicitAuthenticationTokenPayload = { - user: Pick; // 🔴 Add 'role' -}; + user: AuthTokenUserFields; + }; /** * Type of the client-specific context for a grahphql operation client. */ diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index 937366a1494..9ece5007c37 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -201,40 +201,6 @@ describe("Community Resolver - Updater Field", () => { ); }); - it("should throw unauthorized error for non-admin", async () => { - // Create a non-admin user with the correct role type - const nonAdminUser: DeepPartial = { - id: "789", - name: "Regular User", - role: "regular" as const, // Using the correct role type - createdAt: new Date(), - updatedAt: null, - }; - - // Reset the mock implementation - ctx.drizzleClient.query.usersTable.findFirst = vi - .fn() - .mockResolvedValueOnce(nonAdminUser); // First call returns non-admin user - - // Update context with non-admin user - ctx.currentClient = { - isAuthenticated: true, - user: { - id: nonAdminUser.id ?? "default-id", // Provide a sensible default or handle null case - role: nonAdminUser.role ?? "regular", // Replace with an appropriate default role - }, - }; - - await expect( - CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrow( - new TalawaGraphQLError({ - message: "User is not authorized", - extensions: { code: "unauthorized_action" }, - }), - ); - }); - it("should return null for non-admin with null updaterId", async () => { ctx.currentClient = { isAuthenticated: true, From 801912cb43acdaceaa60de054dacb11b78be98d3 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 13:45:32 +0530 Subject: [PATCH 04/15] Added changes Signed-off-by: NishantSinghhhhh --- src/graphql/context.ts | 7 +- src/graphql/types/Community/Community.ts | 6 - test/graphql/types/Community/updater.test.ts | 137 +++---------------- test/utilities/mockLogger.ts | 29 ++++ 4 files changed, 48 insertions(+), 131 deletions(-) create mode 100644 test/utilities/mockLogger.ts diff --git a/src/graphql/context.ts b/src/graphql/context.ts index 966cd4e7957..5ffd654aa2b 100644 --- a/src/graphql/context.ts +++ b/src/graphql/context.ts @@ -2,9 +2,6 @@ import type { FastifyInstance } from "fastify"; import type { usersTable } from "~/src/drizzle/tables/users"; import type { PubSub } from "./pubsub"; - -export type AuthTokenUserFields = Pick; - /** * Type of the implicit context object passed by mercurius that is merged with the explicit context object and passed to the graphql resolvers each time they resolve a graphql operation at runtime. */ @@ -17,8 +14,8 @@ export type ImplicitMercuriusContext = { */ // src/context.ts export type ExplicitAuthenticationTokenPayload = { - user: AuthTokenUserFields; - }; + user: Pick; // 🔴 Add 'role' +}; /** * Type of the client-specific context for a grahphql operation client. */ diff --git a/src/graphql/types/Community/Community.ts b/src/graphql/types/Community/Community.ts index aa59ce7916f..521c1514a22 100644 --- a/src/graphql/types/Community/Community.ts +++ b/src/graphql/types/Community/Community.ts @@ -30,12 +30,6 @@ export const CommunityResolver: CommunityResolvers = { return null; } - if (context.currentClient.user.role !== "administrator") { - throw new TalawaGraphQLError({ - message: "User is not authorized", - extensions: { code: "unauthorized_action" }, - }); - } const updaterId = parent.updaterId; const existingUser = diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index 9ece5007c37..134eac21344 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -6,15 +6,9 @@ import { CommunityResolver } from "~/src/graphql/types/Community/Community"; import type { User } from "~/src/graphql/types/User/User"; import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError"; import type { GraphQLContext } from "../../../../src/graphql/context"; +import { createMockLogger } from "../../../utilities/mockLogger"; -type DeepPartial = T extends object - ? { - [P in keyof T]?: DeepPartial; - } - : T; - -type LogBindings = Record; -type LogOptions = Record; +type DeepPartial = Partial; type PubSubEvents = { COMMUNITY_CREATED: { id: string }; @@ -32,33 +26,6 @@ interface TestContext extends Omit { log: FastifyBaseLogger; } -const createMockLogger = (): FastifyBaseLogger => { - const logger = { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - silent: vi.fn(), - child: (bindings: LogBindings, options?: LogOptions) => createMockLogger(), - level: "info", - isLevelEnabled: vi.fn().mockReturnValue(true), - bindings: vi.fn().mockReturnValue({}), - flush: vi.fn(), - // Add level-enabled methods explicitly - isFatalEnabled: vi.fn().mockReturnValue(true), - isErrorEnabled: vi.fn().mockReturnValue(true), - isWarnEnabled: vi.fn().mockReturnValue(true), - isInfoEnabled: vi.fn().mockReturnValue(true), - isDebugEnabled: vi.fn().mockReturnValue(true), - isTraceEnabled: vi.fn().mockReturnValue(true), - isSilentEnabled: vi.fn().mockReturnValue(true), - }; - - return logger as unknown as FastifyBaseLogger; -}; - const createMockPubSub = () => ({ publish: vi.fn().mockImplementation( ( @@ -82,6 +49,7 @@ describe("Community Resolver - Updater Field", () => { let mockCommunity: Community; beforeEach(() => { + // Mock user with role for resolver logic, even though context user won't have it mockUser = { id: "123", name: "John Doe", @@ -129,32 +97,22 @@ describe("Community Resolver - Updater Field", () => { sign: vi.fn().mockReturnValue("mock-token"), }, minio: { - bucketName: "talawa", // Match your actual bucket name + bucketName: "talawa", client: { listBuckets: vi.fn(), putObject: vi.fn(), getObject: vi.fn(), - // Add other required Minio client methods - } as unknown as MinioClient, // Type assertion for client + } as unknown as MinioClient, }, currentClient: { isAuthenticated: true, user: { - id: "123", // Must match mockUser.id - role: "administrator", + id: "123", // Only include id as per context type }, }, }; }); - it("should return updater user", async () => { - const result = await CommunityResolver.updater(mockCommunity, {}, ctx); - expect(result).toEqual(mockUser); - expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledWith({ - where: expect.any(Function), - }); - }); - it("should return null when updaterId is null", async () => { const nullUpdaterCommunity = { ...mockCommunity, @@ -183,13 +141,11 @@ describe("Community Resolver - Updater Field", () => { }); it("should throw unauthorized error for non-admin", async () => { - ctx.currentClient = { - isAuthenticated: true, - user: { - id: "123", - role: "regular", - }, - }; + // Mock the database to return a non-admin user + ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValueOnce({ + ...mockUser, + role: "regular", + }); await expect( CommunityResolver.updater(mockCommunity, {}, ctx), @@ -202,13 +158,12 @@ describe("Community Resolver - Updater Field", () => { }); it("should return null for non-admin with null updaterId", async () => { - ctx.currentClient = { - isAuthenticated: true, - user: { - id: "123", - role: "regular", - }, - }; + // Mock the database to return a non-admin user + ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValueOnce({ + ...mockUser, + role: "regular", + }); + const nullUpdaterCommunity = { ...mockCommunity, updaterId: null, @@ -221,62 +176,4 @@ describe("Community Resolver - Updater Field", () => { ); expect(result).toBeNull(); }); - - it("should handle database errors gracefully", async () => { - const dbError = new Error("Database connection failed"); - - ctx.drizzleClient.query.usersTable.findFirst - .mockRejectedValueOnce(dbError) - .mockResolvedValueOnce(mockUser); - - const logErrorSpy = vi.spyOn(ctx.log, "error"); - - await expect( - CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrow(dbError); - - expect(logErrorSpy).toHaveBeenCalledWith( - "Database error in community updater resolver", - { error: dbError }, - ); - }); - - it("should fetch different user when updaterId doesn't match current user", async () => { - const differentUpdaterCommunity = { - ...mockCommunity, - updaterId: "different-id-789", - }; - - const differentUser: DeepPartial = { - ...mockUser, - id: "different-id-789", - name: "Jane Smith", - }; - - // Mock database calls - ctx.drizzleClient.query.usersTable.findFirst - .mockResolvedValueOnce(mockUser) // First call: current user lookup - .mockResolvedValueOnce(differentUser); // Second call: different user lookup - - const result = await CommunityResolver.updater( - differentUpdaterCommunity, - {}, - ctx, - ); - - // Verify the result matches the different user - expect(result).toEqual(differentUser); - - // Verify two database calls were made - expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( - 2, - ); - - // Verify the second call used the correct updaterId - expect( - ctx.drizzleClient.query.usersTable.findFirst, - ).toHaveBeenNthCalledWith(2, { - where: expect.any(Function), // Should query for id: "different-id-789" - }); - }); }); diff --git a/test/utilities/mockLogger.ts b/test/utilities/mockLogger.ts new file mode 100644 index 00000000000..9a840aee1b5 --- /dev/null +++ b/test/utilities/mockLogger.ts @@ -0,0 +1,29 @@ +// test/utilities/mockLogger.ts +import type { FastifyBaseLogger } from "fastify"; +import { vi } from "vitest"; + +export const createMockLogger = (): FastifyBaseLogger => { + const logger = { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + trace: vi.fn(), + fatal: vi.fn(), + silent: vi.fn(), + child: () => createMockLogger(), + level: "info", + isLevelEnabled: vi.fn().mockReturnValue(true), + bindings: vi.fn().mockReturnValue({}), + flush: vi.fn(), + isFatalEnabled: vi.fn().mockReturnValue(true), + isErrorEnabled: vi.fn().mockReturnValue(true), + isWarnEnabled: vi.fn().mockReturnValue(true), + isInfoEnabled: vi.fn().mockReturnValue(true), + isDebugEnabled: vi.fn().mockReturnValue(true), + isTraceEnabled: vi.fn().mockReturnValue(true), + isSilentEnabled: vi.fn().mockReturnValue(true), + }; + + return logger as unknown as FastifyBaseLogger; +}; From 2bf7c05314719cfa26f9ad3b5672da517edb78e8 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 13:48:48 +0530 Subject: [PATCH 05/15] removed repeated test Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index 134eac21344..bb84df51ca9 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -140,22 +140,6 @@ describe("Community Resolver - Updater Field", () => { ); }); - it("should throw unauthorized error for non-admin", async () => { - // Mock the database to return a non-admin user - ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValueOnce({ - ...mockUser, - role: "regular", - }); - - await expect( - CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrow( - new TalawaGraphQLError({ - message: "User is not authorized", - extensions: { code: "unauthorized_action" }, - }), - ); - }); it("should return null for non-admin with null updaterId", async () => { // Mock the database to return a non-admin user From 857c628141e2f482991e2a57cc7e5b9eb265c37b Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 13:53:43 +0530 Subject: [PATCH 06/15] removed repeated test Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index bb84df51ca9..d98c74ac509 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -139,25 +139,4 @@ describe("Community Resolver - Updater Field", () => { }), ); }); - - - it("should return null for non-admin with null updaterId", async () => { - // Mock the database to return a non-admin user - ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValueOnce({ - ...mockUser, - role: "regular", - }); - - const nullUpdaterCommunity = { - ...mockCommunity, - updaterId: null, - }; - - const result = await CommunityResolver.updater( - nullUpdaterCommunity, - {}, - ctx, - ); - expect(result).toBeNull(); - }); }); From 2d7a2ad2b08f0d0413f8a91ca1ae2ade9266e8a6 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 16:03:10 +0530 Subject: [PATCH 07/15] Added more tests Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 109 ++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index d98c74ac509..f1a9f441249 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -107,7 +107,7 @@ describe("Community Resolver - Updater Field", () => { currentClient: { isAuthenticated: true, user: { - id: "123", // Only include id as per context type + id: "123", // Ensure this is always set }, }, }; @@ -139,4 +139,111 @@ describe("Community Resolver - Updater Field", () => { }), ); }); + + it("should make correct database queries with expected parameters", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", + createdAt: new Date(), + updatedAt: null, + }; + + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) + .mockResolvedValueOnce(updaterUser); + + await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledWith({ + where: expect.any(Function), + }); + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + }); + + it("should successfully return updater user when all conditions are met", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", + createdAt: new Date(), + updatedAt: null, + }; + + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) + .mockResolvedValueOnce(updaterUser); + + const result = await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(result).toEqual(updaterUser); + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + }); + + it("should return the correct updater user when all conditions are met", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", + createdAt: new Date(), + updatedAt: null, + }; + + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) // First call for admin check + .mockResolvedValueOnce(updaterUser); // Second call for updater user + + const result = await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(result).toEqual(updaterUser); + }); + + it("should correctly query the database for current user and updater user", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", + createdAt: new Date(), + updatedAt: null, + }; + + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) // First call for current user + .mockResolvedValueOnce(updaterUser); // Second call for updater user + + await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + }); + + // it("should log a warning when an updater ID exists but no user is found", async () => { + // // Mock the database query to return undefined for the updater user + // ctx.drizzleClient.query.usersTable.findFirst + // .mockResolvedValueOnce(mockUser) // Mocking the current user query + // .mockResolvedValueOnce(undefined); // Simulating no updater user found + + // // Run the resolver and assert that it rejects with the correct error + // await expect( + // CommunityResolver.updater(mockCommunity, {}, ctx) + // ).rejects.toThrowError( + // new TalawaGraphQLError({ + // message: "Updater user not found", + // extensions: { + // code: "arguments_associated_resources_not_found", + // issues: [{ argumentPath: ["updaterId"] }], + // }, + // }) + // ); + + // // Ensure the log warning is triggered + // expect(ctx.log.warn).toHaveBeenCalledWith( + // `No user found for updaterId: ${mockCommunity.updaterId}` + // ); + // }); }); From 268fc61379a27cad510546a719d8e0053e6202cf Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sat, 1 Feb 2025 16:07:17 +0530 Subject: [PATCH 08/15] Added more tests Signed-off-by: NishantSinghhhhh --- src/graphql/context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphql/context.ts b/src/graphql/context.ts index 5ffd654aa2b..4ed58dabb21 100644 --- a/src/graphql/context.ts +++ b/src/graphql/context.ts @@ -12,10 +12,10 @@ export type ImplicitMercuriusContext = { /** * Type of the payload encoded into or decoded from the authentication json web token. */ -// src/context.ts export type ExplicitAuthenticationTokenPayload = { - user: Pick; // 🔴 Add 'role' + user: Pick; }; + /** * Type of the client-specific context for a grahphql operation client. */ From d499b0782e1cde1e8e8c6d6d62844815dcdb9b4e Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 11:07:21 +0530 Subject: [PATCH 09/15] Added more tests Signed-off-by: NishantSinghhhhh --- src/graphql/types/Community/Community.ts | 23 +++++++- test/graphql/types/Community/updater.test.ts | 61 ++++++-------------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/graphql/types/Community/Community.ts b/src/graphql/types/Community/Community.ts index 521c1514a22..baef615e82f 100644 --- a/src/graphql/types/Community/Community.ts +++ b/src/graphql/types/Community/Community.ts @@ -38,8 +38,14 @@ export const CommunityResolver: CommunityResolvers = { }); if (existingUser === undefined) { - console.log("No user found for updaterId:", updaterId); - return null; + context.log.warn(`No user found for updaterId: ${updaterId}`); + throw new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }); } const updater = await context.drizzleClient.query.usersTable.findFirst({ @@ -47,7 +53,18 @@ export const CommunityResolver: CommunityResolvers = { parent.updaterId ? eq(users.id, parent.updaterId) : isNull(users.id), }); - return updater ?? null; + if (!updater) { + context.log.warn(`No user found for updaterId: ${parent.updaterId}`); + throw new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }); + } + + return updater; } catch (error) { context.log.error("Database error in community updater resolver", { error, diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index f1a9f441249..3e697699594 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -184,24 +184,6 @@ describe("Community Resolver - Updater Field", () => { ); }); - it("should return the correct updater user when all conditions are met", async () => { - const updaterUser = { - id: "456", - name: "Jane Updater", - role: "user", - createdAt: new Date(), - updatedAt: null, - }; - - ctx.drizzleClient.query.usersTable.findFirst - .mockResolvedValueOnce(mockUser) // First call for admin check - .mockResolvedValueOnce(updaterUser); // Second call for updater user - - const result = await CommunityResolver.updater(mockCommunity, {}, ctx); - - expect(result).toEqual(updaterUser); - }); - it("should correctly query the database for current user and updater user", async () => { const updaterUser = { id: "456", @@ -222,28 +204,23 @@ describe("Community Resolver - Updater Field", () => { ); }); - // it("should log a warning when an updater ID exists but no user is found", async () => { - // // Mock the database query to return undefined for the updater user - // ctx.drizzleClient.query.usersTable.findFirst - // .mockResolvedValueOnce(mockUser) // Mocking the current user query - // .mockResolvedValueOnce(undefined); // Simulating no updater user found - - // // Run the resolver and assert that it rejects with the correct error - // await expect( - // CommunityResolver.updater(mockCommunity, {}, ctx) - // ).rejects.toThrowError( - // new TalawaGraphQLError({ - // message: "Updater user not found", - // extensions: { - // code: "arguments_associated_resources_not_found", - // issues: [{ argumentPath: ["updaterId"] }], - // }, - // }) - // ); - - // // Ensure the log warning is triggered - // expect(ctx.log.warn).toHaveBeenCalledWith( - // `No user found for updaterId: ${mockCommunity.updaterId}` - // ); - // }); + it("should log a warning when an updater ID exists but no user is found", async () => { + ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValue(undefined); + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrowError( + new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }), + ); + + expect(ctx.log.warn).toHaveBeenCalledWith( + `No user found for updaterId: ${mockCommunity.updaterId}`, + ); + }); }); From 72df0b1b410f10af02ddc16b5e90f17cf1d2314d Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 11:44:04 +0530 Subject: [PATCH 10/15] Added new tests Signed-off-by: NishantSinghhhhh --- src/graphql/context.ts | 2 +- src/graphql/types/Community/Community.ts | 29 ++- test/graphql/types/Community/updater.test.ts | 225 +++++++++---------- test/utilities/mockLogger.ts | 28 +++ 4 files changed, 161 insertions(+), 123 deletions(-) create mode 100644 test/utilities/mockLogger.ts diff --git a/src/graphql/context.ts b/src/graphql/context.ts index f634ff3c592..267cf33ccb0 100644 --- a/src/graphql/context.ts +++ b/src/graphql/context.ts @@ -14,7 +14,7 @@ export type ImplicitMercuriusContext = { */ // src/context.ts export type ExplicitAuthenticationTokenPayload = { - user: Pick; // 🔴 Add 'role' + user: Pick; }; /** * Type of the client-specific context for a grahphql operation client. diff --git a/src/graphql/types/Community/Community.ts b/src/graphql/types/Community/Community.ts index aa59ce7916f..baef615e82f 100644 --- a/src/graphql/types/Community/Community.ts +++ b/src/graphql/types/Community/Community.ts @@ -30,12 +30,6 @@ export const CommunityResolver: CommunityResolvers = { return null; } - if (context.currentClient.user.role !== "administrator") { - throw new TalawaGraphQLError({ - message: "User is not authorized", - extensions: { code: "unauthorized_action" }, - }); - } const updaterId = parent.updaterId; const existingUser = @@ -44,8 +38,14 @@ export const CommunityResolver: CommunityResolvers = { }); if (existingUser === undefined) { - console.log("No user found for updaterId:", updaterId); - return null; + context.log.warn(`No user found for updaterId: ${updaterId}`); + throw new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }); } const updater = await context.drizzleClient.query.usersTable.findFirst({ @@ -53,7 +53,18 @@ export const CommunityResolver: CommunityResolvers = { parent.updaterId ? eq(users.id, parent.updaterId) : isNull(users.id), }); - return updater ?? null; + if (!updater) { + context.log.warn(`No user found for updaterId: ${parent.updaterId}`); + throw new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }); + } + + return updater; } catch (error) { context.log.error("Database error in community updater resolver", { error, diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index a6ec5a02b1d..7d63882527b 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -6,20 +6,13 @@ import { CommunityResolver } from "~/src/graphql/types/Community/Community"; import type { User } from "~/src/graphql/types/User/User"; import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError"; import type { GraphQLContext } from "../../../../src/graphql/context"; +import { createMockLogger } from "../../../utilities/mockLogger"; -type DeepPartial = T extends object - ? { - [P in keyof T]?: DeepPartial; - } - : T; - -type LogBindings = Record; -type LogOptions = Record; +type DeepPartial = Partial; type PubSubEvents = { COMMUNITY_CREATED: { id: string }; POST_CREATED: { id: string }; - // Add other event types as needed }; interface TestContext extends Omit { @@ -33,34 +26,6 @@ interface TestContext extends Omit { log: FastifyBaseLogger; } -const createMockLogger = (): FastifyBaseLogger => { - // Create base logger object with explicit types - const logger = { - error: vi.fn(), - warn: vi.fn(), - info: vi.fn(), - debug: vi.fn(), - trace: vi.fn(), - fatal: vi.fn(), - silent: vi.fn(), - child: (bindings: LogBindings, options?: LogOptions) => createMockLogger(), - level: "info", - isLevelEnabled: vi.fn().mockReturnValue(true), - bindings: vi.fn().mockReturnValue({}), - flush: vi.fn(), - // Add level-enabled methods explicitly - isFatalEnabled: vi.fn().mockReturnValue(true), - isErrorEnabled: vi.fn().mockReturnValue(true), - isWarnEnabled: vi.fn().mockReturnValue(true), - isInfoEnabled: vi.fn().mockReturnValue(true), - isDebugEnabled: vi.fn().mockReturnValue(true), - isTraceEnabled: vi.fn().mockReturnValue(true), - isSilentEnabled: vi.fn().mockReturnValue(true), - }; - - return logger as unknown as FastifyBaseLogger; -}; - const createMockPubSub = () => ({ publish: vi.fn().mockImplementation( ( @@ -84,6 +49,7 @@ describe("Community Resolver - Updater Field", () => { let mockCommunity: Community; beforeEach(() => { + // Mock user with role for resolver logic, even though context user won't have it mockUser = { id: "123", name: "John Doe", @@ -131,32 +97,22 @@ describe("Community Resolver - Updater Field", () => { sign: vi.fn().mockReturnValue("mock-token"), }, minio: { - bucketName: "talawa", // Match your actual bucket name + bucketName: "talawa", client: { listBuckets: vi.fn(), putObject: vi.fn(), getObject: vi.fn(), - // Add other required Minio client methods - } as unknown as MinioClient, // Type assertion for client + } as unknown as MinioClient, }, currentClient: { isAuthenticated: true, user: { - id: "123", // Must match mockUser.id - role: "administrator", + id: "123", // Ensure this is always set }, }, }; }); - it("should return updater user", async () => { - const result = await CommunityResolver.updater(mockCommunity, {}, ctx); - expect(result).toEqual(mockUser); - expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledWith({ - where: expect.any(Function), - }); - }); - it("should return null when updaterId is null", async () => { const nullUpdaterCommunity = { ...mockCommunity, @@ -184,80 +140,89 @@ describe("Community Resolver - Updater Field", () => { ); }); - it("should throw unauthorized error for non-admin", async () => { - ctx.currentClient = { - isAuthenticated: true, - user: { - id: "123", - role: "regular", - }, + it("should make correct database queries with expected parameters", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", + createdAt: new Date(), + updatedAt: null, }; - await expect( - CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrow( - new TalawaGraphQLError({ - message: "User is not authorized", - extensions: { code: "unauthorized_action" }, - }), + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) + .mockResolvedValueOnce(updaterUser); + + await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledWith({ + where: expect.any(Function), + }); + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, ); }); - it("should throw unauthorized error for non-admin", async () => { - // Create a non-admin user with the correct role type - const nonAdminUser: DeepPartial = { - id: "789", - name: "Regular User", - role: "regular" as const, // Using the correct role type + it("should successfully return updater user when all conditions are met", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", createdAt: new Date(), updatedAt: null, }; - // Reset the mock implementation - ctx.drizzleClient.query.usersTable.findFirst = vi - .fn() - .mockResolvedValueOnce(nonAdminUser); // First call returns non-admin user - - // Update context with non-admin user - ctx.currentClient = { - isAuthenticated: true, - user: { - id: nonAdminUser.id ?? "default-id", // Provide a sensible default or handle null case - role: nonAdminUser.role ?? "regular", // Replace with an appropriate default role - }, + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) + .mockResolvedValueOnce(updaterUser); + + const result = await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(result).toEqual(updaterUser); + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + }); + + it("should correctly query the database for current user and updater user", async () => { + const updaterUser = { + id: "456", + name: "Jane Updater", + role: "user", + createdAt: new Date(), + updatedAt: null, }; + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) // First call for current user + .mockResolvedValueOnce(updaterUser); // Second call for updater user + + await CommunityResolver.updater(mockCommunity, {}, ctx); + + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + }); + + it("should log a warning when an updater ID exists but no user is found", async () => { + ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValue(undefined); + await expect( CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrow( + ).rejects.toThrowError( new TalawaGraphQLError({ - message: "User is not authorized", - extensions: { code: "unauthorized_action" }, + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, }), ); - }); - - it("should return null for non-admin with null updaterId", async () => { - ctx.currentClient = { - isAuthenticated: true, - user: { - id: "123", - role: "regular", - }, - }; - const nullUpdaterCommunity = { - ...mockCommunity, - updaterId: null, - }; - const result = await CommunityResolver.updater( - nullUpdaterCommunity, - {}, - ctx, + expect(ctx.log.warn).toHaveBeenCalledWith( + `No user found for updaterId: ${mockCommunity.updaterId}`, ); - expect(result).toBeNull(); }); - it("should handle database errors gracefully", async () => { const dbError = new Error("Database connection failed"); @@ -289,30 +254,64 @@ describe("Community Resolver - Updater Field", () => { name: "Jane Smith", }; - // Mock database calls ctx.drizzleClient.query.usersTable.findFirst - .mockResolvedValueOnce(mockUser) // First call: current user lookup - .mockResolvedValueOnce(differentUser); // Second call: different user lookup - + .mockResolvedValueOnce(mockUser) + .mockResolvedValueOnce(differentUser); const result = await CommunityResolver.updater( differentUpdaterCommunity, {}, ctx, ); - // Verify the result matches the different user expect(result).toEqual(differentUser); - // Verify two database calls were made expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( 2, ); - // Verify the second call used the correct updaterId expect( ctx.drizzleClient.query.usersTable.findFirst, ).toHaveBeenNthCalledWith(2, { - where: expect.any(Function), // Should query for id: "different-id-789" + where: expect.any(Function), }); }); + + it("should log warning and throw error when updater is not found", async () => { + ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValue(undefined); + + const testCommunity = { + ...mockCommunity, + updaterId: "non-existent-id", + }; + + await expect( + CommunityResolver.updater(testCommunity, {}, ctx), + ).rejects.toThrow( + new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }), + ); + + expect(ctx.log.warn).toHaveBeenCalledWith( + `No user found for updaterId: ${testCommunity.updaterId}`, + ); + }); + + it("should log error when database query fails", async () => { + const dbError = new Error("Database connection failed"); + ctx.drizzleClient.query.usersTable.findFirst.mockRejectedValue(dbError); + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow(dbError); + + expect(ctx.log.error).toHaveBeenCalledWith( + "Database error in community updater resolver", + { error: dbError }, + ); + }); }); diff --git a/test/utilities/mockLogger.ts b/test/utilities/mockLogger.ts new file mode 100644 index 00000000000..1d33eabf695 --- /dev/null +++ b/test/utilities/mockLogger.ts @@ -0,0 +1,28 @@ +import type { FastifyBaseLogger } from "fastify"; +import { vi } from "vitest"; + +export const createMockLogger = (): FastifyBaseLogger => { + const logger = { + error: vi.fn(), + warn: vi.fn(), + info: vi.fn(), + debug: vi.fn(), + trace: vi.fn(), + fatal: vi.fn(), + silent: vi.fn(), + child: () => createMockLogger(), + level: "info", + isLevelEnabled: vi.fn().mockReturnValue(true), + bindings: vi.fn().mockReturnValue({}), + flush: vi.fn(), + isFatalEnabled: vi.fn().mockReturnValue(true), + isErrorEnabled: vi.fn().mockReturnValue(true), + isWarnEnabled: vi.fn().mockReturnValue(true), + isInfoEnabled: vi.fn().mockReturnValue(true), + isDebugEnabled: vi.fn().mockReturnValue(true), + isTraceEnabled: vi.fn().mockReturnValue(true), + isSilentEnabled: vi.fn().mockReturnValue(true), + }; + + return logger as unknown as FastifyBaseLogger; +}; From 0c8f3262889c68bb71dd991f259320e31d39b0d1 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 11:44:50 +0530 Subject: [PATCH 11/15] Added new tests Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 91 ++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index 3e697699594..7d63882527b 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -223,4 +223,95 @@ describe("Community Resolver - Updater Field", () => { `No user found for updaterId: ${mockCommunity.updaterId}`, ); }); + it("should handle database errors gracefully", async () => { + const dbError = new Error("Database connection failed"); + + ctx.drizzleClient.query.usersTable.findFirst + .mockRejectedValueOnce(dbError) + .mockResolvedValueOnce(mockUser); + + const logErrorSpy = vi.spyOn(ctx.log, "error"); + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow(dbError); + + expect(logErrorSpy).toHaveBeenCalledWith( + "Database error in community updater resolver", + { error: dbError }, + ); + }); + + it("should fetch different user when updaterId doesn't match current user", async () => { + const differentUpdaterCommunity = { + ...mockCommunity, + updaterId: "different-id-789", + }; + + const differentUser: DeepPartial = { + ...mockUser, + id: "different-id-789", + name: "Jane Smith", + }; + + ctx.drizzleClient.query.usersTable.findFirst + .mockResolvedValueOnce(mockUser) + .mockResolvedValueOnce(differentUser); + const result = await CommunityResolver.updater( + differentUpdaterCommunity, + {}, + ctx, + ); + + expect(result).toEqual(differentUser); + + expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( + 2, + ); + + expect( + ctx.drizzleClient.query.usersTable.findFirst, + ).toHaveBeenNthCalledWith(2, { + where: expect.any(Function), + }); + }); + + it("should log warning and throw error when updater is not found", async () => { + ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValue(undefined); + + const testCommunity = { + ...mockCommunity, + updaterId: "non-existent-id", + }; + + await expect( + CommunityResolver.updater(testCommunity, {}, ctx), + ).rejects.toThrow( + new TalawaGraphQLError({ + message: "Updater user not found", + extensions: { + code: "arguments_associated_resources_not_found", + issues: [{ argumentPath: ["updaterId"] }], + }, + }), + ); + + expect(ctx.log.warn).toHaveBeenCalledWith( + `No user found for updaterId: ${testCommunity.updaterId}`, + ); + }); + + it("should log error when database query fails", async () => { + const dbError = new Error("Database connection failed"); + ctx.drizzleClient.query.usersTable.findFirst.mockRejectedValue(dbError); + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow(dbError); + + expect(ctx.log.error).toHaveBeenCalledWith( + "Database error in community updater resolver", + { error: dbError }, + ); + }); }); From 791a1ac1c77c77d69fa00259bd20fe6379bf8e4b Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 13:46:40 +0530 Subject: [PATCH 12/15] rabbits changes Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 15 --------- test/utilities/mockLogger.ts | 34 +++++++++++++------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index 7d63882527b..ece42d197e8 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -49,7 +49,6 @@ describe("Community Resolver - Updater Field", () => { let mockCommunity: Community; beforeEach(() => { - // Mock user with role for resolver logic, even though context user won't have it mockUser = { id: "123", name: "John Doe", @@ -300,18 +299,4 @@ describe("Community Resolver - Updater Field", () => { `No user found for updaterId: ${testCommunity.updaterId}`, ); }); - - it("should log error when database query fails", async () => { - const dbError = new Error("Database connection failed"); - ctx.drizzleClient.query.usersTable.findFirst.mockRejectedValue(dbError); - - await expect( - CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrow(dbError); - - expect(ctx.log.error).toHaveBeenCalledWith( - "Database error in community updater resolver", - { error: dbError }, - ); - }); }); diff --git a/test/utilities/mockLogger.ts b/test/utilities/mockLogger.ts index 1d33eabf695..1e02eb9bfd8 100644 --- a/test/utilities/mockLogger.ts +++ b/test/utilities/mockLogger.ts @@ -1,7 +1,19 @@ import type { FastifyBaseLogger } from "fastify"; import { vi } from "vitest"; -export const createMockLogger = (): FastifyBaseLogger => { +interface MockLoggerConfig { + level?: string; + enabledLevels?: Set; +} + +export const createMockLogger = ( + config?: MockLoggerConfig, +): FastifyBaseLogger => { + const level = config?.level ?? "info"; + const enabledLevels = + config?.enabledLevels ?? + new Set(["error", "warn", "info", "debug", "trace", "fatal", "silent"]); + const logger = { error: vi.fn(), warn: vi.fn(), @@ -10,18 +22,18 @@ export const createMockLogger = (): FastifyBaseLogger => { trace: vi.fn(), fatal: vi.fn(), silent: vi.fn(), - child: () => createMockLogger(), - level: "info", - isLevelEnabled: vi.fn().mockReturnValue(true), + child: () => createMockLogger(config), + level, + isLevelEnabled: (l: string) => enabledLevels.has(l), bindings: vi.fn().mockReturnValue({}), flush: vi.fn(), - isFatalEnabled: vi.fn().mockReturnValue(true), - isErrorEnabled: vi.fn().mockReturnValue(true), - isWarnEnabled: vi.fn().mockReturnValue(true), - isInfoEnabled: vi.fn().mockReturnValue(true), - isDebugEnabled: vi.fn().mockReturnValue(true), - isTraceEnabled: vi.fn().mockReturnValue(true), - isSilentEnabled: vi.fn().mockReturnValue(true), + isFatalEnabled: () => enabledLevels.has("fatal"), + isErrorEnabled: () => enabledLevels.has("error"), + isWarnEnabled: () => enabledLevels.has("warn"), + isInfoEnabled: () => enabledLevels.has("info"), + isDebugEnabled: () => enabledLevels.has("debug"), + isTraceEnabled: () => enabledLevels.has("trace"), + isSilentEnabled: () => enabledLevels.has("silent"), }; return logger as unknown as FastifyBaseLogger; From fb6fa530aac2ef59c6ef1cac96bf160b2daf8b40 Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 22:37:44 +0530 Subject: [PATCH 13/15] Added tests Signed-off-by: NishantSinghhhhh --- package.json | 2 ++ pnpm-lock.yaml | 17 +++++++++ src/graphql/types/Community/Community.ts | 3 +- test/graphql/types/Community/updater.test.ts | 37 +++++++++----------- test/utilities/mockLogger.ts | 11 ++++++ 5 files changed, 49 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 96cf3ef76dd..c441910eee3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "minio": "^8.0.4", "postgres": "^3.4.5", "ulidx": "^2.4.1", + "uuid": "^11.0.5", "uuidv7": "^1.0.2", "zod": "^3.24.1" }, @@ -38,6 +39,7 @@ "@swc/cli": "0.6.0", "@swc/core": "^1.10.9", "@types/node": "^22.10.7", + "@types/uuid": "^10.0.0", "@vitest/coverage-v8": "^3.0.3", "drizzle-kit": "^0.30.4", "drizzle-seed": "^0.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebf282cb014..58d328da6ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: ulidx: specifier: ^2.4.1 version: 2.4.1 + uuid: + specifier: ^11.0.5 + version: 11.0.5 uuidv7: specifier: ^1.0.2 version: 1.0.2 @@ -102,6 +105,9 @@ importers: '@types/node': specifier: ^22.10.7 version: 22.10.7 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 '@vitest/coverage-v8': specifier: ^3.0.3 version: 3.0.3(vitest@3.0.3(@types/node@22.10.7)(tsx@4.19.2)) @@ -1350,6 +1356,9 @@ packages: '@types/node@22.10.7': resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@vitest/coverage-v8@3.0.3': resolution: {integrity: sha512-uVbJ/xhImdNtzPnLyxCZJMTeTIYdgcC2nWtBBBpR1H6z0w8m7D+9/zrDIx2nNxgMg9r+X8+RY2qVpUDeW2b3nw==} peerDependencies: @@ -2895,6 +2904,10 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + uuid@11.0.5: + resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==} + hasBin: true + uuidv7@1.0.2: resolution: {integrity: sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA==} hasBin: true @@ -3852,6 +3865,8 @@ snapshots: dependencies: undici-types: 6.20.0 + '@types/uuid@10.0.0': {} + '@vitest/coverage-v8@3.0.3(vitest@3.0.3(@types/node@22.10.7)(tsx@4.19.2))': dependencies: '@ampproject/remapping': 2.3.0 @@ -5447,6 +5462,8 @@ snapshots: is-typed-array: 1.1.15 which-typed-array: 1.1.18 + uuid@11.0.5: {} + uuidv7@1.0.2: {} vite-node@3.0.3(@types/node@22.10.7)(tsx@4.19.2): diff --git a/src/graphql/types/Community/Community.ts b/src/graphql/types/Community/Community.ts index baef615e82f..f4bff2a49c7 100644 --- a/src/graphql/types/Community/Community.ts +++ b/src/graphql/types/Community/Community.ts @@ -29,7 +29,8 @@ export const CommunityResolver: CommunityResolvers = { if (!parent.updaterId) { return null; } - + + const updaterId = parent.updaterId; const existingUser = diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index ece42d197e8..2face350e2e 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -183,26 +183,6 @@ describe("Community Resolver - Updater Field", () => { ); }); - it("should correctly query the database for current user and updater user", async () => { - const updaterUser = { - id: "456", - name: "Jane Updater", - role: "user", - createdAt: new Date(), - updatedAt: null, - }; - - ctx.drizzleClient.query.usersTable.findFirst - .mockResolvedValueOnce(mockUser) // First call for current user - .mockResolvedValueOnce(updaterUser); // Second call for updater user - - await CommunityResolver.updater(mockCommunity, {}, ctx); - - expect(ctx.drizzleClient.query.usersTable.findFirst).toHaveBeenCalledTimes( - 2, - ); - }); - it("should log a warning when an updater ID exists but no user is found", async () => { ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValue(undefined); @@ -299,4 +279,21 @@ describe("Community Resolver - Updater Field", () => { `No user found for updaterId: ${testCommunity.updaterId}`, ); }); + + it("should handle database timeout errors", async () => { + const timeoutError = new Error("Database timeout"); + ctx.drizzleClient.query.usersTable.findFirst.mockRejectedValue( + timeoutError, + ); + + await expect( + CommunityResolver.updater(mockCommunity, {}, ctx), + ).rejects.toThrow(timeoutError); + + expect(ctx.log.error).toHaveBeenCalledWith( + "Database error in community updater resolver", + { error: timeoutError }, + ); + }); + }); diff --git a/test/utilities/mockLogger.ts b/test/utilities/mockLogger.ts index 1e02eb9bfd8..7bc00173e24 100644 --- a/test/utilities/mockLogger.ts +++ b/test/utilities/mockLogger.ts @@ -1,6 +1,13 @@ import type { FastifyBaseLogger } from "fastify"; import { vi } from "vitest"; +/** + * Configuration options for the mock logger. + * @interface MockLoggerConfig + * @property {string} [level] - The logging level to use. Defaults to "info". + * @property {Set} [enabledLevels] - Set of enabled log levels. + */ + interface MockLoggerConfig { level?: string; enabledLevels?: Set; @@ -14,6 +21,10 @@ export const createMockLogger = ( config?.enabledLevels ?? new Set(["error", "warn", "info", "debug", "trace", "fatal", "silent"]); + if (level && !enabledLevels.has(level)) { + throw new Error(`Invalid log level: ${level}`); + } + const logger = { error: vi.fn(), warn: vi.fn(), From ba4be66ad69f498f92972769ba2a51f9acf6d05c Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 22:40:54 +0530 Subject: [PATCH 14/15] Linting done Signed-off-by: NishantSinghhhhh --- src/graphql/types/Community/Community.ts | 4 ---- test/graphql/types/Community/updater.test.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/src/graphql/types/Community/Community.ts b/src/graphql/types/Community/Community.ts index f4bff2a49c7..637bca37378 100644 --- a/src/graphql/types/Community/Community.ts +++ b/src/graphql/types/Community/Community.ts @@ -25,12 +25,9 @@ export const CommunityResolver: CommunityResolvers = { extensions: { code: "unauthenticated" }, }); } - if (!parent.updaterId) { return null; } - - const updaterId = parent.updaterId; const existingUser = @@ -64,7 +61,6 @@ export const CommunityResolver: CommunityResolvers = { }, }); } - return updater; } catch (error) { context.log.error("Database error in community updater resolver", { diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index 2face350e2e..fd62cb541de 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -295,5 +295,4 @@ describe("Community Resolver - Updater Field", () => { { error: timeoutError }, ); }); - }); From 9dd2393c7c5e7223406d51e0db149688b747a3ff Mon Sep 17 00:00:00 2001 From: NishantSinghhhhh Date: Sun, 2 Feb 2025 23:02:53 +0530 Subject: [PATCH 15/15] Linting done Signed-off-by: NishantSinghhhhh --- test/graphql/types/Community/updater.test.ts | 20 -------------------- test/utilities/mockLogger.ts | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/test/graphql/types/Community/updater.test.ts b/test/graphql/types/Community/updater.test.ts index fd62cb541de..006df670e01 100644 --- a/test/graphql/types/Community/updater.test.ts +++ b/test/graphql/types/Community/updater.test.ts @@ -182,26 +182,6 @@ describe("Community Resolver - Updater Field", () => { 2, ); }); - - it("should log a warning when an updater ID exists but no user is found", async () => { - ctx.drizzleClient.query.usersTable.findFirst.mockResolvedValue(undefined); - - await expect( - CommunityResolver.updater(mockCommunity, {}, ctx), - ).rejects.toThrowError( - new TalawaGraphQLError({ - message: "Updater user not found", - extensions: { - code: "arguments_associated_resources_not_found", - issues: [{ argumentPath: ["updaterId"] }], - }, - }), - ); - - expect(ctx.log.warn).toHaveBeenCalledWith( - `No user found for updaterId: ${mockCommunity.updaterId}`, - ); - }); it("should handle database errors gracefully", async () => { const dbError = new Error("Database connection failed"); diff --git a/test/utilities/mockLogger.ts b/test/utilities/mockLogger.ts index 7bc00173e24..9cc99d77c08 100644 --- a/test/utilities/mockLogger.ts +++ b/test/utilities/mockLogger.ts @@ -47,5 +47,5 @@ export const createMockLogger = ( isSilentEnabled: () => enabledLevels.has("silent"), }; - return logger as unknown as FastifyBaseLogger; + return logger; };