Firebase Emulator Suite ile Yerel Geliştirme

1. Başlamadan önce

Cloud Firestore ve Cloud Functions gibi sunucusuz arka uç araçlarının kullanımı çok kolaydır, ancak test edilmesi zor olabilir. Firebase Local Emulator Suite, uygulamanızı hızlı ve güvenli bir şekilde geliştirebilmeniz için bu hizmetlerin yerel sürümlerini geliştirme makinenizde çalıştırmanıza olanak tanır.

Önkoşullar

  • Visual Studio Code, Atom veya Sublime Text gibi basit bir düzenleyici
  • Node.js 10.0.0 veya üstü (node.js, yüklemek için NVM'yi sürümünüzü kontrol etmek, koşmak node --version )
  • Java 7 veya daha yüksek (Java yüklemek için bu talimatları kullanın sürümünüzü kontrol etmek, koşmak java -version )

ne yapacaksın

Bu kod laboratuvarında, birden fazla Firebase hizmeti tarafından desteklenen basit bir çevrimiçi alışveriş uygulamasını çalıştıracak ve hatalarını ayıklayacaksınız:

  • Bulut Firestore: Gerçek zamanlı yetenekleri olan küresel olarak ölçeklenebilir, sunucusuz, NoSQL veritabanı.
  • Bulut Fonksiyonlar: Bir sunucusuz arka uç kodu olduğunu olaylar veya HTTP isteklerine yanıt olarak çalışır.
  • Firebase Doğrulama: Diğer Firebase ürünlerle bütünleştirir o yönetilen bir kimlik doğrulama hizmeti.
  • Hızlı ve web uygulamaları için hosting güvenli: Firebase Barındırma.

Yerel gelişimi etkinleştirmek için uygulamayı Emulator Suite'e bağlayacaksınız.

2589e2f95b74fa88.png

Ayrıca şunları nasıl yapacağınızı da öğreneceksiniz:

  • Uygulamanızı Emulator Suite'e nasıl bağlayacağınız ve çeşitli emülatörlerin nasıl bağlandığı.
  • Firebase Güvenlik Kuralları nasıl çalışır ve yerel bir öykünücüye karşı Firestore Güvenlik Kuralları nasıl test edilir.
  • Firestore olayları tarafından tetiklenen bir Firebase İşlevi nasıl yazılır ve Emulator Suite'e karşı çalışan entegrasyon testleri nasıl yazılır.

2. Kurulum

Kaynak kodunu alın

Bu kod laboratuvarında, neredeyse tamamlanmış olan The Fire Store örneğinin bir sürümüyle başlarsınız, bu nedenle yapmanız gereken ilk şey kaynak kodunu klonlamaktır:

$ git clone https://github.com/firebase/emulators-codelab.git

Ardından, bu codelab'in geri kalanı için çalışacağınız codelab dizinine gidin:

$ cd emulators-codelab/codelab-initial-state

Şimdi, kodu çalıştırabilmeniz için bağımlılıkları kurun. Daha yavaş bir internet bağlantınız varsa, bu bir veya iki dakika sürebilir:

# Move into the functions directory
$ cd functions

# Install dependencies
$ npm install

# Move back into the previous directory
$ cd ../

Firebase CLI'yi edinin

Emulator Suite, aşağıdaki komutla makinenize yüklenebilen Firebase CLI'nin (komut satırı arabirimi) bir parçasıdır:

$ npm install -g firebase-tools

Ardından, CLI'nin en son sürümüne sahip olduğunuzu onaylayın. Bu kod laboratuvarı, 9.0.0 veya üzeri sürümlerle çalışmalıdır, ancak sonraki sürümler daha fazla hata düzeltmesi içerir.

$ firebase --version
9.6.0

Firebase projenize bağlanın

Eğer bir Firebase projesi yoksa, içinde Firebase konsoluna , yeni Firebase proje oluşturun. Seçtiğiniz Proje Kimliğini not edin, daha sonra ihtiyacınız olacak.

Şimdi bu kodu Firebase projenize bağlamamız gerekiyor. Firebase CLI'de oturum açmak için önce aşağıdaki komutu çalıştırın:

$ firebase login

Ardından, bir proje takma adı oluşturmak için aşağıdaki komutu çalıştırın. Değiştir $YOUR_PROJECT_ID sizin Firebase projesinin kimliğiyle.

$ firebase use $YOUR_PROJECT_ID

Artık uygulamayı çalıştırmaya hazırsınız!

3. Emülatörleri çalıştırın

Bu bölümde, uygulamayı yerel olarak çalıştıracaksınız. Bu, Emulator Suite'i başlatma zamanının geldiği anlamına gelir.

Emülatörleri Başlatın

Codelab kaynak dizininin içinden öykünücüleri başlatmak için aşağıdaki komutu çalıştırın:

$ firebase emulators:start --import=./seed

Bunun gibi bir çıktı görmelisiniz:

$ firebase emulators:start --import=./seed
i  emulators: Starting emulators: auth, functions, firestore, hosting
⚠  functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: database, pubsub
i  firestore: Importing data from /Users/samstern/Projects/emulators-codelab/codelab-initial-state/seed/firestore_export/firestore_export.overall_export_metadata
i  firestore: Firestore Emulator logging to firestore-debug.log
i  hosting: Serving hosting files from: public
✔  hosting: Local server: http://localhost:5000
i  ui: Emulator UI logging to ui-debug.log
i  functions: Watching "/Users/samstern/Projects/emulators-codelab/codelab-initial-state/functions" for Cloud Functions...
✔  functions[calculateCart]: firestore function initialized.

┌─────────────────────────────────────────────────────────────┐
│ ✔  All emulators ready! It is now safe to connect your app. │
│ i  View Emulator UI at http://localhost:4000                │
└─────────────────────────────────────────────────────────────┘

┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator       │ Host:Port      │ View in Emulator UI             │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth      │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Functions      │ localhost:5001 │ http://localhost:4000/functions │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore      │ localhost:8080 │ http://localhost:4000/firestore │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Hosting        │ localhost:5000 │ n/a                             │
└────────────────┴────────────────┴─────────────────────────────────┘
  Emulator Hub running at localhost:4400
  Other reserved ports: 4500

Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.

Mesajı başlayan tüm emülatörler bkz sonra, uygulama kullanıma hazırdır.

Web uygulamasını öykünücülere bağlayın

Günlüklerinde masaya dayanarak Bulut Firestore emülatörü bağlantı noktasını dinlediğini görebilirsiniz 8080 ve Kimlik emülatörü bağlantı noktasını dinlediğini 9099 .

┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator       │ Host:Port      │ View in Emulator UI             │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth      │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Functions      │ localhost:5001 │ http://localhost:4000/functions │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore      │ localhost:8080 │ http://localhost:4000/firestore │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Hosting        │ localhost:5000 │ n/a                             │
└────────────────┴────────────────┴─────────────────────────────────┘

Ön uç kodunuzu üretim yerine öykünücüye bağlayalım. Aç public/js/homepage.js dosyası ve bulmak onDocumentReady işlevi. Kodun standart Firestore ve Auth örneklerine eriştiğini görebiliriz:

public/js/homepage.js

  const auth = firebaseApp.auth();
  const db = firebaseApp.firestore();

Hadi güncelleme db ve auth yerel emülatörlerine noktasına nesneler:

public/js/homepage.js

  const auth = firebaseApp.auth();
  const db = firebaseApp.firestore();

  // ADD THESE LINES
  if (location.hostname === "localhost") {
    console.log("localhost detected!");
    auth.useEmulator("http://localhost:9099");
    db.useEmulator("localhost", 8080);
  }

Artık uygulama localhost'ta (Barındırma öykünücüsü tarafından sunulur) çalışırken, Firestore istemcisi bir üretim veritabanı yerine yerel öykünücüyü de işaret eder.

EmulatorUI'yi açın

Web tarayıcısında, hiç gezinmek http: // localhost: 4000 / . Emulator Suite kullanıcı arayüzünü görmelisiniz.

Emülatörler kullanıcı arayüzü ana ekranı

Firestore Emulator için kullanıcı arayüzünü görmek için tıklayın. items toplama zaten çünkü ile alınan verilerin verileri içeren --import bayrağı.

4ef88d0148405d36.png

4. Uygulamayı çalıştırın

Uygulamayı aç

Web tarayıcısında, hiç gezinmek http: // localhost: 5000 ve Yangın Mağaza makinenizde yerel olarak çalışan görmelisiniz!

939f87946bac2ee4.png

Uygulamayı kullan

Ana sayfada bir öğe seçin ve Sepete Ekle tıklayın. Ne yazık ki, aşağıdaki hatayla karşılaşacaksınız:

a11bd59933a8e885.png

Şu hatayı düzeltelim! Öykünücülerde her şey çalıştığı için, gerçek verileri etkileme konusunda endişelenmeden deney yapabiliriz.

5. Uygulamada hata ayıklayın

hatayı bul

Tamam, Chrome geliştirici konsoluna bakalım. Basın Control+Shift+J (Windows, Linux, Chrome OS) veya Command+Option+J konsolda hatayı görmeye (Mac):

74c45df55291dab1.png

Biraz hata oluşmuş gibi görünüyor addToCart yöntemi Şuna bir bakalım. Nerede denilen erişim şeye çalışın uid o yöntemde ve neden olsun ki null ? Şu anda böyle bir yöntem görünüyor public/js/homepage.js :

public/js/homepage.js

  addToCart(id, itemData) {
    console.log("addToCart", id, JSON.stringify(itemData));
    return this.db
      .collection("carts")
      .doc(this.auth.currentUser.uid)
      .collection("items")
      .doc(id)
      .set(itemData);
  }

Aha! Uygulamaya giriş yapmadık. Göre Firebase Kimlik docs biz imzalanan değilken, auth.currentUser olan null . Bunun için bir çek ekleyelim:

public/js/homepage.js

  addToCart(id, itemData) {
    // ADD THESE LINES
    if (this.auth.currentUser === null) {
      this.showError("You must be signed in!");
      return;
    }

    // ...
  }

Uygulamayı test edin

Şimdi, sayfayı yenileyin ve ardından Sepete Ekle tıklayın. Bu sefer daha güzel bir hata almalısınız:

c65f6c05588133f7.png

Eğer üst araç çubuğundaki Oturum tıklayın ve sonra tekrar Sepete Ekle 'yi Ama eğer sepeti güncellendiğini göreceksiniz.

Ancak, rakamlar hiç de doğru gibi görünmüyor:

239f26f02f959eef.png

Endişelenme, bu hatayı yakında düzelteceğiz. Öncelikle, sepetinize bir ürün eklediğinizde gerçekte neler olduğuna derinlemesine bakalım.

6. Yerel işlev tetikleyicileri

Birden emülatörler içeren olaylar zincirinin kapalı Sepeti tekmelerde Ekle tıklayarak. Firebase CLI günlüklerinde, sepetinize bir ürün ekledikten sonra aşağıdaki mesajlara benzer bir şey görmelisiniz:

i  functions: Beginning execution of "calculateCart"
i  functions: Finished "calculateCart" in ~1s

Bu günlükleri ve gözlemlediğiniz kullanıcı arayüzü güncellemesini oluşturmak için meydana gelen dört önemli olay vardı:

68c9323f2ad10f7a.png

1) Firestore Yazma - İstemci

