Skip to content

Commit

Permalink
fixup! Initial test suites and CI
Browse files Browse the repository at this point in the history
  • Loading branch information
jits committed Feb 10, 2024
1 parent 76020bd commit aeef76c
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 14 deletions.
24 changes: 15 additions & 9 deletions firebase/test/firestore/firestore-rules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
assertFails,
initializeTestEnvironment,
} from '@firebase/rules-unit-testing';
import { doc, getDoc, serverTimestamp, setDoc, setLogLevel } from 'firebase/firestore';
import { deleteDoc, doc, getDoc, setDoc, setLogLevel } from 'firebase/firestore';
import { createWriteStream, readFileSync } from 'node:fs';
import { get as httpGet } from 'node:http';
import { afterAll, beforeAll, beforeEach, describe, test } from 'vitest';
Expand All @@ -29,6 +29,8 @@ beforeAll(async () => {
});

afterAll(async () => {
await testEnv.cleanup();

// Write the coverage report to a file
const { coverageUrl } = getFirestoreMeta(PROJECT_ID);
const coverageFile = './firestore-coverage.html';
Expand All @@ -48,21 +50,25 @@ beforeEach(async () => {
});

describe('Firestore security rules', () => {
test('does not allow any reads or writes to an unused collection by an unauthenticated user', async () => {
test('does not allow any reads, writes or deletes to an unused collection by an unauthenticated user', async () => {
const db = testEnv.unauthenticatedContext().firestore();
const docref = doc(db, 'test/test');
const docRef = doc(db, 'unused/1');

await assertFails(getDoc(docRef));

await assertFails(getDoc(docref));
await assertFails(setDoc(docRef, { name: 'someone' }));

await assertFails(setDoc(docref, { createdAt: serverTimestamp() }));
await assertFails(deleteDoc(docRef));
});

test('does not allow any reads or writes to an unused collection by an authenticated user', async () => {
test('does not allow any reads, writes or deletes to an unused collection by an authenticated user', async () => {
const db = testEnv.authenticatedContext('alice').firestore();
const docref = doc(db, 'test/test');
const docRef = doc(db, 'unused/1');

await assertFails(getDoc(docRef));

await assertFails(getDoc(docref));
await assertFails(setDoc(docRef, { name: 'someone' }));

await assertFails(setDoc(docref, { createdAt: serverTimestamp() }));
await assertFails(deleteDoc(docRef));
});
});
2 changes: 1 addition & 1 deletion firebase/test/helpers/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function getFirestoreMeta(projectId: string) {
const { hostname: host, port } = parseHost(process.env.FIRESTORE_EMULATOR_HOST);

if (!host || !port) {
throw new Error('Could not parse host and/orport from FIRESTORE_EMULATOR_HOST');
throw new Error('Could not parse host and/or port from FIRESTORE_EMULATOR_HOST');
}

const coverageUrl = `http://${host}:${port}/emulator/v1/projects/${projectId}:ruleCoverage.html`;
Expand Down
2 changes: 1 addition & 1 deletion firebase/test/helpers/rtdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function getRtdbMeta(databaseName: string) {
const { hostname: host, port } = parseHost(process.env.FIREBASE_DATABASE_EMULATOR_HOST);

if (!host || !port) {
throw new Error('Could not parse host and/orport from FIREBASE_DATABASE_EMULATOR_HOST');
throw new Error('Could not parse host and/or port from FIREBASE_DATABASE_EMULATOR_HOST');
}

const coverageUrl = `http://${host}:${port}/.inspect/coverage?ns=${databaseName}`;
Expand Down
14 changes: 14 additions & 0 deletions firebase/test/helpers/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { parseHost } from 'ufo';

export function getStorageMeta() {
const { hostname: host, port } = parseHost(process.env.FIREBASE_STORAGE_EMULATOR_HOST);

if (!host || !port) {
throw new Error('Could not parse host and/or port from FIREBASE_STORAGE_EMULATOR_HOST');
}

return {
host,
port: Number(port),
};
}
11 changes: 8 additions & 3 deletions firebase/test/rtdb/rtdb-rules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
assertFails,
initializeTestEnvironment,
} from '@firebase/rules-unit-testing';
import { get, ref, set } from 'firebase/database';
import { get, ref, remove, set } from 'firebase/database';
import { createWriteStream, readFileSync } from 'node:fs';
import { get as httpGet } from 'node:http';
import { afterAll, beforeAll, beforeEach, describe, test } from 'vitest';
Expand All @@ -28,6 +28,7 @@ beforeAll(async () => {

afterAll(async () => {
await testEnv.cleanup();

// Write the coverage report to a file
const { coverageUrl } = getRtdbMeta(DATABASE_NAME);
const coverageFile = './rtdb-coverage.html';
Expand All @@ -48,21 +49,25 @@ beforeEach(async () => {
});

describe('RTDB security rules', () => {
test('does not allow any reads or writes to an unused key by an unauthenticated user', async () => {
test('does not allow any reads, writes or deletes to an unused key by an unauthenticated user', async () => {
const db = testEnv.unauthenticatedContext().database();
const valueRef = ref(db, 'unusedKey');

await assertFails(get(valueRef));

await assertFails(set(valueRef, 'foo'));

await assertFails(remove(valueRef));
});

test('does not allow any reads or writes to an unused key by an authenticated user', async () => {
test('does not allow any reads, writes or deletes to an unused key by an authenticated user', async () => {
const db = testEnv.authenticatedContext('alice').database();
const valueRef = ref(db, 'unusedKey');

await assertFails(get(valueRef));

await assertFails(set(valueRef, 'foo'));

await assertFails(remove(valueRef));
});
});
58 changes: 58 additions & 0 deletions firebase/test/storage/storage-rules.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
RulesTestEnvironment,
assertFails,
initializeTestEnvironment,
} from '@firebase/rules-unit-testing';
import { deleteObject, getDownloadURL, ref, updateMetadata } from 'firebase/storage';
import { readFileSync } from 'node:fs';
import { afterAll, beforeAll, beforeEach, describe, test } from 'vitest';
import { getStorageMeta } from '../helpers/storage';

const PROJECT_ID = 'demo-test'; // Must match the project name used to start the emulators

let testEnv: RulesTestEnvironment;

beforeAll(async () => {
const { host, port } = getStorageMeta();
testEnv = await initializeTestEnvironment({
projectId: PROJECT_ID,
storage: {
port,
host,
rules: readFileSync('storage.rules', 'utf8'),
},
});
});

afterAll(async () => {
await testEnv.cleanup();
});

beforeEach(async () => {
await testEnv.clearStorage();
});

describe('Storage security rules', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
test('does not allow any reads, writes or deletes to an unused object by an unauthenticated user', async () => {
const storage = testEnv.unauthenticatedContext().storage();
const objectRef = ref(storage, 'unused.jpg');

await assertFails(getDownloadURL(objectRef));

await assertFails(updateMetadata(objectRef, { cacheControl: 'public, max-age=300' }));

await assertFails(deleteObject(objectRef));
});

test('does not allow any reads, writes or deletes to an unused object by an authenticated user', async () => {
const storage = testEnv.authenticatedContext('alice').storage();
const objectRef = ref(storage, 'unused.jpg');

await assertFails(getDownloadURL(objectRef));

await assertFails(updateMetadata(objectRef, { cacheControl: 'public, max-age=300' }));

await assertFails(deleteObject(objectRef));
});
});

0 comments on commit aeef76c

Please sign in to comment.