Remove autosubmit on fail (#2091)
diff --git a/auto_submit/lib/service/approver_service.dart b/auto_submit/lib/service/approver_service.dart
index 4699a2f..49a14ff 100644
--- a/auto_submit/lib/service/approver_service.dart
+++ b/auto_submit/lib/service/approver_service.dart
@@ -50,7 +50,7 @@
if (labelNames.contains(Config.kRevertLabel) &&
(config.rollerAccounts.contains(author) || approvedAuthorAssociations.contains(authorAssociation))) {
log.info(
- 'Revert label and author has been validated. Attempting to approve the pull request. ${pullRequest.repo.toString()} by $author');
+ 'Revert label and author has been validated. Attempting to approve the pull request. ${pullRequest.repo} by $author');
await _approve(pullRequest, author);
} else {
log.info('Auto-review ignored for $author');
@@ -71,7 +71,7 @@
final gh.CreatePullRequestReview review =
gh.CreatePullRequestReview(slug.owner, slug.name, pullRequest.number!, 'APPROVE');
- botClient.pullRequests.createReview(slug, review);
+ await botClient.pullRequests.createReview(slug, review);
log.info('Review for $author complete');
}
}
diff --git a/auto_submit/lib/service/github_service.dart b/auto_submit/lib/service/github_service.dart
index c1649a8..3eda222 100644
--- a/auto_submit/lib/service/github_service.dart
+++ b/auto_submit/lib/service/github_service.dart
@@ -47,16 +47,24 @@
}
/// Create a new issue in github.
- Future<Issue> createIssue(
- RepositorySlug repositorySlug,
- String title,
- String body,
- ) async {
+ Future<Issue> createIssue({
+ required RepositorySlug slug,
+ required String title,
+ required String body,
+ List<String>? labels,
+ String? assignee,
+ List<String>? assignees,
+ String? state,
+ }) async {
IssueRequest issueRequest = IssueRequest(
title: title,
body: body,
+ labels: labels,
+ assignee: assignee,
+ assignees: assignees,
+ state: state,
);
- return await github.issues.create(repositorySlug, issueRequest);
+ return await github.issues.create(slug, issueRequest);
}
/// Fetches the specified pull request.
diff --git a/auto_submit/lib/service/revert_review_template.dart b/auto_submit/lib/service/revert_review_template.dart
new file mode 100644
index 0000000..73ab094
--- /dev/null
+++ b/auto_submit/lib/service/revert_review_template.dart
@@ -0,0 +1,54 @@
+// 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.
+
+/// A template for creating follow up review issues for created revert requests.
+class RevertReviewTemplate {
+ RevertReviewTemplate({
+ required this.repositorySlug,
+ required this.revertPrNumber,
+ required this.revertPrAuthor,
+ required this.originalPrLink,
+ });
+
+ final String repositorySlug;
+ final int revertPrNumber;
+ final String revertPrAuthor;
+ final String originalPrLink;
+
+ /// Constructs the issues title.
+ String _constructTitle() {
+ return '''
+Review request for Revert PR $repositorySlug#$revertPrNumber
+''';
+ }
+
+ /// Constructs the issues body.
+ String _constructBody() {
+ return '''
+Pull request $repositorySlug#$revertPrNumber was submitted and merged by
+@$revertPrAuthor in order to revert changes made in this pull request $originalPrLink.
+
+Please assign this issue to the person that will make the formal review on the
+revert request listed above.
+
+Please do the following so that we may track this issue to completion:
+1. Add the reviewer of revert pull request as the assignee of this issue.
+2. Add the label 'revert_review' to this issue.
+3. Close only when the review has been completed.
+
+
+DO NOT EDIT, REVERT METADATA
+<!--
+{
+ 'originalPrLink': '$originalPrLink',
+ 'revertPrLink': '$repositorySlug#$revertPrNumber',
+ 'revertPrAuthor': '$revertPrAuthor'
+}
+-->
+''';
+ }
+
+ String? get title => _constructTitle();
+ String? get body => _constructBody();
+}
diff --git a/auto_submit/lib/service/validation_service.dart b/auto_submit/lib/service/validation_service.dart
index ad7968d..486d5eb 100644
--- a/auto_submit/lib/service/validation_service.dart
+++ b/auto_submit/lib/service/validation_service.dart
@@ -9,6 +9,7 @@
import 'package:auto_submit/service/graphql_service.dart';
import 'package:auto_submit/service/log.dart';
import 'package:auto_submit/service/process_method.dart';
+import 'package:auto_submit/service/revert_review_template.dart';
import 'package:auto_submit/validations/ci_successful.dart';
import 'package:auto_submit/validations/revert.dart';
import 'package:auto_submit/validations/unknown_mergeable.dart';
@@ -30,6 +31,7 @@
ValidationService(this.config) {
/// Validates a PR marked with the reverts label.
revertValidation = Revert(config: config);
+ approverService = ApproverService(config);
validations.addAll({
/// Validates the PR has been approved following the codereview guidelines.
@@ -53,21 +55,30 @@
}
Revert? revertValidation;
+ ApproverService? approverService;
final Config config;
final Set<Validation> validations = <Validation>{};
/// Processes a pub/sub message associated with PullRequest event.
Future<void> processMessage(github.PullRequest messagePullRequest, String ackId, PubSub pubsub) async {
- ProcessMethod processMethod = await processPullRequestMethod(messagePullRequest);
+ final ProcessMethod processMethod = await processPullRequestMethod(messagePullRequest);
switch (processMethod) {
case ProcessMethod.processAutosubmit:
await processPullRequest(
- config, await getNewestPullRequestInfo(config, messagePullRequest), messagePullRequest, ackId, pubsub);
+ config: config,
+ result: await getNewestPullRequestInfo(config, messagePullRequest),
+ messagePullRequest: messagePullRequest,
+ ackId: ackId,
+ pubsub: pubsub);
break;
case ProcessMethod.processRevert:
await processRevertRequest(
- config, await getNewestPullRequestInfo(config, messagePullRequest), messagePullRequest, ackId, pubsub);
+ config: config,
+ result: await getNewestPullRequestInfo(config, messagePullRequest),
+ messagePullRequest: messagePullRequest,
+ ackId: ackId,
+ pubsub: pubsub);
break;
case ProcessMethod.doNotProcess:
log.info('Should not process ${messagePullRequest.toJson()}, and ack the message.');
@@ -81,7 +92,7 @@
final github.RepositorySlug slug = pullRequest.base!.repo!.slug();
final graphql.GraphQLClient graphQLClient = await config.createGitHubGraphQLClient(slug);
final int? prNumber = pullRequest.number;
- GraphQlService graphQlService = GraphQlService();
+ final GraphQlService graphQlService = GraphQlService();
final Map<String, dynamic> data = await graphQlService.queryGraphQL(
slug,
prNumber!,
@@ -110,16 +121,21 @@
/// Processes a PullRequest running several validations to decide whether to
/// land the commit or remove the autosubmit label.
- Future<void> processPullRequest(
- Config config, QueryResult result, github.PullRequest messagePullRequest, String ackId, PubSub pubsub) async {
+ Future<void> processPullRequest({
+ required Config config,
+ required QueryResult result,
+ required github.PullRequest messagePullRequest,
+ required String ackId,
+ required PubSub pubsub,
+ }) async {
List<ValidationResult> results = <ValidationResult>[];
/// Runs all the validation defined in the service.
for (Validation validation in validations) {
- ValidationResult validationResult = await validation.validate(result, messagePullRequest);
+ final ValidationResult validationResult = await validation.validate(result, messagePullRequest);
results.add(validationResult);
}
- github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
+ final github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
final GithubService gitHubService = await config.createGithubService(slug);
/// If there is at least one action that requires to remove label do so and add comments for all the failures.
@@ -128,40 +144,161 @@
for (ValidationResult result in results) {
if (!result.result && result.action == Action.REMOVE_LABEL) {
final String commmentMessage = result.message.isEmpty ? 'Validations Fail.' : result.message;
- await gitHubService.createComment(slug, prNumber, commmentMessage);
- await gitHubService.removeLabel(slug, prNumber, Config.kAutosubmitLabel);
- log.info('auto label is removed for ${slug.fullName}, pr: $prNumber, due to $commmentMessage');
+
+ final String message = 'auto label is removed for ${slug.fullName}, pr: $prNumber, due to $commmentMessage';
+
+ await removeLabelAndComment(
+ githubService: gitHubService,
+ repositorySlug: slug,
+ prNumber: prNumber,
+ prLabel: Config.kAutosubmitLabel,
+ message: message);
+
+ log.info(message);
+
shouldReturn = true;
}
}
+
if (shouldReturn) {
log.info('The pr ${slug.fullName}/$prNumber with message: $ackId should be acknowledged.');
await pubsub.acknowledge('auto-submit-queue-sub', ackId);
log.info('The pr ${slug.fullName}/$prNumber is not feasible for merge and message: $ackId is acknowledged.');
return;
}
+
// If PR has some failures to ignore temporarily do nothing and continue.
for (ValidationResult result in results) {
if (!result.result && result.action == Action.IGNORE_TEMPORARILY) {
return;
}
}
+
// If we got to this point it means we are ready to submit the PR.
- bool processed = await processMerge(config, result, messagePullRequest);
- if (processed) await pubsub.acknowledge('auto-submit-queue-sub', ackId);
+ final ProcessMergeResult processed =
+ await processMerge(config: config, queryResult: result, messagePullRequest: messagePullRequest);
+
+ if (!processed.result) {
+ final String message = 'auto label is removed for ${slug.fullName}, pr: $prNumber, ${processed.message}.';
+
+ await removeLabelAndComment(
+ githubService: gitHubService,
+ repositorySlug: slug,
+ prNumber: prNumber,
+ prLabel: Config.kAutosubmitLabel,
+ message: message);
+
+ log.info(message);
+ }
+
log.info('Ack the processed message : $ackId.');
+ await pubsub.acknowledge('auto-submit-queue-sub', ackId);
+ }
+
+ /// The logic for processing a revert request and opening the follow up
+ /// review issue in github.
+ Future<void> processRevertRequest({
+ required Config config,
+ required QueryResult result,
+ required github.PullRequest messagePullRequest,
+ required String ackId,
+ required PubSub pubsub,
+ }) async {
+ final ValidationResult revertValidationResult = await revertValidation!.validate(result, messagePullRequest);
+
+ final github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
+ final int prNumber = messagePullRequest.number!;
+ final GithubService gitHubService = await config.createGithubService(slug);
+
+ if (revertValidationResult.result) {
+ // Approve the pull request automatically as it has been validated.
+ await approverService!.revertApproval(result, messagePullRequest);
+
+ final ProcessMergeResult processed = await processMerge(
+ config: config,
+ queryResult: result,
+ messagePullRequest: messagePullRequest,
+ );
+
+ if (processed.result) {
+ try {
+ final RevertReviewTemplate revertReviewTemplate = RevertReviewTemplate(
+ repositorySlug: slug.fullName,
+ revertPrNumber: prNumber,
+ revertPrAuthor: result.repository!.pullRequest!.author!.login!,
+ originalPrLink: revertValidation!.extractLinkFromText(messagePullRequest.body)!);
+
+ final github.Issue issue = await gitHubService.createIssue(
+ // Created issues are created and tracked within flutter/flutter.
+ slug: github.RepositorySlug('flutter', 'flutter'),
+ title: revertReviewTemplate.title!,
+ body: revertReviewTemplate.body!,
+ labels: <String>['P1'],
+ );
+ log.info('Issue #${issue.id} was created to track the review for $prNumber in ${slug.fullName}');
+ } on github.GitHubError catch (exception) {
+ // We have merged but failed to create follow up issue.
+ final String errorMessage = '''
+An exception has occurred while attempting to create the follow up review issue for $prNumber.
+Please create a follow up issue to track a review for this pull request.
+Exception: ${exception.message}
+''';
+ log.warning(errorMessage);
+ await gitHubService.createComment(slug, prNumber, errorMessage);
+ }
+ } else {
+ final String message = 'revert label is removed for ${slug.fullName}, pr: $prNumber, ${processed.message}.';
+
+ await removeLabelAndComment(
+ githubService: gitHubService,
+ repositorySlug: slug,
+ prNumber: prNumber,
+ prLabel: Config.kRevertLabel,
+ message: message,
+ );
+
+ log.info(message);
+ }
+ } else {
+ // since we do not temporarily ignore anything with a revert request we
+ // know we will report the error and remove the label.
+ final String commentMessage =
+ revertValidationResult.message.isEmpty ? 'Validations Fail.' : revertValidationResult.message;
+
+ await removeLabelAndComment(
+ githubService: gitHubService,
+ repositorySlug: slug,
+ prNumber: prNumber,
+ prLabel: Config.kRevertLabel,
+ message: commentMessage,
+ );
+
+ log.info('revert label is removed for ${slug.fullName}, pr: $prNumber, due to $commentMessage');
+ log.info('The pr ${slug.fullName}/$prNumber is not feasible for merge and message: $ackId is acknowledged.');
+ }
+
+ log.info('Ack the processed message : $ackId.');
+ await pubsub.acknowledge('auto-submit-queue-sub', ackId);
}
/// Merges the commit if the PullRequest passes all the validations.
- Future<bool> processMerge(Config config, QueryResult queryResult, github.PullRequest messagePullRequest) async {
- String id = queryResult.repository!.pullRequest!.id!;
- github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
+ Future<ProcessMergeResult> processMerge({
+ required Config config,
+ required QueryResult queryResult,
+ required github.PullRequest messagePullRequest,
+ }) async {
+ final String id = queryResult.repository!.pullRequest!.id!;
+ final github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
final PullRequest pullRequest = queryResult.repository!.pullRequest!;
- Commit commit = pullRequest.commits!.nodes!.single.commit!;
+ final Commit commit = pullRequest.commits!.nodes!.single.commit!;
final String? sha = commit.oid;
- int number = messagePullRequest.number!;
- final graphql.GraphQLClient client = await config.createGitHubGraphQLClient(slug);
+ final int number = messagePullRequest.number!;
+
try {
+ // The createGitHubGraphQLClient can throw Exception on github permissions
+ // errors.
+ final graphql.GraphQLClient client = await config.createGitHubGraphQLClient(slug);
+
final graphql.QueryResult result = await client.mutate(graphql.MutationOptions(
document: mergePullRequestMutation,
variables: <String, dynamic>{
@@ -171,60 +308,36 @@
},
));
if (result.hasException) {
- log.severe('Failed to merge pr#: $number with ${result.exception.toString()}');
- return false;
+ final String message = 'Failed to merge pr#: $number with ${result.exception}';
+ log.severe(message);
+ return ProcessMergeResult(false, message);
}
} catch (e) {
- log.severe('_processMerge error in $slug: $e');
- return false;
+ final String message = 'Failed to merge pr#: $number with $e';
+ log.severe(message);
+ return ProcessMergeResult(false, message);
}
- return true;
+ return ProcessMergeResult.noMessage(true);
}
- /// The logic for processing a revert request and opening the follow up
- /// review issue in github.
- Future<void> processRevertRequest(
- Config config,
- QueryResult result,
- github.PullRequest messagePullRequest,
- String ackId,
- PubSub pubsub,
- ) async {
- ValidationResult revertValidationResult = await revertValidation!.validate(result, messagePullRequest);
-
- github.RepositorySlug slug = messagePullRequest.base!.repo!.slug();
- final int prNumber = messagePullRequest.number!;
- final GithubService githubService = await config.createGithubService(slug);
-
- if (revertValidationResult.result) {
- // Approve the pull request automatically as it has been validated.
- ApproverService approverService = ApproverService(config);
- await approverService.revertApproval(result, messagePullRequest);
-
- bool processed = await processMerge(config, result, messagePullRequest);
- if (processed) {
- await pubsub.acknowledge('auto-submit-queue-sub', ackId);
- log.info('Ack the processed message : $ackId.');
- github.Issue issue = await githubService.createIssue(
- github.RepositorySlug('flutter', 'flutter'),
- 'Follow up review for revert pull request $prNumber',
- 'Revert request by author ${result.repository!.pullRequest!.author}',
- );
- log.info('Issue #${issue.id} was created to track the review for $prNumber in ${slug.fullName}');
- } else {
- log.warning('Could not process pull merge request.');
- }
- } else {
- // since we do not temporarily ignore anything with a revert request we
- // know we will report the error and remove the label.
- final String commmentMessage =
- revertValidationResult.message.isEmpty ? 'Validations Fail.' : revertValidationResult.message;
- await githubService.createComment(slug, prNumber, commmentMessage);
- await githubService.removeLabel(slug, prNumber, Config.kRevertLabel);
- log.info('revert label is removed for ${slug.fullName}, pr: $prNumber, due to $commmentMessage');
- log.info('The pr ${slug.fullName}/$prNumber with message: $ackId should be acknowledged.');
- await pubsub.acknowledge('auto-submit-queue-sub', ackId);
- log.info('The pr ${slug.fullName}/$prNumber is not feasible for merge and message: $ackId is acknowledged.');
- }
+ /// Remove a pull request label and add a comment to the pull request.
+ Future<void> removeLabelAndComment(
+ {required GithubService githubService,
+ required github.RepositorySlug repositorySlug,
+ required int prNumber,
+ required String prLabel,
+ required String message}) async {
+ await githubService.removeLabel(repositorySlug, prNumber, prLabel);
+ await githubService.createComment(repositorySlug, prNumber, message);
}
}
+
+/// Small wrapper class to allow us to capture and create a comment in the PR with
+/// the issue that caused the merge failure.
+class ProcessMergeResult {
+ ProcessMergeResult.noMessage(this.result);
+ ProcessMergeResult(this.result, this.message);
+
+ bool result = false;
+ String? message;
+}
diff --git a/auto_submit/test/requests/check_pull_request_test.dart b/auto_submit/test/requests/check_pull_request_test.dart
index 1ff9815..863ecb9 100644
--- a/auto_submit/test/requests/check_pull_request_test.dart
+++ b/auto_submit/test/requests/check_pull_request_test.dart
@@ -167,11 +167,13 @@
};
final List<LogRecord> records = <LogRecord>[];
log.onRecord.listen((LogRecord record) => records.add(record));
+ // this is the test.
await checkPullRequest.get();
- expect(pubsub.messagesQueue.length, 1);
+ // every failure is now acknowledged from the queue.
+ expect(pubsub.messagesQueue.length, 0);
final List<LogRecord> errorLogs = records.where((LogRecord record) => record.level == Level.SEVERE).toList();
expect(errorLogs.length, 1);
- expect(errorLogs[0].message.contains('_processMerge'), true);
+ expect(errorLogs[0].message.contains('Failed to merge'), true);
pubsub.messagesQueue.clear();
});
diff --git a/auto_submit/test/requests/github_webhook_test_data.dart b/auto_submit/test/requests/github_webhook_test_data.dart
index 54fea84..062575d 100644
--- a/auto_submit/test/requests/github_webhook_test_data.dart
+++ b/auto_submit/test/requests/github_webhook_test_data.dart
@@ -70,15 +70,17 @@
}''';
}
-PullRequest generatePullRequest(
- {String? labelName,
- String? autosubmitLabel = Config.kAutosubmitLabel,
- String? repoName,
- String? login,
- String? authorAssociation,
- String? author,
- int? prNumber,
- String? state}) {
+PullRequest generatePullRequest({
+ String? labelName,
+ String? autosubmitLabel = Config.kAutosubmitLabel,
+ String? repoName,
+ String? login,
+ String? authorAssociation,
+ String? author,
+ int? prNumber,
+ String? state,
+ String? body,
+}) {
return PullRequest.fromJson(json.decode('''{
"id": 1,
"number": ${prNumber ?? 1347},
@@ -88,7 +90,7 @@
"login": "${author ?? "octocat"}",
"id": 1
},
- "body": "Please pull these awesome changes in!",
+ "body": "${body ?? "Please pull these awesome changes in!"}",
"labels": [
{
"id": 487496476,
diff --git a/auto_submit/test/service/approver_service_test.dart b/auto_submit/test/service/approver_service_test.dart
index b6c16bf..f44a6f7 100644
--- a/auto_submit/test/service/approver_service_test.dart
+++ b/auto_submit/test/service/approver_service_test.dart
@@ -29,14 +29,14 @@
});
test('Verify approval ignored', () async {
- gh.PullRequest pr = generatePullRequest(author: 'not_a_user');
+ final gh.PullRequest pr = generatePullRequest(author: 'not_a_user');
await service.autoApproval(pr);
verifyNever(pullRequests.createReview(any, captureAny));
});
test('Verify approve', () async {
when(pullRequests.listReviews(any, any)).thenAnswer((_) => const Stream<gh.PullRequestReview>.empty());
- gh.PullRequest pr = generatePullRequest(author: 'dependabot[bot]');
+ final gh.PullRequest pr = generatePullRequest(author: 'dependabot[bot]');
await service.autoApproval(pr);
final List<dynamic> reviews = verify(pullRequests.createReview(any, captureAny)).captured;
expect(reviews.length, 1);
@@ -45,18 +45,18 @@
});
test('Already approved', () async {
- gh.PullRequestReview review =
+ final gh.PullRequestReview review =
gh.PullRequestReview(id: 123, user: gh.User(login: 'fluttergithubbot'), state: 'APPROVED');
when(pullRequests.listReviews(any, any)).thenAnswer((_) => Stream<gh.PullRequestReview>.value(review));
- gh.PullRequest pr = generatePullRequest(author: 'dependabot[bot]');
+ final gh.PullRequest pr = generatePullRequest(author: 'dependabot[bot]');
await service.autoApproval(pr);
verifyNever(pullRequests.createReview(any, captureAny));
});
test('AutoApproval does not approve revert pull request.', () async {
- gh.PullRequest pr = generatePullRequest(author: 'not_a_user');
- List<gh.IssueLabel> issueLabels = pr.labels ?? [];
- gh.IssueLabel issueLabel = gh.IssueLabel(name: 'revert');
+ final gh.PullRequest pr = generatePullRequest(author: 'not_a_user');
+ final List<gh.IssueLabel> issueLabels = pr.labels ?? [];
+ final gh.IssueLabel issueLabel = gh.IssueLabel(name: 'revert');
issueLabels.add(issueLabel);
await service.autoApproval(pr);
verifyNever(pullRequests.createReview(any, captureAny));
@@ -64,18 +64,18 @@
test('Revert request is auto approved.', () async {
when(pullRequests.listReviews(any, any)).thenAnswer((_) => const Stream<gh.PullRequestReview>.empty());
- gh.PullRequest pr = generatePullRequest(author: 'dependabot[bot]');
+ final gh.PullRequest pr = generatePullRequest(author: 'dependabot[bot]');
- List<gh.IssueLabel> issueLabels = pr.labels ?? [];
- gh.IssueLabel issueLabel = gh.IssueLabel(name: 'revert');
+ final List<gh.IssueLabel> issueLabels = pr.labels ?? [];
+ final gh.IssueLabel issueLabel = gh.IssueLabel(name: 'revert');
issueLabels.add(issueLabel);
- PullRequestHelper flutterRequest = PullRequestHelper(
+ final PullRequestHelper flutterRequest = PullRequestHelper(
prNumber: 0,
lastCommitHash: oid,
reviews: <PullRequestReviewHelper>[],
);
- QueryResult queryResult = createQueryResult(flutterRequest);
+ final QueryResult queryResult = createQueryResult(flutterRequest);
await service.revertApproval(queryResult, pr);
final List<dynamic> reviews = verify(pullRequests.createReview(any, captureAny)).captured;
@@ -85,28 +85,28 @@
});
test('Revert request is not auto approved when the revert label is not present.', () async {
- gh.PullRequest pr = generatePullRequest(author: 'not_a_user');
+ final gh.PullRequest pr = generatePullRequest(author: 'not_a_user');
- PullRequestHelper flutterRequest = PullRequestHelper(
+ final PullRequestHelper flutterRequest = PullRequestHelper(
prNumber: 0,
lastCommitHash: oid,
reviews: <PullRequestReviewHelper>[],
);
- QueryResult queryResult = createQueryResult(flutterRequest);
+ final QueryResult queryResult = createQueryResult(flutterRequest);
await service.revertApproval(queryResult, pr);
verifyNever(pullRequests.createReview(any, captureAny));
});
test('Revert request is not auto approved on bad author association.', () async {
- gh.PullRequest pr = generatePullRequest(author: 'not_a_user', authorAssociation: 'CONTRIBUTOR');
+ final gh.PullRequest pr = generatePullRequest(author: 'not_a_user', authorAssociation: 'CONTRIBUTOR');
- PullRequestHelper flutterRequest = PullRequestHelper(
+ final PullRequestHelper flutterRequest = PullRequestHelper(
prNumber: 0,
lastCommitHash: oid,
reviews: <PullRequestReviewHelper>[],
);
- QueryResult queryResult = createQueryResult(flutterRequest);
+ final QueryResult queryResult = createQueryResult(flutterRequest);
await service.revertApproval(queryResult, pr);
verifyNever(pullRequests.createReview(any, captureAny));
diff --git a/auto_submit/test/service/validation_service_test.dart b/auto_submit/test/service/validation_service_test.dart
index 9c04cf7..3bee455 100644
--- a/auto_submit/test/service/validation_service_test.dart
+++ b/auto_submit/test/service/validation_service_test.dart
@@ -2,17 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:auto_submit/model/auto_submit_query_result.dart' hide PullRequest;
+import 'package:auto_submit/model/auto_submit_query_result.dart' as auto hide PullRequest;
import 'package:auto_submit/service/process_method.dart';
import 'package:auto_submit/service/validation_service.dart';
+import 'package:auto_submit/validations/validation.dart';
import 'package:github/github.dart';
+import 'package:graphql/client.dart';
import 'package:test/test.dart';
import '../requests/github_webhook_test_data.dart';
import '../src/request_handling/fake_pubsub.dart';
+import '../src/service/fake_approver_service.dart';
import '../src/service/fake_config.dart';
import '../src/service/fake_graphql_client.dart';
import '../src/service/fake_github_service.dart';
+import '../src/validations/fake_revert.dart';
import '../utilities/utils.dart';
import '../utilities/mocks.dart';
@@ -31,8 +35,8 @@
slug = RepositorySlug('flutter', 'cocoon');
});
- test('removes label and post comment when no approval', () async {
- PullRequestHelper flutterRequest = PullRequestHelper(
+ test('Removes label and post comment when no approval', () async {
+ final PullRequestHelper flutterRequest = PullRequestHelper(
prNumber: 0,
lastCommitHash: oid,
reviews: <PullRequestReviewHelper>[],
@@ -42,17 +46,194 @@
final FakePubSub pubsub = FakePubSub();
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
pubsub.publish('auto-submit-queue-sub', pullRequest);
- QueryResult queryResult = createQueryResult(flutterRequest);
+ final auto.QueryResult queryResult = createQueryResult(flutterRequest);
- await validationService.processPullRequest(config, queryResult, pullRequest, 'test', pubsub);
+ await validationService.processPullRequest(
+ config: config, result: queryResult, messagePullRequest: pullRequest, ackId: 'test', pubsub: pubsub);
expect(githubService.issueComment, isNotNull);
expect(githubService.labelRemoved, true);
assert(pubsub.messagesQueue.isEmpty);
});
- group('shouldProcess pull request', () {
- test('should process message when autosubmit label exists and pr is open', () async {
+ test('Remove label and post comment when no revert label.', () async {
+ final PullRequestHelper flutterRequest = PullRequestHelper(
+ prNumber: 0,
+ lastCommitHash: oid,
+ reviews: <PullRequestReviewHelper>[],
+ );
+ githubService.checkRunsData = checkRunsMock;
+ githubService.createCommentData = createCommentMock;
+ final FakePubSub pubsub = FakePubSub();
+ final PullRequest pullRequest = generatePullRequest(
+ prNumber: 0,
+ repoName: slug.name,
+ );
+ pubsub.publish('auto-submit-queue-sub', pullRequest);
+ final auto.QueryResult queryResult = createQueryResult(flutterRequest);
+
+ await validationService.processRevertRequest(
+ config: config, result: queryResult, messagePullRequest: pullRequest, ackId: 'test', pubsub: pubsub);
+
+ expect(githubService.issueComment, isNotNull);
+ expect(githubService.labelRemoved, true);
+ assert(pubsub.messagesQueue.isEmpty);
+ });
+
+ group('Processing revert reqeuests.', () {
+ test('Merge valid revert request, issue created and message is acknowledged.', () async {
+ githubGraphQLClient.mutateResultForOptions = (MutationOptions options) => createFakeQueryResult();
+ final PullRequestHelper flutterRequest = PullRequestHelper(
+ prNumber: 0,
+ lastCommitHash: oid,
+ reviews: <PullRequestReviewHelper>[],
+ );
+
+ githubService.checkRunsData = checkRunsMock;
+ githubService.createCommentData = createCommentMock;
+ final FakePubSub pubsub = FakePubSub();
+ final PullRequest pullRequest = generatePullRequest(
+ prNumber: 0,
+ repoName: slug.name,
+ authorAssociation: 'OWNER',
+ labelName: 'revert',
+ body: 'Reverts flutter/flutter#1234');
+
+ final FakeRevert fakeRevert = FakeRevert(config: config);
+ fakeRevert.validationResult = ValidationResult(true, Action.REMOVE_LABEL, '');
+ validationService.revertValidation = fakeRevert;
+ final FakeApproverService fakeApproverService = FakeApproverService(config);
+ validationService.approverService = fakeApproverService;
+
+ final Issue issue = Issue(
+ id: 1234,
+ );
+ githubService.githubIssueMock = issue;
+
+ pubsub.publish('auto-submit-queue-sub', pullRequest);
+ final auto.QueryResult queryResult = createQueryResult(flutterRequest);
+
+ await validationService.processRevertRequest(
+ config: config, result: queryResult, messagePullRequest: pullRequest, ackId: 'test', pubsub: pubsub);
+
+ // if the merge is successful we do not remove the label and we do not add a comment to the issue.
+ expect(githubService.issueComment, isNull);
+ expect(githubService.labelRemoved, false);
+ // We acknowledge the issue.
+ assert(pubsub.messagesQueue.isEmpty);
+ });
+
+ test('Fail to merge non valid revert, issue not created, comment is added and message is acknowledged.', () async {
+ githubGraphQLClient.mutateResultForOptions = (MutationOptions options) => createFakeQueryResult();
+ final PullRequestHelper flutterRequest = PullRequestHelper(
+ prNumber: 0,
+ lastCommitHash: oid,
+ reviews: <PullRequestReviewHelper>[],
+ );
+
+ githubService.checkRunsData = checkRunsMock;
+ githubService.createCommentData = createCommentMock;
+ final FakePubSub pubsub = FakePubSub();
+ final PullRequest pullRequest = generatePullRequest(
+ prNumber: 0,
+ repoName: slug.name,
+ authorAssociation: 'OWNER',
+ labelName: 'revert',
+ body: 'Reverts flutter/flutter#1234',
+ );
+
+ final FakeRevert fakeRevert = FakeRevert(config: config);
+ fakeRevert.validationResult = ValidationResult(false, Action.REMOVE_LABEL, '');
+ validationService.revertValidation = fakeRevert;
+ final FakeApproverService fakeApproverService = FakeApproverService(config);
+ validationService.approverService = fakeApproverService;
+
+ pubsub.publish('auto-submit-queue-sub', pullRequest);
+ final auto.QueryResult queryResult = createQueryResult(flutterRequest);
+
+ await validationService.processRevertRequest(
+ config: config, result: queryResult, messagePullRequest: pullRequest, ackId: 'test', pubsub: pubsub);
+
+ // if the merge is successful we do not remove the label and we do not add a comment to the issue.
+ expect(githubService.issueComment, isNotNull);
+ expect(githubService.labelRemoved, true);
+ // We acknowledge the issue.
+ assert(pubsub.messagesQueue.isEmpty);
+ });
+
+ test('Remove label and post comment when unable to process merge.', () async {
+ final PullRequestHelper flutterRequest = PullRequestHelper(
+ prNumber: 0,
+ lastCommitHash: oid,
+ reviews: <PullRequestReviewHelper>[],
+ );
+ githubService.checkRunsData = checkRunsMock;
+ githubService.createCommentData = createCommentMock;
+ final FakePubSub pubsub = FakePubSub();
+ final PullRequest pullRequest =
+ generatePullRequest(prNumber: 0, repoName: slug.name, authorAssociation: 'OWNER', labelName: 'revert');
+
+ final FakeRevert fakeRevert = FakeRevert(config: config);
+ fakeRevert.validationResult = ValidationResult(true, Action.REMOVE_LABEL, '');
+ validationService.revertValidation = fakeRevert;
+ final FakeApproverService fakeApproverService = FakeApproverService(config);
+ validationService.approverService = fakeApproverService;
+
+ pubsub.publish('auto-submit-queue-sub', pullRequest);
+ final auto.QueryResult queryResult = createQueryResult(flutterRequest);
+
+ await validationService.processRevertRequest(
+ config: config, result: queryResult, messagePullRequest: pullRequest, ackId: 'test', pubsub: pubsub);
+
+ expect(githubService.issueComment, isNotNull);
+ expect(githubService.labelRemoved, true);
+ assert(pubsub.messagesQueue.isEmpty);
+ });
+
+ test('Fail to create follow up review issue, comment is added and message is acknowledged.', () async {
+ githubGraphQLClient.mutateResultForOptions = (MutationOptions options) => createFakeQueryResult();
+ final PullRequestHelper flutterRequest = PullRequestHelper(
+ prNumber: 0,
+ lastCommitHash: oid,
+ reviews: <PullRequestReviewHelper>[],
+ );
+
+ githubService.checkRunsData = checkRunsMock;
+ githubService.createCommentData = createCommentMock;
+ githubService.throwOnCreateIssue = true;
+ githubService.useRealComment = true;
+ final FakePubSub pubsub = FakePubSub();
+ final PullRequest pullRequest = generatePullRequest(
+ prNumber: 0,
+ repoName: slug.name,
+ authorAssociation: 'OWNER',
+ labelName: 'revert',
+ body: 'Reverts flutter/flutter#1234');
+
+ final FakeRevert fakeRevert = FakeRevert(config: config);
+ fakeRevert.validationResult = ValidationResult(true, Action.REMOVE_LABEL, '');
+ validationService.revertValidation = fakeRevert;
+ final FakeApproverService fakeApproverService = FakeApproverService(config);
+ validationService.approverService = fakeApproverService;
+
+ pubsub.publish('auto-submit-queue-sub', pullRequest);
+ final auto.QueryResult queryResult = createQueryResult(flutterRequest);
+
+ await validationService.processRevertRequest(
+ config: config, result: queryResult, messagePullRequest: pullRequest, ackId: 'test', pubsub: pubsub);
+
+ // if the merge is successful we do not remove the label and we do not add a comment to the issue.
+ expect(githubService.issueComment, isNotNull);
+ final IssueComment issueComment = githubService.issueComment!;
+ assert(issueComment.body!.contains('create the follow up review issue'));
+ expect(githubService.labelRemoved, false);
+ // We acknowledge the issue.
+ assert(pubsub.messagesQueue.isEmpty);
+ });
+ });
+
+ group('Process pull request', () {
+ test('Should process message when autosubmit label exists and pr is open', () async {
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
githubService.pullRequestData = pullRequest;
final ProcessMethod processMethod = await validationService.processPullRequestMethod(pullRequest);
@@ -60,7 +241,7 @@
expect(processMethod, ProcessMethod.processAutosubmit);
});
- test('skip processing message when autosubmit label does not exist anymore', () async {
+ test('Skip processing message when autosubmit label does not exist anymore', () async {
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
pullRequest.labels = <IssueLabel>[];
githubService.pullRequestData = pullRequest;
@@ -69,7 +250,7 @@
expect(processMethod, ProcessMethod.doNotProcess);
});
- test('skip processing message when the pull request is closed', () async {
+ test('Skip processing message when the pull request is closed', () async {
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
pullRequest.state = 'closed';
githubService.pullRequestData = pullRequest;
@@ -78,9 +259,9 @@
expect(processMethod, ProcessMethod.doNotProcess);
});
- test('should process message when revert label exists and pr is open', () async {
+ test('Should process message when revert label exists and pr is open', () async {
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
- IssueLabel issueLabel = IssueLabel(name: 'revert');
+ final IssueLabel issueLabel = IssueLabel(name: 'revert');
pullRequest.labels = <IssueLabel>[issueLabel];
githubService.pullRequestData = pullRequest;
final ProcessMethod processMethod = await validationService.processPullRequestMethod(pullRequest);
@@ -88,9 +269,9 @@
expect(processMethod, ProcessMethod.processRevert);
});
- test('should process message as revert when revert and autosubmit labels are present and pr is open', () async {
+ test('Should process message as revert when revert and autosubmit labels are present and pr is open', () async {
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
- IssueLabel issueLabel = IssueLabel(name: 'revert');
+ final IssueLabel issueLabel = IssueLabel(name: 'revert');
pullRequest.labels!.add(issueLabel);
githubService.pullRequestData = pullRequest;
final ProcessMethod processMethod = await validationService.processPullRequestMethod(pullRequest);
@@ -98,10 +279,10 @@
expect(processMethod, ProcessMethod.processRevert);
});
- test('skip processing message when revert label exists and pr is closed', () async {
+ test('Skip processing message when revert label exists and pr is closed', () async {
final PullRequest pullRequest = generatePullRequest(prNumber: 0, repoName: slug.name);
pullRequest.state = 'closed';
- IssueLabel issueLabel = IssueLabel(name: 'revert');
+ final IssueLabel issueLabel = IssueLabel(name: 'revert');
pullRequest.labels = <IssueLabel>[issueLabel];
githubService.pullRequestData = pullRequest;
final ProcessMethod processMethod = await validationService.processPullRequestMethod(pullRequest);
diff --git a/auto_submit/test/src/service/fake_approver_service.dart b/auto_submit/test/src/service/fake_approver_service.dart
new file mode 100644
index 0000000..fb25486
--- /dev/null
+++ b/auto_submit/test/src/service/fake_approver_service.dart
@@ -0,0 +1,21 @@
+// 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 'package:auto_submit/model/auto_submit_query_result.dart';
+import 'package:auto_submit/service/approver_service.dart';
+import 'package:github/github.dart' as gh;
+
+class FakeApproverService extends ApproverService {
+ FakeApproverService(super.config);
+
+ @override
+ Future<void> autoApproval(gh.PullRequest pullRequest) async {
+ // no op
+ }
+
+ @override
+ Future<void> revertApproval(QueryResult queryResult, gh.PullRequest pullRequest) async {
+ // no op
+ }
+}
diff --git a/auto_submit/test/src/service/fake_github_service.dart b/auto_submit/test/src/service/fake_github_service.dart
index 4dafb32..05ba02d 100644
--- a/auto_submit/test/src/service/fake_github_service.dart
+++ b/auto_submit/test/src/service/fake_github_service.dart
@@ -36,6 +36,8 @@
String? pullRequestFilesJsonMock;
Issue? githubIssueMock;
+ 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.
@@ -46,6 +48,7 @@
List<PullRequest?> pullRequestMockList = [];
IssueComment? issueComment;
+ bool useRealComment = false;
bool labelRemoved = false;
bool compareReturnValue = false;
@@ -134,7 +137,11 @@
@override
Future<IssueComment> createComment(RepositorySlug slug, int number, String commentBody) async {
- issueComment = IssueComment.fromJson(jsonDecode(createCommentMock!) as Map<String, dynamic>);
+ if (useRealComment) {
+ issueComment = IssueComment(id: number, body: commentBody);
+ } else {
+ issueComment = IssueComment.fromJson(jsonDecode(createCommentMock!) as Map<String, dynamic>);
+ }
return issueComment!;
}
@@ -174,7 +181,18 @@
}
@override
- Future<Issue> createIssue(RepositorySlug repositorySlug, String title, String body) async {
+ 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!;
}
diff --git a/auto_submit/test/src/validations/fake_revert.dart b/auto_submit/test/src/validations/fake_revert.dart
new file mode 100644
index 0000000..63c84e3
--- /dev/null
+++ b/auto_submit/test/src/validations/fake_revert.dart
@@ -0,0 +1,20 @@
+// 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 'package:auto_submit/model/auto_submit_query_result.dart' hide PullRequest;
+import 'package:auto_submit/validations/revert.dart';
+import 'package:auto_submit/validations/validation.dart';
+
+import 'package:github/github.dart';
+
+class FakeRevert extends Revert {
+ FakeRevert({required super.config});
+
+ ValidationResult? validationResult;
+
+ @override
+ Future<ValidationResult> validate(QueryResult result, PullRequest messagePullRequest) async {
+ return validationResult ?? ValidationResult(false, Action.IGNORE_TEMPORARILY, '');
+ }
+}