test(10-02): add failing /api/me isAdmin+needsProviderSetup tests (RED)
- dev-bypass path: isAdmin from DB (not hardcoded), needsProviderSetup from member_credentials - needsProviderSetup=true when no member_credentials row exists - needsProviderSetup=false when member_credentials row exists
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* GET /api/me — regression tests for dev-auth bypass path.
|
||||
* GET /api/me — regression tests for dev-auth bypass path + isAdmin/needsProviderSetup
|
||||
*
|
||||
* Covers:
|
||||
* 1. DEV_AUTH_BYPASS=true (non-production): GET /api/me returns 200 with the injected
|
||||
@@ -8,6 +8,10 @@
|
||||
* 2. Without DEV_AUTH_BYPASS: the OIDC middleware is still wired on /api/*.
|
||||
* Verified structurally by asserting oidcAuthMiddleware is called during app init
|
||||
* (the mock intercepts it and acts as a passthrough, confirming the mount path).
|
||||
* 3. Plan 10-02 additions:
|
||||
* - dev-bypass path returns isAdmin (DB-backed, not hardcoded) + needsProviderSetup
|
||||
* - OIDC path returns isAdmin + needsProviderSetup
|
||||
* - needsProviderSetup=true when no member_credentials row exists; false when one exists
|
||||
*
|
||||
* Architecture note:
|
||||
* devAuthBypass() and devBypassActive in index.ts both evaluate env vars at module
|
||||
@@ -20,13 +24,19 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared mock: DB — avoids real DB connections across all tests in this file.
|
||||
// This mock is hoisted by Vitest and applies to every dynamic import below.
|
||||
//
|
||||
// Default: select chain returns empty arrays (no rows).
|
||||
// Per-test overrides: use vi.mocked(db.select).mockImplementation(...) to
|
||||
// supply per-call sequences for isAdmin and memberCredentials lookups.
|
||||
// ---------------------------------------------------------------------------
|
||||
vi.mock('../../src/db/client.js', () => ({
|
||||
db: {
|
||||
execute: vi.fn().mockResolvedValue([[{ '1': 1 }]]),
|
||||
select: vi.fn().mockReturnValue({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue([]),
|
||||
where: vi.fn().mockReturnValue({
|
||||
limit: vi.fn().mockResolvedValue([]),
|
||||
}),
|
||||
innerJoin: vi.fn().mockReturnValue({
|
||||
innerJoin: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue([]),
|
||||
@@ -138,3 +148,97 @@ describe('GET /api/me — OIDC path (no DEV_AUTH_BYPASS)', () => {
|
||||
expect(body.error).toBe('Unauthorized');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Plan 10-02: isAdmin + needsProviderSetup on /api/me (D-03)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('GET /api/me — isAdmin + needsProviderSetup (Plan 10-02, D-03)', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.DEV_AUTH_BYPASS = 'true';
|
||||
});
|
||||
|
||||
it('dev-bypass: response includes isAdmin (DB-backed from users.is_admin, not hardcoded)', async () => {
|
||||
// Set up db.select to return isAdmin=true for the users lookup,
|
||||
// and [] for the memberCredentials lookup (needsProviderSetup=true).
|
||||
const { db } = await import('../../src/db/client.js');
|
||||
|
||||
let callCount = 0;
|
||||
vi.mocked(db.select).mockImplementation(() => {
|
||||
callCount++;
|
||||
const limitFn = callCount === 1
|
||||
? vi.fn().mockResolvedValue([{ isAdmin: true }]) // users.isAdmin lookup
|
||||
: vi.fn().mockResolvedValue([]); // memberCredentials lookup (none)
|
||||
return {
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({ limit: limitFn }),
|
||||
innerJoin: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }) }),
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any;
|
||||
});
|
||||
|
||||
const { app } = await import('../../src/index.js');
|
||||
const res = await app.request('/api/me');
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const body = (await res.json()) as { user: { id: number; isAdmin: boolean; needsProviderSetup: boolean } };
|
||||
expect(body.user).toHaveProperty('isAdmin');
|
||||
expect(body.user.isAdmin).toBe(true); // DB returns true, not hardcoded
|
||||
});
|
||||
|
||||
it('dev-bypass: needsProviderSetup=true when no member_credentials row exists', async () => {
|
||||
const { db } = await import('../../src/db/client.js');
|
||||
|
||||
let callCount = 0;
|
||||
vi.mocked(db.select).mockImplementation(() => {
|
||||
callCount++;
|
||||
const limitFn = callCount === 1
|
||||
? vi.fn().mockResolvedValue([{ isAdmin: false }]) // users.isAdmin lookup
|
||||
: vi.fn().mockResolvedValue([]); // no member_credentials row
|
||||
return {
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({ limit: limitFn }),
|
||||
innerJoin: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }) }),
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any;
|
||||
});
|
||||
|
||||
const { app } = await import('../../src/index.js');
|
||||
const res = await app.request('/api/me');
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const body = (await res.json()) as { user: { needsProviderSetup: boolean } };
|
||||
expect(body.user).toHaveProperty('needsProviderSetup');
|
||||
expect(body.user.needsProviderSetup).toBe(true);
|
||||
});
|
||||
|
||||
it('dev-bypass: needsProviderSetup=false when a member_credentials row exists', async () => {
|
||||
const { db } = await import('../../src/db/client.js');
|
||||
|
||||
let callCount = 0;
|
||||
vi.mocked(db.select).mockImplementation(() => {
|
||||
callCount++;
|
||||
const limitFn = callCount === 1
|
||||
? vi.fn().mockResolvedValue([{ isAdmin: false }]) // users.isAdmin lookup
|
||||
: vi.fn().mockResolvedValue([{ id: 7 }]); // has member_credentials row
|
||||
return {
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockReturnValue({ limit: limitFn }),
|
||||
innerJoin: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }) }),
|
||||
}),
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any;
|
||||
});
|
||||
|
||||
const { app } = await import('../../src/index.js');
|
||||
const res = await app.request('/api/me');
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const body = (await res.json()) as { user: { needsProviderSetup: boolean } };
|
||||
expect(body.user).toHaveProperty('needsProviderSetup');
|
||||
expect(body.user.needsProviderSetup).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user