Session 17: Audit response — checkout 401 fix, hero prop 404 fix, Slate parsing fix, ALL tab cascade isolation, cookie/nav/footer/autocomplete polish (1438 tests)
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user