Skip to content
Merged
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
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ Prioritize testing:

Avoid tests that depend heavily on implementation details.

Keep scenario-specific data local. Extract stable objects or configuration
repeated across test files into typed factories or fixtures under the relevant
application's `src/test/fixtures/` directory; prefer override-based factories
for domain variants and do not share fixtures across unrelated boundaries.

Do not weaken, remove, or rewrite a legitimate failing test merely to make a
change pass.

Expand Down
16 changes: 2 additions & 14 deletions apps/api/src/auth/google/google-oidc.routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,9 @@ import {
validateReturnTo,
} from './google-oidc.routes.js';
import { createSessionMiddleware } from '../session/session.middleware.js';
import { testSessionConfiguration } from '../../test/fixtures/session-configuration.fixture.js';

const redirectUri = 'http://localhost:3000/auth/google/callback';
const sessionConfiguration = {
SESSION_SECRET: 'test-session-secret-at-least-32-characters',
SESSION_DURATION_SECONDS: 604800,
SESSION_COOKIE: {
name: 'goforlift.sid',
httpOnly: true,
secure: false,
sameSite: 'lax' as const,
path: '/',
maxAgeMs: 604800000,
},
};

type TestContextOptions = {
exchangeAuthorizationCode?: ExchangeAuthorizationCode;
now?: () => number;
Expand All @@ -49,7 +37,7 @@ function createTestContext(options: TestContextOptions = {}) {
'google-client-secret',
);
const app = express();
app.use(createSessionMiddleware(store, sessionConfiguration));
app.use(createSessionMiddleware(store, testSessionConfiguration));
app.use(
'/auth',
createGoogleOidcRouter({
Expand Down
19 changes: 5 additions & 14 deletions apps/api/src/auth/guards/authorization.middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,22 @@ import {
establishAuthenticatedTestSession,
withAuthenticatedTestUser,
} from '../../test/authenticated-session.test-helper.js';
import { testSessionConfiguration } from '../../test/fixtures/session-configuration.fixture.js';
import {
getAuthenticatedUserId,
requireAuthentication,
} from './authentication.middleware.js';
import { requireAuthorization } from './authorization.middleware.js';

const sessionConfiguration = {
SESSION_SECRET: 'test-session-secret-at-least-32-characters',
SESSION_DURATION_SECONDS: 604800,
SESSION_COOKIE: {
name: 'goforlift.sid',
httpOnly: true,
secure: false,
sameSite: 'lax' as const,
path: '/',
maxAgeMs: 604800000,
},
};

function createProtectedTestApp(
userOwnsResource: (resourceId: string, userId: string) => Promise<boolean>,
) {
const app = express();
app.use(
createSessionMiddleware(new session.MemoryStore(), sessionConfiguration),
createSessionMiddleware(
new session.MemoryStore(),
testSessionConfiguration,
),
);
app.use(establishAuthenticatedTestSession());
app.get(
Expand Down
16 changes: 2 additions & 14 deletions apps/api/src/auth/session/session.middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,12 @@ import session from 'express-session';
import request from 'supertest';
import { describe, expect, it } from 'vitest';

import { testSessionConfiguration } from '../../test/fixtures/session-configuration.fixture.js';
import { createSessionMiddleware } from './session.middleware.js';

const sessionConfiguration = {
SESSION_SECRET: 'test-session-secret-at-least-32-characters',
SESSION_DURATION_SECONDS: 604800,
SESSION_COOKIE: {
name: 'goforlift.sid',
httpOnly: true,
secure: false,
sameSite: 'lax' as const,
path: '/',
maxAgeMs: 604800000,
},
};

function createSessionTestApp(store: session.MemoryStore) {
const app = express();
app.use(createSessionMiddleware(store, sessionConfiguration));
app.use(createSessionMiddleware(store, testSessionConfiguration));

app.post('/session', (request, response) => {
request.session.userId = 'user-123';
Expand Down
28 changes: 7 additions & 21 deletions apps/api/src/auth/session/session.routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,15 @@ import request from 'supertest';
import { describe, expect, it, vi } from 'vitest';
import { z } from 'zod';

import { createPublicUser } from '../../test/fixtures/public-user.fixture.js';
import { testSessionConfiguration } from '../../test/fixtures/session-configuration.fixture.js';
import { csrfErrorHandler, csrfProtection } from '../csrf/csrf.middleware.js';
import { createSessionMiddleware } from './session.middleware.js';
import { createSessionRouter } from './session.routes.js';

const sessionConfiguration = {
SESSION_SECRET: 'test-session-secret-at-least-32-characters',
SESSION_DURATION_SECONDS: 604800,
SESSION_COOKIE: {
name: 'goforlift.sid',
httpOnly: true,
secure: false,
sameSite: 'lax' as const,
path: '/',
maxAgeMs: 604800000,
},
};

const publicUser = {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
const publicUser = createPublicUser({
avatarUrl: 'https://images.example.com/avatar.png',
};
});

const csrfTokenResponseSchema = z.object({ csrfToken: z.string() });

Expand All @@ -36,7 +22,7 @@ function createTestContext(
) {
const store = new session.MemoryStore();
const app = express();
app.use(createSessionMiddleware(store, sessionConfiguration));
app.use(createSessionMiddleware(store, testSessionConfiguration));
app.use((request, _response, next) => {
const testUserId = request.header('x-test-user-id');
if (testUserId) {
Expand All @@ -48,8 +34,8 @@ function createTestContext(
app.use(
'/auth',
createSessionRouter({
cookieName: sessionConfiguration.SESSION_COOKIE.name,
cookiePath: sessionConfiguration.SESSION_COOKIE.path,
cookieName: testSessionConfiguration.SESSION_COOKIE.name,
cookiePath: testSessionConfiguration.SESSION_COOKIE.path,
findPublicUser,
}),
);
Expand Down
8 changes: 2 additions & 6 deletions apps/api/src/auth/user/current-user.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@ import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { describe, expect, it, vi } from 'vitest';

import { users } from '../../db/schema/index.js';
import { createPublicUser } from '../../test/fixtures/public-user.fixture.js';
import { findPublicUserById } from './current-user.service.js';

describe('findPublicUserById', () => {
it('returns the public user projection', async () => {
const publicUser = {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
avatarUrl: null,
};
const publicUser = createPublicUser();
const limit = vi.fn().mockResolvedValue([publicUser]);
const where = vi.fn(() => ({ limit }));
const from = vi.fn(() => ({ where }));
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/test/fixtures/public-user.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/** Creates typed public-user API test data with optional scenario overrides. */
import type { PublicUser } from '@goforlift/contracts';

export function createPublicUser(
overrides: Partial<PublicUser> = {},
): PublicUser {
return {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
avatarUrl: null,
...overrides,
};
}
18 changes: 18 additions & 0 deletions apps/api/src/test/fixtures/session-configuration.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/** Provides the shared secure session configuration used by API tests. */
import type { Env } from '../../config/env-schema.js';

export const testSessionConfiguration = {
SESSION_SECRET: 'test-session-secret-at-least-32-characters',
SESSION_DURATION_SECONDS: 604800,
SESSION_COOKIE: {
name: 'goforlift.sid',
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
maxAgeMs: 604800000,
},
} satisfies Pick<
Env,
'SESSION_COOKIE' | 'SESSION_DURATION_SECONDS' | 'SESSION_SECRET'
>;
8 changes: 2 additions & 6 deletions apps/web/src/features/auth/auth.api.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
/** Verifies credential inclusion, response validation, CSRF attachment, and 401 behavior. */
import { afterEach, describe, expect, it, vi } from 'vitest';

import { createPublicUser } from '../../test/fixtures/public-user.fixture';
import {
AuthApiError,
fetchCurrentUser,
logout,
requestWithCsrf,
} from './auth.api';

const publicUser = {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
avatarUrl: null,
};
const publicUser = createPublicUser();

afterEach(() => vi.unstubAllGlobals());

Expand Down
8 changes: 2 additions & 6 deletions apps/web/src/features/auth/current-user.query.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ import { renderHook, waitFor } from '@testing-library/react';
import type { PropsWithChildren } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { createPublicUser } from '../../test/fixtures/public-user.fixture';
import { useCurrentUser } from './current-user.query';

const publicUser = {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
avatarUrl: null,
};
const publicUser = createPublicUser();

afterEach(() => vi.unstubAllGlobals());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ import {
import { cleanup, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { createPublicUser } from '../../test/fixtures/public-user.fixture';
import { startGuestSession } from '../auth/guest-session';
import { DashboardAccessBoundary } from './dashboard-access-boundary';

const publicUser = {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
avatarUrl: null,
};
const publicUser = createPublicUser();

afterEach(() => {
cleanup();
Expand Down
14 changes: 14 additions & 0 deletions apps/web/src/test/fixtures/public-user.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Creates typed public-user test data with optional scenario-specific overrides.
import type { PublicUser } from '@goforlift/contracts';

export function createPublicUser(
overrides: Partial<PublicUser> = {},
): PublicUser {
return {
id: '26d34dc0-8e4c-4bd0-9e3b-7b839b44e486',
email: 'lifter@example.com',
displayName: 'Go For Lifter',
avatarUrl: null,
...overrides,
};
}