forked from baron/baron-sso
계정 생성, 비밀번호변경, 삭제 테스트케이스 추가
This commit is contained in:
@@ -40,11 +40,26 @@
|
|||||||
- `template.toml` 제외
|
- `template.toml` 제외
|
||||||
- 유효 locale 파일(`en.toml`, `ko.toml`)만 지원 목록에 반영
|
- 유효 locale 파일(`en.toml`, `ko.toml`)만 지원 목록에 반영
|
||||||
|
|
||||||
|
### S8. 실계정 비밀번호 변경 스모크(E2E)
|
||||||
|
- 목적: 로그인 상태 플로우가 기존 동작을 깨지 않았는지 확인
|
||||||
|
- 절차:
|
||||||
|
- Kratos Admin API로 임시 계정 생성(초기 비밀번호 포함)
|
||||||
|
- 구 비밀번호 로그인 성공 확인
|
||||||
|
- Settings API로 비밀번호 변경
|
||||||
|
- 구 비밀번호 로그인 실패 확인
|
||||||
|
- 신 비밀번호 로그인 성공 확인
|
||||||
|
- 테스트 계정 삭제(정리)
|
||||||
|
- 기대:
|
||||||
|
- 비밀번호 변경 전/후 인증 결과가 정확히 반전
|
||||||
|
- 테스트 종료 후 identity 삭제 완료(잔존 계정 없음)
|
||||||
|
|
||||||
## 실행 방법
|
## 실행 방법
|
||||||
```bash
|
```bash
|
||||||
cd userfront
|
cd userfront
|
||||||
flutter test test/locale_utils_test.dart
|
flutter test test/locale_utils_test.dart
|
||||||
flutter test test/locale_registry_test.dart
|
flutter test test/locale_registry_test.dart
|
||||||
|
flutter test test/router_redirect_widget_test.dart
|
||||||
|
flutter test --platform chrome test/locale_utils_test.dart test/locale_registry_test.dart test/router_redirect_widget_test.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
## 자동화 매핑
|
## 자동화 매핑
|
||||||
@@ -52,3 +67,17 @@ flutter test test/locale_registry_test.dart
|
|||||||
- S1~S6 전부 커버
|
- S1~S6 전부 커버
|
||||||
- `userfront/test/locale_registry_test.dart`
|
- `userfront/test/locale_registry_test.dart`
|
||||||
- S7 커버
|
- S7 커버
|
||||||
|
- `userfront/test/router_redirect_widget_test.dart`
|
||||||
|
- 로그인/비로그인 redirect 동작 검증(`redirect_uri`, `redirect_url`)
|
||||||
|
|
||||||
|
## 최근 실행 결과
|
||||||
|
- 실행일: 2026-02-19
|
||||||
|
- 결과:
|
||||||
|
- Flutter 테스트(VM): 통과
|
||||||
|
- Flutter 테스트(Chrome): 통과
|
||||||
|
- S8 실계정 E2E: 통과
|
||||||
|
- `login_old_password=200`
|
||||||
|
- `change_password=200`
|
||||||
|
- `login_old_after_change=400`
|
||||||
|
- `login_new_after_change=200`
|
||||||
|
- `cleanup(delete identity)=204`
|
||||||
|
|||||||
@@ -63,6 +63,14 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('buildLocalizedPath preserves redirect_url parameter', () {
|
||||||
|
final uri = Uri.parse('/signin?redirect_url=https://example.com/after');
|
||||||
|
expect(
|
||||||
|
buildLocalizedPath('ko', uri),
|
||||||
|
'/ko/signin?redirect_url=https://example.com/after',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('buildLocalizedPath preserves raw query order and duplicates', () {
|
test('buildLocalizedPath preserves raw query order and duplicates', () {
|
||||||
final uri = Uri.parse(
|
final uri = Uri.parse(
|
||||||
'/signin?a=1&a=2&redirect_uri=https%3A%2F%2Fexample.com%2Fcb%3Fx%3D1%26y%3D2',
|
'/signin?a=1&a=2&redirect_uri=https%3A%2F%2Fexample.com%2Fcb%3Fx%3D1%26y%3D2',
|
||||||
@@ -109,5 +117,15 @@ void main() {
|
|||||||
'/ko/signin?a=1&a=2&redirect_uri=https%3A%2F%2Fexample.com%2Fcb%3Fx%3D1%26y%3D2¬ice=qr_login_required',
|
'/ko/signin?a=1&a=2&redirect_uri=https%3A%2F%2Fexample.com%2Fcb%3Fx%3D1%26y%3D2¬ice=qr_login_required',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('buildSigninRedirectPath preserves redirect_url and redirect_uri', () {
|
||||||
|
final uri = Uri.parse(
|
||||||
|
'/ko/profile?redirect_url=https%3A%2F%2Fa.example.com%2Fcb&redirect_uri=https%3A%2F%2Fb.example.com%2Fcb',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
buildSigninRedirectPath('ko', uri),
|
||||||
|
'/ko/signin?redirect_url=https%3A%2F%2Fa.example.com%2Fcb&redirect_uri=https%3A%2F%2Fb.example.com%2Fcb',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
121
userfront/test/router_redirect_widget_test.dart
Normal file
121
userfront/test/router_redirect_widget_test.dart
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:userfront/core/i18n/locale_registry.dart';
|
||||||
|
import 'package:userfront/core/i18n/locale_utils.dart';
|
||||||
|
import 'package:userfront/core/services/auth_token_store.dart';
|
||||||
|
|
||||||
|
Widget _buildTestApp(String initialLocation) {
|
||||||
|
final router = GoRouter(
|
||||||
|
initialLocation: initialLocation,
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/:locale',
|
||||||
|
builder: (context, state) => const Scaffold(body: Text('root')),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'signin',
|
||||||
|
builder: (context, state) {
|
||||||
|
final challenge = state.uri.queryParameters['login_challenge'];
|
||||||
|
final redirect =
|
||||||
|
state.uri.queryParameters['redirect_uri'] ??
|
||||||
|
state.uri.queryParameters['redirect_url'] ??
|
||||||
|
'';
|
||||||
|
return Scaffold(
|
||||||
|
body: Text(
|
||||||
|
'signin|challenge=${challenge ?? ''}|redirect=$redirect',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: 'profile',
|
||||||
|
builder: (context, state) =>
|
||||||
|
const Scaffold(body: Text('profile-page')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
redirect: (context, state) {
|
||||||
|
final requestedLocale = extractLocaleFromPath(state.uri);
|
||||||
|
if (requestedLocale == null) {
|
||||||
|
return buildLocalizedPath(resolvePreferredLocaleCode(), state.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
final isLoggedIn =
|
||||||
|
AuthTokenStore.getToken() != null || AuthTokenStore.usesCookie();
|
||||||
|
final path = stripLocalePath(state.uri);
|
||||||
|
final isPublicPath = path == '/signin';
|
||||||
|
if (isPublicPath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return buildSigninRedirectPath(requestedLocale, state.uri);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return MaterialApp.router(routerConfig: router);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
LocaleRegistry.setSupportedLocaleCodesForTest(['en', 'ko']);
|
||||||
|
AuthTokenStore.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
AuthTokenStore.clear();
|
||||||
|
LocaleRegistry.resetForTest();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('비로그인: redirect_uri/login_challenge가 signin으로 전달', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final encodedRedirectUri = Uri.encodeComponent(
|
||||||
|
'https://rp.example.com/cb?x=1',
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
_buildTestApp(
|
||||||
|
'/en/profile?login_challenge=lc_123&redirect_uri=$encodedRedirectUri',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.text(
|
||||||
|
'signin|challenge=lc_123|redirect=https://rp.example.com/cb?x=1',
|
||||||
|
),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('비로그인: redirect_uri가 없으면 redirect_url을 전달', (tester) async {
|
||||||
|
final encodedRedirectUrl = Uri.encodeComponent(
|
||||||
|
'https://legacy.example.com/cb',
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
_buildTestApp('/en/profile?redirect_url=$encodedRedirectUrl'),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.text('signin|challenge=|redirect=https://legacy.example.com/cb'),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('로그인 상태: profile 접근 시 signin으로 리다이렉트하지 않음', (tester) async {
|
||||||
|
AuthTokenStore.setToken('test-token', provider: 'ory');
|
||||||
|
await tester.pumpWidget(
|
||||||
|
_buildTestApp(
|
||||||
|
'/en/profile?redirect_uri=https%3A%2F%2Frp.example.com%2Fcb',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text('profile-page'), findsOneWidget);
|
||||||
|
expect(find.textContaining('signin|'), findsNothing);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user