Yeni bir belge Firestore koleksiyonuna eklenir /carts/{cartId}/items/{itemId}/ . Sen Bu kodu görebilirsiniz addToCart içindeki fonksiyonu public/js/homepage.js :

public/js/homepage.js

  addToCart(id, itemData) {
    // ...
    console.log("addToCart", id, JSON.stringify(itemData));
    return this.db
      .collection("carts")
      .doc(this.auth.currentUser.uid)
      .collection("items")
      .doc(id)
      .set(itemData);
  }

2) Bulut İşlevi Tetiklendi

Bulut Fonksiyon calculateCart kullanarak sepeti öğeleri başına herhangi yazma etkinlikleri (oluşturma, güncelleme veya silme) için dinler onWrite sen görebileceğiniz tetiği, functions/index.js :

işlevler/index.js

exports.calculateCart = functions.firestore
    .document("carts/{cartId}/items/{itemId}")
    .onWrite(async (change, context) => {
      try {
        let totalPrice = 125.98;
        let itemCount = 8;

        const cartRef = db.collection("carts").doc(context.params.cartId);

        await cartRef.update({
          totalPrice,
          itemCount
        });
      } catch(err) {
      }
    }
);

3) Firestore Yazma - Yönetici

calculateCart işlevi sepetinde öğelerin tümünü okur ve toplam miktar ve fiyatını yukarı ekler, o zaman yeni toplamları içeren "cart" belgesini günceller (bkz cartRef.update(...) üstünde).

