import { readFileSync } from 'node:fs' import { describe, it, beforeAll, afterAll, beforeEach } from 'vitest' import { initializeTestEnvironment, assertSucceeds, assertFails, type RulesTestEnvironment, } from '@firebase/rules-unit-testing' import { doc, getDoc, setDoc, addDoc, collection } from 'firebase/firestore' const ADMIN_UID = 'dev-admin-placeholder-uid' let testEnv: RulesTestEnvironment beforeAll(async () => { testEnv = await initializeTestEnvironment({ projectId: 'demo-braids-by-mira', firestore: { rules: readFileSync('firestore.rules', 'utf8'), }, }) }) afterAll(async () => { await testEnv.cleanup() }) beforeEach(async () => { await testEnv.clearFirestore() }) describe('Firestore security rules', () => { it('allows anyone to read products', async () => { await testEnv.withSecurityRulesDisabled(async (ctx) => { await setDoc(doc(ctx.firestore(), 'products/edge-control'), { name: 'Edge Control' }) }) const unauthed = testEnv.unauthenticatedContext() await assertSucceeds(getDoc(doc(unauthed.firestore(), 'products/edge-control'))) }) it('blocks a non-admin from writing to products', async () => { const unauthed = testEnv.unauthenticatedContext() await assertFails(setDoc(doc(unauthed.firestore(), 'products/hacked'), { name: 'Hack' })) }) it('allows the admin to write to products', async () => { const admin = testEnv.authenticatedContext(ADMIN_UID) await assertSucceeds(setDoc(doc(admin.firestore(), 'products/new-item'), { name: 'New Item' })) }) it('allows anyone to create a booking', async () => { const unauthed = testEnv.unauthenticatedContext() await assertSucceeds( addDoc(collection(unauthed.firestore(), 'bookings'), { serviceId: 'knotless', date: '2026-09-01', time: '10:00 AM', contact: { name: 'Jane', phone: '555-0100', email: 'jane@example.com', notes: '' }, status: 'confirmed', }), ) }) it('blocks creating a booking that is missing a required field', async () => { const unauthed = testEnv.unauthenticatedContext() await assertFails( addDoc(collection(unauthed.firestore(), 'bookings'), { serviceId: 'knotless', date: '2026-09-01', time: '10:00 AM', status: 'confirmed', // missing contact }), ) }) it('blocks creating a booking with a malformed date', async () => { const unauthed = testEnv.unauthenticatedContext() await assertFails( addDoc(collection(unauthed.firestore(), 'bookings'), { serviceId: 'knotless', date: 'not-a-date', time: '10:00 AM', contact: { name: 'Jane', phone: '555-0100', email: 'jane@example.com', notes: '' }, status: 'confirmed', }), ) }) it('blocks a non-admin from reading bookings (PII protection)', async () => { await testEnv.withSecurityRulesDisabled(async (ctx) => { await setDoc(doc(ctx.firestore(), 'bookings/b1'), { serviceId: 'knotless', contact: { name: 'Jane', phone: '555-0100', email: 'jane@example.com', notes: '' }, }) }) const unauthed = testEnv.unauthenticatedContext() await assertFails(getDoc(doc(unauthed.firestore(), 'bookings/b1'))) }) it('allows the admin to read bookings', async () => { await testEnv.withSecurityRulesDisabled(async (ctx) => { await setDoc(doc(ctx.firestore(), 'bookings/b1'), { serviceId: 'knotless' }) }) const admin = testEnv.authenticatedContext(ADMIN_UID) await assertSucceeds(getDoc(doc(admin.firestore(), 'bookings/b1'))) }) it('allows anyone to create an inquiry with a valid shape', async () => { const unauthed = testEnv.unauthenticatedContext() await assertSucceeds( addDoc(collection(unauthed.firestore(), 'inquiries'), { productId: 'edge-control', contact: { name: 'Jane', email: 'jane@example.com', notes: '' }, status: 'new', }), ) }) it('blocks creating an inquiry that is missing a required field', async () => { const unauthed = testEnv.unauthenticatedContext() await assertFails( addDoc(collection(unauthed.firestore(), 'inquiries'), { productId: 'edge-control', status: 'new', // missing contact }), ) }) it('blocks a non-admin from reading inquiries (PII protection)', async () => { await testEnv.withSecurityRulesDisabled(async (ctx) => { await setDoc(doc(ctx.firestore(), 'inquiries/i1'), { productId: 'edge-control', contact: { name: 'Jane', email: 'jane@example.com', notes: '' }, }) }) const unauthed = testEnv.unauthenticatedContext() await assertFails(getDoc(doc(unauthed.firestore(), 'inquiries/i1'))) }) it('allows anyone to read bookedSlots (no PII there)', async () => { await testEnv.withSecurityRulesDisabled(async (ctx) => { await setDoc(doc(ctx.firestore(), 'bookedSlots/2026-09-01'), { times: ['10:00 AM'] }) }) const unauthed = testEnv.unauthenticatedContext() await assertSucceeds(getDoc(doc(unauthed.firestore(), 'bookedSlots/2026-09-01'))) }) it('blocks a non-admin from writing bookedSlots directly', async () => { const unauthed = testEnv.unauthenticatedContext() await assertFails(setDoc(doc(unauthed.firestore(), 'bookedSlots/2026-09-01'), { times: ['1:00 PM'] })) }) })