Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to setup test data when testing Firestore Rules with Emulator?

I am working on tests for Cloud Firestore Rules, using mocha and Firestore Emulator, and the question is how to initialize some test data before running tests?

To test my rules, I first need to initialize some test data. The problem is that I cannot put any data into a document when working with Emulator, documents only have id. I didn't find any example of setting up test data for Rules tests in the docs, so I tried to use both makeDocumentSnapshot from @firebase/testing and document creation via admin app created with initializeAdminApp.

Use case:

To get access to a document at /objects/{object_id}, a User must be authenticated and have read permission: get('/objects/{object_id}/users/{$(request.auth.uid)}').data.read == true. Also, object must be available: get('/objects/{object_id}').data.available == true.

So, to test my rules I need some preset test data with User permissions.

Expected DB structure:

objects collection:
  object_id: {
    // document fields:
    available (bool)

    // nested collection:
    users collection: {
      user_id: {
        // document fields:
        read (bool)
      }
    }
  }

Example of my rules:

service cloud.firestore {
  match /databases/{database}/documents {
    match /objects/{object} {
      function objectAvailable() {
        return resource.data.available;
      }
      // User has read access.
      function userCanReadObject() {
        return get(/databases/$(database)/documents/objects/$(object)/users/$(request.auth.uid)).data.read == true;
      }
      // Objects Permission Rules
      allow read: if objectAvailable() && userCanReadObject();
      allow write: if false;

      // Access forbidden. Used for permission rules only.
      match /users/{document=**} {
        allow read, write: if false;
      }
    }
  }
}

Example of my tests:

const firebase = require('@firebase/testing');
const fs = require('fs');

// Load Firestore rules from file
const firestoreRules = fs.readFileSync('../firestore.rules', 'utf8');
const projectId = 'test-application';
const test = require('firebase-functions-test')({ projectId, databaseName: projectId });

describe('Tests for Rules', () => {
  let adminApp;

  const testData = {
    myObj: {
      id: 'test',
      data: {
        available: true,
      },
    },
    alice: {
      id: 1,
      data: {
        read: true,
      },
    },
  };

  before(async () => {
    // Load Rules
    await firebase.loadFirestoreRules({ projectId,  rules: firestoreRules });

    // Initialize admin app.
    adminApp = firebase.initializeAdminApp({ projectId }).firestore();

    // Create test data
    await adminApp.doc(`objects/${testData.myObj.id}`).set(testData.myObj.data);
    await adminApp
      .doc(`objects/${testData.myObj.id}/users/${testData.alice.id}`)
      .set(testData.alice.data);

    // Create test data with  `firebase-functions-test`
    // test.firestore.makeDocumentSnapshot(testData.myObj.data, `objects/${testData.myObj.id}`);
    // test.firestore.makeDocumentSnapshot(
    //   testData.alice.data,
    //   `objects/${testData.myObj.id}/users/${testData.alice.id}`,
    // );
  });

  beforeEach(async () => {
    await firebase.clearFirestoreData({ projectId });
  });

  after(async () => {
    // Shut down all testing Firestore applications after testing is done.
    await Promise.all(firebase.apps().map(app => app.delete()));
  });

  describe('Testing', () => {
    it('User with permission can read objects data', async () => {
      const db = firebase
        .initializeTestApp({ projectId, auth: { uid: testData.alice.id } })
        .firestore();
      const testObj = db.doc(`objects/${testData.myObj.id}`);

      await firebase.assertSucceeds(testObj.get());
    });
  });
});

Console output for test run:

1) User with permission can read objects data
0 passing (206ms)
1 failing
1) Tests for Rules
 Testing
   User with permission can read objects data:
FirebaseError: 
false for 'get' @ L53

To check created test data I added the following code before await firebase.assertSucceeds(testObj.get()); line:

const o = await adminApp.doc(`objects/${testData.myObj.id}`).get();
const u = await adminApp.doc(`objects/${testData.myObj.id}/users/${testData.alice.id}`).get();
console.log('obj data: ', o.id, o.data());
console.log('user data: ', u.id, u.data());

Output is the following:

obj data:  test undefined
user data:  1 undefined

I also tried to remove the code from beforeEach, the result is the same.

like image 982
greg0r Avatar asked May 23 '19 04:05

greg0r


People also ask

How do I use firestore emulator?

Connect Flutter to the Firestore Emulator On an Android emulator, you must point to the IP 10.0. 2.2 to connect to the 'localhost'. That's it! Your app will now use the local emulated database values instead of your real production data in the cloud.

How do I import firestore data into an emulator?

Go to my local Firebase project path. Start the emulators using: firebase emulators:start. Create manually some mockup data using the GUI at http://localhost:4000/firestore using the buttons provided: + Start Collection and + Add Document. Export this data locally using: emulators:export ./mydirectory.

How do you check data on firestore?

Cloud Firestore doesn't support native indexing or search for text fields in documents. Additionally, downloading an entire collection to search for fields client-side isn't practical. To enable full text search of your Cloud Firestore data, use a dedicated third-party search service.


2 Answers

You can use initializeAdminApp to get admin privilegies (all operations are allowed):


    const dbAdmin = firebase.initializeAdminApp({projectId}).firestore();

    // Write mock documents
    if (data) {
        for (const key in data) {
            if (data.hasOwnProperty(key)) {
                const ref = dbAdmin.doc(key);
                await ref.set(data[key]);
            }
        }
    }

Data is supposed to have the following format:

  data = {
    'user/alice': {
      name:'Alice'
    },
    'user/bob': {
      name:'Bob'
    },
  };
like image 89
MaxXx1313 Avatar answered Oct 01 '22 00:10

MaxXx1313


You have to add data before you apply the rules.

Detailed information you can find here

const firebase = require('@firebase/testing');
const fs = require('fs');
let db
let projectId = `my-project-id-${Date.now()}`

async function setup(auth) {
  const app = await firebase.initializeTestApp({
    projectId: projectId,
    auth: auth
  });

  db = app.firestore();

  let data = {
    'users/alovelace': {
      first: 'Ada',
      last: 'Lovelace'
    }
  }

  // Add data before apply rules
  for (const key in data) {
    const ref = db.doc(key);
    await ref.set(data[key]);
  }

  // Apply rules
  await firebase.loadFirestoreRules({
    projectId,
    rules: fs.readFileSync('firestore.rules', 'utf8')
  });
}

test('logged in', async () => {
  await setup({ uid: "alovelace" })

  let docRef = db.collection('users');

  // check if there is data
  let users = await docRef.get()
  users.forEach(user => {
  console.warn(user.id, user.data())
  });

  let read = await firebase.assertSucceeds(docRef.get());
  let write = await firebase.assertFails(docRef.add({}));

  await expect(read)
  await expect(write)
});

afterAll(async () => {
  Promise.all(firebase.apps().map(app => app.delete()))
});

firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
       allow read:if request.auth.uid != null;
       allow write: if false
    }
  }
}
like image 34
Yoruba Avatar answered Oct 01 '22 00:10

Yoruba