4) Firestore Okuma - İstemci

Web ön ucu, alışveriş sepetindeki değişikliklerle ilgili güncellemeleri almak için abone oldu. Bulut Fonksiyonu yeni toplamları yazar ve görebileceğiniz gibi, UI günceller sonra bir gerçek zamanlı güncelleme alır public/js/homepage.js :

public/js/homepage.js

this.cartUnsub = cartRef.onSnapshot(cart => {
   // The cart document was changed, update the UI
   // ...
});

Özet

İyi iş! Tamamen yerel test için üç farklı Firebase öykünücüsü kullanan tamamen yerel bir uygulama kurmanız yeterlidir.

db82eef1706c9058.gif

Ama bekleyin, dahası var! Bir sonraki bölümde öğreneceksiniz:

  • Firebase Emülatörlerini kullanan birim testleri nasıl yazılır.
  • Güvenlik Kurallarınızda hata ayıklamak için Firebase Öykünücüleri nasıl kullanılır?

7. Uygulamanıza özel güvenlik kuralları oluşturun

Web uygulamamız verileri okur ve yazar, ancak şu ana kadar güvenlik konusunda gerçekten endişelenmedik. Cloud Firestore, kimlerin veri okuma ve yazma erişimi olduğunu bildirmek için "Güvenlik Kuralları" adlı bir sistem kullanır. Emulator Suite, bu kuralların prototipini oluşturmanın harika bir yoludur.

