Files
vyndr/tests/unit/requireAuth.test.js
T

183 lines
7.4 KiB
JavaScript

// Session 17 — requireAuth's on-demand users-row upsert path.
//
// The audit found that authenticated users with valid Supabase auth
// tokens were getting 401 "User profile not found" on /api/stripe/
// checkout because their corresponding row in public.users had never
// been inserted. The fix: when `.single()` on the users select
// returns PGRST116 ("no rows"), upsert a default row and re-read.
//
// These tests build a chainable Supabase fake that lets us steer the
// select to PGRST116 / success and assert which path requireAuth
// takes.
const { requireAuth } = require('../../src/middleware/auth');
function makeFake({ getUserResult, selectResult, selectAfterUpsertResult, upsertError = null }) {
// The middleware calls `supabase.from('users')` multiple times in
// the missing-profile recovery path (initial select, then upsert,
// then reread). State that distinguishes "first select" from
// "select-after-upsert" has to live on the supabase fake, not on
// a fresh-per-call `from()` proxy. We track `hasUpserted` and
// route `.single()` to the appropriate result.
const upserts = [];
let hasUpserted = false;
const supabase = {
auth: { getUser: () => Promise.resolve(getUserResult) },
from() {
const proxy = {
select() { return proxy; },
eq() { return proxy; },
single() {
return Promise.resolve(hasUpserted ? selectAfterUpsertResult : selectResult);
},
upsert(row, opts) {
upserts.push({ row, opts });
if (!upsertError) hasUpserted = true;
return Promise.resolve({ data: null, error: upsertError });
},
};
return proxy;
},
_upserts: upserts,
};
return supabase;
}
let mockSupabaseFake;
jest.mock('../../src/utils/supabase', () => ({
getSupabaseServiceClient: () => mockSupabaseFake,
}));
function runMiddleware(req) {
return new Promise((resolve) => {
const res = {
_status: null,
_body: null,
status(code) { this._status = code; return this; },
json(body) { this._body = body; resolve({ status: this._status, body, fellThrough: false }); return this; },
};
requireAuth(req, res, () => resolve({ status: 200, body: null, fellThrough: true, req }));
});
}
const VALID_USER = { id: 'user-1', email: 'a@b.com' };
const EXISTING_PROFILE = {
id: 'user-1', email: 'a@b.com', tier: 'free', scan_count: 0,
scan_reset_date: '2026-06-12', founder_status: false,
grace_period_until: null, stripe_customer_id: null,
};
describe('requireAuth — pre-Session 17 behavior preserved', () => {
test('valid token + existing profile → next() called with req.user set', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: VALID_USER }, error: null },
selectResult: { data: EXISTING_PROFILE, error: null },
});
const req = { headers: { authorization: 'Bearer good-token' } };
const out = await runMiddleware(req);
expect(out.fellThrough).toBe(true);
expect(req.user.id).toBe('user-1');
expect(req.user.tier).toBe('free');
expect(mockSupabaseFake._upserts).toHaveLength(0);
});
test('missing Authorization header → 401', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: null }, error: { message: 'no' } },
selectResult: { data: null, error: null },
});
const out = await runMiddleware({ headers: {} });
expect(out.status).toBe(401);
expect(out.body.error).toMatch(/Authentication required/);
});
test('non-Bearer scheme → 401', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: null }, error: { message: 'no' } },
selectResult: { data: null, error: null },
});
const out = await runMiddleware({ headers: { authorization: 'Basic xyz' } });
expect(out.status).toBe(401);
});
test('Supabase getUser error → 401 invalid token', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: null }, error: { message: 'token expired' } },
selectResult: { data: null, error: null },
});
const out = await runMiddleware({ headers: { authorization: 'Bearer bad' } });
expect(out.status).toBe(401);
expect(out.body.error).toMatch(/Invalid or expired/);
});
});
describe('requireAuth — Session 17 missing-profile recovery', () => {
test('missing users row (PGRST116) → upsert default + retry succeeds', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: VALID_USER }, error: null },
selectResult: { data: null, error: { code: 'PGRST116', message: 'no rows' } },
selectAfterUpsertResult: { data: EXISTING_PROFILE, error: null },
});
const req = { headers: { authorization: 'Bearer good' } };
const out = await runMiddleware(req);
expect(out.fellThrough).toBe(true);
expect(req.user.id).toBe('user-1');
expect(mockSupabaseFake._upserts).toHaveLength(1);
const u = mockSupabaseFake._upserts[0];
expect(u.row.id).toBe('user-1');
expect(u.row.email).toBe('a@b.com');
expect(u.row.tier).toBe('free');
expect(u.row.scan_count).toBe(0);
expect(u.row.founder_status).toBe(false);
expect(u.opts.onConflict).toBe('id');
});
test('PGRST116 detected by message text (no code field)', async () => {
// Some Supabase client versions surface the error sans code field.
mockSupabaseFake = makeFake({
getUserResult: { data: { user: VALID_USER }, error: null },
selectResult: { data: null, error: { message: 'JSON object requested, multiple (or no) rows returned' } },
selectAfterUpsertResult: { data: EXISTING_PROFILE, error: null },
});
const out = await runMiddleware({ headers: { authorization: 'Bearer good' } });
expect(out.fellThrough).toBe(true);
expect(mockSupabaseFake._upserts).toHaveLength(1);
});
test('upsert itself fails → 401 with distinct message', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: VALID_USER }, error: null },
selectResult: { data: null, error: { code: 'PGRST116', message: 'no rows' } },
selectAfterUpsertResult: { data: EXISTING_PROFILE, error: null },
upsertError: { message: 'check_violation' },
});
const out = await runMiddleware({ headers: { authorization: 'Bearer good' } });
expect(out.status).toBe(401);
expect(out.body.error).toMatch(/User profile creation failed/);
});
test('post-upsert reread still empty → 401 User profile not found', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: VALID_USER }, error: null },
selectResult: { data: null, error: { code: 'PGRST116', message: 'no rows' } },
selectAfterUpsertResult: { data: null, error: null },
});
const out = await runMiddleware({ headers: { authorization: 'Bearer good' } });
expect(out.status).toBe(401);
expect(out.body.error).toMatch(/User profile not found/);
});
test('non-PGRST116 select error → 401 immediately (no upsert)', async () => {
mockSupabaseFake = makeFake({
getUserResult: { data: { user: VALID_USER }, error: null },
selectResult: { data: null, error: { code: '42501', message: 'permission denied' } },
selectAfterUpsertResult: { data: EXISTING_PROFILE, error: null },
});
const out = await runMiddleware({ headers: { authorization: 'Bearer good' } });
expect(out.status).toBe(401);
// Critically, no upsert attempt — we don't recover from a real DB error.
expect(mockSupabaseFake._upserts).toHaveLength(0);
});
});