Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
roots: ["<rootDir>/src"],
testMatch: ["**/__tests__/**/*.test.ts"],
moduleFileExtensions: ["ts", "js", "json"],
collectCoverageFrom: [
"src/**/*.ts",
"!src/**/*.d.ts",
"!src/**/index.ts",
"!src/server.ts",
],
setupFilesAfterEnv: ["<rootDir>/src/__tests__/setup.ts"],
moduleNameMapper: {
"^common$": "<rootDir>/../common/dist/index.js",
},
transform: {
"^.+\\.ts$": [
"ts-jest",
{
tsconfig: {
module: "commonjs",
esModuleInterop: true,
},
},
],
},
};
8 changes: 7 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/express-serve-static-core": "^5.0.6",
"@types/jest": "^30.0.0",
"@types/memjs": "^1.3.3",
"@types/node": "^22.13.9",
"@types/supertest": "^6.0.3",
"dotenv-cli": "^8.0.0",
"jest": "^30.2.0",
"jest-mock-extended": "^4.0.0",
"nodemon": "^3.1.9",
"prisma": "^6.4.1",
"supertest": "^7.1.4",
"ts-jest": "^29.4.5",
"ts-node": "^10.9.2",
"typescript": "^5.8.2",
"uuid": "^11.1.0"
}
}
}
89 changes: 89 additions & 0 deletions backend/src/__tests__/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { User, Order, Organization } from "@prisma/client";

/**
* Test data factory functions
*
* These functions create mock data objects that match Prisma's generated types.
* Use these in tests to create consistent, valid test data.
*/

/**
* Create a mock User object
*
* @param overrides - Optional properties to override default values
* @returns A User object with test data
*
* @example
* const user = createMockUser({ name: 'John Doe' });
*/
export const createMockUser = (overrides?: Partial<User>): User => ({
id: "123e4567-e89b-12d3-a456-426614174000",
email: "test@example.com",
name: "Test User",
venmoUsername: null,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
...overrides,
});

/**
* Create multiple mock User objects
*
* @param count - Number of users to create
* @returns An array of User objects
*
* @example
* const users = createMockUsers(3);
*/
export const createMockUsers = (count: number): User[] => {
return Array.from({ length: count }, (_, i) =>
createMockUser({
id: `123e4567-e89b-12d3-a456-42661417400${i}`,
email: `test${i}@example.com`,
name: `Test User ${i}`,
})
);
};

/**
* Create a mock Organization object
*
* @param overrides - Optional properties to override default values
* @returns An Organization object with test data
*
* @example
* const org = createMockOrganization({ name: 'My Org' });
*/
export const createMockOrganization = (
overrides?: Partial<Organization>
): Organization => ({
id: "223e4567-e89b-12d3-a456-426614174000",
name: "Test Organization",
description: "A test organization",
authorized: true,
logoUrl: "https://example.com/logo.png",
websiteUrl: "https://example.com",
instagramUsername: "testorg",
createdAt: new Date("2024-01-01T00:00:00.000Z"),
...overrides,
});

/**
* Create a mock Order object
*
* @param overrides - Optional properties to override default values
* @returns An Order object with test data
*
* @example
* const order = createMockOrder({ paymentStatus: 'CONFIRMED' });
*/
export const createMockOrder = (overrides?: Partial<Order>): Order => ({
id: "323e4567-e89b-12d3-a456-426614174000",
paymentMethod: "VENMO" as const,
paymentStatus: "PENDING" as const,
pickedUp: false,
buyerId: "123e4567-e89b-12d3-a456-426614174000",
fundraiserId: "423e4567-e89b-12d3-a456-426614174000",
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-01T00:00:00.000Z"),
...overrides,
});
14 changes: 14 additions & 0 deletions backend/src/__tests__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Test utilities and mocks
*
* This is the main entry point for all test utilities.
* Import from this file in your tests for a clean, consistent interface.
*
* @example
* import { prismaMock, createMockUser, mockResponse } from '../../__tests__';
*/

// Re-export everything from setup
export * from "./setup";

// Additional test utilities can be added here as needed
56 changes: 56 additions & 0 deletions backend/src/__tests__/mocks/express.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Express.js test utilities
*
* Provides mock implementations of Express request, response, and next function
* for use in handler and middleware tests.
*/

/**
* Create a mock Express request object
*
* @param overrides - Optional properties to override default request values
* @returns A mock request object with common properties
*
* @example
* const req = mockRequest({ params: { id: '123' }, body: { name: 'Test' } });
*/
export const mockRequest = <T = any>(overrides?: Partial<T>): T =>
({
params: {},
query: {},
body: {},
headers: {},
...overrides,
} as T);

