127 lines
4.5 KiB
JavaScript
127 lines
4.5 KiB
JavaScript
// Run:
|
|
// node backend/tests/auth_signup_signin.mjs
|
|
// BASE=https://staging.aptivaai.com ALLOW_NON_DEV=1 node backend/tests/auth_signup_signin.mjs
|
|
//
|
|
// Behavior:
|
|
// - Creates a brand-new user each run (unique email/username)
|
|
// - Cookie-based auth only (captures Set-Cookie from register/signin)
|
|
// - Verifies /api/signin returns { message }, /api/user-profile returns 200 JSON w/ NO id leakage
|
|
// - Verifies /api/user-profile?fields=… respects allowlist
|
|
// - Verifies /api/logout clears cookie and subsequent /api/user-profile is unauthorized
|
|
// - Defaults to dev; requires ALLOW_NON_DEV=1 to run on non-dev BASE
|
|
|
|
import assert from 'node:assert/strict';
|
|
|
|
const BASE = process.env.BASE || 'https://dev1.aptivaai.com';
|
|
if (BASE !== 'https://dev1.aptivaai.com' && process.env.ALLOW_NON_DEV !== '1') {
|
|
console.error(`Refusing to run against non-dev BASE='${BASE}'. Set ALLOW_NON_DEV=1 to override.`);
|
|
process.exit(2);
|
|
}
|
|
|
|
const j = (o) => JSON.stringify(o);
|
|
const rand = () => Math.random().toString(36).slice(2, 10);
|
|
const email = `jcoakley@aptivaai.com`;
|
|
const username = `qa_${rand()}`;
|
|
const password = `Aa1!${rand()}Z`;
|
|
|
|
let cookie = ''; // session cookie (auth)
|
|
function captureSetCookie(headers) {
|
|
// In ESM fetch, headers.get('set-cookie') returns the first Set-Cookie (enough for session)
|
|
const sc = headers.get('set-cookie');
|
|
if (sc) cookie = sc.split(';')[0];
|
|
}
|
|
|
|
async function req(path, { method = 'GET', headers = {}, body } = {}) {
|
|
const h = {
|
|
'Content-Type': 'application/json',
|
|
...(cookie ? { Cookie: cookie } : {}),
|
|
...headers,
|
|
};
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
method,
|
|
headers: h,
|
|
body: body ? j(body) : undefined,
|
|
});
|
|
const text = await res.text();
|
|
let json = null;
|
|
try { json = JSON.parse(text); } catch {}
|
|
return { res, text, json };
|
|
}
|
|
|
|
(async () => {
|
|
// 1) Register (201)
|
|
{
|
|
const { res, json } = await req('/api/register', {
|
|
method: 'POST',
|
|
body: {
|
|
username,
|
|
password,
|
|
firstname: 'QA',
|
|
lastname: 'Bot',
|
|
email,
|
|
zipcode: '30024',
|
|
state: 'GA',
|
|
area: 'Atlanta',
|
|
career_situation: 'planning',
|
|
},
|
|
});
|
|
assert.equal(res.status, 201, `register should 201, got ${res.status}`);
|
|
captureSetCookie(res.headers);
|
|
assert.ok(cookie, 'session cookie must be set after register');
|
|
}
|
|
|
|
// 2) Sign in (200) — cookie refreshed, { message } in body
|
|
{
|
|
const { res, json } = await req('/api/signin', {
|
|
method: 'POST',
|
|
body: { username, password },
|
|
});
|
|
assert.equal(res.status, 200, `signin should 200, got ${res.status}`);
|
|
assert.ok(json && typeof json.message === 'string', 'signin returns { message }');
|
|
captureSetCookie(res.headers);
|
|
assert.ok(cookie, 'session cookie must be present after signin');
|
|
}
|
|
|
|
// 3) Profile (200, JSON, no id leakage)
|
|
{
|
|
const { res, json, text } = await req('/api/user-profile');
|
|
assert.equal(res.status, 200, `profile fetch should 200, got ${res.status}, body=${text.slice(0,120)}`);
|
|
assert.ok(json && typeof json === 'object', 'profile returns JSON object');
|
|
if ('id' in json || 'user_id' in json) throw new Error('profile must NOT include id/user_id');
|
|
}
|
|
|
|
// 4) Field-filtered profile (allowlist)
|
|
{
|
|
const fields = 'firstname,lastname,career_situation';
|
|
const { res, json, text } = await req(`/api/user-profile?fields=${encodeURIComponent(fields)}`);
|
|
assert.equal(res.status, 200, `filtered profile should 200, got ${res.status}, body=${text.slice(0,120)}`);
|
|
const keys = Object.keys(json || {});
|
|
for (const k of keys) {
|
|
if (!['firstname','lastname','career_situation','sms_opt_in','phone_e164','email'].includes(k)) {
|
|
throw new Error(`unexpected field '${k}' in filtered profile`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5) Username existence
|
|
{
|
|
const { res, json } = await req(`/api/check-username/${encodeURIComponent(username)}`);
|
|
assert.equal(res.status, 200, 'check-username should 200');
|
|
assert.equal(json?.exists, true, 'new username should exist');
|
|
}
|
|
|
|
// 6) Logout then profile blocked
|
|
{
|
|
const out = await req('/api/logout', { method: 'POST' });
|
|
assert.equal(out.res.status, 200, `logout should 200, got ${out.res.status}`);
|
|
cookie = ''; // simulate cleared cookie
|
|
const { res } = await req('/api/user-profile');
|
|
if (res.status === 200) throw new Error('profile should NOT be accessible after logout');
|
|
}
|
|
|
|
console.log('✓ AUTH regression suite passed');
|
|
})().catch((e) => {
|
|
console.error('✖ AUTH regression failed:', e?.message || e);
|
|
process.exit(1);
|
|
});
|