feat: initial commit — Curio mastery tutor
Full Next.js App Router application with: - AI-graded lesson checkpoints (BKT/Elo mastery tracking) - Auth.js v5: credentials, Google, GitHub, generic OIDC - Anonymous-session-first with migrate-on-signin - Admin panel: users, blueprints, reports, site settings - Password reset + email verification (nodemailer/SMTP) - Site config: require_auth + signups_enabled flags - server-only guards on all DB/generation/verification modules - PostgreSQL 16 + pgvector, Redis cache, Drizzle ORM - 271 unit tests (Vitest), golden-eval harness, Playwright e2e stubs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { requireAuth } from '@/lib/auth-helpers';
|
||||
|
||||
const mockTx = {
|
||||
delete: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]) }),
|
||||
};
|
||||
|
||||
const mockDb = {
|
||||
transaction: vi.fn().mockImplementation(async (fn: (tx: typeof mockTx) => Promise<void>) => fn(mockTx)),
|
||||
};
|
||||
|
||||
vi.mock('@/lib/db', () => ({ db: mockDb }));
|
||||
|
||||
vi.mock('@/lib/db/schema', () => ({
|
||||
users: {},
|
||||
responses: {},
|
||||
mastery: {},
|
||||
journeyInstances: {},
|
||||
}));
|
||||
|
||||
vi.mock('drizzle-orm', () => ({
|
||||
eq: vi.fn((col, val) => ({ col, val })),
|
||||
}));
|
||||
|
||||
function makeRequest(): NextRequest {
|
||||
return new NextRequest('http://localhost/api/user/account', { method: 'DELETE' });
|
||||
}
|
||||
|
||||
describe('DELETE /api/user/account', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockTx.delete.mockReturnValue({ where: vi.fn().mockResolvedValue([]) });
|
||||
mockDb.transaction.mockImplementation(async (fn: (tx: typeof mockTx) => Promise<void>) => fn(mockTx));
|
||||
});
|
||||
|
||||
it('returns 200 {ok:true} on happy path', async () => {
|
||||
const { DELETE } = await import('../route');
|
||||
const res = await DELETE();
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(body).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it('runs transaction and deletes all four tables', async () => {
|
||||
const { DELETE } = await import('../route');
|
||||
await DELETE();
|
||||
|
||||
expect(mockDb.transaction).toHaveBeenCalledOnce();
|
||||
expect(mockTx.delete).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('returns 401 when requireAuth throws', async () => {
|
||||
vi.mocked(requireAuth).mockRejectedValueOnce(new Error('redirect'));
|
||||
|
||||
const { DELETE } = await import('../route');
|
||||
await expect(DELETE()).rejects.toThrow('redirect');
|
||||
});
|
||||
|
||||
it('propagates transaction errors', async () => {
|
||||
mockDb.transaction.mockRejectedValueOnce(new Error('db error'));
|
||||
|
||||
const { DELETE } = await import('../route');
|
||||
await expect(DELETE()).rejects.toThrow('db error');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user