Skip to content
Merged
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
72 changes: 42 additions & 30 deletions test/e2e/e2e_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import 'helpers/firestore_client.dart';
import 'helpers/http_client.dart';
import 'helpers/pubsub_client.dart';
import 'helpers/storage_client.dart';
import 'helpers/test_client_base.dart';
import 'tests/database_tests.dart';
import 'tests/firestore_tests.dart';
import 'tests/https_oncall_tests.dart';
Expand All @@ -39,13 +40,13 @@ import 'tests/scheduler_tests.dart';
import 'tests/storage_tests.dart';

void main() {
late EmulatorHelper emulator;
late FunctionsHttpClient client;
late PubSubClient pubsubClient;
late FirestoreClient firestoreClient;
late DatabaseClient databaseClient;
late AuthClient authClient;
late StorageClient storageClient;
EmulatorHelper? emulator;
FunctionsHttpClient? client;
PubSubClient? pubsubClient;
FirestoreClient? firestoreClient;
DatabaseClient? databaseClient;
AuthClient? authClient;
StorageClient? storageClient;

// Debug: Show Directory.current.path at module load time
print('DEBUG e2e_test: Directory.current.path = ${Directory.current.path}');
Expand Down Expand Up @@ -89,26 +90,26 @@ void main() {

// Start the emulator
emulator = EmulatorHelper(projectPath: examplePath);
await emulator.start();
await emulator!.start();

// Create HTTP client
client = FunctionsHttpClient(emulator.functionsUrl);
client = FunctionsHttpClient(emulator!.functionsUrl);

// Create Pub/Sub client
pubsubClient = PubSubClient(emulator.pubsubUrl, 'demo-test');
pubsubClient = PubSubClient(emulator!.pubsubUrl, 'demo-test');

// Create Firestore client
firestoreClient = FirestoreClient(emulator.firestoreUrl);
firestoreClient = FirestoreClient(emulator!.firestoreUrl);

// Create Database client
databaseClient = DatabaseClient(emulator.databaseUrl, 'demo-test');
databaseClient = DatabaseClient(emulator!.databaseUrl, 'demo-test');

// Create Auth client
authClient = AuthClient(emulator.authUrl, 'demo-test');
authClient = AuthClient(emulator!.authUrl, 'demo-test');

// Create Storage client
storageClient = StorageClient(
emulator.storageUrl,
emulator!.storageUrl,
'demo-test.firebasestorage.app',
);

Expand All @@ -123,27 +124,38 @@ void main() {
print('========================================');
print('');

client.close();
pubsubClient.close();
firestoreClient.close();
databaseClient.close();
authClient.close();
storageClient.close();
await emulator.stop();
try {
for (final client in <TestClientBase>[
?client,
?pubsubClient,
?firestoreClient,
?databaseClient,
?authClient,
?storageClient,
]) {
try {
client.close();
} catch (e, s) {
print('Error closing client in tearDownAll: $e\n$s');
}
}
} finally {
await emulator?.stop();
}
});

// Run all test groups (pass closures to defer value access)
runHttpsOnRequestTests(() => client, () => emulator);
runHttpsOnCallTests(() => client);
runHttpsOnRequestTests(() => client!, () => emulator!);
runHttpsOnCallTests(() => client!);
runIntegrationTests(() => examplePath);
runPubSubTests(() => examplePath, () => pubsubClient, () => emulator);
runFirestoreTests(() => examplePath, () => firestoreClient, () => emulator);
runDatabaseTests(() => examplePath, () => databaseClient, () => emulator);
runIdentityTests(() => examplePath, () => authClient, () => emulator);
runStorageTests(() => examplePath, () => storageClient, () => emulator);
runPubSubTests(() => examplePath, () => pubsubClient!, () => emulator!);
runFirestoreTests(() => examplePath, () => firestoreClient!, () => emulator!);
runDatabaseTests(() => examplePath, () => databaseClient!, () => emulator!);
runIdentityTests(() => examplePath, () => authClient!, () => emulator!);
runStorageTests(() => examplePath, () => storageClient!, () => emulator!);
runSchedulerTests(
() => examplePath,
() => emulator,
() => emulator.functionsPort,
() => emulator!,
() => emulator!.functionsPort,
);
}
21 changes: 7 additions & 14 deletions test/e2e/helpers/auth_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'test_client_base.dart';