Düzenleyicisinde, dosyayı açmaya emulators-codelab/codelab-initial-state/firestore.rules . Kurallarımızda üç ana bölümün olduğunu göreceksiniz:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // User's cart metadata
    match /carts/{cartID} {
      // TODO: Change these! Anyone can read or write.
      allow read, write: if true;
    }

    // Items inside the user's cart
    match /carts/{cartID}/items/{itemID} {
      // TODO: Change these! Anyone can read or write.
      allow read, write: if true;
    }

    // All items available in the store. Users can read
    // items but never write them.
    match /items/{itemID} {
      allow read: if true;
    }
  }
}

Şu anda herkes veri tabanımıza veri okuyabilir ve yazabilir! Yalnızca geçerli işlemlerin gerçekleştiğinden ve hassas bilgileri sızdırmadığımızdan emin olmak istiyoruz.

Bu codelab sırasında, En Az Ayrıcalık İlkesini izleyerek, tüm belgeleri kilitleyeceğiz ve tüm kullanıcılar ihtiyaç duydukları tüm erişime sahip olana kadar kademeli olarak erişim ekleyeceğiz, ancak daha fazlası değil. Hadi güncelleme ilk iki kural için koşul ayarlayarak erişimini engellemek için false :

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // User's cart metadata
    match /carts/{cartID} {
      // UPDATE THIS LINE
      allow read, write: if false;
    }

    // Items inside the user's cart
    match /carts/{cartID}/items/{itemID} {
      // UPDATE THIS LINE
      allow read, write: if false;
    }

    // All items available in the store. Users can read
    // items but never write them.
    match /items/{itemID} {
      allow read: if true;
    }
  }
}

8. Emülatörleri ve testleri çalıştırın

Emülatörleri başlatın

Komut satırında, emin olmalarını sağlıyoruz emulators-codelab/codelab-initial-state/ . Önceki adımlardan itibaren çalışan öykünücüleriniz olabilir. Değilse, öykünücüleri yeniden başlatın:

$ firebase emulators:start --import=./seed

Öykünücüler çalıştığında, bunlara karşı yerel olarak testler çalıştırabilirsiniz.

testleri çalıştırın

Dizininden yeni bir terminal sekmede komut hattı üzerinde emulators-codelab/codelab-initial-state/

İlk önce işlevler dizinine gidin (kod laboratuvarının geri kalanı için burada kalacağız):

$ cd functions

Şimdi işlevler dizininde mocha testlerini çalıştırın ve çıktının en üstüne gidin:

# Run the tests
$ npm test

> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha

  shopping carts
    1) can be created and updated by the cart owner
    2) can be read only by the cart owner

  shopping cart items
    3) can be read only by the cart owner
    4) can be added only by the cart owner

  adding an item to the cart recalculates the cart total. 
    - should sum the cost of their items


  0 passing (364ms)
  1 pending
  4 failing

Şu anda dört başarısızlığımız var. Kurallar dosyasını oluştururken, daha fazla testin geçtiğini izleyerek ilerlemeyi ölçebilirsiniz.

9. Güvenli alışveriş sepeti erişimi

İlk iki başarısızlık, aşağıdakileri test eden "alışveriş sepeti" testleridir:

  • Kullanıcılar yalnızca kendi sepetlerini oluşturabilir ve güncelleyebilir
  • Kullanıcılar yalnızca kendi sepetlerini okuyabilir

işlevler/test.js

  it('can be created and updated by the cart owner', async () => {
    // Alice can create her own cart
    await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart").set({
      ownerUID: "alice",
      total: 0
    }));

    // Bob can't create Alice's cart
    await firebase.assertFails(bobDb.doc("carts/alicesCart").set({
      ownerUID: "alice",
      total: 0
    }));

    // Alice can update her own cart with a new total
    await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart").update({
      total: 1
    }));

    // Bob can't update Alice's cart with a new total
    await firebase.assertFails(bobDb.doc("carts/alicesCart").update({
      total: 1
    }));
  });

  it("can be read only by the cart owner", async () => {
    // Setup: Create Alice's cart as admin
    await admin.doc("carts/alicesCart").set({
      ownerUID: "alice",
      total: 0
    });

    // Alice can read her own cart
    await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart").get());

    // Bob can't read Alice's cart
    await firebase.assertFails(bobDb.doc("carts/alicesCart").get());
  });

Bu testleri geçelim. Düzenleyicisinde, güvenlik kuralları dosyasını açmak firestore.rules ve içindeki ifadeleri güncellemek match /carts/{cartID} :

firestore.kurallar

rules_version = '2';
service cloud.firestore {
    // UPDATE THESE LINES
    match /carts/{cartID} {
      allow create: if request.auth.uid == request.resource.data.ownerUID;
      allow read, update, delete: if request.auth.uid == resource.data.ownerUID;
    }

    // ...
  }
}

