Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(go/plugins/firebase): add Init method to firebase go plugin #790

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions go/plugins/firebase/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func TestProvideAuthContext(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

mockClient := &mockAuthClient{
verifyIDTokenFunc: func(ctx context.Context, token string) (*auth.Token, error) {
if token == "validtoken" {
Expand Down
75 changes: 57 additions & 18 deletions go/plugins/firebase/firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,70 @@ package firebase

import (
"context"
"errors"
"fmt"
"sync"

firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/auth"
)

type FirebaseApp interface {
Auth(ctx context.Context) (*auth.Client, error)
var state struct {
mu sync.Mutex
initted bool
app *firebase.App
}

var (
app *firebase.App
mutex sync.Mutex
)
// FirebasePluginConfig is the configuration for the Firebase plugin.
type FirebasePluginConfig struct {
AuthOverride *map[string]interface{} `json:"databaseAuthVariableOverride"`
DatabaseURL string `json:"databaseURL"`
ProjectID string `json:"projectId"`
ServiceAccountID string `json:"serviceAccountId"`
StorageBucket string `json:"storageBucket"`
}

// Init initializes the Firebase app with the provided configuration.
// If called more than once, it logs a message and returns nil.
func Init(ctx context.Context, cfg *FirebasePluginConfig) error {
state.mu.Lock()
defer state.mu.Unlock()

if state.initted {
// throw an error if the app is already initialized
return errors.New("firebase.Init: Firebase app already initialized")
}

// Prepare the Firebase config
firebaseConfig := &firebase.Config{
AuthOverride: cfg.AuthOverride,
DatabaseURL: cfg.DatabaseURL,
ProjectID: cfg.ProjectID, // Allow ProjectID to be empty
ServiceAccountID: cfg.ServiceAccountID,
StorageBucket: cfg.StorageBucket,
}

// app returns a cached Firebase app.
func App(ctx context.Context) (FirebaseApp, error) {
mutex.Lock()
defer mutex.Unlock()
if app == nil {
newApp, err := firebase.NewApp(ctx, nil)
if err != nil {
return nil, err
}
app = newApp
// Initialize Firebase app with service account key if provided
app, err := firebase.NewApp(ctx, firebaseConfig)
if err != nil {
errorStr := fmt.Sprintf("firebase.Init: %v", err)
return errors.New(errorStr)
}
return app, nil

state.app = app
state.initted = true

return nil
}

// App returns a cached Firebase app.
// If the app is not initialized, it returns an error.
func App(ctx context.Context) (*firebase.App, error) {
state.mu.Lock()
defer state.mu.Unlock()

if !state.initted {
return nil, errors.New("firebase.App: Firebase app not initialized. Call Init first")
}

return state.app, nil
}
77 changes: 77 additions & 0 deletions go/plugins/firebase/firebase_app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firebase

import (
"context"
"testing"
)

func TestApp(t *testing.T) {
t.Parallel()

ctx := context.Background()

tests := []struct {
name string
setup func() error
expectedError string
}{
{
name: "Get App before initialization",
setup: func() error {
// No initialization setup here, calling App directly should fail
return nil
},
expectedError: "firebase.App: Firebase app not initialized. Call Init first",
},
{
name: "Get App after successful initialization",
setup: func() error {
// Properly initialize the app
config := &FirebasePluginConfig{
ProjectID: *firebaseProjectID,
}
return Init(ctx, config)
},
expectedError: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Execute setup
if err := tt.setup(); err != nil {
t.Fatalf("Setup failed: %v", err)
}

// Now test the App function
app, err := App(ctx)

if tt.expectedError != "" {
if err == nil || err.Error() != tt.expectedError {
t.Errorf("Expected error %q, got %v", tt.expectedError, err)
}
if app != nil {
t.Errorf("Expected no app, got %v", app)
}
} else if err != nil {
t.Errorf("Unexpected error: %v", err)
} else if app == nil {
t.Errorf("Expected a valid app instance, got nil")
}
})
}
}
97 changes: 97 additions & 0 deletions go/plugins/firebase/firebase_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firebase

import (
"context"
"flag"
"testing"
)

// UnInit uninitializes the Firebase app.

func uninit() {
state.mu.Lock()
defer state.mu.Unlock()

state.initted = false
state.app = nil
}

// Define the flag with a default value of "demo-test"
var firebaseProjectID = flag.String("firebase-project-id", "demo-test", "Firebase project ID")

func TestInit(t *testing.T) {

ctx := context.Background()

tests := []struct {
name string
config *FirebasePluginConfig
expectedError string
setup func() error
}{
{
name: "Successful initialization",
config: &FirebasePluginConfig{
ProjectID: *firebaseProjectID,
},
expectedError: "",
setup: func() error {
return nil // No setup required, first call should succeed
},
},
{
name: "Initialization when already initialized",
config: &FirebasePluginConfig{
ProjectID: *firebaseProjectID,
},
expectedError: "firebase.Init: Firebase app already initialized",
setup: func() error {
return Init(ctx, &FirebasePluginConfig{ProjectID: *firebaseProjectID}) // Initialize once
},
},
{
name: "Initialization with missing ProjectID",
config: &FirebasePluginConfig{
ProjectID: "",
},
expectedError: "", // No error expected, as ProjectID can be inferred
setup: func() error {
return nil // No setup required
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer uninit()

if err := tt.setup(); err != nil {
t.Fatalf("Setup failed: %v", err)
}

err := Init(ctx, tt.config)

if tt.expectedError != "" {
if err == nil || err.Error() != tt.expectedError {
t.Errorf("Expected error %q, got %v", tt.expectedError, err)
}
} else if err != nil {
t.Errorf("Unexpected error: %v", err)
}
})
}
}
Loading