const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const ts = require('typescript'); // Transpile TypeScript modules on the fly function loadTsModule(relativePath) { const fullPath = path.resolve(__dirname, relativePath); const dir = path.dirname(fullPath); const tsCode = fs.readFileSync(fullPath, 'utf8'); const jsCode = ts.transpileModule(tsCode, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, jsx: ts.JsxEmit.ReactJSX, }, }).outputText; const m = { exports: {} }; const customRequire = (specifier) => { if (specifier === './firebase-client' || specifier.includes('firebase-client')) { return { firestore: {}, firebaseAuth: { currentUser: null, authStateReady: async () => {} } }; } if (specifier === 'firebase/firestore') { return { collection: () => ({}), doc: (_db, ...parts) => ({ path: parts.join('/') }), getDoc: async () => ({ exists: () => false, data: () => ({}) }), getDocs: async () => ({ docs: [] }), query: () => ({}), where: () => ({}), limit: () => ({}), collectionGroup: () => ({}), updateDoc: async () => {}, serverTimestamp: () => new Date(), runTransaction: async (_db, cb) => cb({ get: async () => ({ exists: () => false }), update: () => {} }), }; } if (specifier.startsWith('.')) { const resolved = path.resolve(dir, specifier); if (fs.existsSync(resolved + '.ts')) return loadTsModule(path.relative(__dirname, resolved + '.ts')); if (fs.existsSync(resolved + '.tsx')) return loadTsModule(path.relative(__dirname, resolved + '.tsx')); if (fs.existsSync(resolved)) return require(resolved); } return require(specifier); }; const fn = new Function('require', 'exports', 'module', '__filename', '__dirname', jsCode); fn(customRequire, m.exports, m, fullPath, dir); return m.exports; } console.log('--- STARTING FOUNDER PORTAL TEST SUITE ---'); // 1. Load domain logic & fixtures const clubData = loadTsModule('../lib/club-data.ts'); const clubFixtures = loadTsModule('../lib/club-fixtures.ts'); const { getPortalPermissions, sanitizePortalError, fetchClubData, fetchManageableClubs, fetchFounderClubContext, updateClubProfile, updateClubAccessSettings, fetchEligibleTransferMembers, transferClubOwnership, archiveClub, fetchClubApplications, approveClubApplication, declineClubApplication, validateClubImageUrl, fetchClubRides, } = clubData; const { FIXTURE_FOUNDER_CLUB, FIXTURE_METRICS, FIXTURE_UPCOMING_RIDES, FIXTURE_PAST_RIDES } = clubFixtures; // ========================================================================= // TEST 1: Role-Based Portal Access (Owner vs Admin vs Member vs Unauthenticated) // ========================================================================= console.log('Testing: Role-based portal access...'); // Unauthenticated const unauthPerms = getPortalPermissions('member', false); assert.equal(unauthPerms.isAuthenticated, false); assert.equal(unauthPerms.canManageSettings, false); assert.equal(unauthPerms.canInviteMembers, false); assert.equal(unauthPerms.canApproveApplications, false); assert.equal(unauthPerms.canPlanRides, false); assert.equal(unauthPerms.canTransferOwnership, false); // Authenticated Member (Standard non-admin rider) const memberPerms = getPortalPermissions('member', true); assert.equal(memberPerms.isAuthenticated, true); assert.equal(memberPerms.role, 'member'); assert.equal(memberPerms.canManageSettings, false, 'Member must not manage settings'); assert.equal(memberPerms.canInviteMembers, false, 'Member must not invite members'); assert.equal(memberPerms.canApproveApplications, false, 'Member must not approve applications'); assert.equal(memberPerms.canPlanRides, false, 'Member must not plan club rides'); assert.equal(memberPerms.canTransferOwnership, false, 'Member must not transfer ownership'); // Authenticated Admin const adminPerms = getPortalPermissions('admin', true); assert.equal(adminPerms.isAuthenticated, true); assert.equal(adminPerms.role, 'admin'); assert.equal(adminPerms.canManageSettings, true, 'Admin can manage general settings'); assert.equal(adminPerms.canInviteMembers, true, 'Admin can invite members'); assert.equal(adminPerms.canApproveApplications, true, 'Admin can approve applications'); assert.equal(adminPerms.canPlanRides, true, 'Admin can plan club rides'); assert.equal(adminPerms.canTransferOwnership, false, 'Admin MUST NOT transfer ownership'); // Authenticated Owner const ownerPerms = getPortalPermissions('owner', true); assert.equal(ownerPerms.isAuthenticated, true); assert.equal(ownerPerms.role, 'owner'); assert.equal(ownerPerms.canManageSettings, true); assert.equal(ownerPerms.canInviteMembers, true); assert.equal(ownerPerms.canApproveApplications, true); assert.equal(ownerPerms.canPlanRides, true); assert.equal(ownerPerms.canTransferOwnership, true, 'Only Owner can transfer ownership'); console.log(' PASS: Role-based permissions strictly enforced (Owner, Admin, Member, Unauth).'); // ========================================================================= // TEST 2: Club Display Rank != Administrative Role (Rank never grants access) // ========================================================================= console.log('Testing: Club Rank does not escalate software permissions...'); // If a member has Display Rank 'President' but software role 'member': const spoofedRankPerms = getPortalPermissions('member', true); assert.equal(spoofedRankPerms.canApproveApplications, false); assert.equal(spoofedRankPerms.canTransferOwnership, false); console.log(' PASS: Club Rank is purely display/organizational; software permissions derive only from role.'); // ========================================================================= // TEST 3: Safe Error Sanitization (No raw backend exception leakage) // ========================================================================= console.log('Testing: Safe error sanitization & leak prevention...'); const leaks = [ 'FirebaseException: [cloud_firestore/permission-denied] Missing or insufficient permissions at /databases/(default)/documents/clubs/ms-atl-01', 'FAILED_PRECONDITION: The query requires an index.', 'Error: connect ECONNREFUSED 127.0.0.1:8080 at TCPConnectWrap.afterConnect [as oncomplete]', 'DocumentNotFoundException: document /databases/(default)/documents/clubs/unknown was not found', 'TypeError: Cannot read property uid of undefined at club_service.dart:142', ]; for (const rawLeak of leaks) { const sanitized = sanitizePortalError(rawLeak); assert(!sanitized.includes('FirebaseException'), `Leaked FirebaseException in: ${sanitized}`); assert(!sanitized.includes('FAILED_PRECONDITION'), `Leaked FAILED_PRECONDITION in: ${sanitized}`); assert(!sanitized.includes('/databases/'), `Leaked Firestore path in: ${sanitized}`); assert(!sanitized.includes('127.0.0.1'), `Leaked network internals in: ${sanitized}`); assert(!sanitized.includes('.dart:'), `Leaked stack trace in: ${sanitized}`); assert( sanitized === 'Club information is restricted or requires authentication.' || sanitized === "Club information couldn't be loaded. Check your network connection." || sanitized === 'Club information could not be found.' || sanitized === 'Something went wrong. Please try again.', `Unexpected sanitized message: ${sanitized}` ); } console.log(' PASS: Zero raw backend exception or Firestore path leakage.'); // ========================================================================= // TEST 4: No Silent Production Demo Fallbacks // ========================================================================= async function runAllTests() { console.log('Testing: No silent production demo fallbacks...'); let threw = false; try { await fetchClubData('nonexistent-club-id'); } catch (err) { threw = true; assert.equal(err.message, 'Club information could not be found.'); } assert.equal(threw, true, 'fetchClubData must throw not-found instead of returning demo club'); console.log(' PASS: Production runtime strictly refuses silent demo fallbacks.'); // ========================================================================= // TEST 5: ClubMember != GroupRideParticipant Invariant & Location Privacy // ========================================================================= console.log('Testing: ClubMember != GroupRideParticipant & Location Privacy Invariant...'); // Read ClubRideCard source to verify exact verbatim notice text const rideCardSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/club-ride-card.tsx'), 'utf8'); const verbatimNotice = 'Live location is shared only with eligible ride participants while sharing is enabled. Club membership alone never grants access to rider location.'; assert(rideCardSrc.includes(verbatimNotice), 'Missing verbatim live location privacy notice in ClubRideCard'); // Verify that forbidden claims are NOT made in ClubRideCard assert(!rideCardSrc.includes('end-to-end encryption'), 'ClubRideCard must not falsely claim end-to-end encryption'); assert(!rideCardSrc.includes('RTK'), 'ClubRideCard must not falsely claim RTK'); assert(!rideCardSrc.includes('mesh networking'), 'ClubRideCard must not falsely claim mesh networking'); console.log(' PASS: Verbatim live location privacy notice verified; no unverified claims present.'); // ========================================================================= // TEST 6: Club Mileage Privacy Behavior & Verified Badge Removal // ========================================================================= console.log('Testing: Club Miles privacy behavior & badge hardening...'); const metricCardSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/metric-card.tsx'), 'utf8'); const mileageDisclaimer = 'Calculated from eligible completed Club Rides. Private rider mileage is not automatically included.'; assert(metricCardSrc.includes(mileageDisclaimer), 'Missing mandatory Club Miles privacy disclaimer'); assert(metricCardSrc.includes('Completed Club Rides only'), 'Missing "Completed Club Rides only" badge text'); assert(!metricCardSrc.includes('VERIFIED'), 'MetricCard must NOT contain misleading "VERIFIED" badge'); console.log(' PASS: Club Miles strict privacy semantics enforced (eligible completed rides only, no false VERIFIED badge).'); // ========================================================================= // TEST 7: Roles & Ranks Architecture Note & No Fabricated Numbers // ========================================================================= console.log('Testing: Roles & Ranks separation architecture note...'); const rolesRanksSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/roles-and-ranks-summary.tsx'), 'utf8'); const verbatimRbacNote = 'Administrative roles determine software permissions. Club ranks are organizational titles and do not grant additional access.'; assert(rolesRanksSrc.includes(verbatimRbacNote), 'Missing verbatim RBAC architecture note'); assert(rolesRanksSrc.includes('ADMINISTRATIVE ROLES (Software Permissions)'), 'Missing software permissions section'); assert(rolesRanksSrc.includes('CLUB RANKS (ORGANIZATIONAL TITLES)'), 'Missing organizational titles section'); assert(!rolesRanksSrc.includes('106 Members'), 'RolesAndRanksSummary must not contain hardcoded 106 Members fabrication'); console.log(' PASS: Roles vs Ranks separation and verbatim architecture note verified (no fabricated counts).'); // ========================================================================= // TEST 8: Top Bar Dev-Gating // ========================================================================= console.log('Testing: Top Bar role preview switcher dev-gating...'); const topBarSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/founder-top-bar.tsx'), 'utf8'); assert(topBarSrc.includes("process.env.NODE_ENV === 'development'"), 'Top bar role preview must be dev-gated'); console.log(' PASS: Top Bar role preview switcher strictly dev-gated.'); // ========================================================================= // TEST 9: Dynamic /clubs/manage Discovery & Page Guards // ========================================================================= console.log('Testing: /clubs/manage discovery route & access guards...'); const manageDiscoverySrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/manage/page.tsx'), 'utf8'); assert(!manageDiscoverySrc.includes("redirect('/clubs/motorsignal-atlanta/manage')"), 'Must NOT have static redirect to Atlanta'); assert(manageDiscoverySrc.includes('fetchManageableClubs'), 'Must discover manageable clubs via fetchManageableClubs'); assert(manageDiscoverySrc.includes('No Clubs to Manage'), 'Must render "No Clubs to Manage" empty state'); assert(manageDiscoverySrc.includes('Select Club to Manage'), 'Must render Club Selector for >1 clubs'); // FP-1.2: Administrative Role Terminology (Owner, Admin, Member - NEVER Officer) assert(manageDiscoverySrc.includes("c.role === 'owner' ? 'Owner' : 'Admin'"), 'Manageable-Club selector must map admin role to "Admin", NOT "Officer"'); assert(!manageDiscoverySrc.includes("? 'Owner' : 'Officer'"), 'Manageable-Club selector must NEVER map admin role to "Officer"'); assert(!manageDiscoverySrc.includes("Owner or Officer"), 'Manageable-Club selector copy must say Owner or Admin, never Owner or Officer'); const managePageSrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/[clubId]/manage/page.tsx'), 'utf8'); assert(managePageSrc.includes('Management Access Restricted'), 'Must guard against non-admin member access'); const clubsPageSrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/page.tsx'), 'utf8'); assert(clubsPageSrc.includes('href="/clubs/manage"'), 'Missing link to /clubs/manage on /clubs page'); console.log(' PASS: /clubs/manage dynamic discovery, role terminology (Owner/Admin/Member), and access control verified.'); // ========================================================================= // TEST 10: Responsive Layout Breakpoints // ========================================================================= console.log('Testing: Responsive layout classes across 1440, 1024, 768, and mobile...'); const shellSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/founder-portal-shell.tsx'), 'utf8'); const sidebarSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/founder-sidebar.tsx'), 'utf8'); // Desktop persistent sidebar: lg:pl-72, lg:translate-x-0, max-w-[1536px] assert(shellSrc.includes('lg:pl-72'), 'Missing lg:pl-72 desktop sidebar offset'); assert(shellSrc.includes('max-w-[1536px]'), 'FounderPortalShell must use max-w-[1536px] desktop canvas container'); assert(!shellSrc.includes('max-w-[1440px]'), 'FounderPortalShell must NOT retain old max-w-[1440px] cap'); assert(shellSrc.includes('founder-portal-root'), 'Missing founder-portal-root class on shell'); assert(sidebarSrc.includes('w-72'), 'Missing w-72 persistent sidebar width'); // Mobile drawer: translate-x-full lg:translate-x-0, touch targets assert(sidebarSrc.includes('-translate-x-full lg:translate-x-0'), 'Missing mobile drawer translation classes'); assert(sidebarSrc.includes('fixed left-0 top-0 h-full'), 'Missing fixed sidebar drawer layout'); console.log(' PASS: Responsive shell verified for 1440px desktop, 1024px tablet, and mobile off-canvas drawer.'); // ========================================================================= // TEST 11: FP-1.3 Founder Identity Contract // ========================================================================= console.log('Testing: FP-1.3 Founder Identity Contract (/public_profiles point-read & conservative fallback)...'); const clubDataSrc = fs.readFileSync(path.resolve(__dirname, '../lib/club-data.ts'), 'utf8'); const typesSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/types.ts'), 'utf8'); // Verify ownerUid is documented as ownership identity only, not display name assert(typesSrc.includes('Identifies ownership identity only'), 'Club.ownerUid must document ownership identity only'); assert(typesSrc.includes('Canonical profile resolution: /public_profiles/{ownerUid}'), 'Club must document profile resolution path'); assert(typesSrc.includes("Never persist a redundant 'founderName' field on /clubs/{clubId}"), 'Club must forbid redundant founderName in schema'); // Verify club-data.ts point-reads public_profiles/{ownerUid} assert(clubDataSrc.includes("doc(firestore, 'public_profiles', d.ownerUid)"), 'fetchClubData must perform point read to public_profiles/{ownerUid}'); assert(clubDataSrc.includes("founderName = d.ownerUid === callerUid ? 'You' : 'Club Founder'"), 'Must maintain conservative fallback if profile not resolved'); console.log(' PASS: Founder identity contract verified (ownerUid is identity only; profile read via /public_profiles/{ownerUid}; conservative fallback).'); // ========================================================================= // TEST 12: FP-1.3 Club Ride Association Architecture Status // ========================================================================= console.log('Testing: FP-1.3 Club Ride Association Architecture Status...'); assert(typesSrc.includes('Production GroupRide documents support optional immutable clubId affiliation'), 'Types must document live optional GroupRide.clubId support'); assert(typesSrc.includes('GroupRide.clubId == club.id'), 'Types must document the canonical Club affiliation link'); assert(typesSrc.includes('Broad client-side Club discovery remains deferred for mutual-block privacy'), 'Types must document the deferred discovery boundary'); assert(typesSrc.includes('ClubMember != GroupRideParticipant'), 'Must document ClubMember != GroupRideParticipant invariant'); console.log(' PASS: Club affiliation is documented as live while broad privacy-preserving discovery remains deferred.'); // ========================================================================= // TEST 13: FP-1.3 Route Distance & Duration Storage Contract // ========================================================================= console.log('Testing: FP-1.3 Route Distance & Duration Storage Contract...'); // Verify types.ts documents persisted straightLineDistance vs optional Valhalla road estimates assert(typesSrc.includes('straightLineDistanceMeters: Geodesic Haversine distance between ordered stops (persisted)'), 'Must document straightLineDistanceMeters contract'); assert(typesSrc.includes('estimatedDistanceMeters & estimatedDurationSeconds: Optional Valhalla road estimates'), 'Must document optional road estimates contract'); // Verify club-ride-card renders '--' when metrics are unpopulated assert(rideCardSrc.includes("ride.distanceMiles ? `${ride.distanceMiles} mi` : '--'"), 'ClubRideCard must render -- when distance is unpopulated'); assert(rideCardSrc.includes("ride.durationString ?? '--'"), 'ClubRideCard must render -- when duration is unpopulated'); console.log(' PASS: Route distance & duration contract verified (persisted straight-line vs optional road estimates; fallback to --).'); // ========================================================================= // TEST 14 (Test A): FP-13 Profile Authorization Guard // ========================================================================= console.log('Testing: FP-13 Test A - Profile Authorization Guard...'); // Owner and Admin allowed const ownerPerms14 = getPortalPermissions('owner', true); assert.equal(ownerPerms14.canManageSettings, true, 'Owner must have canManageSettings'); const adminPerms14 = getPortalPermissions('admin', true); assert.equal(adminPerms14.canManageSettings, true, 'Admin must have canManageSettings'); // Member (non-admin) forbidden const memberPerms14 = getPortalPermissions('member', true); assert.equal(memberPerms14.canManageSettings, false, 'Member must NOT have canManageSettings'); // Unauthenticated forbidden const unauthPerms14 = getPortalPermissions('member', false); assert.equal(unauthPerms14.isAuthenticated, false, 'Unauth must not be authenticated'); assert.equal(unauthPerms14.canManageSettings, false, 'Unauth must NOT have canManageSettings'); // Verify page component inspects auth and permissions const profilePageSrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/[clubId]/manage/profile/page.tsx'), 'utf8'); assert(profilePageSrc.includes('!isDemoPreview && !currentUser'), 'Profile page must check authentication'); assert(profilePageSrc.includes('title="Authentication Required"'), 'Profile page must render Authentication Required error'); assert(profilePageSrc.includes("router.push('/login')"), 'Profile page must direct unauthenticated users to /login'); assert(profilePageSrc.includes('!isDemoPreview && !ctx.permissions.canManageSettings'), 'Profile page must check canManageSettings permission'); assert(profilePageSrc.includes('title="Management Access Restricted"'), 'Profile page must render Management Access Restricted error'); console.log(' PASS: Test A verified (Owner/Admin allowed; Member forbidden; Unauthenticated rejected with SafeErrorState -> /login).'); // ========================================================================= // TEST 15 (Test B): FP-13 Profile Query Gate (O(1) Point-Read Only) // ========================================================================= console.log('Testing: FP-13 Test B - Profile Query Gate (O(1) Point-Read Only)...'); // Extract fetchFounderClubContext body from club-data.ts source const fetchCtxMatch = clubDataSrc.match(/export async function fetchFounderClubContext[\s\S]*?^}/m); assert(fetchCtxMatch, 'fetchFounderClubContext must exist in club-data.ts'); const fetchCtxBody = fetchCtxMatch[0]; // Forbid collection queries in profile context assert(!fetchCtxBody.includes('group_rides'), 'fetchFounderClubContext must NOT query group_rides'); assert(!fetchCtxBody.includes('club_applications'), 'fetchFounderClubContext must NOT query club_applications'); assert(!fetchCtxBody.includes('collectionGroup'), 'fetchFounderClubContext must NOT perform collectionGroup queries'); assert(!fetchCtxBody.includes('getDocs('), 'fetchFounderClubContext must NOT call getDocs (strictly point-reads getDoc only)'); console.log(' PASS: Test B verified (Zero collection queries; zero group_rides; zero club_applications; strictly O(1) point-reads).'); // ========================================================================= // TEST 16 (Test C): FP-13 Profile Field Mutability & Whitelist Enforcement // ========================================================================= console.log('Testing: FP-13 Test C - Profile Field Mutability & Whitelist Enforcement...'); // Test input validations via updateClubProfile await assert.rejects( async () => updateClubProfile('test-club', '', { name: 'Valid Name', description: '', homeRegion: '', motorcycleFocus: '' }), /Authentication required/, 'Must require callerUid' ); await assert.rejects( async () => updateClubProfile('test-club', 'uid-1', { name: 'ab', description: '', homeRegion: '', motorcycleFocus: '' }), /Club name must be between 3 and 50 characters/, 'Must reject name shorter than 3 chars' ); await assert.rejects( async () => updateClubProfile('test-club', 'uid-1', { name: 'A'.repeat(51), description: '', homeRegion: '', motorcycleFocus: '' }), /Club name must be between 3 and 50 characters/, 'Must reject name longer than 50 chars' ); await assert.rejects( async () => updateClubProfile('test-club', 'uid-1', { name: 'Valid Club', description: 'A'.repeat(501), homeRegion: '', motorcycleFocus: '' }), /Club description must not exceed 500 characters/, 'Must reject description longer than 500 chars' ); await assert.rejects( async () => updateClubProfile('test-club', 'uid-1', { name: 'Valid Club', description: '', homeRegion: 'A'.repeat(101), motorcycleFocus: '' }), /Home region must not exceed 100 characters/, 'Must reject homeRegion longer than 100 chars' ); await assert.rejects( async () => updateClubProfile('test-club', 'uid-1', { name: 'Valid Club', description: '', homeRegion: '', motorcycleFocus: 'A'.repeat(101) }), /Motorcycle focus must not exceed 100 characters/, 'Must reject motorcycleFocus longer than 100 chars' ); // Verify updateClubProfile implementation in club-data.ts strictly writes whitelisted fields const updateMatch = clubDataSrc.match(/export async function updateClubProfile[\s\S]*?^}/m); assert(updateMatch, 'updateClubProfile must exist in club-data.ts'); const updateBody = updateMatch[0]; assert(updateBody.includes('updateDoc(clubRef, {'), 'updateClubProfile must call updateDoc'); assert(!updateBody.includes('slug:'), 'updateClubProfile MUST NOT write slug'); assert(!updateBody.includes('visibility:'), 'updateClubProfile MUST NOT write visibility'); assert(!updateBody.includes('admissionPolicy:'), 'updateClubProfile MUST NOT write admissionPolicy'); assert(!updateBody.includes('ownerUid:'), 'updateClubProfile MUST NOT write ownerUid'); assert(!updateBody.includes('memberCount:'), 'updateClubProfile MUST NOT write memberCount'); assert(!updateBody.includes('archivedAt:'), 'updateClubProfile MUST NOT write archivedAt'); console.log(' PASS: Test C verified (Strict input length bounds, whitelisted update, forbidden fields rejected).'); // ========================================================================= // TEST 17 (Test D): FP-13 Development Preview Fixture Safety // ========================================================================= console.log('Testing: FP-13 Test D - Development Preview Fixture Safety...'); // In development with allowDemoPreview: true -> returns fixture const prevEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; const demoCtx = await fetchFounderClubContext('any-id', undefined, { allowDemoPreview: true }); assert.equal(demoCtx.isDemoFixture, true, 'Must return isDemoFixture: true when opt-in in development'); assert.equal(demoCtx.club.name, FIXTURE_FOUNDER_CLUB.name); // In production with allowDemoPreview: true -> fixture MUST NOT be returned process.env.NODE_ENV = 'production'; let prodThrew = false; try { await fetchFounderClubContext('non-existent-club-id', undefined, { allowDemoPreview: true }); } catch { prodThrew = true; } assert.equal(prodThrew, true, 'Fixture MUST NEVER be returned in production, even if allowDemoPreview is set'); process.env.NODE_ENV = prevEnv; console.log(' PASS: Test D verified (Fixture returned strictly in development with explicit allowDemoPreview opt-in; never in production).'); // ========================================================================= // TEST 18 (Test E): FP-13 Shared Sub-Routing & Active Navigation State // ========================================================================= console.log('Testing: FP-13 Test E - Shared Sub-Routing & Active Navigation State...'); // Verify route links exist in FounderSidebar assert(sidebarSrc.includes('getRouteHref'), 'FounderSidebar must include getRouteHref'); assert(sidebarSrc.includes('/manage/profile'), 'FounderSidebar must route to /manage/profile'); assert(sidebarSrc.includes('/manage'), 'FounderSidebar must route to /manage'); // Verify exact active styling for profile assert(sidebarSrc.includes('bg-surface-container-high text-primary-container border-l-2 border-primary-container font-medium'), 'Profile item must have active highlight border and background'); // Verify profile page passes activeNav="profile" assert(profilePageSrc.includes('activeNav="profile"'), 'Profile page must pass activeNav="profile" to FounderPortalShell'); assert(profilePageSrc.includes(' active const approveBody = clubDataSrc.substring(clubDataSrc.indexOf('function approveClubApplication'), clubDataSrc.indexOf('function declineClubApplication')); assert(approveBody.includes("status: 'active'"), 'approveClubApplication must set status: active'); // I. Decline semantics: requested -> declined const declineBody = clubDataSrc.substring(clubDataSrc.indexOf('function declineClubApplication')); assert(declineBody.includes("status: 'declined'"), 'declineClubApplication must set status: declined'); // J. Default role: member assert(approveBody.includes("role: 'member'"), 'approveClubApplication must set role: member'); // K. Default display rank: Member assert(approveBody.includes("displayRank: 'Member'"), 'approveClubApplication must set displayRank: Member'); // L. memberCount +1 atomic approval behavior assert(approveBody.includes("memberCount: (cd.memberCount || 0) + 1"), 'approveClubApplication must increment memberCount by 1'); assert(approveBody.includes("lastMembershipChangeUid: targetUid"), 'approveClubApplication must set lastMembershipChangeUid to targetUid'); // M. Decline memberCount delta 0 (no memberCount mutation) assert(!declineBody.includes('memberCount:'), 'declineClubApplication must not mutate memberCount'); // N. Banned application cannot approve assert(approveBody.includes("bans', targetUid)"), 'approveClubApplication must check bans subcollection'); assert(approveBody.includes('This application can no longer be approved.'), 'approveClubApplication must reject banned applicant'); // O. Stale request fails safely assert(approveBody.includes("targetData.status !== 'requested'"), 'approveClubApplication must verify status is requested'); assert(declineBody.includes("targetData.status !== 'requested'"), 'declineClubApplication must verify status is requested'); // P. Duplicate mutation protection assert(applicationsCompSrc.includes('disabled={Boolean(mutatingUid)}'), 'Buttons must be disabled while mutating'); assert(applicationsCompSrc.includes('if (mutatingUid) return;'), 'Mutation handlers must prevent concurrent double-clicks'); // Q. displayRank does not grant authority assert.equal(getPortalPermissions('member').canApproveApplications, false, 'Member role cannot approve regardless of display rank'); // R. No private identity fields exposed assert(!applicationsCompSrc.includes('app.telemetry'), 'Applications component must not expose telemetry data'); assert(!applicationsCompSrc.includes('speedMph'), 'Applications component must not display speedMph'); assert(!applicationsCompSrc.includes('leanAngle'), 'Applications component must not display leanAngle'); assert(!applicationsCompSrc.includes('gForce'), 'Applications component must not display gForce'); assert(!applicationsCompSrc.includes('stripe'), 'Applications component must not display billing info'); assert(!applicationsCompSrc.includes('app.email'), 'Applications component must not display email'); assert(!applicationsCompSrc.includes('app.phone'), 'Applications component must not display phone'); // S. Production failure does not use fixtures let appProdThrew = false; try { process.env.NODE_ENV = 'production'; await fetchClubApplications('test-club', ''); } catch { appProdThrew = true; } finally { process.env.NODE_ENV = 'test'; } assert.equal(appProdThrew, true, 'fetchClubApplications must reject unauthenticated call in production without fallback'); // T. Applications sidebar active state assert(sidebarSrc.includes("if (id === 'applications') return `/clubs/${clubParam}/manage/applications${queryParam}`;"), 'FounderSidebar must route to applications'); assert(sidebarSrc.includes("isApplications = item.id === 'applications';"), 'FounderSidebar must treat applications as subroute'); // U. Top bar breadcrumb assert(topBarSrc.includes("activeNav === 'applications'"), 'FounderTopBar must render applications breadcrumb'); assert(topBarSrc.includes('APPLICATIONS'), 'FounderTopBar must include APPLICATIONS title'); // V. Non-destructive decline (no deletion, history preserved) assert(!declineBody.includes('deleteDoc('), 'declineClubApplication must NOT delete document'); assert(!applicationsCompSrc.includes('deleteDoc('), 'FounderApplications must NOT delete document'); console.log(' PASS: FP-15 Tests A through V verified (Route exists, Owner/Admin access, Member denial, Unauth denial, Bounded query limit 25, Status requested only, Approve requested->active, Decline requested->declined, Default role member, Default rank Member, memberCount +1 atomic, memberCount delta 0 decline, Ban rejection, Stale request protection, Duplicate click guard, Zero rank privilege, Zero private telemetry/contact leak, Production fixture isolation, Sidebar routing & active state, TopBar breadcrumb, Non-destructive history preservation).'); // ========================================================================= // TEST 24: Codex Review Findings — Ranks-Roles Access Guard + HTTPS Image URL Invariant // ========================================================================= console.log('Testing: Codex findings — Ranks-Roles access guard & HTTPS image URL invariant...'); // --- 24A: Ranks & Roles route enforces canManageSettings guard --- const ranksRolesPageSrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/[clubId]/manage/ranks-roles/page.tsx'), 'utf8'); // Owner/Admin allowed by the established permission model assert.equal(getPortalPermissions('owner', true).canManageSettings, true, 'Owner must have canManageSettings'); assert.equal(getPortalPermissions('admin', true).canManageSettings, true, 'Admin must have canManageSettings'); // Ordinary member denied assert.equal(getPortalPermissions('member', true).canManageSettings, false, 'Member must not have canManageSettings'); // Unauthenticated denied assert.equal(getPortalPermissions('owner', false).isAuthenticated, false, 'Unauthenticated user must have isAuthenticated = false'); assert.equal(getPortalPermissions('owner', false).canManageSettings, false, 'Unauthenticated user must not have canManageSettings'); // Route source: unauthenticated guard, authorization guard, safe unauthorized state, demo-preview preserved assert(ranksRolesPageSrc.includes("if (!isDemoPreview && !currentUser)"), 'Ranks-Roles page must guard unauthenticated access'); assert(ranksRolesPageSrc.includes('title="Authentication Required"'), 'Ranks-Roles page must render Authentication Required safe state'); assert(ranksRolesPageSrc.includes('!ctx.permissions.canManageSettings'), 'Ranks-Roles page must enforce canManageSettings gate'); assert(ranksRolesPageSrc.includes('setUnauthorized(true)'), 'Ranks-Roles page must set unauthorized state'); assert(ranksRolesPageSrc.includes('title="Management Access Restricted"'), 'Ranks-Roles page must render unauthorized safe state'); assert(ranksRolesPageSrc.includes('allowDemoPreview: isDemoPreview'), 'Ranks-Roles page must preserve demo-preview behavior'); // --- 24B: validateClubImageUrl shared data-layer validator --- // Accepted: valid https URLs (returns trimmed value) assert.equal(validateClubImageUrl('https://example.com/logo.png', 'Club logo URL'), 'https://example.com/logo.png'); assert.equal(validateClubImageUrl(' https://cdn.example.com/b banner.jpg ', 'Club banner URL'), 'https://cdn.example.com/b banner.jpg'); // Accepted: blank/empty values clear the field (returns null) assert.equal(validateClubImageUrl('', 'Club logo URL'), null); assert.equal(validateClubImageUrl(' ', 'Club banner URL'), null); // Rejected: http://, data:, blob:, javascript:, ftp:, malformed const rejectedUrls = [ 'http://example.com/logo.png', 'data:image/png;base64,iVBORw0KGgo=', 'blob:https://example.com/uuid', 'javascript:alert(1)', 'ftp://example.com/logo.png', 'not-a-url', 'example.com/logo.png', 'https://', ]; for (const bad of rejectedUrls) { let threw = false; try { validateClubImageUrl(bad, 'Club logo URL'); } catch (err) { threw = true; assert( err.message === 'Club logo URL must be a valid HTTPS URL.' || err.message === 'Club logo URL must use a secure https:// URL.', `Unexpected validation error message for ${bad}: ${err.message}` ); } assert.equal(threw, true, `validateClubImageUrl must reject: ${bad}`); } // Never silently normalizes an invalid URL into a valid one assert.equal(validateClubImageUrl('https://example.com/x.png', 'Club logo URL'), 'https://example.com/x.png'); // --- 24C: updateClubProfile invokes validator before Firestore reads --- const updateProfileBody = clubDataSrc.substring(clubDataSrc.indexOf('export async function updateClubProfile'), clubDataSrc.indexOf('export interface UpdateClubAccessSettingsInput')); assert(updateProfileBody.includes('validateClubImageUrl(input.logoUrl'), 'updateClubProfile must validate logoUrl via validateClubImageUrl'); assert(updateProfileBody.includes('validateClubImageUrl(input.bannerUrl'), 'updateClubProfile must validate bannerUrl via validateClubImageUrl'); assert(updateProfileBody.indexOf('validateClubImageUrl(input.logoUrl') < updateProfileBody.indexOf('getDoc(clubRef)'), 'URL validation must run before Firestore reads'); assert(!updateProfileBody.includes('input.logoUrl.trim()'), 'updateClubProfile must not write unvalidated logoUrl'); assert(!updateProfileBody.includes('input.bannerUrl.trim()'), 'updateClubProfile must not write unvalidated bannerUrl'); // Behavioral: invalid URL rejected before any Firestore read (mocked getDoc never resolves a club) let urlThrew = false; try { await updateClubProfile('any-club', 'caller-uid', { name: 'Valid Club Name', description: '', homeRegion: '', motorcycleFocus: '', logoUrl: 'http://evil.example.com/x.png', }); } catch (err) { urlThrew = true; assert.equal(err.message, 'Club logo URL must use a secure https:// URL.'); } assert.equal(urlThrew, true, 'updateClubProfile must reject http:// logoUrl before touching Firestore'); // Behavioral: valid input with no URL fields reaches the not-found read (proves validation did not throw) let notFoundThrew = false; try { await updateClubProfile('any-club', 'caller-uid', { name: 'Valid Club Name', description: '', homeRegion: '', motorcycleFocus: '', logoUrl: 'https://example.com/ok.png', bannerUrl: '', }); } catch (err) { notFoundThrew = true; assert.equal(err.message, 'Club not found.', `Expected Firestore read after passing validation, got: ${err.message}`); } assert.equal(notFoundThrew, true, 'updateClubProfile with valid URLs must proceed to Firestore read'); // --- 24D: Firestore rules enforce the same invariant at the storage layer --- const rulesSrc = fs.readFileSync(path.resolve(__dirname, '../../firestore.rules'), 'utf8'); assert(rulesSrc.includes('function isValidClubImageUrl(url)'), 'firestore.rules must define isValidClubImageUrl'); assert(rulesSrc.includes("url.matches('(?i)^https://\\\\S+$')"), 'firestore.rules must require https:// scheme'); const rulesCase2 = rulesSrc.substring(rulesSrc.indexOf('// Case 2: Owner general metadata update'), rulesSrc.indexOf('// Case 3: Admin editing metadata')); const rulesCase3 = rulesSrc.substring(rulesSrc.indexOf('// Case 3: Admin editing metadata'), rulesSrc.indexOf('// Case 4: Member count')); for (const [label, block] of [['Case 2 (owner)', rulesCase2], ['Case 3 (admin)', rulesCase3]]) { assert(block.includes("isValidClubImageUrl(request.resource.data.logoUrl)"), `firestore.rules ${label} must validate logoUrl`); assert(block.includes("isValidClubImageUrl(request.resource.data.bannerUrl)"), `firestore.rules ${label} must validate bannerUrl`); } console.log(' PASS: Ranks-Roles canManageSettings guard verified (owner/admin allowed, member/unauth denied, demo-preview preserved); HTTPS image URL invariant enforced in app layer and Firestore rules (https accepted, blank clears, http/data/blob/javascript/malformed rejected).'); // ========================================================================= // TEST 25: FP-16 Clubs Rides Management Suite (Truthfulness Hardening) // ========================================================================= console.log('Testing: FP-16 Clubs Rides Management Suite (Truthfulness Hardening)...'); const ridesPageSrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/[clubId]/manage/rides/page.tsx'), 'utf8'); const founderRidesSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/founder-rides.tsx'), 'utf8'); const clubRideCardSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/club-ride-card.tsx'), 'utf8'); const clubFixturesSrc = fs.readFileSync(path.resolve(__dirname, '../lib/club-fixtures.ts'), 'utf8'); // A. Route exists and reuses FounderPortalShell with activeNav="rides" assert(fs.existsSync(path.resolve(__dirname, '../app/clubs/[clubId]/manage/rides/page.tsx')), 'Rides page route must exist'); assert(ridesPageSrc.includes(' 0, 'FIXTURE_PAST_RIDES must not be empty'); assert.equal(FIXTURE_PAST_RIDES[0].status, 'completed', 'Past rides must have completed status'); console.log(' PASS: FP-16 Tests A through N verified (Route exists, Owner/Admin access, Member denial, Unauth denial, discovery-pending production state, unsupported broad query absent, actions disabled, model corrections, privacy notice, dev-gated preview, production fixture isolation, Sidebar/TopBar, fixtures valid).'); // ========================================================================= // TEST 26: Founder Portal Page Width Normalization Invariant // ========================================================================= console.log('Testing: Founder Portal page width normalization across Profile, Applications, and Ranks & Roles...'); const profileEditorSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/founder-profile-editor.tsx'), 'utf8'); const appNormalizedSrc = fs.readFileSync(path.resolve(__dirname, '../components/founder-portal/founder-applications.tsx'), 'utf8'); const ranksPageSrc = fs.readFileSync(path.resolve(__dirname, '../app/clubs/[clubId]/manage/ranks-roles/page.tsx'), 'utf8'); // Assert Profile Editor canvas normalization assert(!profileEditorSrc.includes('max-w-6xl mx-auto'), 'founder-profile-editor.tsx must NOT contain restrictive max-w-6xl mx-auto container'); assert(!profileEditorSrc.includes('max-w-7xl mx-auto'), 'founder-profile-editor.tsx must NOT contain max-w-7xl mx-auto container'); assert(profileEditorSrc.includes('
'), 'founder-profile-editor.tsx must use w-full canvas matching Overview'); // Assert Applications canvas normalization assert(!appNormalizedSrc.includes('max-w-6xl mx-auto'), 'founder-applications.tsx must NOT contain restrictive max-w-6xl mx-auto container'); assert(!appNormalizedSrc.includes('max-w-7xl mx-auto'), 'founder-applications.tsx must NOT contain max-w-7xl mx-auto container'); assert(appNormalizedSrc.includes('
'), 'founder-applications.tsx must use w-full canvas matching Overview'); // Assert Ranks & Roles canvas normalization assert(!ranksPageSrc.includes('max-w-6xl mx-auto'), 'ranks-roles/page.tsx must NOT contain restrictive max-w-6xl mx-auto container'); assert(!ranksPageSrc.includes('max-w-7xl mx-auto'), 'ranks-roles/page.tsx must NOT contain max-w-7xl mx-auto container'); assert(ranksPageSrc.includes('
'), 'ranks-roles/page.tsx must use w-full canvas matching Overview'); console.log(' PASS: Page width normalization verified (Profile, Applications, Ranks & Roles all use w-full canvas without restrictive max-w-6xl caps).'); console.log('--- ALL FOUNDER PORTAL TESTS PASSED (26/26) ---'); } runAllTests().catch((err) => { console.error('Test Suite Failure:', err); process.exit(1); });