Bu kurallar artık yalnızca alışveriş sepeti sahibinin okuma ve yazma erişimine izin veriyor.

Gelen verileri ve kullanıcının kimlik doğrulamasını doğrulamak için her kural bağlamında mevcut olan iki nesne kullanıyoruz:

  • request nesnesi teşebbüs ediliyor işleyişi hakkında veri ve meta verileri içerir.
  • Bir Firebase proje kullanıyorsa Firebase Authentication , request.auth nesne istekte bulunan kullanıcıya açıklanır.

10. Sepet erişimini test edin

Emülatörü Suite otomatik zaman kuralları günceller firestore.rules kaydedilir. Sen emülatör mesajı için emülatörü çalışan sekmesine bakarak kuralları güncelledi teyit edebiliriz Rules updated :

5680da418b420226.png

Testleri yeniden çalıştırın ve ilk iki testin şimdi geçtiğini kontrol edin:

$ npm test

> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha

  shopping carts
    ✓ can be created and updated by the cart owner (195ms)
    ✓ can be read only by the cart owner (136ms)

  shopping cart items
    1) can be read only by the cart owner
    2) can be added only by the cart owner

  adding an item to the cart recalculates the cart total. 
    - should sum the cost of their items

  2 passing (482ms)
  1 pending
  2 failing

Aferin! Artık alışveriş sepetlerine erişimi güvenli hale getirdiniz. Bir sonraki başarısız teste geçelim.

11. Arayüzdeki "Sepete Ekle" akışını kontrol edin

Şu anda, alışveriş sepeti sahipleri sepetlerini okuyup yazsalar da, sepetlerindeki öğeleri tek tek okuyamıyor veya yazamıyorlar. Sahipleri sepeti belgeye erişim varken çünkü en bunlar sepetinizin ürün subcollection erişimi yok.

Bu, kullanıcılar için bozuk bir durumdur.

Çalışıyorsa, web UI, geri dönün http://localhost:5000, ve sepetinize şey eklemek deneyin. Bir olsun Permission Denied henüz oluşturulan belgelere kullanıcıların erişimine izin değil, çünkü hata ayıklama konsolundan görünen hata, items subcollection.

12. Sepet öğelerine erişime izin verin

Bu iki test, kullanıcıların yalnızca kendi sepetlerine ürün ekleyebileceğini veya kendi sepetlerinden ürün okuyabildiğini onaylar:

  it("can be read only by the cart owner", async () => {
    // Alice can read items in her own cart
    await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart/items/milk").get());

    // Bob can't read items in alice's cart
    await firebase.assertFails(bobDb.doc("carts/alicesCart/items/milk").get())
  });

  it("can be added only by the cart owner",  async () => {
    // Alice can add an item to her own cart
    await firebase.assertSucceeds(aliceDb.doc("carts/alicesCart/items/lemon").set({
      name: "lemon",
      price: 0.99
    }));

    // Bob can't add an item to alice's cart
    await firebase.assertFails(bobDb.doc("carts/alicesCart/items/lemon").set({
      name: "lemon",
      price: 0.99
    }));
  });

Böylece, cari kullanıcının sepet belgesindeki OwnerUID ile aynı UID'ye sahip olması durumunda erişime izin veren bir kural yazabiliriz. İçin farklı kurallar belirtmek için gerek yoktur yana create, update, delete , bir kullanabilirsiniz write modify verilerin hepsi isteklerine uygulanır kuralı.

Öğeler alt koleksiyonundaki belgeler için kuralı güncelleyin. get koşullu Firestore bu örnekte, bir değer okuyor ownerUID sepeti belge üzerinde.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ...

    // UPDATE THESE LINES
    match /carts/{cartID}/items/{itemID} {
      allow read, write: if get(/databases/$(database)/documents/carts/$(cartID)).data.ownerUID == request.auth.uid;
    }

    // ...
  }
}

13. Sepet öğeleri erişimini test edin

Şimdi testi tekrar çalıştırabiliriz. Çıktının en üstüne gidin ve daha fazla testin geçtiğini kontrol edin:

$ npm test

> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha

  shopping carts
    ✓ can be created and updated by the cart owner (195ms)
    ✓ can be read only by the cart owner (136ms)

  shopping cart items
    ✓ can be read only by the cart owner (111ms)
    ✓ can be added only by the cart owner


  adding an item to the cart recalculates the cart total. 
    - should sum the cost of their items


  4 passing (401ms)
  1 pending

Güzel! Artık tüm testlerimiz geçiyor. Bekleyen bir testimiz var, ancak buna birkaç adımda ulaşacağız.

14. "Sepete ekle" akışını tekrar kontrol edin

