Skip to content

Commit 3e4fd47

Browse files
authored
Merge pull request #77 from passageidentity/PSG-4629
PSG-4629
2 parents 4dc3875 + 12aa0cb commit 3e4fd47

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2009
-980
lines changed

.github/workflows/integration-tests.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Set up Google Chrome and ChromeDriver
3333
uses: browser-actions/setup-chrome@v1
3434
with:
35-
chrome-version: 127
35+
chrome-version: 129
3636
install-chromedriver: true
3737
install-dependencies: true
3838

@@ -135,12 +135,13 @@ jobs:
135135
working-directory: ./integrationtestapp
136136

137137
- name: Install simulator
138+
id: install-simulator
138139
uses: futureware-tech/simulator-action@v3
139140
with:
140141
model: 'iPhone 14'
141142

142143
- name: Run iOS tests
143-
run: flutter test integration_test/*.dart -d 2CBBC7F8-8C98-4142-8292-D5833BB30F8F --dart-define=MAILOSAUR_API_KEY=${{ secrets.MAILOSAUR_API_KEY }}
144+
run: flutter test integration_test/*.dart -d ${{ steps.install-simulator.outputs.udid }} --dart-define=MAILOSAUR_API_KEY=${{ secrets.MAILOSAUR_API_KEY }}
144145
working-directory: ./integrationtestapp
145146
env:
146147
MAILOSAUR_API_KEY: ${{ secrets.MAILOSAUR_API_KEY }}

android/build.gradle

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ android {
3838
dependencies {
3939
testImplementation 'org.jetbrains.kotlin:kotlin-test'
4040
testImplementation 'org.mockito:mockito-core:5.0.0'
41-
implementation 'id.passage.android:passage:1.8.2'
41+
implementation 'id.passage.android:passage:2.0.1'
4242
implementation 'com.google.code.gson:gson:2.10.1'
43+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1'
44+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
45+
implementation 'androidx.annotation:annotation:1.8.2'
4346
}
4447

4548
testOptions {

android/settings.gradle

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
rootProject.name = 'passage_flutter'
21
pluginManagement {
32
def flutterSdkPath = {
43
def properties = new Properties()
@@ -22,5 +21,5 @@ plugins {
2221
id "com.android.application" version "8.5.2" apply false
2322
id "org.jetbrains.kotlin.android" version "2.0.10" apply false
2423
}
25-
24+
rootProject.name = 'passage_flutter'
2625
include ":app"

android/src/main/kotlin/id/passage/passage_flutter/PassageFlutter.kt

+292-106
Large diffs are not rendered by default.

android/src/main/kotlin/id/passage/passage_flutter/PassageFlutterError.kt

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ internal enum class PassageFlutterError {
55
PASSKEY_ERROR,
66
USER_CANCELLED,
77
USER_UNAUTHORIZED,
8+
USER_NOT_FOUND,
9+
USER_INACTIVE,
10+
USER_REQUEST,
11+
USER_SERVER_ERROR,
12+
METADATA_APP_NOT_FOUND,
13+
METADATA_INVALID,
14+
METADATA_FORBIDDEN,
15+
METADATA_SERVER_ERROR,
816
OTP_ERROR,
917
MAGIC_LINK_ERROR,
1018
TOKEN_ERROR,

android/src/main/kotlin/id/passage/passage_flutter/PassageFlutterExtensions.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package id.passage.passage_flutter
22

3-
import id.passage.android.PassageAuthResult
3+
import id.passage.android.model.AuthResult
44

5-
internal fun PassageAuthResult.convertToMap(): HashMap<String, Any> {
5+
internal fun AuthResult.convertToMap(): HashMap<String, Any> {
66
val authResultHashMap: HashMap<String, Any> = hashMapOf(
77
"auth_token" to authToken,
88
"redirect_url" to redirectUrl

android/src/main/kotlin/id/passage/passage_flutter/PassageFlutterPlugin.kt

+9-9
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,14 @@ class PassageFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
3434

3535
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
3636
if (passageFlutter == null) {
37-
passageFlutter = if (call.method == "initWithAppId") {
37+
if (call.method == "initialize") {
3838
val appId = call.argument<String>("appId") ?: return invalidArgumentError(result)
39-
PassageFlutter(activity, appId)
40-
} else {
41-
PassageFlutter(activity)
39+
passageFlutter = PassageFlutter(activity, appId)
4240
}
4341
}
4442

4543
when (call.method) {
46-
"initWithAppId" -> {}
47-
"overrideBasePath" -> {
48-
passageFlutter?.overrideBasePath(call, result)
49-
}
44+
"initialize" -> {}
5045
"registerWithPasskey" -> passageFlutter?.registerWithPasskey(call, result)
5146
"loginWithPasskey" -> passageFlutter?.loginWithPasskey(call, result)
5247
"deviceSupportsPasskeys" -> passageFlutter?.deviceSupportsPasskeys(result)
@@ -73,7 +68,12 @@ class PassageFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
7368
"changePhone" -> passageFlutter?.changePhone(call, result)
7469
"hostedAuthStart" -> passageFlutter?.hostedAuthStart(result)
7570
"hostedAuthFinish" -> passageFlutter?.hostedAuthFinish(call, result)
76-
"hostedLogout" -> passageFlutter?.hostedLogout(result)
71+
"passkeys" -> passageFlutter?.passkeys(result);
72+
"socialConnections" -> passageFlutter?.socialConnections(result)
73+
"deleteSocialConnection" -> passageFlutter?.deleteSocialConnection(call, result)
74+
"metaData" -> passageFlutter?.metaData(result);
75+
"updateMetaData" -> passageFlutter?.updateMetaData(call, result)
76+
"createUser" -> passageFlutter?.createUser(call, result)
7777
else -> {
7878
result.notImplemented()
7979
}

integrationtestapp/android/app/src/main/res/values/strings.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<string name="passage_auth_origin">YOUR_AUTH_ORIGIN</string>
3+
<string name="passage_auth_origin">try-uat.passage.dev</string>
4+
<string name="clientApiBasePath">https://auth-uat.passage.dev/v1</string>
45
<string name="asset_statements">
56
[{
67
\"include\": \"https://@string/passage_auth_origin/.well-known/assetlinks.json\"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:passage_flutter/passage_flutter.dart';
4+
import 'helper/integration_test_config.dart';
5+
import 'package:integration_test/integration_test.dart';
6+
7+
void main() {
8+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
9+
PassageFlutter passage = PassageFlutter(IntegrationTestConfig.appIdPassage);
10+
11+
setUp(() async {
12+
if (!kIsWeb) {
13+
String basePath = IntegrationTestConfig.apiBaseUrl;
14+
}
15+
});
16+
17+
18+
group('PassageAppTests', () {
19+
test('testAppInfo', () async {
20+
try {
21+
final appInfo = await passage.app.info();
22+
expect(appInfo.id.toString(), IntegrationTestConfig.appIdPassage);
23+
} catch (e) {
24+
fail('Test failed due to unexpected exception: $e');
25+
}
26+
});
27+
28+
test('testUserExists', () async {
29+
try {
30+
const identifier = IntegrationTestConfig.existingUserPassage;
31+
final userInfo = await passage.app.userExists(identifier);
32+
expect(userInfo, isNotNull);
33+
} catch (e) {
34+
fail('Test failed due to unexpected exception: $e');
35+
}
36+
});
37+
38+
test('testUserDoesNotExist', () async {
39+
try {
40+
final identifier = 'nonexistent_${DateTime.now().millisecondsSinceEpoch}@example.com';
41+
final userInfo = await passage.app.userExists(identifier);
42+
fail('Expected PassageError but got success');
43+
} catch (e) {
44+
//success
45+
}
46+
});
47+
48+
test('testUserDoesExist', () async {
49+
try {
50+
final newUser = await passage.app.createUser('newuser_${DateTime.now().millisecondsSinceEpoch}@example.com');
51+
expect(newUser, isNotNull);
52+
} catch (e) {
53+
fail('Test failed due to unexpected exception: $e');
54+
}
55+
});
56+
});
57+
}

integrationtestapp/integration_test/change_user_info_test.dart

+8-9
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ void main() {
1414
setUp(() async {
1515
if (!kIsWeb) {
1616
String basePath = IntegrationTestConfig.apiBaseUrl;
17-
await passage.overrideBasePath(basePath);
1817
}
1918
});
2019

2120
tearDown(() async {
2221
try {
23-
await passage.signOut();
22+
await passage.currentUser.logout();
2423
} catch (e) {
2524
// an error happened during sign out
2625
}
@@ -29,14 +28,14 @@ void main() {
2928
Future<void> loginWithMagicLink() async {
3029
try {
3130
await passage
32-
.newLoginMagicLink(IntegrationTestConfig.existingUserEmailMagicLink);
31+
.magliclink.login(IntegrationTestConfig.existingUserEmailMagicLink);
3332
await Future.delayed(const Duration(
3433
milliseconds: IntegrationTestConfig.waitTimeMilliseconds));
3534
final magicLinkStr = await MailosaurAPIClient.getMostRecentMagicLink();
3635
if (magicLinkStr.isEmpty) {
3736
fail('Test failed: Magic link is empty');
3837
}
39-
await passage.magicLinkActivate(magicLinkStr);
38+
await passage.magliclink.activate(magicLinkStr);
4039
} catch (e) {
4140
fail('Expected to activate login magic link, but got an exception: $e');
4241
}
@@ -48,7 +47,7 @@ void main() {
4847
await loginWithMagicLink();
4948
final date = DateTime.now().millisecondsSinceEpoch;
5049
final identifier = 'authentigator+$date@passage.id';
51-
final response = await passage.changeEmail(identifier);
50+
final response = await passage.currentUser.changeEmail(identifier);
5251
expect(response, isNotNull);
5352
} catch (e) {
5453
fail('Test failed due to unexpected exception: $e');
@@ -57,10 +56,10 @@ void main() {
5756

5857
test('testChangeEmailUnAuthed', () async {
5958
try {
60-
await passage.signOut();
59+
await passage.currentUser.logout();
6160
final date = DateTime.now().millisecondsSinceEpoch;
6261
final identifier = 'authentigator+$date@passage.id';
63-
await passage.changeEmail(identifier);
62+
await passage.currentUser.changeEmail(identifier);
6463
fail('Test should throw PassageError');
6564
} catch (e) {
6665
if (e is PassageError) {
@@ -74,7 +73,7 @@ void main() {
7473
test('testChangePhoneInvalid', () async {
7574
try {
7675
await loginWithMagicLink();
77-
final response = await passage.changePhone('444');
76+
final response = await passage.currentUser.changePhone('444');
7877
expect(response, isNotNull);
7978
fail('Test should throw PassageError');
8079
} catch (e) {
@@ -88,7 +87,7 @@ void main() {
8887

8988
test('testChangePhoneUnAuthed', () async {
9089
try {
91-
await passage.changePhone('+14155552671');
90+
await passage.currentUser.changePhone('+14155552671');
9291
fail('Test should throw PassageError');
9392
} catch (e) {
9493
if (e is PassageError) {

integrationtestapp/integration_test/current_user_test.dart

+79-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import 'package:flutter_test/flutter_test.dart';
22
import 'package:passage_flutter/passage_flutter.dart';
3+
import 'package:passage_flutter/passage_flutter_models/meta_data.dart';
4+
import 'package:passage_flutter/passage_flutter_models/passage_social_connection.dart';
5+
import 'package:passage_flutter/passage_flutter_models/passage_user_social_connections.dart';
6+
import 'package:passage_flutter/passage_flutter_models/passkey.dart';
37
import 'helper/integration_test_config.dart';
48
import 'helper/mailosaur_api_client.dart';
59
import 'package:flutter/foundation.dart';
@@ -10,16 +14,15 @@ void main() {
1014
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
1115
PassageFlutter passage = PassageFlutter(IntegrationTestConfig.appIdMagicLink);
1216

13-
setUp(() async {
17+
setUpAll(() async {
1418
if (!kIsWeb) {
1519
String basePath = IntegrationTestConfig.apiBaseUrl;
16-
await passage.overrideBasePath(basePath);
1720
}
1821
});
1922

2023
tearDown(() async {
2124
try {
22-
await passage.signOut();
25+
await passage.currentUser.logout();
2326
} catch (e) {
2427
// an error happened during sign out
2528
}
@@ -28,14 +31,15 @@ void main() {
2831
Future<void> loginWithMagicLink() async {
2932
try {
3033
await passage
31-
.newLoginMagicLink(IntegrationTestConfig.existingUserEmailMagicLink);
32-
await Future.delayed(const Duration(
33-
milliseconds: IntegrationTestConfig.waitTimeMilliseconds));
34+
.magliclink
35+
.login(IntegrationTestConfig.existingUserEmailMagicLink);
36+
await Future.delayed(
37+
const Duration(milliseconds: IntegrationTestConfig.waitTimeMilliseconds));
3438
final magicLinkStr = await MailosaurAPIClient.getMostRecentMagicLink();
3539
if (magicLinkStr.isEmpty) {
3640
fail('Test failed: Magic link is empty');
3741
}
38-
await passage.magicLinkActivate(magicLinkStr);
42+
await passage.magliclink.activate(magicLinkStr);
3943
} catch (e) {
4044
fail('Expected to activate login magic link, but got an exception: $e');
4145
}
@@ -45,15 +49,15 @@ void main() {
4549
test('testCurrentUser', () async {
4650
try {
4751
await loginWithMagicLink();
48-
final response = await passage.getCurrentUser();
52+
final response = await passage.currentUser.userInfo();
4953
expect(response?.id, IntegrationTestConfig.currentUser.id);
5054
expect(response?.email, IntegrationTestConfig.currentUser.email);
5155
expect(response?.status, IntegrationTestConfig.currentUser.status);
52-
expect(response?.emailVerified,
53-
IntegrationTestConfig.currentUser.emailVerified);
56+
expect(
57+
response?.emailVerified, IntegrationTestConfig.currentUser.emailVerified);
5458
expect(response?.phone, IntegrationTestConfig.currentUser.phone);
55-
expect(response?.phoneVerified,
56-
IntegrationTestConfig.currentUser.phoneVerified);
59+
expect(
60+
response?.phoneVerified, IntegrationTestConfig.currentUser.phoneVerified);
5761
expect(response?.webauthn, IntegrationTestConfig.currentUser.webauthn);
5862
} catch (e) {
5963
fail('Test failed due to unexpected exception: $e');
@@ -62,13 +66,70 @@ void main() {
6266

6367
test('testCurrentUserNotAuthorized', () async {
6468
try {
65-
final response = await passage.getCurrentUser();
66-
if (response == null) {
67-
expect(true, true);
68-
} else {
69-
fail('Test failed: response must be null');
70-
}
69+
final response = await passage.currentUser.userInfo();
70+
fail('Test failed: must throw an error');
7171
} catch (e) {
72+
//success
73+
}
74+
});
75+
76+
test('testPasskeys', () async {
77+
try {
78+
await loginWithMagicLink();
79+
final passkeys = await passage.currentUser.passkeys();
80+
expect(passkeys, isNotNull);
81+
expect(passkeys, isA<List<Passkey>>());
82+
} catch (e) {
83+
fail('Test failed due to unexpected exception: $e');
84+
}
85+
});
86+
87+
test('testSocialConnections', () async {
88+
try {
89+
await loginWithMagicLink();
90+
final socialConnections = await passage.currentUser.socialConnections();
91+
expect(socialConnections, isNotNull);
92+
expect(socialConnections, isA<UserSocialConnections>());
93+
} catch (e) {
94+
fail('Test failed due to unexpected exception: $e');
95+
}
96+
});
97+
98+
test('testDeleteSocialConnection', () async {
99+
try {
100+
await loginWithMagicLink();
101+
102+
final socialConnectionType = SocialConnection.github;
103+
await passage.currentUser.deleteSocialConnection(socialConnectionType);
104+
final socialConnections = await passage.currentUser.socialConnections();
105+
expect(socialConnections, isNotNull);
106+
} catch (e) {
107+
fail('Test failed due to unexpected exception: $e');
108+
}
109+
});
110+
111+
test('testMetadata', () async {
112+
try {
113+
await loginWithMagicLink();
114+
final metadata = await passage.currentUser.metadata();
115+
expect(metadata, isNotNull);
116+
expect(metadata, isA<Metadata>());
117+
} catch (e) {
118+
print(e.toString());
119+
fail('Test failed due to unexpected exception: $e');
120+
}
121+
});
122+
123+
test('testUpdateMetadata', () async {
124+
try {
125+
await loginWithMagicLink();
126+
127+
Metadata newMetadata = Metadata(userMetadata: {'testkey': 'testValue'});
128+
await passage.currentUser.updateMetadata(newMetadata);
129+
final updatedMetadata = await passage.currentUser.metadata();
130+
expect(updatedMetadata, isNotNull);
131+
} catch (e) {
132+
print(e.toString());
72133
fail('Test failed due to unexpected exception: $e');
73134
}
74135
});

0 commit comments

Comments
 (0)