blob: 31e07eef6105865dac98d8961e6eba00f7101261 [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/model/auto_submit_query_result.dart';
import 'package:auto_submit/validations/validation.dart';
import 'package:github/github.dart' as github;
import 'package:retry/retry.dart';
import 'package:test/test.dart';
import 'revert_test_data.dart';
import 'package:auto_submit/validations/revert.dart';
import '../utilities/mocks.dart';
import '../src/service/fake_config.dart';
import '../src/service/fake_github_service.dart';
import '../src/service/fake_graphql_client.dart';
void main() {
late FakeConfig config;
late FakeGithubService githubService;
late FakeGraphQLClient githubGraphQLClient;
final MockGitHub gitHub = MockGitHub();
late Revert revert;
/// Setup objects needed across test groups.
setUp(() {
githubService = FakeGithubService();
githubGraphQLClient = FakeGraphQLClient();
config = FakeConfig(githubService: githubService, githubGraphQLClient: githubGraphQLClient, githubClient: gitHub);
revert = Revert(
config: config,
retryOptions: const RetryOptions(delayFactor: Duration.zero, maxDelay: Duration.zero, maxAttempts: 1),
);
});
group('Author validation tests.', () {
test('Validate author association member is valid.', () {
const String authorAssociation = 'MEMBER';
assert(revert.isValidAuthor('octocat', authorAssociation));
});
test('Validate author association owner is valid.', () {
const String authorAssociation = 'OWNER';
assert(revert.isValidAuthor('octocat', authorAssociation));
});
test('Validate author dependabot is valid.', () {
const String author = 'dependabot';
const String authorAssociation = 'NON_MEMBER';
assert(revert.isValidAuthor(author, authorAssociation));
});
test('Validate autoroller account is valid.', () {
const String author = 'engine-flutter-autoroll';
const String authorAssociation = 'CONTRIBUTOR';
assert(revert.isValidAuthor(author, authorAssociation));
});
});
group('Pattern matching for revert text link', () {
test('Link extraction from description is successful.', () {
// input, expected
final Map<String, String> tests = <String, String>{};
tests['Reverts flutter/cocoon#123456'] = 'flutter/cocoon#123456';
tests['Reverts flutter/cocoon#123456'] = 'flutter/cocoon#123456';
tests['Reverts flutter/flutter-intellij#123456'] = 'flutter/flutter-intellij#123456';
tests['Reverts flutter/platform_tests#123456'] = 'flutter/platform_tests#123456';
tests['Reverts flutter/.github#123456'] = 'flutter/.github#123456';
tests['Reverts flutter/assets-for-api-docs#123456'] = 'flutter/assets-for-api-docs#123456';
tests['Reverts flutter/flutter.github.io#123456'] = 'flutter/flutter.github.io#123456';
tests['Reverts flutter/flutter_gallery_assets#123456'] = 'flutter/flutter_gallery_assets#123456';
tests['reverts flutter/cocoon#12323'] = 'flutter/cocoon#12323';
tests['reverts flutter/cocoon#223'] = 'flutter/cocoon#223';
tests["""Reverts flutter/cocoon#123456
Some other notes in the description a developer might add.
And another note."""] = 'flutter/cocoon#123456';
tests['Pull request Reverts flutter/flutter#12334 is happening continuously.'] = 'flutter/flutter#12334';
tests['Reverts reverts flutter/flutter#9876'] = 'flutter/flutter#9876';
tests["""Some junk reverts flutter/cocoon#4563 is happening continuously.
Some other tests in the description that someone might add.
"""] = 'flutter/cocoon#4563';
tests['This some text to add flavor before reverts flutter/flutter#8888.'] = 'flutter/flutter#8888';
tests.forEach((key, value) {
final String? linkFound = revert.extractLinkFromText(key);
assert(linkFound != null);
assert(linkFound == value);
});
});
test('Link extraction from description returns null', () {
final Map<String, String> tests = <String, String>{};
tests['Revert flutter/cocoon#123456'] = '';
tests['revert flutter/cocoon#123456'] = '';
tests['Reverts flutter/cocoon#'] = '';
tests['Reverts flutter123'] = '';
// We should not allow processing of more than one link as this can be cause
// suspicion of other non revert changes in the pull request.
tests["""Reverts flutter/flutter#12345
Reverts flutter/flutter#34543"""] = '';
tests["""This some text to add flavor before reverts flutter/flutter#8888.
Also please reverts flutter/flutter#7678.
And reverts flutter/flutter#8763.
"""] = '';
tests['This is some text flutter/flutter#456...44'] = '';
tests.forEach((key, value) {
final String? linkFound = revert.extractLinkFromText(key);
assert(linkFound == null);
});
});
});
group('Validate Pull Requests.', () {
test('Validation fails on author validation, returns error.', () async {
final Map<String, dynamic> pullRequestJsonMap = jsonDecode(revertPullRequestJson) as Map<String, dynamic>;
final github.PullRequest revertPullRequest = github.PullRequest.fromJson(pullRequestJsonMap);
revertPullRequest.authorAssociation = 'CONTRIBUTOR';
final Map<String, dynamic> queryResultJsonDecode =
jsonDecode(queryResultRepositoryContributorJson) as Map<String, dynamic>;
final QueryResult queryResult = QueryResult.fromJson(queryResultJsonDecode);
final ValidationResult validationResult = await revert.validate(queryResult, revertPullRequest);
assert(!validationResult.result);
assert(validationResult.action == Action.REMOVE_LABEL);
assert(validationResult.message.contains(RegExp(r'The author.*does not have permissions to make this request.')));
});
test('Validation fails on merge conflict flag.', () async {
final Map<String, dynamic> pullRequestJsonMap = jsonDecode(revertPullRequestJson) as Map<String, dynamic>;
final github.PullRequest revertPullRequest = github.PullRequest.fromJson(pullRequestJsonMap);
revertPullRequest.mergeable = false;
final Map<String, dynamic> queryResultJsonDecode =
jsonDecode(queryResultRepositoryOwnerJson) as Map<String, dynamic>;
final QueryResult queryResult = QueryResult.fromJson(queryResultJsonDecode);
final ValidationResult validationResult = await revert.validate(queryResult, revertPullRequest);
assert(!validationResult.result);
assert(validationResult.action == Action.REMOVE_LABEL);
assert(
validationResult.message ==
'This pull request cannot be merged due to conflicts. Please resolve conflicts and re-add the revert label.',
);
});
test('Validation fails on malformed reverts link in the pr body.', () async {
final Map<String, dynamic> pullRequestJsonMap = jsonDecode(revertPullRequestJson) as Map<String, dynamic>;
final github.PullRequest revertPullRequest = github.PullRequest.fromJson(pullRequestJsonMap);
revertPullRequest.body = 'Reverting flutter/cocoon#1234';
final Map<String, dynamic> queryResultJsonDecode =
jsonDecode(queryResultRepositoryOwnerJson) as Map<String, dynamic>;
final QueryResult queryResult = QueryResult.fromJson(queryResultJsonDecode);
final ValidationResult validationResult = await revert.validate(queryResult, revertPullRequest);
assert(!validationResult.result);
assert(validationResult.action == Action.REMOVE_LABEL);
assert(
validationResult.message ==
'A reverts link could not be found or was formatted incorrectly. Format is \'Reverts owner/repo#id\'',
);
});
test('Validation returns on checkRun that has not completed.', () async {
final Map<String, dynamic> pullRequestJsonMap = jsonDecode(revertPullRequestJson) as Map<String, dynamic>;
final github.PullRequest revertPullRequest = github.PullRequest.fromJson(pullRequestJsonMap);
final Map<String, dynamic> queryResultJsonDecode =
jsonDecode(queryResultRepositoryOwnerJson) as Map<String, dynamic>;
final QueryResult queryResult = QueryResult.fromJson(queryResultJsonDecode);
final Map<String, dynamic> originalPullRequestJsonMap =
jsonDecode(originalPullRequestJson) as Map<String, dynamic>;
final github.PullRequest originalPullRequest = github.PullRequest.fromJson(originalPullRequestJsonMap);
githubService.pullRequestData = originalPullRequest;
// code gets the original file list then the current file list.
githubService.usePullRequestFilesList = true;
githubService.pullRequestFilesMockList.add(originalPullRequestFilesJson);
githubService.pullRequestFilesMockList.add(revertPullRequestFilesJson);
// Need to set the mock checkRuns for required CheckRun validation
githubService.checkRunsData = ciyamlCheckRunNotComplete;
revert = Revert(
config: config,
retryOptions: const RetryOptions(
delayFactor: Duration.zero,
maxDelay: Duration.zero,
maxAttempts: 1,
),
);
final ValidationResult validationResult = await revert.validate(queryResult, revertPullRequest);
expect(validationResult.result, isFalse);
expect(validationResult.action, Action.IGNORE_TEMPORARILY);
expect(validationResult.message, 'Some of the required checks did not complete in time.');
});
test('Validation fails on pull request file lists not matching.', () async {
final Map<String, dynamic> pullRequestJsonMap = jsonDecode(revertPullRequestJson) as Map<String, dynamic>;
final github.PullRequest revertPullRequest = github.PullRequest.fromJson(pullRequestJsonMap);
final Map<String, dynamic> queryResultJsonDecode =
jsonDecode(queryResultRepositoryOwnerJson) as Map<String, dynamic>;
final QueryResult queryResult = QueryResult.fromJson(queryResultJsonDecode);
final Map<String, dynamic> originalPullRequestJsonMap =
jsonDecode(originalPullRequestJson) as Map<String, dynamic>;
final github.PullRequest originalPullRequest = github.PullRequest.fromJson(originalPullRequestJsonMap);
githubService.pullRequestData = originalPullRequest;
// code gets the original file list then the current file list.
githubService.usePullRequestFilesList = true;
githubService.pullRequestFilesMockList.add(originalPullRequestFilesSubsetJson);
githubService.pullRequestFilesMockList.add(revertPullRequestFilesJson);
// Need to set the mock checkRuns for required CheckRun validation
githubService.checkRunsData = ciyamlCheckRun;
final ValidationResult validationResult = await revert.validate(queryResult, revertPullRequest);
assert(!validationResult.result);
assert(validationResult.action == Action.REMOVE_LABEL);
assert(
validationResult.message ==
'Validation of the revert request has failed. Verify the files in the revert request are the same as the original PR and resubmit the revert request.',
);
});
test('Validation is successful.', () async {
final Map<String, dynamic> pullRequestJsonMap = jsonDecode(revertPullRequestJson) as Map<String, dynamic>;
final github.PullRequest revertPullRequest = github.PullRequest.fromJson(pullRequestJsonMap);
final Map<String, dynamic> queryResultJsonDecode =
jsonDecode(queryResultRepositoryOwnerJson) as Map<String, dynamic>;
final QueryResult queryResult = QueryResult.fromJson(queryResultJsonDecode);
final Map<String, dynamic> originalPullRequestJsonMap =
jsonDecode(originalPullRequestJson) as Map<String, dynamic>;
final github.PullRequest originalPullRequest = github.PullRequest.fromJson(originalPullRequestJsonMap);
githubService.pullRequestData = originalPullRequest;
// code gets the original file list then the current file list.
githubService.usePullRequestFilesList = true;
githubService.pullRequestFilesMockList.add(originalPullRequestFilesJson);
githubService.pullRequestFilesMockList.add(revertPullRequestFilesJson);
// Need to set the mock checkRuns for required CheckRun validation
githubService.checkRunsData = ciyamlCheckRun;
final ValidationResult validationResult = await revert.validate(queryResult, revertPullRequest);
assert(validationResult.result);
assert(validationResult.message == 'Revert request has been verified and will be queued for merge.');
});
});
}