From 36831784b51c47cb43109838b16a25d1d09a37c8 Mon Sep 17 00:00:00 2001 From: Dave Liu <7david12liu@gmail.com> Date: Sun, 12 Jul 2026 12:25:12 -0700 Subject: [PATCH 1/2] test: cover members-only Firestore access --- tests/firestore-rules/members-only.test.js | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/firestore-rules/members-only.test.js diff --git a/tests/firestore-rules/members-only.test.js b/tests/firestore-rules/members-only.test.js new file mode 100644 index 0000000..311a44d --- /dev/null +++ b/tests/firestore-rules/members-only.test.js @@ -0,0 +1,53 @@ +/* eslint-env jest */ + +const { + clearFirestore, teardown, db, seed, assertFails, assertSucceeds, +} = require('./setup'); + +const PRIVATE_CONTENT = { + discounts: '

Members save 10%.

', +}; + +beforeEach(clearFirestore); +afterAll(teardown); + +describe('members_only collection', () => { + test('anonymous users CANNOT read private content', async () => { + await seed('members_only/benefits', PRIVATE_CONTENT); + const anon = await db(); + + await assertFails(anon.doc('members_only/benefits').get()); + }); + + test('unverified users CANNOT read private content', async () => { + await seed('members_only/benefits', PRIVATE_CONTENT); + const unverified = await db({ uid: 'u1', role: 'unverified' }); + + await assertFails(unverified.doc('members_only/benefits').get()); + }); + + test('members CAN read private content', async () => { + await seed('members_only/benefits', PRIVATE_CONTENT); + const member = await db({ uid: 'u1', role: 'member' }); + + await assertSucceeds(member.doc('members_only/benefits').get()); + }); + + test('admins CAN read private content through the admin catch-all', async () => { + await seed('members_only/benefits', PRIVATE_CONTENT); + const admin = await db({ uid: 'a1', role: 'admin' }); + + await assertSucceeds(admin.doc('members_only/benefits').get()); + }); + + test('members CANNOT create, update, or delete private content', async () => { + await seed('members_only/benefits', PRIVATE_CONTENT); + const member = await db({ uid: 'u1', role: 'member' }); + + await assertFails(member.doc('members_only/new-benefit').set(PRIVATE_CONTENT)); + await assertFails(member.doc('members_only/benefits').update({ + discounts: '

Changed by a member.

', + })); + await assertFails(member.doc('members_only/benefits').delete()); + }); +}); From 479d20840895df172512739f0594e90078fea88f Mon Sep 17 00:00:00 2001 From: Dave Liu <7david12liu@gmail.com> Date: Sun, 12 Jul 2026 12:30:17 -0700 Subject: [PATCH 2/2] test: cover all non-admin private-content writes --- tests/firestore-rules/members-only.test.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/firestore-rules/members-only.test.js b/tests/firestore-rules/members-only.test.js index 311a44d..12fd63e 100644 --- a/tests/firestore-rules/members-only.test.js +++ b/tests/firestore-rules/members-only.test.js @@ -40,14 +40,19 @@ describe('members_only collection', () => { await assertSucceeds(admin.doc('members_only/benefits').get()); }); - test('members CANNOT create, update, or delete private content', async () => { + test.each([ + ['anonymous users', undefined], + ['users without a role claim', { uid: 'u1' }], + ['unverified users', { uid: 'u2', role: 'unverified' }], + ['members', { uid: 'u3', role: 'member' }], + ])('%s CANNOT create, update, or delete private content', async (_label, auth) => { await seed('members_only/benefits', PRIVATE_CONTENT); - const member = await db({ uid: 'u1', role: 'member' }); + const user = await db(auth); - await assertFails(member.doc('members_only/new-benefit').set(PRIVATE_CONTENT)); - await assertFails(member.doc('members_only/benefits').update({ - discounts: '

Changed by a member.

', + await assertFails(user.doc('members_only/new-benefit').set(PRIVATE_CONTENT)); + await assertFails(user.doc('members_only/benefits').update({ + discounts: '

Changed without admin access.

', })); - await assertFails(member.doc('members_only/benefits').delete()); + await assertFails(user.doc('members_only/benefits').delete()); }); });