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
16 changes: 10 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ jobs:
test:
name: Test (Dart ${{ matrix.dart-version }})
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'

strategy:
fail-fast: false
Expand All @@ -75,7 +78,7 @@ jobs:
working-directory: packages/dart_firebase_admin

steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4
with:
fetch-depth: 2

Expand All @@ -90,6 +93,12 @@ jobs:
with:
channel: ${{ matrix.dart-version }}

- name: Authenticate to Google Cloud/Firebase
uses: google-github-actions/auth@v2
with:
workload_identity_provider: '${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}'
service_account: '${{ secrets.SERVICE_ACCOUNT }}'

- name: Cache pub dependencies
uses: actions/cache@v3
with:
Expand Down Expand Up @@ -123,11 +132,6 @@ jobs:
- name: Install Firebase CLI
run: npm install -g firebase-tools

- name: Setup gcloud credentials
run: |
mkdir -p $HOME/.config/gcloud
echo '${{ secrets.CREDS }}' > $HOME/.config/gcloud/application_default_credentials.json

- name: Run dart_firebase_admin tests with coverage
run: ${{ github.workspace }}/scripts/coverage.sh

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import '../app.dart';

extension AppExtension on FirebaseApp {
Future<String> get serviceAccountEmail async =>
options.credential?.serviceAccountCredentials?.email ??
options.credential?.serviceAccountId ??
(await client).getServiceAccountEmail();

/// Signs the given data using the IAM Credentials API or local credentials.
Expand All @@ -18,6 +18,7 @@ extension AppExtension on FirebaseApp {
data,
serviceAccountCredentials:
options.credential?.serviceAccountCredentials,
serviceAccountEmail: options.credential?.serviceAccountId,
endpoint: endpoint,
);
}
2 changes: 1 addition & 1 deletion packages/dart_firebase_admin/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dependencies:
google_cloud_firestore: ^0.1.0
google_cloud_storage: ^0.5.1
googleapis: ^15.0.0
googleapis_auth: ^2.1.0
googleapis_auth: ^2.2.0
googleapis_beta: ^9.0.0
http: ^1.0.0
intl: ^0.20.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void main() {
},
);
},
skip: !hasGoogleEnv
skip: !hasProdEnv
? 'Skipping client creation tests. '
'Set GOOGLE_APPLICATION_CREDENTIALS to run these tests.'
: false,
Expand Down
81 changes: 77 additions & 4 deletions packages/dart_firebase_admin/test/app/firebase_app_prod_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';

import 'package:dart_firebase_admin/auth.dart';
import 'package:dart_firebase_admin/src/app.dart';
import 'package:test/test.dart';

Expand All @@ -26,7 +27,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv()});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Requires GOOGLE_APPLICATION_CREDENTIALS to be set',
timeout: const Timeout(Duration(seconds: 30)),
Expand All @@ -47,7 +48,7 @@ void main() {
expect(app.isDeleted, isTrue);
}, zoneValues: {envSymbol: prodEnv()});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Requires GOOGLE_APPLICATION_CREDENTIALS to be set',
timeout: const Timeout(Duration(seconds: 30)),
Expand Down Expand Up @@ -77,7 +78,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv()});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Requires GOOGLE_APPLICATION_CREDENTIALS to be set',
timeout: const Timeout(Duration(seconds: 30)),
Expand All @@ -102,11 +103,83 @@ void main() {
}
}, zoneValues: {envSymbol: null});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Requires GOOGLE_APPLICATION_CREDENTIALS to be set',
timeout: const Timeout(Duration(seconds: 30)),
);
});