Web arabiriminde Dönüş ( http: // localhost: 5000 ) ve sepetine ürün ekleyin. Bu, testlerimizin ve kurallarımızın müşterinin gerektirdiği işlevsellikle eşleştiğini doğrulamak için önemli bir adımdır. (Arayüz kullanıcılarının en son denediğimizde sepetlerine ürün ekleyemediğini unutmayın!)

69ad26cee520bf24.png

İstemci otomatik olarak kuralları yeniden yükler firestore.rules kaydedilir. Bu nedenle, sepete bir şeyler eklemeyi deneyin.

Özet

İyi iş! Uygulamanızın güvenliğini iyileştirdiniz, üretime hazır hale getirmek için önemli bir adım! Bu bir üretim uygulaması olsaydı, bu testleri sürekli entegrasyon hattımıza ekleyebilirdik. Bu, başkaları kuralları değiştiriyor olsa bile, alışveriş sepeti verilerimizin bu erişim kontrollerine sahip olacağı konusunda ileriye dönük olarak bize güven verecektir.

ba5440b193e75967.gif

Ama bekleyin, dahası var!

devam ederseniz öğreneceksiniz:

  • Firestore olayı tarafından tetiklenen bir işlev nasıl yazılır
  • Birden çok öykünücüde çalışan testler nasıl oluşturulur?

15. Cloud Functions testlerini ayarlayın

Şimdiye kadar web uygulamamızın ön ucuna ve Firestore Güvenlik Kurallarına odaklandık. Ancak bu uygulama, kullanıcının alışveriş sepetini güncel tutmak için Bulut İşlevlerini de kullanır, bu nedenle bu kodu da test etmek istiyoruz.

Emulator Suite, Cloud Firestore ve diğer hizmetleri kullanan fonksiyonlar dahil olmak üzere Cloud Functions'ı test etmeyi çok kolaylaştırır.

Düzenleyicisinde, açmak emulators-codelab/codelab-initial-state/functions/test.js dosyasındaki son teste dosya ve kaydırma. Şu anda, beklemede olarak işaretlendi:

//  REMOVE .skip FROM THIS LINE
describe.skip("adding an item to the cart recalculates the cart total. ", () => {
  // ...

  it("should sum the cost of their items", async () => {
    ...
  });
});

Testi etkinleştirmek için kaldırmak .skip böyle görünecek şekilde,:

describe("adding an item to the cart recalculates the cart total. ", () => {
  // ...

  it("should sum the cost of their items", async () => {
    ...
  });
});

Daha sonra, bulmak REAL_FIREBASE_PROJECT_ID dosyasının en üstünde değişken ve gerçek Firebase Projesi kimliği olarak değiştirin .:

// CHANGE THIS LINE
const REAL_FIREBASE_PROJECT_ID = "changeme";

Proje kimliğinizi unuttuysanız, Firebase Proje Kimliğinizi Firebase Konsolundaki Proje Ayarlarında bulabilirsiniz:

d6d0429b700d2b21.png

16. İşlevler testlerini gözden geçirin

Bu test, Cloud Firestore ve Cloud Functions arasındaki etkileşimi doğruladığı için önceki kod laboratuvarlarındaki testlerden daha fazla kurulum gerektirir. Bu testi gözden geçirelim ve ne beklediğine dair bir fikir edinelim.

Sepet oluştur

Bulut İşlevleri, güvenilir bir sunucu ortamında çalışır ve Yönetici SDK'sı tarafından kullanılan hizmet hesabı kimlik doğrulamasını kullanabilir. Öncelikle, kullandığınız bir uygulamayı başlatmak initializeAdminApp yerine initializeApp . Ardından, bir oluşturmak DocumentReference biz öğeler ekleyeceğiz sepeti için ve araba başlatmak:

it("should sum the cost of their items", async () => {
    const db = firebase
        .initializeAdminApp({ projectId: REAL_FIREBASE_PROJECT_ID })
        .firestore();

    // Setup: Initialize cart
    const aliceCartRef = db.doc("carts/alice")
    await aliceCartRef.set({ ownerUID: "alice", totalPrice: 0 });

    ...
  });

Fonksiyonu tetikle

Ardından, belgeler eklemek items fonksiyonunu tetiklemek için bizim sepeti belgesinin subcollection. İşlevde gerçekleşen eklemeyi test ettiğinizden emin olmak için iki öğe ekleyin.

it("should sum the cost of their items", async () => {
    const db = firebase
        .initializeAdminApp({ projectId: REAL_FIREBASE_PROJECT_ID })
        .firestore();

    // Setup: Initialize cart
    const aliceCartRef = db.doc("carts/alice")
    await aliceCartRef.set({ ownerUID: "alice", totalPrice: 0 });

    //  Trigger calculateCart by adding items to the cart
    const aliceItemsRef = aliceCartRef.collection("items");
    await aliceItemsRef.doc("doc1").set({name: "nectarine", price: 2.99});
    await aliceItemsRef.doc("doc2").set({ name: "grapefruit", price: 6.99 });

    ...
    });
  });

