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 üzeri (Node.js'yi yüklemek için nvm kullanın , sürümünüzü kontrol etmek için
node --version
çalıştırın) - Java 7 veya üzeri (Java'yı yüklemek için bu talimatları kullanın , sürümünüzü kontrol etmek için
java -version
çalıştırın)
Ne yapacaksın
Bu codelab'de, 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:
- Cloud Firestore: gerçek zamanlı özelliklere sahip, küresel olarak ölçeklenebilir, sunucusuz bir NoSQL veritabanı.
- Bulut İşlevleri : olaylara veya HTTP isteklerine yanıt olarak çalışan sunucusuz bir arka uç kodu.
- Firebase Kimlik Doğrulaması : diğer Firebase ürünleriyle entegre olan, yönetilen bir kimlik doğrulama hizmeti.
- 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ı 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ğlanacağı.
- Firebase Güvenlik Kuralları nasıl çalışır ve Firestore Güvenlik Kurallarının yerel bir emülatöre karşı nasıl test edileceği.
- 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 codelab'de The Fire Store örneğinin neredeyse tamamlanmış bir sürümüyle başlıyorsunuz, 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ında ç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. Daha 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'yi edinin
Emulator Suite, aşağıdaki komutla makinenize yüklenebilen Firebase CLI'nin (komut satırı arayüzü) bir parçasıdır:
$ npm install -g firebase-tools
Ardından CLI'nin en son sürümüne sahip olduğunuzu doğrulayın. Bu codelab'in 9.0.0 veya üzeri sürümlerle çalışması gerekir ancak sonraki sürümler daha fazla hata düzeltmesi içerir.
$ 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. Firebase CLI'de oturum açmak için öncelikle aşağıdaki komutu çalıştırın:
$ firebase login
Daha sonra bir proje takma adı oluşturmak için aşağıdaki komutu çalıştırın. $YOUR_PROJECT_ID
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ı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
Emülatörleri başlatmak için codelab kaynak dizininin içinden aşağıdaki komutu çalıştırın:
$ firebase emulators:start --import=./seed
Bunun gibi bazı çıktılar 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://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ğlayın
Günlüklerdeki tabloya dayanarak Cloud Firestore emülatörünün 8080
numaralı bağlantı noktasını dinlediğini ve Kimlik Doğrulama emülatörünün 9099
bağlantı noktasını dinlediğini görebiliriz.
┌────────────────┬────────────────┬─────────────────────────────────┐ │ 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örebiliriz:
public/js/homepage.js
const auth = firebaseApp.auth();
const db = firebaseApp.firestore();
Yerel öykünücülere işaret edecek şekilde db
ve auth
nesnelerini güncelleyelim:
public/js/homepage.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 (Barındırma öykünücüsü tarafından sunulur), Firestore istemcisi üretim veritabanı yerine yerel öykünücüyü de 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örmelisiniz.
Firestore Emulator kullanıcı arayüzünü görmek için tıklayın. --import
bayrağıyla içe aktarılan veriler nedeniyle items
koleksiyonu 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; Fire Store'un makinenizde yerel olarak çalıştığını görmelisiniz!
Uygulamayı kullan
Ana sayfadan bir ürün seçin ve Sepete Ekle'ye tıklayın. Maalesef aşağıdaki hatayla karşılaşacaksınız:
Bu hatayı düzeltelim! Her şey emülatörlerde çalıştığı için deney yapabiliyoruz ve gerçek verileri etkileme konusunda endişelenmiyoruz.
5. Uygulamada hata ayıklama
Hatayı bulun
Tamam, 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 bir hata var gibi görünüyor, gelin ona bir göz atalım. Bu yöntemde uid
adı verilen bir şeye nereden erişmeye çalışacağız ve bu neden null
olsun? Şu anda yöntem public/js/homepage.js
dosyasında şöyle görünüyor:
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! Uygulamada oturum açmadık. Firebase Authentication belgelerine göre, oturum açmadığımızda auth.currentUser
null
olur. 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 seçeneğine tıklayın. Bu sefer daha hoş bir hata almalısınız:
Ancak üst araç çubuğunda Giriş Yap'a tıklayıp ardından tekrar Sepete Ekle'ye tıklarsanız sepetin güncellendiğini göreceksiniz.
Ancak rakamlar hiç de doğru gibi görünmüyor:
Merak etmeyin, bu hatayı yakında düzelteceğiz. Öncelikle sepetinize bir ürün eklediğinizde gerçekte ne olduğuna derinlemesine bakalım.
6. Yerel işlevler tetiklenir
Sepete Ekle'ye tıklamak, birden fazla emülatör içeren bir olaylar zincirini başlatır. 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 dört önemli olay gerçekleşti:
1) Firestore Yazma - İstemci
Firestore koleksiyonuna /carts/{cartId}/items/{itemId}/
yeni bir belge eklendi. Bu kodu public/js/homepage.js
içindeki addToCart
işlevinde görebilirsiniz:
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
calculateCart
Bulut İşlevi, sepet öğelerinde meydana gelen tüm yazma olaylarını (oluşturma, güncelleme veya silme), functions/index.js
dosyasında görebileceğiniz onWrite
tetikleyicisini kullanarak dinler:
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, sepetteki tüm ürünleri okur ve toplam miktar ile fiyatı toplar, ardından "sepet" belgesini yeni toplamlarla günceller (yukarıdaki cartRef.update(...)
bakın).
4) Firestore Okuma - İstemci
Web ön ucu, sepette yapılan değişikliklerle ilgili güncellemeleri almak üzere abone oldu. public/js/homepage.js
dosyasında görebileceğiniz gibi, Bulut İşlevi yeni toplamları yazıp kullanıcı arayüzünü güncelledikten sonra gerçek zamanlı bir güncelleme alır:
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 emülatörü kullanan tamamen yerel bir uygulama kurdunuz.
Ama durun, dahası da var! Bir sonraki bölümde şunları öğreneceksiniz:
- Firebase Emülatörlerini kullanan birim testleri nasıl yazılır?
- Güvenlik Kurallarınızda hata ayıklamak için Firebase Emülatörleri nasıl kullanılır?
7. Uygulamanıza özel güvenlik kuralları oluşturun
Web uygulamamız verileri okuyor ve yazıyor ancak şu ana kadar güvenlik konusunda hiç endişe duymadık. Cloud Firestore, verileri okuma ve yazma erişimine kimin sahip olduğunu bildirmek için "Güvenlik Kuralları" adı verilen bir sistem kullanır. 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ıza veri okuyabilir ve yazabilir! Yalnızca geçerli işlemlerin yapıldığından ve hassas bilgilerin sızdırılmadığından 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, ancak daha fazlasını 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ırın
Emülatörleri başlat
Komut satırında emulators-codelab/codelab-initial-state/
konumunda olduğunuzdan emin olun. Önceki adımlardan itibaren emülatörleri hâlâ çalıştırıyor olabilirsiniz. Değilse emülatörleri tekrar başlatın:
$ firebase emulators:start --import=./seed
Emülatörler çalışmaya başladıktan sonra yerel olarak onlara karşı testler yapabilirsiniz.
Testleri çalıştırın
emulators-codelab/codelab-initial-state/
dizinindeki yeni terminal sekmesindeki komut satırında
Öncelikle işlevler dizinine geçin (codelab'in geri kalanında 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. Kural dosyasını oluştururken daha fazla testin geçişini izleyerek ilerlemeyi ölçebilirsiniz.
9. Güvenli alışveriş sepeti erişimi
İlk iki başarısızlık şunları 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 testlerin geçmesini sağlayalı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 sepet 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ırız:
-
request
nesnesi, denenen 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. Sepet erişimini test edin
Emulator Suite, firestore.rules
her kaydedildiğinde kuralları otomatik olarak günceller. Öykünücüyü çalıştıran sekmede Kurallar Rules updated
mesajına bakarak öykünücünün kuralların güncellendiğini doğrulayabilirsiniz:
Testleri yeniden çalıştırın ve ilk iki 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 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üvence altına aldınız. Bir sonraki başarısız teste geçelim.
11. Kullanıcı arayüzündeki "Sepete Ekle" akışını kontrol edin
Şu anda, sepet sahipleri sepetlerini okuyup yazmasına rağmen, sepetlerindeki ürünleri tek tek okuyamıyor veya yazamıyorlar. Bunun nedeni, sahiplerin sepet belgesine erişimi olmasına rağmen, sepetteki öğeler alt koleksiyonuna erişimlerinin olmamasıdır.
Bu kullanıcılar için bozuk bir durumdur.
http://127.0.0.1:5000,
üzerinde çalışan web kullanıcı arayüzüne dönün ve sepetinize bir şeyler eklemeyi deneyin. items
alt koleksiyonunda oluşturulan belgelere henüz kullanıcılara erişim izni vermediğimiz için hata ayıklama konsolundan görülebilen bir Permission Denied
hatası alıyorsunuz.
12. Sepet öğelerine erişime izin verin
Bu iki test, kullanıcıların yalnızca kendi sepetlerine ürün ekleyebileceğini veya sepetteki ürünleri okuyabildiğini doğrular:
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, mevcut kullanıcının sepet belgesindeki sahip UID'si ile aynı UID'ye sahip olması durumunda 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 belgelere ilişkin kuralı güncelleyin. Koşuldaki get
Firestore'dan bir değer okuyor; bu durumda, sepet 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. Sepet öğelerine erişimi test edin
Artık testi tekrar çalıştırabiliriz. Çıktının en üstüne gidin ve daha fazla testin 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 ✓ 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 birkaç adımda ona ulaşacağız.
14. "Sepete ekle" akışını tekrar kontrol edin
Web ön ucuna dönün ( http://127.0.0.1:5000 ) ve sepete bir ü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. (En son denediğimizde kullanıcı arayüzü kullanıcılarının sepetlerine ürün ekleyemediğini unutmayın!)
firestore.rules
kaydedildiğinde istemci kuralları otomatik olarak yeniden yükler. Bu yüzden sepete bir şey eklemeyi deneyin.
Özet
İyi iş! Uygulamanızın güvenliğini artırdınız; bu, uygulamanızı üretime hazır hale getirmek için önemli bir adımdır! Bu bir üretim uygulaması olsaydı bu testleri sürekli entegrasyon hattımıza ekleyebilirdik. Bu, başkaları kuralları değiştirse bile, alışveriş sepeti verilerimizin bu erişim kontrollerine sahip olacağı konusunda bize güven verecektir.
Ama durun, dahası da var!
devam ederseniz şunları öğreneceksiniz:
- Firestore olayı tarafından tetiklenen bir işlev nasıl yazılır?
- Birden fazla emülatörde çalışan testler nasıl oluşturulur?
15. Bulut İşlevleri testlerini ayarlayın
Şu ana kadar web uygulamamızın ön ucuna ve Firestore Güvenlik Kurallarına odaklandık. Ancak bu uygulama aynı zamanda kullanıcının sepetini güncel tutmak için Bulut İşlevlerini de kullanıyor, bu nedenle bu kodu da test etmek istiyoruz.
Emulator Suite, Bulut İşlevlerini, hatta Cloud Firestore ve diğer hizmetleri kullanan işlevleri bile test etmeyi çok kolaylaştırır.
Düzenleyicide emulators-codelab/codelab-initial-state/functions/test.js
dosyasını açın ve dosyadaki son teste ilerleyin. Ş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 .skip
kaldırın, böylece şöyle görünecektir:
describe("adding an item to the cart recalculates the cart total. ", () => {
// ...
it("should sum the cost of their items", async () => {
...
});
});
Daha sonra, dosyanın üst kısmındaki REAL_FIREBASE_PROJECT_ID
değişkenini bulun ve bunu gerçek Firebase Proje Kimliğinizle 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:
16. İşlev testlerini gözden geçirin
Bu test, Cloud Firestore ile Cloud Functions arasındaki etkileşimi doğruladığından önceki codelab'lerdeki testlerden daha fazla kurulum gerektirir. Bu testi geçelim ve ne beklediğine dair bir fikir edinelim.
Sepet oluştur
Cloud Functions, güvenilir bir sunucu ortamında çalışır ve Admin SDK tarafından kullanılan hizmet hesabı kimlik doğrulamasını kullanabilir. İlk olarak, bir uygulamayı, initializeApp
yerine, initializeAdminApp
kullanarak başlatırsınız. Ardından, öğeleri ekleyeceğimiz sepet için bir DocumentReference oluşturursunuz ve sepeti başlatırsınız:
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 tetikleyin
Ardından, işlevi tetiklemek için belgeleri sepet belgemizin items
alt koleksiyonuna ekleyin. İşlevde meydana gelen 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
Sepet belgesindeki herhangi bir değişiklik için bir dinleyici kaydetmek üzere onSnapshot()
işlevini kullanın. onSnapshot()
dinleyicinin kaydını silmek için arayabileceğiniz bir işlev döndürür.
Bu test için toplam maliyeti 9,98 ABD doları olan iki öğe ekleyin. Ardından, sepette beklenen itemCount
ve totalPrice
olup olmadığını kontrol edin. Eğer öyleyse, o zaman 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
Önceki testlerden kalan emülatörleri hâlâ çalıştırıyor olabilirsiniz. Değilse emülatörleri başlatın. Komut satırından çalıştırın
$ firebase emulators:start --import=./seed
Yeni bir terminal sekmesi açın (emülatörleri çalışır durumda bırakın) ve işlevler dizinine gidin. Bunu hala güvenlik kuralları testlerinden 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 hataya bakarsanız, bunun bir zaman aşımı hatası olduğunu görürsünüz. Bunun nedeni, testin işlevin doğru şekilde güncellenmesini beklemesine rağmen hiçbir zaman güncellememesidir. Artık testi sağlayacak fonksiyonu yazmaya hazırız.
18. Bir fonksiyon yazın
Bu testi düzeltmek için, functions/index.js
dosyasındaki işlevi güncellemeniz gerekir. Her ne kadar bu fonksiyonun bir kısmı yazılmış olsa da tam değildir. İşlev şu anda şu şekilde 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) {
}
});
İşlev, sepet referansını doğru şekilde ayarlıyor, ancak totalPrice
ve itemCount
değerlerini hesaplamak yerine bunları sabit kodlanmış değerlere güncelliyor.
Getir ve yinele
items
alt koleksiyonu
items
alt koleksiyonu olacak yeni bir sabiti 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'u hesaplayın
Öncelikle totalPrice
ve itemCount
değerlerini sıfıra ayarlayalım.
Ardından mantığı yineleme bloğumuza ekleyin. Öncelikle ürünün bir fiyatı olup olmadığını kontrol edin. Öğenin belirtilen bir miktarı yoksa varsayılan olarak 1
olmasına izin verin. Ardından miktarı itemCount
cari toplamına ekleyin. Son olarak, öğenin fiyatını miktarla çarparak totalPrice
toplamına 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) {
}
});
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. Testleri tekrar çalıştırın
Komut satırında emülatörlerin hala çalıştığından emin olun ve testleri yeniden çalıştırın. Emülatörleri yeniden başlatmanıza gerek yoktur çünkü işlevlerde yapılan değişiklikleri otomatik olarak alırlar. 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
Son test için web uygulamasına dönün ( http://127.0.0.1:5000/ ) ve sepete bir ürün ekleyin.
Sepetin doğru toplamla güncellendiğini onaylayın. Fantastik!
Özet
Cloud Functions for Firebase ile Cloud Firestore arasındaki karmaşık bir test örneğini incelediniz. Testi geçmek için bir Bulut İşlevi yazdınız. Ayrıca yeni işlevselliğin kullanıcı arayüzünde çalıştığını da doğruladı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ülerle çalışan bir web istemcisi oluşturdunuz, verileri korumak için güvenlik kurallarını uyarladınız ve yerel öykünücüleri kullanarak güvenlik kurallarını test ettiniz.