import 'dart:convert'; import 'dart:io'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:appsok/src/models/jenkins_build.dart'; import 'package:appsok/src/services/artifact_staging_service.dart'; import 'package:appsok/src/services/jenkins_artifact_session.dart'; import 'package:appsok/src/services/jenkins_client.dart'; import 'package:appsok/src/services/token_store.dart'; void main() { const credentials = JenkinsCredentials(username: 'user', apiToken: 'token'); const baseUrl = 'https://jenkins.example'; setUp(() { FlutterSecureStorage.setMockInitialValues({}); }); test('restore validates stored session with current user api', () async { FlutterSecureStorage.setMockInitialValues({ 'jenkins.baseUrl': baseUrl, 'jenkins.username': credentials.username, 'jenkins.apiToken': credentials.apiToken, }); Uri? capturedUri; Map? capturedHeaders; final session = JenkinsArtifactSession( store: TokenStore(), client: JenkinsClient( client: MockClient((request) async { capturedUri = request.url; capturedHeaders = request.headers; if (request.url.path.endsWith('whoAmI/api/json')) { return http.Response(jsonEncode({'id': 'user'}), 200); } return http.Response('unexpected', 400); }), ), stager: ArtifactStagingService(), ); final restored = await session.restore(); expect(restored, isTrue); expect(capturedUri?.path, '/whoAmI/api/json'); expect( capturedHeaders?['Authorization'], 'Basic ${base64Encode(utf8.encode('user:token'))}', ); }); test( 'loadJobs delegates to JenkinsClient with stored base url and credentials', () async { FlutterSecureStorage.setMockInitialValues({ 'jenkins.baseUrl': baseUrl, 'jenkins.username': credentials.username, 'jenkins.apiToken': credentials.apiToken, }); Uri? jobsUri; 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); } if (request.url.path.endsWith('api/json')) { jobsUri = request.url; return http.Response(jsonEncode({'jobs': []}), 200); } return http.Response('unexpected', 400); }), ), stager: ArtifactStagingService(), ); await session.restore(); await session.loadJobs(); expect(jobsUri, isNotNull); expect(jobsUri?.path, '/api/json'); expect(jobsUri?.host, 'jenkins.example'); }, ); test( 'downloadArtifact builds Jenkins artifact URL from build url and relative path', () async { FlutterSecureStorage.setMockInitialValues({ 'jenkins.baseUrl': baseUrl, 'jenkins.username': credentials.username, 'jenkins.apiToken': credentials.apiToken, }); Uri? artifactUri; 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); } if (request.url.path.endsWith('artifact/file.apk')) { artifactUri = request.url; return http.Response.bytes([], 200); } if (request.url.path.endsWith('api/json')) { return http.Response(jsonEncode({'jobs': []}), 200); } return http.Response('unexpected', 400); }), ), stager: ArtifactStagingService(), ); await session.restore(); final task = session.downloadArtifact( JenkinsBuild( number: 1, jobName: 'app', url: Uri.parse('$baseUrl/job/app/1/'), startedAt: DateTime(2026), result: 'SUCCESS', artifacts: const [ BuildArtifact( fileName: 'app.apk', relativePath: 'artifact/file.apk', ), ], ), const BuildArtifact( fileName: 'file.apk', relativePath: 'artifact/file.apk', ), ); await task.events.toList(); expect(artifactUri, isNotNull); expect(artifactUri?.path, '/job/app/1/artifact/artifact/file.apk'); }, ); test('returns unavailable state when session config is missing', () async { final session = JenkinsArtifactSession( store: TokenStore(), client: JenkinsClient( client: MockClient((_) async => http.Response('', 200)), ), stager: ArtifactStagingService(), ); final restored = await session.restore(); expect(restored, isFalse); expect(session.isRestored, isFalse); }); test('clear removes stored session and in-memory restored state', () 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((request) async { if (request.url.path.endsWith('whoAmI/api/json')) { return http.Response(jsonEncode({'id': 'user'}), 200); } return http.Response('unexpected', 400); }), ), stager: ArtifactStagingService(), ); expect(await session.restore(), isTrue); expect(session.isRestored, isTrue); await session.clear(); expect(session.isRestored, isFalse); expect(await store.readSession(), isNull); }); test('restoreDetailed returns missing when config is missing', () async { final session = JenkinsArtifactSession( store: TokenStore(), client: JenkinsClient( client: MockClient((_) async => http.Response('', 200)), ), stager: ArtifactStagingService(), ); final result = await session.restoreDetailed(); expect(result.kind, JenkinsSessionRestoreKind.missing); 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, }); 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(); expect(result.kind, JenkinsSessionRestoreKind.restored); expect(result.isRestored, isTrue); expect(session.isRestored, isTrue); }, ); test( 'restoreDetailed returns reauthRequired and clears session on 401', () 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('Unauthorized', 401)), ), 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 403', () 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)), ), 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( jsonEncode({ 'anonymous': true, 'id': 'anonymous', 'fullName': 'anonymous', }), 200, ), ), ), 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 networkFailure and keeps session on IOException', () 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 => throw const SocketException('offline'), ), ), stager: ArtifactStagingService(), ); final result = await session.restoreDetailed(); expect(result.kind, JenkinsSessionRestoreKind.networkFailure); expect(result.isRestored, isFalse); expect(session.isRestored, isFalse); expect(await store.readSession(), isNotNull); }, ); test( 'restoreDetailed returns serverFailure and keeps session on non-auth JenkinsClientException', () 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('Internal Server Error', 500), ), ), stager: ArtifactStagingService(), ); final result = await session.restoreDetailed(); expect(result.kind, JenkinsSessionRestoreKind.serverFailure); expect(result.isRestored, isFalse); expect(session.isRestored, isFalse); expect(await store.readSession(), isNotNull); }, ); test('restore bool wrapper remains true only for restored', () 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(), ); expect(await session.restore(), isTrue); FlutterSecureStorage.setMockInitialValues({}); final sessionMissing = JenkinsArtifactSession( store: TokenStore(), client: JenkinsClient( client: MockClient((_) async => http.Response('', 200)), ), stager: ArtifactStagingService(), ); expect(await sessionMissing.restore(), isFalse); }); }