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, geliştirme makinenizde bu hizmetlerin yerel sürümlerini çalıştırmanızı sağlar. Böylece uygulamanızı hızlı ve güvenli bir şekilde geliştirebilirsiniz.
Ön koşullar
- Visual Studio Code, Atom veya Sublime Text gibi basit bir düzenleyici
- Node.js 10.0.0 veya sonraki sürümleri (Node.js'yi yüklemek için nvm'yi kullanın, sürümünüzü kontrol etmek için
node --version
komutunu çalıştırın) - Java 7 veya üstü (Java'yı yüklemek için bu talimatları uygulayın, sürümünüzü kontrol etmek için
java -version
komutunu çalıştırın)
Yapacaklarınız
Bu codelab'de, birden fazla Firebase hizmeti tarafından desteklenen basit bir online alışveriş uygulaması çalıştıracak ve bu uygulamada hata ayıklama yapacaksınız:
- Cloud Firestore: Gerçek zamanlı özelliklere sahip, küresel olarak ölçeklenebilir, sunucusuz bir NoSQL veritabanı.
- Cloud Functions: Etkinliklere veya HTTP isteklerine yanıt olarak çalışan sunucusuz bir arka uç kodu.
- Firebase Authentication: Diğer Firebase ürünleriyle entegre olan, yönetilen bir kimlik doğrulama hizmetidir.
- Firebase Hosting: Web uygulamaları için hızlı ve güvenli barındırma.
Yerel geliştirmeyi etkinleştirmek için uygulamayı Emulator Suite'e bağlayacaksınız.
Ayrıca şunları öğreneceksiniz:
- Uygulamanızı Emulator Suite'e bağlama ve çeşitli emülatörlerin birbirine nasıl bağlandığı
- Firebase Güvenlik Kuralları'nın işleyiş şekli ve Firestore Güvenlik Kuralları'nın yerel bir emülatörle karşılaştırılarak test edilmesi.
- Firestore etkinlikleri tarafından tetiklenen bir Firebase Functions işlevi yazma ve Emulator Suite'e yönelik olarak çalıştırılan entegrasyon testlerini yazma.
2. Ayarla
Kaynak kodunu alma
Bu codelab'de, The Fire Store örneğinin neredeyse tamamlanmış bir sürümüyle başlayacaksınız. Bu nedenle, yapmanız gereken ilk şey kaynak kodu 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 geçin:
$ cd emulators-codelab/codelab-initial-state
Şimdi kodu çalıştırabilmek için bağımlılıkları yükleyin. Yavaş bir internet bağlantınız varsa bu işlem 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'ı edinme
Emulator Suite, Firebase CLI'ın (komut satırı arayüzünün) bir parçasıdır ve aşağıdaki komutla makinenize yüklenebilir:
$ npm install -g firebase-tools
Ardından, KSA'nın en son sürümüne sahip olduğunuzu onaylayın. Bu codelab'in 9.0.0 veya sonraki sürümlerde çalışması gerekir ancak sonraki sürümlerde daha fazla hata düzeltmesi bulunmaktadır.
$ firebase --version 9.6.0
Firebase projenize bağlanın
Firebase projeniz yoksa Firebase konsolunda yeni bir Firebase projesi 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. Öncelikle Firebase CLI'ya giriş yapmak için aşağıdaki komutu çalıştırın:
$ firebase login
Ardından, proje takma adı oluşturmak için aşağıdaki komutu çalıştırın. $YOUR_PROJECT_ID
kısmını Firebase projenizin kimliğiyle değiştirin.
$ firebase use $YOUR_PROJECT_ID
Artık uygulamayı çalıştırmaya hazırsınız.
3. Emülatörleri çalıştırma
Bu bölümde uygulamayı yerel olarak çalıştıracaksınız. Emulator Suite'i başlatma zamanı geldi.
Emülatörleri başlatın
Emülatörleri başlatmak için codelab kaynak dizininin içinde aşağıdaki komutu çalıştırın:
$ firebase emulators:start --import=./seed
Şuna benzer bir çıkış alırsınız:
$ 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://127.0.0.1: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://127.0.0.1:4000 │ └─────────────────────────────────────────────────────────────┘ ┌────────────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Authentication │ 127.0.0.1:9099 │ http://127.0.0.1:4000/auth │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Functions │ 127.0.0.1:5001 │ http://127.0.0.1:4000/functions │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ 127.0.0.1:8080 │ http://127.0.0.1:4000/firestore │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Hosting │ 127.0.0.1:5000 │ n/a │ └────────────────┴────────────────┴─────────────────────────────────┘ Emulator Hub running at 127.0.0.1:4400 Other reserved ports: 4500 Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
Tüm emülatörler başlatıldı mesajını gördüğünüzde uygulama kullanıma hazırdır.
Web uygulamasını emülatörlere bağlama
Günlüklerdeki tabloya baktığımızda, Cloud Firestore emülatörünün 8080
bağlantı noktasında, Kimlik Doğrulama emülatörünün ise 9099
bağlantı noktasını dinlediğini görebiliyoruz.
┌────────────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Authentication │ 127.0.0.1:9099 │ http://127.0.0.1:4000/auth │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Functions │ 127.0.0.1:5001 │ http://127.0.0.1:4000/functions │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ 127.0.0.1:8080 │ http://127.0.0.1:4000/firestore │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Hosting │ 127.0.0.1:5000 │ n/a │ └────────────────┴────────────────┴─────────────────────────────────┘
Ön uç kodunuzu üretim yerine emülatöre bağlayalım. public/js/homepage.js
dosyasını açın ve onDocumentReady
işlevini bulun. Kodun standart Firestore ve Auth örneklerine eriştiğini görüyoruz:
public/js/anasayfa.js
const auth = firebaseApp.auth();
const db = firebaseApp.firestore();
db
ve auth
nesnelerini yerel emülatörlere işaret edecek şekilde güncelleyelim:
public/js/anasayfa.js
const auth = firebaseApp.auth();
const db = firebaseApp.firestore();
// ADD THESE LINES
if (location.hostname === "127.0.0.1") {
console.log("127.0.0.1 detected!");
auth.useEmulator("http://127.0.0.1:9099");
db.useEmulator("127.0.0.1", 8080);
}
Artık uygulama yerel makinenizde çalışırken (Hosting emülatörü tarafından sunulur) Firestore istemcisi de üretim veritabanı yerine yerel emülatöre işaret eder.
EmulatorUI'yi açın
Web tarayıcınızda http://127.0.0.1:4000/ adresine gidin. Emulator Suite kullanıcı arayüzünü görürsünüz.
Firestore Emülatörü'nün kullanıcı arayüzünü görmek için tıklayın. items
koleksiyonu, --import
işaretiyle içe aktarılan veriler nedeniyle zaten veri içeriyor.
4. Uygulamayı çalıştırın
Uygulamayı aç
Web tarayıcınızda http://127.0.0.1:5000 adresine gidin. The Fire Store'un makinenizde yerel olarak çalıştığını göreceksiniz.
Uygulamayı kullanma
Ana sayfadan bir ürün seçin ve Alışveriş Sepetine Ekle'yi tıklayın. Maalesef aşağıdaki hatayla karşılaşırsınız:
Bu hatayı düzeltelim. Her şey emülatörlerde çalıştığından, gerçek verileri etkileme konusunda endişe duymadan denemeler yapabiliriz.
5. Uygulamada hata ayıkla
Hatayı bulma
Şimdi Chrome geliştirici konsoluna bakalım. Konsolda hatayı görmek için Control+Shift+J
(Windows, Linux, Chrome OS) veya Command+Option+J
(Mac) tuşlarına basın:
addToCart
yönteminde hata olduğu anlaşılıyor. Şimdi buna bakalım. Bu yöntemde uid
adlı öğeye nereden erişmeye çalışırız ve neden null
olur? Bu yöntem şu anda public/js/homepage.js
içinde aşağıdaki gibi görünüyor:
public/js/anasayfa.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);
}
İşte! Uygulamada oturum açmadık. Firebase Authentication belgelerine göre oturum açmadığımızda auth.currentUser
şu anda null
. Bunu kontrol edelim:
public/js/anasayfa.js
addToCart(id, itemData) {
// ADD THESE LINES
if (this.auth.currentUser === null) {
this.showError("You must be signed in!");
return;
}
// ...
}
Uygulamayı test etme
Şimdi sayfayı yenileyin ve ardından Alışveriş Sepetine Ekle'yi tıklayın. Bu sefer daha güzel bir hata alacaksınız:
Ancak, üstteki araç çubuğunda Oturum Aç'ı ve ardından Alışveriş Sepetine Ekle'yi tekrar tıklarsanız alışveriş sepetinin güncellendiğini görürsünüz.
Ancak rakamlar hiç doğru görünmüyor:
Endişelenmeyin, bu hatayı yakında düzelteceğiz. Öncelikle, alışveriş sepetinize bir ürün eklediğinizde gerçekte ne olduğundan bahsedelim.
6. Yerel işlev tetikleyicileri
Alışveriş Sepetine Ekle'yi tıkladığınızda birden fazla emülatör içeren etkinlik zinciri başlatılır. Firebase CLI günlüklerinde, alışveriş sepetinize bir öğe ekledikten sonra aşağıdakine benzer mesajlar görürsünüz:
i functions: Beginning execution of "calculateCart" i functions: Finished "calculateCart" in ~1s
Bu günlükleri oluştururken dört önemli etkinlik meydana geldi ve gözlemlediğiniz kullanıcı arayüzü güncellemesi:
1) Firestore Yazma - İstemci
/carts/{cartId}/items/{itemId}/
adlı Firestore koleksiyonuna yeni bir belge eklendi. Bu kodu public/js/homepage.js
içindeki addToCart
işlevinde görebilirsiniz:
public/js/anasayfa.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) Cloud Functions İşlevi Tetiklendi
calculateCart
adlı Cloud Functions işlevi, functions/index.js
içinde görebileceğiniz onWrite
tetikleyicisini kullanarak alışveriş sepeti öğelerine yapılan yazma etkinliklerini (oluşturma, güncelleme veya silme) işler:
fonksiyonlar/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, alışveriş sepetindeki tüm öğeleri okur, toplam miktarı ve fiyatı toplar, ardından "alışveriş sepeti"ni günceller yeni toplamları içeren belge (yukarıdaki cartRef.update(...)
bölümüne bakın).
4) Firestore Okuma - İstemci
Web ön ucu, alışveriş sepetindeki değişikliklerle ilgili güncellemeleri almak için abone oldu. public/js/homepage.js
ürününde görebileceğiniz gibi, Cloud Functions işlevi yeni toplamları yazdıktan ve kullanıcı arayüzünü güncelledikten sonra anlık bir güncelleme alır:
public/js/anasayfa.js
this.cartUnsub = cartRef.onSnapshot(cart => {
// The cart document was changed, update the UI
// ...
});
Özet
Harika! Tamamen yerel test için üç farklı Firebase emülatörü kullanan tamamen yerel bir uygulama oluşturdunuz.
Bir saniye, hepsi bu kadar değil! Bir sonraki bölümde şunları öğreneceksiniz:
- Firebase Emülatörleri kullanan birim testleri yazma.
- Güvenlik kurallarınızda hata ayıklamak için Firebase Emulators'ı kullanma
7. Uygulamanız için özel güvenlik kuralları oluşturun
Web uygulamamız veri okuyor ve yazıyor. Ancak şu ana kadar güvenlik konusunda endişe duymadık. Cloud Firestore, "Güvenlik Kuralları" adlı bir sistem kullanır izin verilenler listesine eklenir. Emulator Suite, bu kuralların prototipini oluşturmanın harika bir yoludur.
Düzenleyicide emulators-codelab/codelab-initial-state/firestore.rules
dosyasını açın. 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 veritabanımızda veri okuyup yazabiliyor. Yalnızca geçerli işlemlerin ilişkilendirildiğinden ve hassas bilgilerin sızdırılmadığından emin olmak isteriz.
Bu codelab'de, En Az Ayrıcalık İlkesi uyarınca tüm dokümanları kilitleyecek ve tüm kullanıcılar ihtiyaç duydukları tüm erişime sahip oluncaya kadar (daha fazla değil) kademeli olarak erişim ekleyeceğiz. Koşulu false
olarak ayarlayarak erişimi reddetmek için ilk iki kuralı güncelleyelim:
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ırma
Emülatörleri başlatma
Komut satırında emulators-codelab/codelab-initial-state/
konumunda olduğunuzdan emin olun. Önceki adımlara uygulanan emülatörler çalışmaya devam edebilir. Çalışmıyorsa emülatörleri tekrar başlatın:
$ firebase emulators:start --import=./seed
Emülatörler çalışmaya başladıktan sonra bunlara yerel olarak testler yapabilirsiniz.
Testleri çalıştırma
Komut satırında, emulators-codelab/codelab-initial-state/
dizininden yeni bir terminal sekmesinde
İlk olarak işlevler dizinine geçiş yapın (codelab'in kalanı boyunca burada kalacağız):
$ cd functions
Şimdi işlevler dizininde mocha testlerini çalıştırın ve çıkışı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 hatamız var. Kural dosyasını oluştururken, başarılı olan daha fazla testi izleyerek ilerlemeyi ölçebilirsiniz.
9. Güvenli alışveriş sepeti erişimi
İlk iki hata "alışveriş sepeti" aşağıdakileri test eden bir testtir:
- Kullanıcılar yalnızca kendi alışveriş sepetlerini oluşturup güncelleyebilir
- Kullanıcılar yalnızca kendi alışveriş sepetlerini okuyabilir
fonksiyonlar/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());
});
Hadi bu testleri başarılı yapalım. Düzenleyicide firestore.rules
güvenlik kuralları dosyasını açın ve match /carts/{cartID}
içindeki ifadeleri güncelleyin:
firestore.rules
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 sahibi tarafından okuma ve yazma erişimine izin vermektedir.
Gelen verileri ve kullanıcının kimlik doğrulamasını doğrulamak için her kuralın bağlamında mevcut olan iki nesne kullanırız:
request
nesnesi, denenmekte olan işlemle ilgili verileri ve meta verileri içerir.- Bir Firebase projesi Firebase Authentication kullanıyorsa
request.auth
nesnesi, istekte bulunan kullanıcıyı tanımlar.
10. Alışveriş sepeti erişimini test etme
Emulator Suite, firestore.rules
kaydedildiğinde kuralları otomatik olarak günceller. Rules updated
mesajı için emülatörü çalıştıran sekmeye bakarak emülatörün kuralları güncellediğini onaylayabilirsiniz:
Testleri tekrar çalıştırın ve ilk iki testin başarıyla geçip geçmediğ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
Tebrikler! Artık alışveriş sepetlerine güvenle erişebilirsiniz. Bir sonraki başarısız teste geçelim.
11. "Alışveriş Sepetine Ekle"yi işaretleyin. kullanıcı arayüzünde
Şu anda, alışveriş sepeti sahipleri alışveriş sepetlerine yazma ve okuma yapmalarına rağmen sepetlerindeki ürünleri tek tek okuyamaz veya yazamaz. Bunun nedeni, sahiplerin alışveriş sepeti dokümanına erişimi olsa da alışveriş sepetinin öğeler alt koleksiyonuna erişememesidir.
Bu, kullanıcılar için bozuk bir durumdur.
http://127.0.0.1:5000,
tarihinde çalışan web kullanıcı arayüzüne dönün ve alışveriş sepetinize ürün eklemeyi deneyin. Kullanıcılara henüz items
alt koleksiyonunda oluşturulan dokümanlar için erişim vermediğimiz için hata ayıklama konsolunda gösterilen bir Permission Denied
hatası alıyorsunuz.
12. Alışveriş sepeti öğelerine erişim izni verin
Bu iki test, kullanıcıların yalnızca kendi alışveriş sepetlerine öğe ekleyebildiğini veya alışveriş sepetlerindeki öğeleri 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
}));
});
Dolayısıyla, geçerli kullanıcı alışveriş sepeti dokümanındaki sahipUID ile aynı UID'ye sahipse erişime izin veren bir kural yazabiliriz. create, update, delete
için farklı kurallar belirtmenize gerek olmadığından, verileri değiştiren tüm istekler için geçerli olan bir write
kuralı kullanabilirsiniz.
Öğeler alt koleksiyonundaki dokümanlarla ilgili kuralı güncelleyin. Koşullu öğedeki get
, Firestore'dan bir değer okuyor (bu örnekte, alışveriş sepeti belgesindeki ownerUID
).
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. Alışveriş sepeti öğelerine erişimi test etme
Artık testi tekrar çalıştırabiliriz. Çıkışın en üstüne gidin ve daha fazla testin başarılı olup olmadığını 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 başarılı oldu. Bekleyen bir testimiz var, ancak birkaç adımda bu teste ulaşacağız.
14. "Sepete ekle"yi işaretleyin tekrar akış
Web kullanıcı arabirimine geri dönün ( http://127.0.0.1:5000) ve alışveriş sepetine bir öğe ekleyin. Bu, testlerimizin ve kurallarımızın müşterinin gerektirdiği işlevle eşleştiğini doğrulamak için önemli bir adımdır. (Kullanıcı arayüzünü denediğimizde kullanıcıların alışveriş sepetlerine ürün ekleyemediğini unutmayın.)
firestore.rules
kaydedildiğinde istemci kuralları otomatik olarak yeniden yükler. Bu nedenle, alışveriş sepetine bir şey eklemeyi deneyin.
Özet
Harika! Uygulamanızın güvenliğini artırdınız. Bu, uygulamanızı üretime hazırlamak için önemli bir adımdır. Bu bir üretim uygulaması olsaydı bu testleri sürekli entegrasyon ardışık düzenimize ekleyebilirdik. Bu yöntem, başkaları kuralları değiştiriyor olsa bile, gelecekte alışveriş sepeti verilerimizde bu erişim denetimlerine sahip olacağına dair bize güven verir.
Ancak dahası da var!
devam ederseniz şunları öğreneceksiniz:
- Firestore etkinliği tarafından tetiklenen bir işlev nasıl yazılır?
- Birden fazla emülatörde çalışan testler oluşturma
15. Cloud Functions testlerini ayarlama
Ş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 Cloud Functions'ı da kullanıyor. Bu nedenle, ilgili kodu da test etmek istiyoruz.
Emulator Suite, Cloud Functions ve diğer hizmetleri kullanan işlevler de dahil olmak üzere Cloud Functions'ı test etmeyi son derece kolay hale getirir.
Düzenleyicide emulators-codelab/codelab-initial-state/functions/test.js
dosyasını açın ve dosyadaki son teste gidin. Şu anda beklemede:
// 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 .skip
kodlayıcısını kaldırın. Bu işlem aşağıdaki gibi görünür:
describe("adding an item to the cart recalculates the cart total. ", () => {
// ...
it("should sum the cost of their items", async () => {
...
});
});
Ardından, dosyanın üst kısmındaki REAL_FIREBASE_PROJECT_ID
değişkenini bulun ve gerçek Firebase Proje Kimliğiniz ile değiştirin.
// CHANGE THIS LINE
const REAL_FIREBASE_PROJECT_ID = "changeme";
Proje kimliğinizi unuttuysanız Firebase Proje Kimliğinizi Firebase Konsolu'ndaki Proje Ayarları bölümünde bulabilirsiniz:
16. Functions testlerini adım adım inceleyin
Bu test Cloud Firestore ile Cloud Functions arasındaki etkileşimi doğruladığı için önceki codelab'lerdeki testlere kıyasla daha fazla kurulum gerektirir. Şimdi, bu testin üzerinden geçip testin ne beklendiği hakkında bir fikir edinelim.
Alışveriş sepeti oluşturma
Cloud Functions güvenilir bir sunucu ortamında çalışır ve Yönetici SDK'si tarafından kullanılan hizmet hesabı kimlik doğrulamasını kullanabilir . Öncelikle, initializeApp
yerine initializeAdminApp
kullanarak bir uygulamayı ilk kullanıma hazırlıyorsunuz. Ardından, öğeleri ekleyeceğimiz alışveriş sepeti için bir DocumentReference oluşturun ve alışveriş sepetini başlatın:
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 });
...
});
İşlevi tetikleme
Ardından, işlevi tetiklemek için alışveriş sepeti belgemizin items
alt koleksiyonuna belge ekleyin. İşlevde gerçekleşen ekleme işlemini 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
Alışveriş sepeti dokümanındaki değişiklikler için bir işleyici kaydetmek üzere onSnapshot()
öğesini kullanın. onSnapshot()
, işleyicinin kaydını iptal etmek için çağırabileceğiniz bir işlev döndürür.
Bu test için, toplamı 9,98 TL olan iki öğe ekleyin. Ardından, alışveriş sepetinde beklenen itemCount
ve totalPrice
olup olmadığını kontrol edin. Çıktıysa işlev işini yapmıştır.
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 yapın
Önceki testlere göre çalışan emülatörler çalışmaya devam edebilir. Çalışmıyorsa emülatörleri başlatın. Komut satırından
$ firebase emulators:start --import=./seed
Yeni bir terminal sekmesi açın (emülatörleri çalışır durumda bırakın) ve işlevler dizinine geçin. Güvenlik kuralları testlerinde bu test açık olabilir.
$ cd functions
Şimdi birim testlerini çalıştırdığınızda toplam 5 test göreceksiniz:
$ 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
Söz konusu hataya bakarsanız bunun bir zaman aşımı hatası olduğu anlaşılıyor. Bunun nedeni, testin işlevin doğru bir şekilde güncellenmesini beklemesi ancak hiçbir zaman güncellememesidir. Artık testi karşılayacak işlevi yazmaya hazırız.
18. Fonksiyon yazma
Bu testi düzeltmek için functions/index.js
ürününde işlevi güncellemeniz gerekir. Bu işlevin bir kısmı yazılmış olsa da eksiktir. Fonksiyon şu anda aşağıdaki gibi görünecektir:
// 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) {
}
});
İşlev, alışveriş sepeti referansını doğru şekilde ayarlıyor ancak totalPrice
ve itemCount
değerlerini hesaplamak yerine bunları sabit kodlu olarak günceller.
Aynı yöntemi kullanarak
items
alt koleksiyon
items
alt koleksiyonu olacak yeni bir sabit değer (itemsSnap
) başlatın. 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 hesaplama
Öncelikle totalPrice
ve itemCount
değerlerini sıfıra başlatalım.
Ardından, mantığı yineleme bloğumuza ekleyin. Öncelikle öğenin bir fiyatı olup olmadığını kontrol edin. Öğede miktar belirtilmemişse varsayılan olarak 1
değerine ayarlayın. Ardından, miktarı değişen toplam itemCount
değerine ekleyin. Son olarak, öğenin fiyatının miktarla çarpımını toplam totalPrice
değerine ekleyin:
// 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) {
}
});
Hata ayıklama başarısı ve hata durumlarına 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. Testleri yeniden çalıştır
Komut satırında emülatörlerin hâlâ çalıştığından emin olun ve testleri yeniden çalıştırın. İşlevlerdeki değişiklikleri otomatik olarak aldıklarından emülatörleri yeniden başlatmanız gerekmez. Tüm testlerde başarılı olduğunuzu göreceksiniz:
$ 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)
Tebrikler!
20. Vitrin kullanıcı arayüzünü kullanarak deneyin
Son test için web uygulamasına ( http://127.0.0.1:5000/) dönün ve alışveriş sepetine bir ürün ekleyin.
Alışveriş sepetinin doğru toplam tutarla güncellendiğini doğrulayın. Çok teşekkür ederim.
Özet
Cloud Functions for Firebase ve Cloud Firestore arasındaki karmaşık bir test durumunu adım adım gösterdiniz. Testte başarılı olmak için bir Cloud Functions işlevi yazdınız. Ayrıca yeni işlevin kullanıcı arayüzünde çalıştığını doğruladınız. Tüm bunları yerel olarak, emülatörleri kendi makinenizde çalıştırarak gerçekleştirdiniz.
Ayrıca yerel emülatörlerde çalışan bir web istemcisi oluşturdunuz, verileri korumak için güvenlik kurallarını uyarladınız ve yerel emülatörleri kullanarak güvenlik kurallarını test ettiniz.