Files
braidsbymira/firebase-tests/firestore.rules.test.ts
belviskhoremk ffd31ca1da test: add Firestore/Storage security rules tests
Adds emulator-backed rules-unit tests verifying the Firestore rules
committed in Task 3, including the global constraint that
bookings/inquiries are never publicly readable. Excludes
firebase-tests/ from the default vitest run since it needs the
emulator (via `npm run test:rules`) rather than jsdom.
2026-08-05 00:09:57 +00:00

107 lines
3.8 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { describe, it, expect, 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 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('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'] }))
})
})