From 9f4048a889455e619443fc1615098ed4f57c7f26 Mon Sep 17 00:00:00 2001 From: toki Date: Tue, 16 Jun 2026 09:37:21 +0900 Subject: [PATCH] fix: jenkins client and test updates --- lib/src/services/jenkins_client.dart | 14 ++++ test/jenkins_artifact_session_test.dart | 94 +++++++++++++++++-------- test/jenkins_client_test.dart | 48 +++++++++++++ 3 files changed, 127 insertions(+), 29 deletions(-) diff --git a/lib/src/services/jenkins_client.dart b/lib/src/services/jenkins_client.dart index f3672a8..e52fdf9 100644 --- a/lib/src/services/jenkins_client.dart +++ b/lib/src/services/jenkins_client.dart @@ -201,7 +201,21 @@ class JenkinsClient { _throwIfFailed(response); final json = jsonDecode(response.body) as Map; + if (json['anonymous'] == true) { + throw const JenkinsClientException( + statusCode: 401, + message: 'Jenkins whoAmI returned anonymous user', + ); + } + final id = (json['id'] as String?)?.trim() ?? ''; + if (id.isEmpty) { + throw const JenkinsClientException( + statusCode: 401, + message: 'Jenkins whoAmI returned empty user id', + ); + } + final displayName = _firstNonBlank([ json['fullName'] as String?, json['name'] as String?, diff --git a/test/jenkins_artifact_session_test.dart b/test/jenkins_artifact_session_test.dart index 40a6806..1c188df 100644 --- a/test/jenkins_artifact_session_test.dart +++ b/test/jenkins_artifact_session_test.dart @@ -220,33 +220,35 @@ void main() { expect(result.isRestored, isFalse); }); - test('restoreDetailed returns restored after current user validation', - () async { - FlutterSecureStorage.setMockInitialValues({ - 'jenkins.baseUrl': baseUrl, - 'jenkins.username': credentials.username, - 'jenkins.apiToken': credentials.apiToken, - }); + test( + 'restoreDetailed returns restored after current user validation', + () async { + FlutterSecureStorage.setMockInitialValues({ + 'jenkins.baseUrl': baseUrl, + 'jenkins.username': credentials.username, + 'jenkins.apiToken': credentials.apiToken, + }); - final session = JenkinsArtifactSession( - store: TokenStore(), - client: JenkinsClient( - client: MockClient((request) async { - if (request.url.path.endsWith('whoAmI/api/json')) { - return http.Response(jsonEncode({'id': 'user'}), 200); - } - return http.Response('unexpected', 400); - }), - ), - stager: ArtifactStagingService(), - ); + final session = JenkinsArtifactSession( + store: TokenStore(), + client: JenkinsClient( + client: MockClient((request) async { + if (request.url.path.endsWith('whoAmI/api/json')) { + return http.Response(jsonEncode({'id': 'user'}), 200); + } + return http.Response('unexpected', 400); + }), + ), + stager: ArtifactStagingService(), + ); - final result = await session.restoreDetailed(); + final result = await session.restoreDetailed(); - expect(result.kind, JenkinsSessionRestoreKind.restored); - expect(result.isRestored, isTrue); - expect(session.isRestored, isTrue); - }); + expect(result.kind, JenkinsSessionRestoreKind.restored); + expect(result.isRestored, isTrue); + expect(session.isRestored, isTrue); + }, + ); test( 'restoreDetailed returns reauthRequired and clears session on 401', @@ -261,9 +263,7 @@ void main() { final session = JenkinsArtifactSession( store: store, client: JenkinsClient( - client: MockClient( - (_) async => http.Response('Unauthorized', 401), - ), + client: MockClient((_) async => http.Response('Unauthorized', 401)), ), stager: ArtifactStagingService(), ); @@ -286,12 +286,46 @@ void main() { 'jenkins.apiToken': credentials.apiToken, }); + final store = TokenStore(); + final session = JenkinsArtifactSession( + store: store, + client: JenkinsClient( + client: MockClient((_) async => http.Response('Forbidden', 403)), + ), + stager: ArtifactStagingService(), + ); + + final result = await session.restoreDetailed(); + + expect(result.kind, JenkinsSessionRestoreKind.reauthRequired); + expect(result.isRestored, isFalse); + expect(session.isRestored, isFalse); + expect(await store.readSession(), isNull); + }, + ); + + test( + 'restoreDetailed returns reauthRequired and clears session on anonymous whoAmI', + () async { + FlutterSecureStorage.setMockInitialValues({ + 'jenkins.baseUrl': baseUrl, + 'jenkins.username': credentials.username, + 'jenkins.apiToken': credentials.apiToken, + }); + final store = TokenStore(); final session = JenkinsArtifactSession( store: store, client: JenkinsClient( client: MockClient( - (_) async => http.Response('Forbidden', 403), + (_) async => http.Response( + jsonEncode({ + 'anonymous': true, + 'id': 'anonymous', + 'fullName': 'anonymous', + }), + 200, + ), ), ), stager: ArtifactStagingService(), @@ -319,7 +353,9 @@ void main() { final session = JenkinsArtifactSession( store: store, client: JenkinsClient( - client: MockClient((_) async => throw const SocketException('offline')), + client: MockClient( + (_) async => throw const SocketException('offline'), + ), ), stager: ArtifactStagingService(), ); diff --git a/test/jenkins_client_test.dart b/test/jenkins_client_test.dart index d6eb21f..c8f5e0c 100644 --- a/test/jenkins_client_test.dart +++ b/test/jenkins_client_test.dart @@ -468,6 +468,54 @@ void main() { ); }, ); + + test('fetchCurrentUser rejects anonymous whoAmI response', () async { + final client = JenkinsClient( + client: MockClient( + (_) async => http.Response( + jsonEncode({ + 'anonymous': true, + 'id': 'anonymous', + 'fullName': 'anonymous', + }), + 200, + ), + ), + ); + + expect( + () => + client.fetchCurrentUser(baseUrl: baseUrl, credentials: credentials), + throwsA( + isA().having( + (e) => e.statusCode, + 'statusCode', + 401, + ), + ), + ); + }); + + test('fetchCurrentUser rejects blank whoAmI id', () async { + final client = JenkinsClient( + client: MockClient( + (_) async => + http.Response(jsonEncode({'anonymous': false, 'id': ' '}), 200), + ), + ); + + expect( + () => + client.fetchCurrentUser(baseUrl: baseUrl, credentials: credentials), + throwsA( + isA().having( + (e) => e.statusCode, + 'statusCode', + 401, + ), + ), + ); + }); }); group('JenkinsClient.fetchRecentBuilds', () {