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.
107 lines
3.8 KiB
TypeScript
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'] }))
|
|
})
|
|
})
|