2026-07-24 21:08:29 +05:30
// DB smoke test — the regression harness for the SQLite→Postgres migration.
//
// Deliberately focused on the DB-backed HTTP paths (the surface the async repo conversion touches):
// auth, users, messages, attachments, conversations/groups, reactions, mentions, edit/delete, polls,
// scheduled meetings, favorites, audit. It asserts against the CURRENT API shapes and runs to completion
// with a pass/fail count, so it stays a trustworthy before/after check as repos go async and, later, as we
// flip the backend from sqlite to pg. It does NOT exercise WebSocket signaling (in-memory, not the DB).
//
// Run: node test/db-smoke.js (uses a throwaway temp DB; DB_BACKEND env selects sqlite|pg)
2026-08-12 08:20:26 +05:30
// SQLite was retired 2026-08-12 — this suite runs against Postgres now. Point DATABASE_URL at a DISPOSABLE
// test database (NEVER production — the suite creates + mutates rows), e.g.:
// DATABASE_URL=postgres://bizgaze:PW@localhost:5432/bizgaze_test node test/db-smoke.js
process . env . DB_BACKEND = process . env . DB_BACKEND || 'pg' ;
if ( ! process . env . DATABASE_URL ) {
console . log ( 'SKIP db-smoke: set DATABASE_URL to a disposable Postgres test DB (SQLite retired 2026-08-12).' );
process . exit ( 0 );
}
2026-07-24 21:08:29 +05:30
const PORT = 8097 ;
process . env . PORT = PORT ;
process . env . HTTPS_PORT = 8446 ;
const { server } = require ( '../server' );
const BASE = `http://localhost: ${ PORT } ` ;
let passed = 0 , failed = 0 ;
const fails = [];
function check ( name , cond ) {
if ( cond ) { passed ++ ; } else { failed ++ ; fails . push ( name ); console . log ( ' FAIL -' , name ); }
}
const wait = ( ms ) => new Promise (( r ) => setTimeout ( r , ms ));
async function post ( p , body , cookie ) {
const r = await fetch ( BASE + p , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , ...( cookie ? { Cookie : cookie } : {}) }, body : body ? JSON . stringify ( body ) : undefined });
const sc = r . headers . get ( 'set-cookie' );
return { status : r . status , data : await r . json (). catch (() => ({})), cookie : sc ? sc . split ( ';' )[ 0 ] : cookie };
}
async function get ( p , cookie ) {
const r = await fetch ( BASE + p , { headers : cookie ? { Cookie : cookie } : {} });
return { status : r . status , data : await r . json (). catch (() => ({})) };
}
( async () => {
2026-08-13 00:29:55 +05:30
// Wait for the server to actually be LISTENING — a cold Postgres boot (connect + apply the full schema)
// takes a few seconds, well past the fixed 300ms that was fine for SQLite's instant in-memory init.
for ( let i = 0 ; i < 150 ; i ++ ) { try { await fetch ( BASE + '/' ); break ; } catch ( _ ) { await wait ( 200 ); } }
2026-08-12 08:20:26 +05:30
console . log ( 'DB smoke tests (backend=' + process . env . DB_BACKEND + '):' );
2026-07-24 21:08:29 +05:30
// Auth
const reg = await post ( '/api/register' , { email : 'admin@smoke.test' , password : 'supersecret' , teamName : 'Smoke Co' });
check ( 'register' , reg . status === 200 && reg . data . ok );
const login = await post ( '/api/login' , { email : 'admin@smoke.test' , password : 'supersecret' });
check ( 'login sets cookie + tokens' , !! login . cookie && !! login . data . token );
const cookie = login . cookie ;
const me = await get ( '/api/me' , cookie );
check ( 'me = admin' , me . status === 200 && me . data . role === 'admin' );
const adminId = me . data . id ;
// Second user + contacts
await post ( '/api/users' , { email : 'bob@smoke.test' , password : 'supersecret' , name : 'Bob' }, cookie );
const bobLogin = await post ( '/api/login' , { email : 'bob@smoke.test' , password : 'supersecret' });
const bobCookie = bobLogin . cookie ;
const contacts = await get ( '/api/messages/contacts' , cookie );
check ( 'contacts include bob' , contacts . status === 200 && contacts . data . some (( c ) => c . email === 'bob@smoke.test' ));
const bobId = ( contacts . data . find (( c ) => c . email === 'bob@smoke.test' ) || {}). id ;
// DM send + thread + conversations
const dm = await post ( '/api/messages' , { to : bobId , body : 'hello bob' }, cookie );
check ( 'send DM' , dm . status === 200 && !! dm . data . id );
const th = await get ( '/api/messages/thread?with=' + bobId , cookie );
check ( 'thread has the DM' , Array . isArray ( th . data ) && th . data . some (( m ) => m . body === 'hello bob' ));
const convs = await get ( '/api/messages/conversations' , cookie );
check ( 'conversations list DM' , convs . status === 200 && convs . data . some (( c ) => c . kind === 'dm' && c . id === bobId ));
// Attachment upload + send + authorized fetch
const up = await fetch ( BASE + '/api/messages/upload' , { method : 'POST' , headers : { Cookie : cookie , 'Content-Type' : 'image/png' , 'X-Filename' : encodeURIComponent ( 'p.png' ) }, body : Buffer . from ([ 0x89 , 0x50 , 0x4e , 0x47 , 0x0d , 0x0a , 0x1a , 0x0a ]) });
const upd = await up . json ();
check ( 'upload attachment' , up . status === 200 && !! upd . id );
const dmA = await post ( '/api/messages' , { to : bobId , attachmentId : upd . id }, cookie );
check ( 'send DM with attachment' , dmA . status === 200 && dmA . data . attachment && dmA . data . attachment . id === upd . id );
const fileOk = await fetch ( BASE + '/files/' + upd . id , { headers : { Cookie : bobCookie } });
check ( 'recipient can fetch the attachment (carrier auth)' , fileOk . status === 200 );
// Edit + delete
const em = await post ( '/api/messages/edit' , { id : dm . data . id , body : 'hello bob (edited)' }, cookie );
check ( 'edit message' , em . status === 200 );
const del = await post ( '/api/messages/delete' , { id : dm . data . id }, cookie );
check ( 'delete message' , del . status === 200 );
// Reactions
const rx = await post ( '/api/messages/react' , { messageId : dmA . data . id , emoji : '👍' }, bobCookie );
check ( 'react to message' , rx . status === 200 && Array . isArray ( rx . data . reactions ) && rx . data . reactions . some (( r ) => r . emoji === '👍' ));
// Group create + add + message + info + mention
const grp = await post ( '/api/groups' , { name : 'Smoke Group' , memberIds : [ bobId ] }, cookie );
check ( 'create group' , grp . status === 200 && !! grp . data . id );
const gid = grp . data . id ;
const gmsg = await post ( '/api/messages' , { group : gid , body : 'hi @everyone' , mentions : [ 'everyone' ] }, cookie );
check ( 'group message with mention' , gmsg . status === 200 && !! gmsg . data . id );
const ginfo = await get ( '/api/groups/info?group=' + gid , cookie );
check ( 'group info members' , ginfo . status === 200 && ginfo . data . members . length === 2 );
// Poll create + vote
const poll = await post ( '/api/polls' , { group : gid , question : 'Lunch?' , options : [ 'Pizza' , 'Sushi' ] }, cookie );
check ( 'create poll' , poll . status === 200 && !! poll . data . id );
const pid = poll . data . id ;
const vote = await post ( '/api/polls/vote' , { pollId : pid , optionIdx : 0 }, bobCookie );
check ( 'vote in poll' , vote . status === 200 );
// Scheduled meeting + paginated list
const sched = await post ( '/api/meetings/schedule' , { group : gid , title : 'Sync' , scheduledAt : Date . now () + 3600000 }, cookie );
check ( 'schedule meeting' , sched . status === 200 && /^\d{6}$/ . test ( sched . data . roomCode || '' ));
const mlist = await get ( '/api/meetings' , cookie );
check ( 'meetings list (paginated) has it' , mlist . status === 200 && Array . isArray ( mlist . data . list ) && mlist . data . list . some (( m ) => m . id === sched . data . id ));
// Favorites
const fav = await post ( '/api/favorites' , { kind : 'dm' , id : bobId , on : true }, cookie );
check ( 'set favorite' , fav . status === 200 && fav . data . favorite === true );
// Audit trail
const audit = await get ( '/api/audit' , cookie );
check ( 'audit has login + register' , Array . isArray ( audit . data ) && audit . data . some (( a ) => a . action === 'login' ) && audit . data . some (( a ) => a . action === 'user_registered' ));
console . log ( `\n ${ passed } passed, ${ failed } failed.` + ( failed ? ' Failing: ' + fails . join ( ', ' ) : '' ));
server . close ();
process . exit ( failed ? 1 : 0 );
})(). catch (( e ) => { console . error ( 'SMOKE ERROR:' , e ); server . close (); process . exit ( 2 ); });