/**
* Create a mock Express response object
*
* @returns A mock response object with chainable methods
*
* @example
* const res = mockResponse();
* await handler(req, res);
* expect(res.status).toHaveBeenCalledWith(200);
*/
export const mockResponse = () => {
const res: any = {
locals: {},
};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
res.send = jest.fn().mockReturnValue(res);
return res;
};

/**
* Create a mock Express next function
*
* @returns A jest mock function for the next callback
*
* @example
* const next = mockNext();
* await middleware(req, res, next);
* expect(next).toHaveBeenCalled();
*/
export const mockNext = () => jest.fn();
28 changes: 28 additions & 0 deletions backend/src/__tests__/mocks/prisma.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PrismaClient } from "@prisma/client";
import { mockDeep, mockReset, DeepMockProxy } from "jest-mock-extended";

/**
* Mocked Prisma Client for testing
*
* This provides a fully mocked Prisma client that can be used in tests
* without requiring an actual database connection.
*/
export const prismaMock =
mockDeep<PrismaClient>() as unknown as DeepMockProxy<PrismaClient>;

/**
* Mock the Prisma utility module
*
* This ensures that any imports of '../utils/prisma' will receive the mocked client
*/
jest.mock("../../utils/prisma", () => ({
__esModule: true,
prisma: prismaMock,
}));

/**
* Reset Prisma mock before each test
*/
export const resetPrismaMock = () => {
mockReset(prismaMock);
};
49 changes: 49 additions & 0 deletions backend/src/__tests__/mocks/supabase.mock.ts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on the pr! this will be really useful for us. Just had one comment to add:

In a few tests there is:

mockSupabaseAuth.getUser.mockResolvedValue({
  data: { user: createMockSupabaseUser(userId) },
});

but the supabase.auth.getUser() and default mock return both data and error:

{ data: { user: ... }, error: null }

You could make the overrides always include error: null or add a small helper func like setAuthenticatedUser() that sets the full shape for consistency. That’ll help us avoid tests breaking if auth logic ever checks error.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Mocked Supabase Auth for testing
*
* This provides a mocked Supabase authentication client that can be
* configured on a per-test basis.
*/

export const mockSupabaseAuth = {
getUser: jest.fn().mockResolvedValue({ data: { user: null }, error: null }),
};

const mockSupabaseClient = {
auth: mockSupabaseAuth,
};

/**
* Mock the Supabase SDK
*
* This ensures that any calls to createClient() will return our mocked client
*/
jest.mock("@supabase/supabase-js", () => ({
createClient: jest.fn(() => mockSupabaseClient),
}));

/**
* Reset Supabase mock to default state (no authenticated user)
*/
export const resetSupabaseMock = () => {
mockSupabaseAuth.getUser.mockResolvedValue({
data: { user: null },
error: null,
});
};

/**
* Helper to create a mock Supabase user object
*/
export const createMockSupabaseUser = (
userId: string = "123e4567-e89b-12d3-a456-426614174000"
) => ({
id: userId,
email: "test@example.com",
aud: "authenticated",
role: "authenticated",
app_metadata: {},
user_metadata: {},
created_at: "2024-01-01T00:00:00.000Z",
updated_at: "2024-01-01T00:00:00.000Z",
});
39 changes: 39 additions & 0 deletions backend/src/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Global test setup
*
* This file is automatically loaded by Jest before each test suite.
* It configures all necessary mocks and resets them between tests.
*/

// Import and configure mocks
import "./mocks/prisma.mock";
import "./mocks/supabase.mock";

// Re-export mocks for use in tests
export { prismaMock, resetPrismaMock } from "./mocks/prisma.mock";
export {
mockSupabaseAuth,
resetSupabaseMock,
createMockSupabaseUser,
} from "./mocks/supabase.mock";
export { mockRequest, mockResponse, mockNext } from "./mocks/express.mock";

// Re-export fixtures for use in tests
export {
createMockUser,
createMockUsers,
createMockOrganization,
createMockOrder,
} from "./fixtures";

// Import reset functions
import { resetPrismaMock } from "./mocks/prisma.mock";
import { resetSupabaseMock } from "./mocks/supabase.mock";

/**
* Reset all mocks before each test
*/
beforeEach(() => {
resetPrismaMock();
resetSupabaseMock();
});
Loading