update roadmap and settings page implementation
This commit is contained in:
parent
9f9ae57636
commit
76827f9ae6
7 changed files with 62 additions and 22 deletions
|
|
@ -11,7 +11,7 @@ AppSok이 Jenkins에 접근할 수 있는 사용자인지 확인하고, 사용
|
|||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
[완료]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
|
|
@ -48,18 +48,22 @@ AppSok이 Jenkins에 접근할 수 있는 사용자인지 확인하고, 사용
|
|||
|
||||
- [x] [startup-check] 앱 시작 시 Keychain credential을 읽고 Jenkins 사용자 확인 API로 조용히 검증한다.
|
||||
- [x] [auth-state] 검증 성공 시 로그인 화면을 건너뛰고 build 목록으로 진입한다.
|
||||
- [ ] [reauth] token 폐기, 권한 없음, Jenkins URL 변경, 네트워크 실패를 구분해 재로그인 또는 재시도 흐름으로 안내한다.
|
||||
- [x] [reauth] token 폐기, 권한 없음, Jenkins URL 변경, 네트워크 실패를 구분해 재로그인 또는 재시도 흐름으로 안내한다.
|
||||
- [x] [logout-clear] 사용자가 저장된 credential을 삭제하고 다시 로그인할 수 있다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 모든 기능 Task와 Task 안에 명시된 검증 충족 후 기록한다.
|
||||
- 상태: 승인됨
|
||||
- 요청일: 2026-06-13
|
||||
- 완료 근거:
|
||||
- `agent-task/archive/2026/06/m-jenkins-credential/04+03_reauth_ui/complete.log`의 Roadmap Completion에서 `reauth` PASS가 기록됐다.
|
||||
- `plan_cloud_G06_2.log`와 `code_review_cloud_G06_2.log`가 PASS evidence로 남았고, `flutter analyze`와 전체 `flutter test` 검증이 통과했다.
|
||||
- Web Login token 발급 실패가 성공 로그인으로 보이는 UX를 보완했고, remote Mac runner에서 `flutter analyze`, `flutter test`, `flutter build macos`가 통과했다.
|
||||
- Milestone 기능 Task 전체가 `[x]`가 되어 Jenkins credential 수명주기 범위가 완료됐다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
- [x] 사용자가 완료 결과를 확인했다
|
||||
- [x] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 사용자 요청에 따라 완료 처리하고 archive로 이동한다.
|
||||
|
||||
## 범위 제외
|
||||
|
||||
|
|
@ -23,10 +23,10 @@ Jenkins에서 Android artifact를 찾고, Mac에 내려받고, USB로 연결된
|
|||
- [완료] USB 설치와 device 선택
|
||||
- 경로: `agent-roadmap/archive/phase/usable-mvp/milestones/usb-install.md`
|
||||
- 요약: 연결된 Android device를 고르고 선택한 APK를 `adb install -r`로 설치한다.
|
||||
- [진행중] Jenkins 로그인과 credential 수명주기
|
||||
- 경로: `agent-roadmap/phase/usable-mvp/milestones/jenkins-credential.md`
|
||||
- [완료] Jenkins 로그인과 credential 수명주기
|
||||
- 경로: `agent-roadmap/archive/phase/usable-mvp/milestones/jenkins-credential.md`
|
||||
- 요약: 사용자별 Jenkins API token 또는 WebView 기반 token 자동화 흐름을 안전하게 저장/검증한다.
|
||||
- [계획] ADB 콘솔과 logcat
|
||||
- [진행중] ADB 콘솔과 logcat
|
||||
- 경로: `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md`
|
||||
- 요약: 설치 후 바로 문제를 볼 수 있도록 logcat stream, filter, pause/clear 기능을 제공한다.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
[진행중]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
|
|
|
|||
|
|
@ -144,14 +144,21 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
final result = await launcher(baseUrl);
|
||||
if (!mounted) return;
|
||||
if (result != null) {
|
||||
if (result.apiToken == null) {
|
||||
setState(() {
|
||||
_webLoginError = 'API token 발급에 실패했습니다. Web Login을 다시 실행하세요.';
|
||||
_lastLoginResult = null;
|
||||
_webLoginLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
widget.onWebLoginCompleted?.call(result);
|
||||
setState(() {
|
||||
_lastLoginResult = result;
|
||||
_webLoginLoading = false;
|
||||
});
|
||||
if (result.apiToken != null) {
|
||||
await _saveToken(baseUrl, result.username, result.apiToken!);
|
||||
}
|
||||
await _saveToken(baseUrl, result.username, result.apiToken!);
|
||||
} else {
|
||||
setState(() => _webLoginLoading = false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ void main() {
|
|||
);
|
||||
|
||||
testWidgets(
|
||||
'SettingsPage skips _saveToken when launcher returns result without apiToken',
|
||||
'SettingsPage shows error when launcher returns result without apiToken',
|
||||
(tester) async {
|
||||
var tokenSaverCalled = false;
|
||||
|
||||
|
|
@ -403,7 +403,8 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
|
||||
expect(tokenSaverCalled, isFalse);
|
||||
expect(find.byKey(const ValueKey('web-login-result')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('web-login-result')), findsNothing);
|
||||
expect(find.textContaining('API token 발급에 실패했습니다'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ void main() {
|
|||
webLoginLauncher: (_) async => const JenkinsWebLoginResult(
|
||||
username: 'toki',
|
||||
displayName: 'Toki Lab',
|
||||
apiToken: 'issued-token',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -99,6 +100,33 @@ void main() {
|
|||
expect(find.textContaining('apiToken'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('shows token issue error when web login returns no apiToken', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_wrap(
|
||||
SettingsPage(
|
||||
webLoginLauncher: (_) async => const JenkinsWebLoginResult(
|
||||
username: 'toki',
|
||||
displayName: 'Toki Lab',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.enterText(
|
||||
find.byType(TextField).first,
|
||||
'https://jenkins.example.com',
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('jenkins-web-login-button')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const ValueKey('web-login-result')), findsNothing);
|
||||
expect(find.textContaining('API token 발급에 실패했습니다'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('clears stored credential through sessionClearer', (
|
||||
tester,
|
||||
) async {
|
||||
|
|
@ -158,10 +186,7 @@ void main() {
|
|||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byKey(const ValueKey('token-saved-label')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byKey(const ValueKey('token-saved-label')), findsOneWidget);
|
||||
|
||||
// Change URL — all transient labels must be cleared.
|
||||
await tester.enterText(
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ class _FakeJenkinsArtifactSession extends JenkinsArtifactSession {
|
|||
|
||||
@override
|
||||
Future<JenkinsSessionRestoreResult> restoreDetailed() async =>
|
||||
const JenkinsSessionRestoreResult(kind: JenkinsSessionRestoreKind.restored);
|
||||
const JenkinsSessionRestoreResult(
|
||||
kind: JenkinsSessionRestoreKind.restored,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<JenkinsJob>> loadJobs() async => [
|
||||
|
|
@ -1395,6 +1397,7 @@ void main() {
|
|||
webLoginLauncher: (_) async => const JenkinsWebLoginResult(
|
||||
username: 'toki',
|
||||
displayName: 'Toki Lab',
|
||||
apiToken: 'issued-token',
|
||||
),
|
||||
onWebLoginCompleted: (result) => capturedResult = result,
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in a new issue