/// Response from creating a new user.
class SignUpResponse {
Expand Down Expand Up @@ -93,12 +93,10 @@ class AuthError implements Exception {
///
/// Note: Admin operations (direct database access) do NOT trigger blocking
/// functions - only client SDK operations do.
class AuthClient {
AuthClient(this.baseUrl, this.projectId) : _client = http.Client();
final class AuthClient extends TestClientBase {
AuthClient(super.baseUrl, this.projectId);

final String baseUrl;
final String projectId;
final http.Client _client;

/// The API key to use for requests. The emulator accepts any non-empty key.
static const String apiKey = 'fake-api-key';
Expand All @@ -118,7 +116,7 @@ class AuthClient {

print('AUTH signUp: $email');

final response = await _client.post(
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
Expand Down Expand Up @@ -152,7 +150,7 @@ class AuthClient {

print('AUTH signInWithPassword: $email');

final response = await _client.post(
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
Expand Down Expand Up @@ -181,7 +179,7 @@ class AuthClient {

print('AUTH deleteAccount');

final response = await _client.post(
final response = await client.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'idToken': idToken}),
Expand All @@ -201,15 +199,10 @@ class AuthClient {

print('AUTH clearAllUsers');

final response = await _client.delete(url);
final response = await client.delete(url);

if (response.statusCode != 200) {
print('Warning: Failed to clear users: ${response.body}');
}
}

/// Closes the HTTP client.
void close() {
_client.close();
}
}
23 changes: 8 additions & 15 deletions test/e2e/helpers/database_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'test_client_base.dart';

/// Helper client for interacting with Firebase Realtime Database emulator REST API.
class DatabaseClient {
DatabaseClient(this.baseUrl, this.projectId);
final class DatabaseClient extends TestClientBase {
DatabaseClient(super.baseUrl, this.projectId);

final String baseUrl;
final String projectId;
final http.Client _client = http.Client();

/// Gets the full URL for a database path.
String _getUrl(String path) {
Expand All @@ -41,7 +39,7 @@ class DatabaseClient {
/// ```
Future<void> setValue(String path, dynamic data) async {
final url = _getUrl(path);
final response = await _client.put(
final response = await client.put(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
Expand All @@ -64,7 +62,7 @@ class DatabaseClient {
/// ```
Future<void> updateValue(String path, Map<String, dynamic> data) async {
final url = _getUrl(path);
final response = await _client.patch(
final response = await client.patch(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
Expand All @@ -90,7 +88,7 @@ class DatabaseClient {
/// ```
Future<String> pushValue(String path, dynamic data) async {
final url = _getUrl(path);
final response = await _client.post(
final response = await client.post(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
Expand All @@ -111,7 +109,7 @@ class DatabaseClient {
/// Returns null if the path doesn't exist.
Future<dynamic> getValue(String path) async {
final url = _getUrl(path);
final response = await _client.get(Uri.parse(url));
final response = await client.get(Uri.parse(url));

if (response.statusCode != 200) {
throw Exception(
Expand All @@ -130,17 +128,12 @@ class DatabaseClient {
/// Deletes a value at the specified path.
Future<void> deleteValue(String path) async {
final url = _getUrl(path);
final response = await _client.delete(Uri.parse(url));
final response = await client.delete(Uri.parse(url));

if (response.statusCode != 200) {
throw Exception(
'Failed to delete value at $path: ${response.statusCode} ${response.body}',
);
}
}

/// Closes the HTTP client.
void close() {
_client.close();
}
}
22 changes: 14 additions & 8 deletions test/e2e/helpers/emulator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,20 @@ class EmulatorHelper {
});

// Wait for emulator to be ready
print('Waiting for emulator to be ready...');
await _waitForReady();

// Additional wait to ensure Dart runtime is fully initialized
// The first request triggers Dart process startup which takes ~2-3 seconds
print('Waiting for Dart runtime to stabilize...');
await Future<void>.delayed(const Duration(seconds: 2));
print('✓ Emulator is ready');
try {
print('Waiting for emulator to be ready...');
await _waitForReady();

// Additional wait to ensure Dart runtime is fully initialized
// The first request triggers Dart process startup which takes ~2-3 seconds
print('Waiting for Dart runtime to stabilize...');
await Future<void>.delayed(const Duration(seconds: 2));
print('✓ Emulator is ready');
} catch (e) {
print('Error starting emulator: $e');
await stop();
rethrow;
}
}

/// Stops the Firebase emulator.
Expand Down
22 changes: 7 additions & 15 deletions test/e2e/helpers/firestore_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,11 @@

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'test_client_base.dart';

/// Helper client for interacting with Firestore emulator REST API.
class FirestoreClient {
FirestoreClient(this.baseUrl);

final String baseUrl;
final http.Client _client = http.Client();
final class FirestoreClient extends TestClientBase {
FirestoreClient(super.baseUrl);

/// Creates a document with the specified ID.
///
Expand All @@ -41,7 +38,7 @@ class FirestoreClient {
Map<String, dynamic> data,
) async {
final url = '$baseUrl/$collectionPath?documentId=$documentId';
final response = await _client.post(
final response = await client.post(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'fields': fields(data)}),
Expand Down Expand Up @@ -72,7 +69,7 @@ class FirestoreClient {
Map<String, dynamic> data,
) async {
final url = '$baseUrl/$documentPath';
final response = await _client.patch(
final response = await client.patch(
Uri.parse(url),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'fields': fields(data)}),
Expand All @@ -90,7 +87,7 @@ class FirestoreClient {
/// Deletes a document.
Future<void> deleteDocument(String documentPath) async {
final url = '$baseUrl/$documentPath';
final response = await _client.delete(Uri.parse(url));
final response = await client.delete(Uri.parse(url));

if (response.statusCode != 200) {
throw Exception(
Expand All @@ -102,7 +99,7 @@ class FirestoreClient {
/// Gets a document.
Future<Map<String, dynamic>?> getDocument(String documentPath) async {
final url = '$baseUrl/$documentPath';
final response = await _client.get(Uri.parse(url));
final response = await client.get(Uri.parse(url));

if (response.statusCode == 404) {
return null;
Expand Down Expand Up @@ -206,9 +203,4 @@ class FirestoreClient {
}
return result;
}

/// Closes the HTTP client.
void close() {
_client.close();
}
}
Loading
Loading