group('Workload Identity Federation tests', () {
late FirebaseApp app;

setUpAll(() async {
// Initialize via WIF (ADC)
app = FirebaseApp.initializeApp(
options: AppOptions(
credential: Credential.fromApplicationDefaultCredentials(
serviceAccountId:
'firebase-adminsdk-fbsvc@dart-firebase-admin.iam.gserviceaccount.com',
),
projectId: 'dart-firebase-admin',
),
);
});

test(
'should initializeApp via WIF (ADC)',
() {
expect(app, isNotNull);
},
skip: hasWifEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
);

test(
'should test Auth (getUsers)',
() async {
final auth = app.auth();
expect(auth, isNotNull);

final listUsersResult = await auth.listUsers(maxResults: 1);
expect(listUsersResult.users, isA<List<UserRecord>>());
},
skip: hasWifEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
);

test(
'should test Firestore (write + read)',
() async {
final db = app.firestore();
expect(db, isNotNull);

final docRef = db.collection('wif-demo').doc('test-connection');
const testMessage = 'Hello from GitHub Actions WIF!';

await docRef.set({
'timestamp': DateTime.now().toIso8601String(),
'message': testMessage,
});

final doc = await docRef.get();
expect(doc.exists, isTrue);
expect(doc.data()?['message'], equals(testMessage));
expect(doc.data()?['timestamp'], isNotNull);
},
skip: hasWifEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
);

test(
'should test signed tokens (createCustomToken)',
() async {
final auth = app.auth();
const uid = 'wif-demo-user-123';

final customToken = await auth.createCustomToken(uid);
expect(customToken, isNotNull);
expect(customToken, isA<String>());
},
skip: hasWifEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ void main() {

group('e2e', () {
test(
skip: hasGoogleEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
skip: hasProdEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
'should create and verify token',
() {
return runZoned(() async {
Expand Down Expand Up @@ -357,7 +357,7 @@ void main() {
);

test(
skip: hasGoogleEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
skip: hasProdEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
'should create token with custom ttl',
() {
return runZoned(() async {
Expand All @@ -382,7 +382,7 @@ void main() {
);

test(
skip: hasGoogleEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
skip: hasProdEnv ? false : 'Requires GOOGLE_APPLICATION_CREDENTIALS',
'should verify token with consume option',
() {
return runZoned(() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Requires production to verify custom claims clearing',
);
Expand Down Expand Up @@ -138,7 +138,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Session cookies require GCIP (not available in emulator)',
);
Expand Down Expand Up @@ -225,7 +225,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Session cookies require GCIP (not available in emulator)',
);
Expand Down Expand Up @@ -293,7 +293,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Session cookies require GCIP (not available in emulator)',
);
Expand Down Expand Up @@ -326,7 +326,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Session cookies require GCIP (not available in emulator)',
);
Expand Down Expand Up @@ -380,7 +380,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'getUsers not fully supported in Firebase Auth Emulator',
);
Expand Down Expand Up @@ -418,7 +418,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'getUsers not fully supported in Firebase Auth Emulator',
);
Expand Down Expand Up @@ -468,7 +468,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Provider configs require GCIP (not available in emulator)',
);
Expand Down Expand Up @@ -515,7 +515,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Provider configs require GCIP (not available in emulator)',
);
Expand Down
44 changes: 16 additions & 28 deletions packages/dart_firebase_admin/test/auth/auth_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ void main() {
}
}, zoneValues: {envSymbol: prodEnv});
},
skip: hasGoogleEnv
skip: hasProdEnv
? false
: 'Requires production mode but runs with emulator auto-detection',
);
Expand Down Expand Up @@ -748,36 +748,24 @@ void main() {
});

group('createCustomToken', () {
test(
'creates a valid JWT token',
() async {
final token = await auth.createCustomToken('test-uid');
test('creates a valid JWT token', () async {
final token = await auth.createCustomToken('test-uid');

expect(token, isNotEmpty);
expect(token, isA<String>());
// Token should be in JWT format (3 parts separated by dots)
expect(token.split('.').length, equals(3));
},
skip: hasGoogleEnv
? false
: 'Requires GOOGLE_APPLICATION_CREDENTIALS for service account',
);
expect(token, isNotEmpty);
expect(token, isA<String>());
// Token should be in JWT format (3 parts separated by dots)
expect(token.split('.').length, equals(3));
});

test(
'creates token with developer claims',
() async {
final token = await auth.createCustomToken(
'test-uid',
developerClaims: {'admin': true, 'level': 5},
);
test('creates token with developer claims', () async {
final token = await auth.createCustomToken(
'test-uid',
developerClaims: {'admin': true, 'level': 5},
);

expect(token, isNotEmpty);
expect(token, isA<String>());
},
skip: hasGoogleEnv
? false
: 'Requires GOOGLE_APPLICATION_CREDENTIALS for service account',
);
expect(token, isNotEmpty);
expect(token, isA<String>());
});

test('throws when uid is empty', () async {
expect(
Expand Down
Loading
Loading