Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ func (a *App) Firestore(ctx context.Context) (*firestore.Client, error) {
return firestore.NewClient(ctx, a.projectID, a.opts...)
}

// FirestoreWithDatabase returns a new firestore.Client instance with the specified named database from the
// https://godoc.org/cloud.google.com/go/firestore package.
func (a *App) FirestoreWithDatabase(ctx context.Context, databaseID string) (*firestore.Client, error) {
if a.projectID == "" {
return nil, errors.New("project id is required to access Firestore")
}
Comment on lines +117 to +119

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This project ID check is duplicated from the Firestore() function. To improve maintainability and reduce code duplication, consider refactoring Firestore() to call FirestoreWithDatabase() with the default database ID. This would centralize the client creation logic and the project ID validation.

return firestore.NewClientWithDatabase(ctx, a.projectID, databaseID, a.opts...)
}

// InstanceID returns an instance of iid.Client.
func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) {
conf := &internal.InstanceIDConfig{
Expand Down
16 changes: 16 additions & 0 deletions firebase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,18 @@ func TestFirestore(t *testing.T) {
}
}

func TestFirestoreWithDatabase(t *testing.T) {
ctx := context.Background()
app, err := NewApp(ctx, nil, option.WithCredentialsFile("testdata/service_account.json"))
if err != nil {
t.Fatal(err)
}

if c, err := app.FirestoreWithDatabase(ctx, "other-db"); c == nil || err != nil {
t.Errorf("FirestoreWithDatabase() = (%v, %v); want (client, nil)", c, err)
}
}

func TestFirestoreWithProjectID(t *testing.T) {
verify := func(varName string) {
current := os.Getenv(varName)
Expand Down Expand Up @@ -336,6 +348,10 @@ func TestFirestoreWithNoProjectID(t *testing.T) {
if c, err := app.Firestore(ctx); c != nil || err == nil {
t.Errorf("Firestore() = (%v, %v); want (nil, error)", c, err)
}

if c, err := app.FirestoreWithDatabase(ctx, "other-db"); c != nil || err == nil {
t.Errorf("FirestoreWithDatabase() = (%v, %v); want (nil, error)", c, err)
}
}

func TestInstanceID(t *testing.T) {
Expand Down
153 changes: 147 additions & 6 deletions integration/firestore/firestore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,27 @@ import (
"reflect"
"testing"

"time"

"cloud.google.com/go/firestore"
"firebase.google.com/go/v4/integration/internal"
)

var (
cityData = map[string]interface{}{
"name": "Mountain View",
"country": "USA",
"population": int64(77846),
"capital": false,
}
movieData = map[string]interface{}{
"Name": "Interstellar",
"Year": int64(2014),
"Runtime": "2h 49m",
"Academy Award Winner": true,
}
)

func TestFirestore(t *testing.T) {
if testing.Short() {
log.Println("skipping Firestore integration tests in short mode.")
Expand All @@ -40,11 +58,130 @@ func TestFirestore(t *testing.T) {
}

doc := client.Collection("cities").Doc("Mountain View")
if _, err := doc.Set(ctx, cityData); err != nil {
t.Fatal(err)
}
snap, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(snap.Data(), cityData) {
t.Errorf("Get() = %v; want %v", snap.Data(), cityData)
}
if _, err := doc.Delete(ctx); err != nil {
t.Fatal(err)
}
Comment on lines +61 to +73

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure test resources are cleaned up even if assertions fail, it's better to use defer for the Delete operation. This prevents leaving orphaned documents in Firestore if the test fails midway. The defer should be placed after the document is successfully created.

if _, err := doc.Set(ctx, cityData); err != nil {
	t.Fatal(err)
}
defer doc.Delete(ctx)

snap, err := doc.Get(ctx)
if err != nil {
	t.Fatal(err)
}
if !reflect.DeepEqual(snap.Data(), cityData) {
	t.Errorf("Get() = %v; want %v", snap.Data(), cityData)
}

}

func TestFirestoreWithDatabase(t *testing.T) {
if testing.Short() {
log.Println("skipping Firestore integration tests in short mode.")
return
}
ctx := context.Background()
app, err := internal.NewTestApp(ctx, nil)
if err != nil {
t.Fatal(err)
}

// This test requires a non-default database to exist in the project.
// If it doesn't exist, this test will fail.
client, err := app.FirestoreWithDatabase(ctx, "testing-database")
if err != nil {
t.Fatal(err)
}

doc := client.Collection("cities").NewDoc()
if _, err := doc.Set(ctx, cityData); err != nil {
t.Fatal(err)
}
snap, err := doc.Get(ctx)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(snap.Data(), cityData) {
t.Errorf("Get() = %v; want %v", snap.Data(), cityData)
}
if _, err := doc.Delete(ctx); err != nil {
t.Fatal(err)
}
Comment on lines +95 to +107

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure test resources are cleaned up even if assertions fail, it's better to use defer for the Delete operation. This prevents leaving orphaned documents in Firestore if the test fails midway. The defer should be placed after the document is successfully created.

if _, err := doc.Set(ctx, cityData); err != nil {
	t.Fatal(err)
}
defer doc.Delete(ctx)

snap, err := doc.Get(ctx)
if err != nil {
	t.Fatal(err)
}
if !reflect.DeepEqual(snap.Data(), cityData) {
	t.Errorf("Get() = %v; want %v", snap.Data(), cityData)
}

}

func TestFirestoreMultiDB(t *testing.T) {
if testing.Short() {
log.Println("skipping Firestore integration tests in short mode.")
return
}
ctx := context.Background()
app, err := internal.NewTestApp(ctx, nil)
if err != nil {
t.Fatal(err)
}

cityClient, err := app.Firestore(ctx)
if err != nil {
t.Fatal(err)
}
// This test requires a non-default database to exist in the project.
movieClient, err := app.FirestoreWithDatabase(ctx, "testing-database")
if err != nil {
t.Fatal(err)
}

cityDoc := cityClient.Collection("cities").NewDoc()
movieDoc := movieClient.Collection("movies").NewDoc()

if _, err := cityDoc.Set(ctx, cityData); err != nil {
t.Fatal(err)
}
if _, err := movieDoc.Set(ctx, movieData); err != nil {
t.Fatal(err)
}

citySnap, err := cityDoc.Get(ctx)
if err != nil {
t.Fatal(err)
}
movieSnap, err := movieDoc.Get(ctx)
if err != nil {
t.Fatal(err)
}

if !reflect.DeepEqual(citySnap.Data(), cityData) {
t.Errorf("City Get() = %v; want %v", citySnap.Data(), cityData)
}
if !reflect.DeepEqual(movieSnap.Data(), movieData) {
t.Errorf("Movie Get() = %v; want %v", movieSnap.Data(), movieData)
}

if _, err := cityDoc.Delete(ctx); err != nil {
t.Fatal(err)
}
if _, err := movieDoc.Delete(ctx); err != nil {
t.Fatal(err)
}
Comment on lines +134 to +162

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure test resources are cleaned up even if assertions fail, it's better to use defer for the Delete operations. This prevents leaving orphaned documents in Firestore if the test fails midway. The defer should be placed after each document is successfully created.

if _, err := cityDoc.Set(ctx, cityData); err != nil {
	t.Fatal(err)
}
defer cityDoc.Delete(ctx)

if _, err := movieDoc.Set(ctx, movieData); err != nil {
	t.Fatal(err)
}
defer movieDoc.Delete(ctx)

citySnap, err := cityDoc.Get(ctx)
if err != nil {
	t.Fatal(err)
}
movieSnap, err := movieDoc.Get(ctx)
if err != nil {
	t.Fatal(err)
}

if !reflect.DeepEqual(citySnap.Data(), cityData) {
	t.Errorf("City Get() = %v; want %v", citySnap.Data(), cityData)
}
if !reflect.DeepEqual(movieSnap.Data(), movieData) {
	t.Errorf("Movie Get() = %v; want %v", movieSnap.Data(), movieData)
}

}

func TestServerTimestamp(t *testing.T) {
if testing.Short() {
log.Println("skipping Firestore integration tests in short mode.")
return
}
ctx := context.Background()
app, err := internal.NewTestApp(ctx, nil)
if err != nil {
t.Fatal(err)
}

client, err := app.Firestore(ctx)
if err != nil {
t.Fatal(err)
}

doc := client.Collection("cities").NewDoc()
data := map[string]interface{}{
"name": "Mountain View",
"country": "USA",
"population": int64(77846),
"capital": false,
"name": "Mountain View",
"timestamp": firestore.ServerTimestamp,
}
if _, err := doc.Set(ctx, data); err != nil {
t.Fatal(err)
Expand All @@ -53,8 +190,12 @@ func TestFirestore(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(snap.Data(), data) {
t.Errorf("Get() = %v; want %v", snap.Data(), data)
got := snap.Data()
if got["name"] != "Mountain View" {
t.Errorf("Name = %v; want Mountain View", got["name"])
}
if _, ok := got["timestamp"].(time.Time); !ok {
t.Errorf("Timestamp is not a time.Time: %v", got["timestamp"])
}
if _, err := doc.Delete(ctx); err != nil {
t.Fatal(err)
Expand Down
Loading