Как настроить тестовые данные при тестировании правил Firestore с помощью эмулятора? - PullRequest
2 голосов
/ 23 мая 2019

Я работаю над тестами для Cloud Firestore Rules, используя mocha и Firestore Emulator , и вопрос в том, как инициализировать некоторые тестовые данные перед выполнением тестов?

Чтобы проверить мои правила, мне сначала нужно инициализировать некоторые тестовые данные. Проблема в том, что я не могу поместить какие-либо данные в документ при работе с Эмулятор , документы имеют только id. Я не нашел ни одного примера настройки тестовых данных для тестов правил в документах , поэтому я попытался использовать оба makeDocumentSnapshot из @firebase/testing и создание документа через приложение администратора , созданное с помощью initializeAdminApp.

Вариант использования :

Чтобы получить доступ к документу на /objects/{object_id}, пользователь должен пройти аутентификацию и иметь разрешение read: get('/objects/{object_id}/users/{$(request.auth.uid)}').data.read == true. Также должно быть доступно object: get('/objects/{object_id}').data.available == true.

Итак, чтобы проверить мои правила, мне нужны предварительно заданные тестовые данные с разрешениями пользователя.

Ожидаемая структура БД :

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

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

Пример моих правил :

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;
      }
    }
  }
}

Пример моих тестов :

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());
    });
  });
});

Вывод на консоль для тестового запуска:

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

Для проверки созданных тестовых данных я добавил следующий код перед строкой await firebase.assertSucceeds(testObj.get());:

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());

Вывод следующий:

obj data:  test undefined
user data:  1 undefined

Я также пытался удалить код из beforeEach, результат тот же.

1 Ответ

0 голосов
/ 19 июля 2019

Вы должны добавить данные, прежде чем применять правила.

Подробную информацию вы можете найти здесь

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
    }
  }
}
...