begin; select plan(12); -- Seed reference data as service_role (bypasses RLS) set local role service_role; insert into public.services (id, name, duration_minutes, price_from, description, image) values ('knotless', 'Knotless Braids', 240, 150, 'Tension-free braids.', 'styles/knotless.jpg'); insert into public.products (id, name, category, price, image, description, in_stock) values ('edge-control', 'Silk Edge Control', 'hair-care', 12, 'products/edge-control.jpg', 'Hold with shine.', true); reset role; -- Anyone can read services set local role anon; select isnt_empty( $$ select * from public.services $$, 'anon can read services' ); -- Anyone can read products select isnt_empty( $$ select * from public.products $$, 'anon can read products' ); -- Anon cannot write to services select throws_ok( $$ insert into public.services (id, name, duration_minutes, price_from, description, image) values ('hack', 'Hack', 1, 1, 'x', 'x') $$, '42501', null, 'anon cannot insert into services' ); -- Anyone can create a booking select lives_ok( $$ insert into public.bookings (service_id, date, time, contact_name, contact_phone, contact_email, contact_notes) values ('knotless', '2026-09-15', '10:00 AM', 'Jane Doe', '555-0100', 'jane@example.com', '') $$, 'anon can create a booking' ); -- Anon cannot read bookings (PII protection). No SELECT privilege is granted -- to anon on this table at all now, so this is blocked at the grant layer -- (permission denied) rather than merely filtered to empty rows by RLS. select throws_ok( $$ select * from public.bookings $$, '42501', null, 'anon cannot read bookings' ); -- Anon cannot update bookings (no UPDATE privilege granted, regardless of RLS) select throws_ok( $$ update public.bookings set contact_notes = 'hacked' $$, '42501', null, 'anon cannot update bookings' ); -- Anyone can create an inquiry select lives_ok( $$ insert into public.inquiries (product_id, contact_name, contact_email, contact_notes) values ('edge-control', 'Jane Doe', 'jane@example.com', 'Is this in stock?') $$, 'anon can create an inquiry' ); -- Anon cannot read inquiries (PII protection). Same as bookings above: no -- SELECT privilege is granted, so this fails at the grant layer. select throws_ok( $$ select * from public.inquiries $$, '42501', null, 'anon cannot read inquiries' ); -- Anyone can read booked_slots (no PII there) select isnt_empty( $$ select * from public.booked_slots $$, 'anon can read booked_slots (populated by the trigger from the booking above)' ); -- Anon cannot write booked_slots directly select throws_ok( $$ insert into public.booked_slots (date, time) values ('2026-09-16', '1:00 PM') $$, '42501', null, 'anon cannot insert into booked_slots directly' ); -- The admin (matching UID) can read bookings set local role authenticated; set local request.jwt.claim.sub = '21c90224-e0b2-412e-a431-8e7f097c1cef'; select isnt_empty( $$ select * from public.bookings $$, 'admin can read bookings' ); -- The admin can write to services select lives_ok( $$ insert into public.services (id, name, duration_minutes, price_from, description, image) values ('cornrows', 'Cornrows', 120, 90, 'Classic style.', 'styles/cornrows.jpg') $$, 'admin can insert into services' ); select * from finish(); rollback;