blob: a420d83242aeec840dc696550cb18fa7e36af94e [file] [log] [blame]
// Copyright 2022 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:auto_submit/service/github_service.dart';
import 'package:github/github.dart';
import 'package:shelf/src/response.dart';
import '../../utilities/mocks.dart';
/// A fake GithubService implementation.
class FakeGithubService implements GithubService {
FakeGithubService({
MockGitHub? client,
String? checkRunsMock,
String? commitMock,
String? pullRequest,
String? compareTwoCommitsMock,
String? successMergeMock,
String? createCommentMock,
String? pullRequestMergeMock,
}) : github = client ?? MockGitHub();
@override
final MockGitHub github;
String? checkRunsMock;
String? commitMock;
PullRequest? pullRequestMock;
String? compareTwoCommitsMock;
String? successMergeMock;
String? createCommentMock;
String? pullRequestMergeMock;
String? pullRequestFilesJsonMock;
Issue? githubIssueMock;
bool useMergeRequestMockList = false;
bool trackMergeRequestCalls = false;
PullRequestMerge? mergeRequestMock;
List<PullRequestMerge> pullRequestMergeMockList = [];
/// map to track pull request calls using pull number and repository slug.
Map<int, RepositorySlug> verifyPullRequestMergeCallMap = {};
bool throwOnCreateIssue = false;
/// Setting either of these flags to true will pop the front element from the
/// list. Setting either to false will just return the non list version from
/// the appropriate method.
bool usePullRequestList = false;
bool usePullRequestFilesList = false;
List<String?> pullRequestFilesMockList = [];
List<PullRequest?> pullRequestMockList = [];
List<IssueComment> issueCommentsMockList = [];
IssueComment? issueComment;
bool useRealComment = false;
bool labelRemoved = false;
bool compareReturnValue = false;
bool skipRealCompare = false;
set checkRunsData(String? checkRunsMock) {
this.checkRunsMock = checkRunsMock;
}
set commitData(String? commitMock) {
this.commitMock = commitMock;
}
set pullRequestData(PullRequest? pullRequestMock) {
this.pullRequestMock = pullRequestMock;
}
set compareTwoCommitsData(String? compareTwoCommitsMock) {
this.compareTwoCommitsMock = compareTwoCommitsMock;
}
set successMergeData(String? successMergeMock) {
this.successMergeMock = successMergeMock;
}
set createCommentData(String? createCommentMock) {
this.createCommentMock = createCommentMock;
}
set pullRequestMergeData(String? pullRequestMergeMock) {
this.pullRequestMergeMock = pullRequestMergeMock;
}
set pullrequestFilesData(String? pullRequestFilesMock) {
pullRequestFilesJsonMock = pullRequestFilesMock;
}
set githubIssue(Issue? issue) {
githubIssueMock = issue;
}
@override
Future<List<CheckRun>> getCheckRuns(
RepositorySlug slug,
String ref,
) async {
final rawBody = json.decode(checkRunsMock!) as Map<String, dynamic>;
final List<dynamic> checkRunsBody = rawBody["check_runs"]! as List<dynamic>;
final List<CheckRun> checkRuns = <CheckRun>[];
if ((checkRunsBody[0] as Map<String, dynamic>).isNotEmpty) {
checkRuns.addAll(
checkRunsBody.map((dynamic checkRun) => CheckRun.fromJson(checkRun as Map<String, dynamic>)).toList(),
);
}
return checkRuns;
}
@override
Future<List<CheckRun>> getCheckRunsFiltered({
required RepositorySlug slug,
required String ref,
String? checkName,
CheckRunStatus? status,
CheckRunFilter? filter,
}) async {
final List<CheckRun> checkRuns = await getCheckRuns(slug, ref);
if (checkName != null) {
final List<CheckRun> checkRunsFilteredByName = [];
for (CheckRun checkRun in checkRuns) {
if (checkRun.name == checkName) {
checkRunsFilteredByName.add(checkRun);
}
}
return checkRunsFilteredByName;
}
return checkRuns;
}
@override
Future<RepositoryCommit> getCommit(RepositorySlug slug, String sha) async {
final RepositoryCommit commit = RepositoryCommit.fromJson(jsonDecode(commitMock!) as Map<String, dynamic>);
return commit;
}
@override
Future<PullRequest> getPullRequest(RepositorySlug slug, int pullRequestNumber) async {
PullRequest pullRequest;
if (usePullRequestList && pullRequestMockList.isNotEmpty) {
pullRequest = pullRequestMockList.removeAt(0)!;
} else if (usePullRequestList && pullRequestMockList.isEmpty) {
throw Exception('List is empty.');
} else {
pullRequest = pullRequestMock!;
}
return pullRequest;
}
@override
Future<GitHubComparison> compareTwoCommits(RepositorySlug slug, String refBase, String refHead) async {
final GitHubComparison githubComparison =
GitHubComparison.fromJson(jsonDecode(compareTwoCommitsMock!) as Map<String, dynamic>);
return githubComparison;
}
@override
Future<bool> removeLabel(RepositorySlug slug, int issueNumber, String label) async {
labelRemoved = true;
return labelRemoved;
}
@override
Future<IssueComment> createComment(RepositorySlug slug, int number, String commentBody) async {
if (useRealComment) {
issueComment = IssueComment(id: number, body: commentBody);
} else {
issueComment = IssueComment.fromJson(jsonDecode(createCommentMock!) as Map<String, dynamic>);
}
return issueComment!;
}
@override
Future<bool> updateBranch(RepositorySlug slug, int number, String headSha) async {
return true;
}
@override
Future<Response> autoMergeBranch(PullRequest pullRequest) {
// TODO: implement autoMergeBranch
throw UnimplementedError();
}
@override
Future<List<PullRequestFile>> getPullRequestFiles(RepositorySlug slug, PullRequest pullRequest) async {
String pullRequestData;
if (usePullRequestFilesList && pullRequestFilesMockList.isNotEmpty) {
pullRequestData = pullRequestFilesMockList.removeAt(0)!;
} else if (usePullRequestFilesList && pullRequestFilesMockList.isEmpty) {
throw Exception('File list is empty.');
} else {
pullRequestData = pullRequestFilesJsonMock as String;
}
final List<PullRequestFile> pullRequestFileList = [];
final dynamic parsedList = jsonDecode(pullRequestData);
for (dynamic d in parsedList) {
final PullRequestFile file = PullRequestFile.fromJson(d as Map<String, dynamic>);
pullRequestFileList.add(file);
}
return pullRequestFileList;
}
@override
Future<Issue> createIssue({
required RepositorySlug slug,
required String title,
required String body,
List<String>? labels,
String? assignee,
List<String>? assignees,
String? state,
}) async {
if (throwOnCreateIssue) {
throw GitHubError(github, 'Exception on github create issue.');
}
return githubIssueMock!;
}
@override
Future<bool> comparePullRequests(RepositorySlug repositorySlug, PullRequest revert, PullRequest current) async {
if (skipRealCompare) {
return compareReturnValue;
}
final List<PullRequestFile> revertPullRequestFiles = await getPullRequestFiles(repositorySlug, revert);
final List<PullRequestFile> currentPullRequestFiles = await getPullRequestFiles(repositorySlug, current);
return validateFileSetsAreEqual(revertPullRequestFiles, currentPullRequestFiles);
}
@override
bool validateFileSetsAreEqual(
List<PullRequestFile> changeList1,
List<PullRequestFile> changeList2,
) {
if (changeList1.length != changeList2.length) {
return false;
}
final List<String?> revertFileNames = [];
final List<String?> currentFileNames = [];
for (PullRequestFile element in changeList1) {
revertFileNames.add(element.filename);
}
for (PullRequestFile element in changeList2) {
currentFileNames.add(element.filename);
}
// At this point we know the file lists have the same amount of files but not the same files.
if (!revertFileNames.toSet().containsAll(currentFileNames) ||
!currentFileNames.toSet().containsAll(revertFileNames)) {
return false;
}
// At this point all the files are the same so we can iterate over one list to
// compare changes.
for (PullRequestFile pullRequestFile in changeList1) {
final PullRequestFile pullRequestFileChangeList2 =
changeList2.firstWhere((element) => element.filename == pullRequestFile.filename);
if (pullRequestFile.changesCount != pullRequestFileChangeList2.changesCount ||
pullRequestFile.additionsCount != pullRequestFileChangeList2.deletionsCount ||
pullRequestFile.deletionsCount != pullRequestFileChangeList2.additionsCount) {
return false;
}
}
return true;
}
@override
Future<Issue> getIssue({required RepositorySlug slug, required int issueNumber}) async {
return githubIssueMock!;
}
/// If useMergeRequestMockList is true then we will return elements from that
/// list until it is empty.
///
/// The developer should track the number of times this method is called as
/// managing an empty list is not done here.
@override
Future<PullRequestMerge> mergePullRequest(
RepositorySlug slug,
int number, {
String? commitMessage,
MergeMethod? mergeMethod,
String? requestSha,
}) async {
verifyPullRequestMergeCallMap[number] = slug;
if (useMergeRequestMockList) {
return pullRequestMergeMockList.removeAt(0);
} else {
return mergeRequestMock!;
}
}
void verifyMergePullRequests(Map<int, RepositorySlug> expected) {
assert(verifyPullRequestMergeCallMap.length == expected.length);
verifyPullRequestMergeCallMap.forEach((key, value) {
assert(expected.containsKey(key));
assert(expected[key] == value);
});
}
@override
Future<List<IssueComment>> listIssueComments(RepositorySlug slug, int issueNumber) async {
final List<IssueComment> issueCommentList = [];
issueCommentList.addAll(issueCommentsMockList);
return issueCommentList;
}
}