import {createRequire} from 'node:module'; import {writeFileSync} from 'node:fs'; const require=createRequire(new URL('../../website/package.json',import.meta.url)); const {initializeApp,deleteApp}=require('firebase/app'); const {getAuth,connectAuthEmulator,signInWithEmailAndPassword,signOut}=require('firebase/auth'); const {getFirestore,connectFirestoreEmulator,doc,setDoc,getDoc,updateDoc,writeBatch}=require('firebase/firestore'); process.env.FIRESTORE_EMULATOR_HOST='127.0.0.1:8088';process.env.FIREBASE_AUTH_EMULATOR_HOST='127.0.0.1:9099'; const {initializeApp:adminInit}=require('firebase-admin/app');const {getFirestore:adminDb}=require('firebase-admin/firestore'); const projectId='demo-motorsignal-qa';const admin=adminDb(adminInit({projectId}));const users={};const results=[]; if(!process.env.QA_DEMO_PASSWORD)throw Error('Set QA_DEMO_PASSWORD'); for(const role of ['owner','admin','organizer','participant','member','outsider']){const app=initializeApp({projectId,apiKey:'demo-key'},role);const auth=getAuth(app);connectAuthEmulator(auth,'http://127.0.0.1:9099',{disableWarnings:true});const db=getFirestore(app);connectFirestoreEmulator(db,'127.0.0.1',8088);const {user}=await signInWithEmailAndPassword(auth,`qa-${role}@motorsignal.example`,process.env.QA_DEMO_PASSWORD);users[role]={app,auth,db,uid:user.uid};} async function check(id,fn){try{await fn();results.push({id,status:'PASS'});}catch(e){results.push({id,status:'FAIL',error:e.message});}console.log(JSON.stringify(results.at(-1)));} async function denied(fn){try{await fn();throw Error('Unexpectedly allowed');}catch(e){if(e.code!=='permission-denied')throw e;}} const {owner,admin:adm,organizer,participant,member,outsider}=users;const now=new Date();const club='qa-extra-club'; await admin.doc(`clubs/${club}`).set({id:club,name:'QA Extra Club',slug:'qa-extra-club',ownerUid:owner.uid,visibility:'public',admissionPolicy:'request',memberCount:2,createdAt:now,updatedAt:now,archivedAt:null}); for(const role of ['owner','admin'])await admin.doc(`clubs/${club}/members/${users[role].uid}`).set({uid:users[role].uid,clubId:club,role,status:'active'}); await check('AUTH-SDK-token-refresh',async()=>{const token=await participant.auth.currentUser.getIdToken(true);if(!token)throw Error('Missing token');}); await check('AUTH-SDK-club-membership-request',()=>setDoc(doc(outsider.db,`clubs/${club}/members/${outsider.uid}`),{uid:outsider.uid,clubId:club,role:'member',status:'requested'})); await check('AUTH-SDK-club-membership-admin-approve',async()=>{const b=writeBatch(adm.db);b.update(doc(adm.db,`clubs/${club}/members/${outsider.uid}`),{status:'active'});b.update(doc(adm.db,`clubs/${club}`),{memberCount:3,lastMembershipChangeUid:outsider.uid,updatedAt:now});await b.commit();if((await getDoc(doc(outsider.db,`clubs/${club}/members/${outsider.uid}`))).data().status!=='active')throw Error('Not active');}); const id='qa-extra-ride';await admin.doc(`group_rides/${id}`).set({id,title:'QA Extra Participants',organizerUid:organizer.uid,status:'scheduled',scheduledStartAt:'2026-10-20T14:00:00Z',participantCount:3,participantLimit:10,accessUids:[organizer.uid,participant.uid,member.uid],visibility:'public',joinPolicy:'open',meetingPoint:{latitude:34.6,longitude:-83.9,displayName:'QA'},createdAt:now,updatedAt:now}); for(const role of ['organizer','participant','member'])await admin.doc(`group_rides/${id}/participants/${users[role].uid}`).set({userId:users[role].uid,role:role==='organizer'?'organizer':'participant',status:'accepted'}); await check('AUTH-SDK-accepted-participant-leave',async()=>{const b=writeBatch(participant.db);b.update(doc(participant.db,`group_rides/${id}/participants/${participant.uid}`),{status:'left'});b.update(doc(participant.db,`group_rides/${id}`),{participantCount:2,accessUids:[organizer.uid,member.uid],updatedAt:now});await b.commit();}); await check('AUTH-SDK-organizer-remove-participant',async()=>{const b=writeBatch(organizer.db);b.update(doc(organizer.db,`group_rides/${id}/participants/${member.uid}`),{status:'removed'});b.update(doc(organizer.db,`group_rides/${id}`),{participantCount:1,accessUids:[organizer.uid],updatedAt:now});await b.commit();if((await getDoc(doc(organizer.db,`group_rides/${id}`))).data().participantCount!==1)throw Error('Incorrect count');}); await check('AUTH-SDK-organizer-cancel',()=>updateDoc(doc(organizer.db,`group_rides/${id}`),{status:'canceled',updatedAt:now})); await check('AUTH-SDK-signed-out-access-denied',async()=>{await signOut(participant.auth);await denied(()=>getDoc(doc(participant.db,`clubs/${club}`)));}); writeFileSync(new URL('auth.extra-results.json',import.meta.url),JSON.stringify({projectId,executedAt:new Date().toISOString(),kind:'Real Auth and Firestore emulator SDK; not UI E2E',results},null,2));await Promise.all(Object.values(users).map(u=>deleteApp(u.app)));await admin.terminate();process.exit(results.some(r=>r.status==='FAIL')?1:0);