Files
fanuzu/app/test/auth_oidc_test.dart
T

142 lines
5.0 KiB
Dart

import 'package:fancake/core/firebase/firebase_boot.dart';
import 'package:fancake/features/auth/auth.dart';
import 'package:fancake/features/auth/auth_repository.dart';
import 'package:fancake/features/auth/firebase_account_linker.dart';
import 'package:fancake/features/auth/social_providers.dart';
import 'package:firebase_auth/firebase_auth.dart' as fb;
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
import 'package:flutter_test/flutter_test.dart';
/// 카카오·LINE OIDC 제공자 — 콘솔 설정 전이라 실 로그인은 못 하고,
/// Firebase 호출 전후 계약(결과 매핑·취소·연결)만 확인한다.
void main() {
setUp(() => FirebaseBoot.debugSetMode(FirebaseMode.live, reason: 'OIDC 테스트'));
tearDown(
() => FirebaseBoot.debugSetMode(FirebaseMode.mock, reason: 'OIDC 테스트 끝'),
);
OidcSignInProvider kakao(fb.FirebaseAuth auth) => OidcSignInProvider(
AuthProviderKind.kakao,
providerId: 'oidc.kakao',
firebaseAuth: auth,
);
test('기본 목록의 카카오·LINE 은 Firebase OIDC 제공자다 — 「준비 중」이 아니다', () {
final providers = {
for (final p in AuthRepository().providers) p.provider: p,
};
for (final kind in [AuthProviderKind.kakao, AuthProviderKind.line]) {
final p = providers[kind];
expect(p, isA<OidcSignInProvider>(), reason: kind.name);
expect(p!.isLive, isTrue);
expect(
(p as OidcSignInProvider).providerId,
FirebaseAccountLinker.providerIds[kind],
);
expect(p.authProvider.providerId, 'oidc.${kind.name}');
}
});
test('Firebase 설정이 없으면 SDK 를 부르지 않고 미설정으로 닫는다', () async {
FirebaseBoot.debugSetMode(FirebaseMode.mock, reason: '설정 없음');
final outcome = await kakao(_ThrowingAuth('should-not-call')).signIn();
expect(outcome, isA<AuthNotConfigured>());
});
test('성공하면 카카오 계정으로 로그인된다', () async {
final auth = MockFirebaseAuth(mockUser: MockUser(uid: 'k-1'));
final outcome = await kakao(auth).signIn();
expect(outcome, isA<AuthSucceeded>());
expect((outcome as AuthSucceeded).account.provider, AuthProviderKind.kakao);
expect(outcome.account.uid, 'k-1');
});
test('인가 화면을 닫으면 Android·iOS 철자 모두 취소로 본다', () async {
for (final code in ['web-context-canceled', 'web-context-cancelled']) {
final outcome = await kakao(_ThrowingAuth(code)).signIn();
expect(outcome, isA<AuthCancelled>(), reason: code);
}
});
test('콘솔에서 제공자를 안 켰으면 E-AUTH-11', () async {
final outcome = await kakao(
_ThrowingAuth('operation-not-allowed'),
).signIn();
expect((outcome as AuthFailed).code, 'E-AUTH-11');
});
group('계정 연결', () {
test('같은 UID 에 oidc.kakao 를 붙인다', () async {
final user = _LinkingUser(linkedEmail: 'fan@fanuzu.co.kr');
final auth = MockFirebaseAuth(signedIn: true, mockUser: user);
final linker = FirebaseAccountLinker(
providers: [kakao(auth)],
firebaseAuth: auth,
);
final outcome = await linker.link(AuthProviderKind.kakao);
expect(outcome, isA<AuthSucceeded>());
expect(linker.linkedAccounts.keys, contains(AuthProviderKind.kakao));
});
test('다른 이메일의 카카오 계정은 붙였다가 바로 떼고 E-AUTH-08', () async {
final user = _LinkingUser(linkedEmail: 'someone@else.com');
final auth = MockFirebaseAuth(signedIn: true, mockUser: user);
final linker = FirebaseAccountLinker(
providers: [kakao(auth)],
firebaseAuth: auth,
);
final outcome = await linker.link(AuthProviderKind.kakao);
expect((outcome as AuthFailed).code, 'E-AUTH-08');
expect(
linker.linkedAccounts.keys,
isNot(contains(AuthProviderKind.kakao)),
);
});
});
}
class _ThrowingAuth extends MockFirebaseAuth {
_ThrowingAuth(this.code);
final String code;
@override
Future<fb.UserCredential> signInWithProvider(fb.AuthProvider provider) =>
throw fb.FirebaseAuthException(code: code);
}
/// Google 로 가입한 사용자. 연결하면 [linkedEmail] 을 가진 제공자 정보가 붙는다.
// ignore: must_be_immutable
class _LinkingUser extends MockUser {
_LinkingUser({required this.linkedEmail})
: super(
uid: 'u-1',
email: 'fan@fanuzu.co.kr',
providerData: [_info('google.com', 'fan@fanuzu.co.kr')],
);
final String linkedEmail;
@override
Future<fb.UserCredential> linkWithProvider(fb.AuthProvider provider) async {
final result = await super.linkWithProvider(provider);
providerData
..removeLast()
..add(_info(provider.providerId, linkedEmail));
return result;
}
}
fb.UserInfo _info(String providerId, String email) =>
// ignore: invalid_use_of_protected_member
fb.UserInfo.fromJson({
'uid': 'p-$providerId',
'email': email,
'providerId': providerId,
'isAnonymous': false,
'isEmailVerified': true,
});