Test beklentilerini belirleyin

Kullanım onSnapshot() sepeti belge üzerinde herhangi bir değişiklik için bir işleyici kaydedebilirsiniz. onSnapshot() Eğer dinleyici kaydını silmek arayabileceğiniz bir işlev döndürür.

Bu test için birlikte maliyeti 9,98 ABD doları olan iki öğe ekleyin. Sepeti beklenen varsa Ardından, kontrol itemCount ve totalPrice . Eğer öyleyse, fonksiyon işini yaptı.

it("should sum the cost of their items", (done) => {
    const db = firebase
        .initializeAdminApp({ projectId: REAL_FIREBASE_PROJECT_ID })
        .firestore();

    // Setup: Initialize cart
    const aliceCartRef = db.doc("carts/alice")
    aliceCartRef.set({ ownerUID: "alice", totalPrice: 0 });

    //  Trigger calculateCart by adding items to the cart
    const aliceItemsRef = aliceCartRef.collection("items");
    aliceItemsRef.doc("doc1").set({name: "nectarine", price: 2.99});
    aliceItemsRef.doc("doc2").set({ name: "grapefruit", price: 6.99 });
    
    // Listen for every update to the cart. Every time an item is added to
    // the cart's subcollection of items, the function updates `totalPrice`
    // and `itemCount` attributes on the cart.
    // Returns a function that can be called to unsubscribe the listener.
    await new Promise((resolve) => {
      const unsubscribe = aliceCartRef.onSnapshot(snap => {
        // If the function worked, these will be cart's final attributes.
        const expectedCount = 2;
        const expectedTotal = 9.98;
  
        // When the `itemCount`and `totalPrice` match the expectations for the
        // two items added, the promise resolves, and the test passes.
        if (snap.data().itemCount === expectedCount && snap.data().totalPrice == expectedTotal) {
          // Call the function returned by `onSnapshot` to unsubscribe from updates
          unsubscribe();
          resolve();
        };
      });
    });
   });
 });

17. Testleri çalıştırın

Yine de önceki testlerden çalışan öykünücüleriniz olabilir. Değilse, öykünücüleri başlatın. Komut satırından çalıştırın

$ firebase emulators:start --import=./seed

Yeni bir terminal sekme açma fonksiyonları dizine ve hareket (koşu taklitlerini bırakın). Bunu güvenlik kuralları testlerinden hala açık tutabilirsiniz.

$ cd functions

Şimdi birim testlerini çalıştırın, toplam 5 test görmelisiniz:

$ npm test

> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha

  shopping cart creation
    ✓ can be created by the cart owner (82ms)

  shopping cart reads, updates, and deletes
    ✓ cart can be read by the cart owner (42ms)

  shopping cart items
    ✓ items can be read by the cart owner (40ms)
    ✓ items can be added by the cart owner

  adding an item to the cart recalculates the cart total. 
    1) should sum the cost of their items

  4 passing (2s)
  1 failing

Belirli bir arızaya bakarsanız, bu bir zaman aşımı hatası gibi görünüyor. Bunun nedeni, testin işlevin doğru şekilde güncellenmesini beklemesidir, ancak hiçbir zaman yapmaz. Şimdi, testi tatmin edecek fonksiyonu yazmaya hazırız.

18. Bir fonksiyon yazın

Bu testi düzeltmek için, işlevi güncellemeniz gerekir functions/index.js . Bu işlevin bir kısmı yazılmış olsa da, tamamlanmamıştır. İşlev şu anda böyle görünüyor:

// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
    .firestore.document("carts/{cartId}/items/{itemId}")
    .onWrite(async (change, context) => {
      console.log(`onWrite: ${change.after.ref.path}`);
      if (!change.after.exists) {
        // Ignore deletes
        return;
      }

      let totalPrice = 125.98;
      let itemCount = 8;
      try {
        
        const cartRef = db.collection("carts").doc(context.params.cartId);

        await cartRef.update({
          totalPrice,
          itemCount
        });
      } catch(err) {
      }
    });

Fonksiyonun doğru sepeti başvuru ayarlayarak, bunun yerine değerlerinin hesaplanması ardından totalPrice ve itemCount , onları kodlanmış olanları günceller.

Getir ve yinele

items subcollection

Yeni bir sabit, başlat itemsSnap olmak, items subcollection. Ardından, koleksiyondaki tüm belgeleri yineleyin.

// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
    .firestore.document("carts/{cartId}/items/{itemId}")
    .onWrite(async (change, context) => {
      console.log(`onWrite: ${change.after.ref.path}`);
      if (!change.after.exists) {
        // Ignore deletes
        return;
      }


      try {
        let totalPrice = 125.98;
        let itemCount = 8;

        const cartRef = db.collection("carts").doc(context.params.cartId);
        // ADD LINES FROM HERE
        const itemsSnap = await cartRef.collection("items").get();

        itemsSnap.docs.forEach(item => {
          const itemData = item.data();
        })
        // TO HERE
       
        return cartRef.update({
          totalPrice,
          itemCount
        });
      } catch(err) {
      }
    });

