433 lines
13 KiB
Dart
433 lines
13 KiB
Dart
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<String, String>? 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);
|
|
});
|
|
}
|