appsok/test/jenkins_client_test.dart
toki 74d1f40465 feat(artifact-flow): Jenkins job 선택 흐름을 추가한다
사용자가 Jenkins job 목록을 먼저 확인하고 선택한 뒤 build 목록 단계로 진입할 수 있도록 기본 조회 UI와 클라이언트 모델을 추가한다.
2026-06-09 18:14:09 +09:00

157 lines
4.3 KiB
Dart

import 'dart:convert';
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/jenkins_client.dart';
void main() {
const credentials = JenkinsCredentials(username: 'user', apiToken: 'token');
final baseUrl = Uri.parse('https://jenkins.example/');
group('JenkinsJob.fromJson', () {
test('parses jenkins jobs from api response', () {
final json = {
'name': 'my-app',
'fullName': 'my-app',
'url': 'https://jenkins.example/job/my-app/',
'color': 'blue',
'_class': 'hudson.model.FreeStyleProject',
};
final job = JenkinsJob.fromJson(json);
expect(job.name, 'my-app');
expect(job.fullName, 'my-app');
expect(job.url, Uri.parse('https://jenkins.example/job/my-app/'));
expect(job.color, 'blue');
expect(job.isFolder, isFalse);
});
test('marks folder jobs from _class', () {
final json = {
'name': 'team-folder',
'fullName': 'team-folder',
'url': 'https://jenkins.example/job/team-folder/',
'_class': 'com.cloudbees.hudson.plugins.folder.Folder',
};
final job = JenkinsJob.fromJson(json);
expect(job.name, 'team-folder');
expect(job.isFolder, isTrue);
expect(job.color, isNull);
});
test('parses job with minimal fields', () {
final json = {
'name': 'minimal-job',
'url': 'https://jenkins.example/job/minimal-job/',
};
final job = JenkinsJob.fromJson(json);
expect(job.name, 'minimal-job');
expect(job.fullName, isNull);
expect(job.color, isNull);
expect(job.isFolder, isFalse);
});
});
group('JenkinsClient.fetchJobs', () {
test('fetchJobs requests root api json with job tree', () async {
Uri? capturedUri;
Map<String, String>? capturedHeaders;
final client = JenkinsClient(
client: MockClient((request) async {
capturedUri = request.url;
capturedHeaders = request.headers;
return http.Response(
jsonEncode({
'jobs': [
{
'name': 'app-release',
'fullName': 'app-release',
'url': 'https://jenkins.example/job/app-release/',
'color': 'blue',
'_class': 'hudson.model.FreeStyleProject',
},
],
}),
200,
);
}),
);
final jobs = await client.fetchJobs(
baseUrl: baseUrl,
credentials: credentials,
);
expect(jobs, hasLength(1));
expect(jobs.first.name, 'app-release');
expect(capturedUri?.path, contains('api/json'));
expect(
capturedUri?.queryParameters['tree'],
'jobs[name,fullName,url,color,_class]',
);
final expectedAuth = 'Basic ${base64Encode(utf8.encode('user:token'))}';
expect(capturedHeaders?['Authorization'], expectedAuth);
});
test('fetchJobs returns empty list when jobs key is missing', () async {
final client = JenkinsClient(
client: MockClient(
(_) async =>
http.Response(jsonEncode({'_class': 'hudson.model.Hudson'}), 200),
),
);
final jobs = await client.fetchJobs(
baseUrl: baseUrl,
credentials: credentials,
);
expect(jobs, isEmpty);
});
test('fetchJobs returns empty list when jobs is empty array', () async {
final client = JenkinsClient(
client: MockClient(
(_) async => http.Response(jsonEncode({'jobs': []}), 200),
),
);
final jobs = await client.fetchJobs(
baseUrl: baseUrl,
credentials: credentials,
);
expect(jobs, isEmpty);
});
test(
'fetchJobs throws JenkinsClientException on forbidden response',
() async {
final client = JenkinsClient(
client: MockClient((_) async => http.Response('Forbidden', 403)),
);
expect(
() => client.fetchJobs(baseUrl: baseUrl, credentials: credentials),
throwsA(
isA<JenkinsClientException>().having(
(e) => e.statusCode,
'statusCode',
403,
),
),
);
},
);
});
}