totalPrice ve itemCount hesaplayın

İlk olarak, değerlerini başlatmak izin totalPrice ve itemCount sıfıra.

Ardından mantığı yineleme bloğumuza ekleyin. İlk olarak, öğenin bir fiyatı olup olmadığını kontrol edin. Öğe bir miktar belirtmiş etmezse, bu varsayılan izin 1 . Sonra, değişen toplama miktarını eklemek itemCount . Son olarak, bir değişen toplama miktarı ile çarpılır öğenin fiyatını eklemek totalPrice :

// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
    .firestore.document("carts/{cartId}/items/{itemId}")
    .onWrite(async (change, context) => {
      console.log(`onWrite: ${change.after.ref.path}`);
      if (!change.after.exists) {
        // Ignore deletes
        return;
      }

      try {
        // CHANGE THESE LINES
        let totalPrice = 0;
        let itemCount = 0;

        const cartRef = db.collection("carts").doc(context.params.cartId);
        const itemsSnap = await cartRef.collection("items").get();

        itemsSnap.docs.forEach(item => {
          const itemData = item.data();
          // ADD LINES FROM HERE
          if (itemData.price) {
            // If not specified, the quantity is 1
            const quantity = itemData.quantity ? itemData.quantity : 1;
            itemCount += quantity;
            totalPrice += (itemData.price * quantity);
          }
          // TO HERE
        })

        await cartRef.update({
          totalPrice,
          itemCount
        });
      } catch(err) {
      }
    });

Başarı ve hata durumlarında hata ayıklamaya yardımcı olmak için günlük kaydı da ekleyebilirsiniz:

// Recalculates the total cost of a cart; triggered when there's a change
// to any items in a cart.
exports.calculateCart = functions
    .firestore.document("carts/{cartId}/items/{itemId}")
    .onWrite(async (change, context) => {
      console.log(`onWrite: ${change.after.ref.path}`);
      if (!change.after.exists) {
        // Ignore deletes
        return;
      }

      let totalPrice = 0;
      let itemCount = 0;
      try {
        const cartRef = db.collection("carts").doc(context.params.cartId);
        const itemsSnap = await cartRef.collection("items").get();

        itemsSnap.docs.forEach(item => {
          const itemData = item.data();
          if (itemData.price) {
            // If not specified, the quantity is 1
            const quantity = (itemData.quantity) ? itemData.quantity : 1;
            itemCount += quantity;
            totalPrice += (itemData.price * quantity);
          }
        });

        await cartRef.update({
          totalPrice,
          itemCount
        });

        // OPTIONAL LOGGING HERE
        console.log("Cart total successfully recalculated: ", totalPrice);
      } catch(err) {
        // OPTIONAL LOGGING HERE
        console.warn("update error", err);
      }
    });

19. Yeniden çalıştırma testleri

Komut satırında, öykünücülerin hala çalıştığından emin olun ve testleri yeniden çalıştırın. Emülatörleri, işlevlerdeki değişiklikleri otomatik olarak aldıkları için yeniden başlatmanız gerekmez. Tüm testlerin geçtiğini görmelisiniz:

$ npm test
> functions@ test .../emulators-codelab/codelab-initial-state/functions
> mocha

  shopping cart creation
    ✓ can be created by the cart owner (306ms)

  shopping cart reads, updates, and deletes
    ✓ cart can be read by the cart owner (59ms)

  shopping cart items
    ✓ items can be read by the cart owner
    ✓ items can be added by the cart owner

  adding an item to the cart recalculates the cart total. 
    ✓ should sum the cost of their items (800ms)


  5 passing (1s)

Aferin!

20. Storefront kullanıcı arayüzünü kullanarak deneyin

Nihai test için, web uygulamasına dönüş ( http: // localhost: 5000 / ) ve sepetine ürün ekleyin.

69ad26cee520bf24.png

Sepetin doğru toplamla güncellendiğini onaylayın. Harika!

Özet

Firebase için Cloud Functions ve Cloud Firestore arasında karmaşık bir test senaryosunu incelediniz. Testi geçmek için bir Bulut İşlevi yazdınız. Ayrıca, yeni işlevin kullanıcı arayüzünde çalıştığını da onayladınız! Tüm bunları yerel olarak yaptınız, öykünücüleri kendi makinenizde çalıştırdınız.

Ayrıca yerel öykünücülere karşı çalışan bir web istemcisi oluşturdunuz, verileri korumak için özel güvenlik kuralları oluşturdunuz ve yerel öykünücüleri kullanarak güvenlik kurallarını test ettiniz.

c6a7aeb91fe97a64.gif