blob: 60b3045b709b52b24353f1732358d1a536713a4a [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/exception/bigquery_exception.dart';
import 'package:auto_submit/model/big_query_pull_request_record.dart';
import 'package:auto_submit/model/big_query_revert_request_record.dart';
import 'package:googleapis/bigquery/v2.dart';
import 'package:mockito/mockito.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
import '../src/service/fake_bigquery_service.dart';
import '../utilities/mocks.dart';
const String revertRequestRecordResponse = '''
{
"jobComplete": true,
"rows": [
{ "f": [
{ "v": "flutter"},
{ "v": "cocoon" },
{ "v": "ricardoamador" },
{ "v": "1024" },
{ "v": "123f124" },
{ "v": "123456789" },
{ "v": "123456999" },
{ "v": "ricardoamador" },
{ "v": "2048" },
{ "v": "ce345dc" },
{ "v": "234567890" },
{ "v": "234567999" },
{ "v": "ricardoamador" },
{ "v": "11304" },
{ "v": "1640979000000" },
{ "v": null }
]
}
]
}
''';
const String pullRequestRecordResponse = '''
{
"jobComplete": true,
"rows": [
{ "f": [
{ "v": "123456789"},
{ "v": "234567890" },
{ "v": "flutter" },
{ "v": "cocoon" },
{ "v": "ricardoamador" },
{ "v": "345" },
{ "v": "ade456" },
{ "v": "merge" }
]
}
]
}
''';
const String successResponseNoRowsAffected = '''
{
"jobComplete": true
}
''';
const String insertDeleteSuccessResponse = '''
{
"jobComplete": true,
"numDmlAffectedRows": "1"
}
''';
const String insertDeleteSuccessTooManyRows = '''
{
"jobComplete": true,
"numDmlAffectedRows": "2"
}
''';
const String selectPullRequestTooManyRowsResponse = '''
{
"jobComplete": true,
"numDmlAffectedRows": "2",
"rows": [
{ "f": [
{ "v": "123456789"},
{ "v": "234567890" },
{ "v": "flutter" },
{ "v": "cocoon" },
{ "v": "ricardoamador" },
{ "v": "345" },
{ "v": "ade456" },
{ "v": "merge" }
]
},
{ "f": [
{ "v": "123456789"},
{ "v": "234567890" },
{ "v": "flutter" },
{ "v": "cocoon" },
{ "v": "ricardoamador" },
{ "v": "345" },
{ "v": "ade456" },
{ "v": "merge" }
]
}
]
}
''';
const String selectRevertRequestTooManyRowsResponse = '''
{
"jobComplete": true,
"numDmlAffectedRows": "2",
"rows": [
{ "f": [
{ "v": "flutter"},
{ "v": "cocoon" },
{ "v": "ricardoamador" },
{ "v": "1024" },
{ "v": "123f124" },
{ "v": "123456789" },
{ "v": "123456999" },
{ "v": "ricardoamador" },
{ "v": "2048" },
{ "v": "ce345dc" },
{ "v": "234567890" },
{ "v": "234567999" }
]
},
{ "f": [
{ "v": "flutter"},
{ "v": "cocoon" },
{ "v": "ricardoamador" },
{ "v": "1024" },
{ "v": "123f124" },
{ "v": "123456789" },
{ "v": "123456999" },
{ "v": "ricardoamador" },
{ "v": "2048" },
{ "v": "ce345dc" },
{ "v": "234567890" },
{ "v": "234567999" }
]
}
]
}
''';
const String errorResponse = '''
{
"jobComplete": false
}
''';
const String expectedProjectId = 'flutter-dashboard';
void main() {
late FakeBigqueryService service;
late MockJobsResource jobsResource;
setUp(() {
jobsResource = MockJobsResource();
service = FakeBigqueryService(jobsResource);
});
test('Insert pull request record is successful.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(insertDeleteSuccessResponse) as Map<dynamic, dynamic>),
);
});
PullRequestRecord pullRequestRecord = PullRequestRecord(
prCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456789),
prLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567890),
organization: 'flutter',
repository: 'cocoon',
author: 'ricardoamador',
prNumber: 345,
prCommit: 'ade456',
prRequestType: 'merge',
);
bool hasError = false;
try {
await service.insertPullRequestRecord(
projectId: expectedProjectId,
pullRequestRecord: pullRequestRecord,
);
} on BigQueryException {
hasError = true;
}
expect(hasError, isFalse);
});
test('Insert pull request record handles unsuccessful job complete error.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(errorResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
PullRequestRecord pullRequestRecord = PullRequestRecord(
prCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456789),
prLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567890),
organization: 'flutter',
repository: 'cocoon',
author: 'ricardoamador',
prNumber: 345,
prCommit: 'ade456',
prRequestType: 'merge',
);
try {
await service.insertPullRequestRecord(
projectId: expectedProjectId,
pullRequestRecord: pullRequestRecord,
);
} on BigQueryException catch (exception) {
expect(exception.cause, 'Insert pull request $pullRequestRecord did not complete.');
hasError = true;
}
expect(hasError, isTrue);
});
test('Insert pull request fails when multiple rows are returned.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(selectPullRequestTooManyRowsResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
PullRequestRecord pullRequestRecord = PullRequestRecord(
prCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456789),
prLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567890),
organization: 'flutter',
repository: 'cocoon',
author: 'ricardoamador',
prNumber: 345,
prCommit: 'ade456',
prRequestType: 'merge',
);
try {
await service.insertPullRequestRecord(
projectId: expectedProjectId,
pullRequestRecord: pullRequestRecord,
);
} on BigQueryException catch (exception) {
expect(exception.cause, 'There was an error inserting $pullRequestRecord into the table.');
hasError = true;
}
expect(hasError, isTrue);
});
test('Select pull request is successful.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(pullRequestRecordResponse) as Map<dynamic, dynamic>),
);
});
PullRequestRecord pullRequestRecord = await service.selectPullRequestRecordByPrNumber(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
expect(pullRequestRecord, isNotNull);
expect(pullRequestRecord.prCreatedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(123456789)));
expect(pullRequestRecord.prLandedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(234567890)));
expect(pullRequestRecord.organization, equals('flutter'));
expect(pullRequestRecord.repository, equals('cocoon'));
expect(pullRequestRecord.author, equals('ricardoamador'));
expect(pullRequestRecord.prNumber, 345);
expect(pullRequestRecord.prCommit, equals('ade456'));
expect(pullRequestRecord.prRequestType, equals('merge'));
});
test('Select pull request handles unsuccessful job failure.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(QueryResponse.fromJson(jsonDecode(errorResponse) as Map<dynamic, dynamic>));
});
bool hasError = false;
try {
await service.selectPullRequestRecordByPrNumber(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(exception.cause, 'Get pull request by pr# 345 in repository cocoon did not complete.');
}
expect(hasError, isTrue);
});
test('Select pull request handles no rows returned failure.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(successResponseNoRowsAffected) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.selectPullRequestRecordByPrNumber(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'Could not find an entry for pull request with pr# 345 in repository cocoon.',
);
}
expect(hasError, isTrue);
});
test('Select pull request handles too many rows returned failure.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(selectPullRequestTooManyRowsResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.selectPullRequestRecordByPrNumber(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'More than one record was returned for pull request with pr# 345 in repository cocoon.',
);
}
expect(hasError, isTrue);
});
test('Delete pull request record handles failure to complete job.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(QueryResponse.fromJson(jsonDecode(errorResponse) as Map<dynamic, dynamic>));
});
bool hasError = false;
try {
await service.deletePullRequestRecord(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'Delete pull request with pr# 345 in repository cocoon did not complete.',
);
}
expect(hasError, isTrue);
});
test('Delete pull request record handles success but no affected rows.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(successResponseNoRowsAffected) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.deletePullRequestRecord(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'Could not find pull request with pr# 345 in repository cocoon to delete.',
);
}
expect(hasError, isTrue);
});
test('Delete pull request record handles success but wrong number of affected rows.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(insertDeleteSuccessTooManyRows) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.deletePullRequestRecord(
projectId: expectedProjectId,
prNumber: 345,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'More than one row was deleted from the database for pull request with pr# 345 in repository cocoon.',
);
}
expect(hasError, isTrue);
});
test('Insert revert request record is successful.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(insertDeleteSuccessResponse) as Map<dynamic, dynamic>),
);
});
RevertRequestRecord revertRequestRecord = RevertRequestRecord(
organization: 'flutter',
repository: 'cocoon',
author: 'ricardoamador',
prNumber: 1024,
prCommit: '123f124',
prCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456789),
prLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456999),
originalPrAuthor: 'ricardoamador',
originalPrNumber: 1000,
originalPrCommit: 'ce345dc',
originalPrCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567890),
originalPrLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567999),
reviewIssueAssignee: 'ricardoamador',
reviewIssueNumber: 11304,
reviewIssueCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(1640979000000),
);
bool hasError = false;
try {
await service.insertRevertRequestRecord(
projectId: expectedProjectId,
revertRequestRecord: revertRequestRecord,
);
} on BigQueryException {
hasError = true;
}
expect(hasError, isFalse);
});
test('Insert revert request record handles unsuccessful job complete error.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(errorResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
RevertRequestRecord revertRequestRecord = RevertRequestRecord(
organization: 'flutter',
repository: 'cocoon',
author: 'ricardoamador',
prNumber: 1024,
prCommit: '123f124',
prCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456789),
prLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(123456999),
originalPrAuthor: 'ricardoamador',
originalPrNumber: 1000,
originalPrCommit: 'ce345dc',
originalPrCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567890),
originalPrLandedTimestamp: DateTime.fromMillisecondsSinceEpoch(234567999),
reviewIssueAssignee: 'ricardoamador',
reviewIssueNumber: 11304,
reviewIssueCreatedTimestamp: DateTime.fromMillisecondsSinceEpoch(1640979000000),
);
try {
await service.insertRevertRequestRecord(
projectId: expectedProjectId,
revertRequestRecord: revertRequestRecord,
);
} on BigQueryException catch (e) {
expect(e.cause, 'Insert revert request $revertRequestRecord did not complete.');
hasError = true;
}
expect(hasError, isTrue);
});
test('Select revert request is successful.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(revertRequestRecordResponse) as Map<dynamic, dynamic>),
);
});
RevertRequestRecord revertRequestRecord = await service.selectRevertRequestByRevertPrNumber(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
expect(revertRequestRecord, isNotNull);
expect(revertRequestRecord.organization, equals('flutter'));
expect(revertRequestRecord.repository, equals('cocoon'));
expect(revertRequestRecord.author, equals('ricardoamador'));
expect(revertRequestRecord.prNumber, equals(1024));
expect(revertRequestRecord.prCommit, equals('123f124'));
expect(revertRequestRecord.prCreatedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(123456789)));
expect(revertRequestRecord.prLandedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(123456999)));
expect(revertRequestRecord.originalPrAuthor, equals('ricardoamador'));
expect(revertRequestRecord.originalPrNumber, equals(2048));
expect(revertRequestRecord.originalPrCommit, equals('ce345dc'));
expect(revertRequestRecord.originalPrCreatedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(234567890)));
expect(revertRequestRecord.originalPrLandedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(234567999)));
// { "v": "ricardoamador" },
// { "v": "11304" },
// { "v": "1640979000000" },
// { "v": null },
expect(revertRequestRecord.reviewIssueAssignee, equals('ricardoamador'));
expect(revertRequestRecord.reviewIssueNumber, equals(11304));
expect(revertRequestRecord.reviewIssueCreatedTimestamp, equals(DateTime.fromMillisecondsSinceEpoch(1640979000000)));
expect(revertRequestRecord.reviewIssueLandedTimestamp, isNull);
});
test('Select revert request is unsuccessful with job did not complete error.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(errorResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.selectRevertRequestByRevertPrNumber(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(exception.cause, 'Get revert request with pr# 2048 in repository cocoon did not complete.');
}
expect(hasError, isTrue);
});
test('Select revert request is successful but does not return any rows.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(successResponseNoRowsAffected) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.selectRevertRequestByRevertPrNumber(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'Could not find an entry for revert request with pr# 2048 in repository cocoon.',
);
}
expect(hasError, isTrue);
});
test('Select is successful but returns more than one row in the request.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(selectRevertRequestTooManyRowsResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.selectRevertRequestByRevertPrNumber(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'More than one record was returned for revert request with pr# 2048 in repository cocoon.',
);
}
expect(hasError, isTrue);
});
test('Delete revert request record handles failure to complete job.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(errorResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.deleteRevertRequestRecord(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'Delete revert request with pr# 2048 in repository cocoon did not complete.',
);
}
expect(hasError, isTrue);
});
test('Delete revert request record handles success but no affected rows.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(successResponseNoRowsAffected) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.deleteRevertRequestRecord(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'Could not find revert request with pr# 2048 in repository cocoon to delete.',
);
}
expect(hasError, isTrue);
});
test('Delete revert request record handles success but wrong number of affected rows.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(insertDeleteSuccessTooManyRows) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.deleteRevertRequestRecord(
projectId: expectedProjectId,
prNumber: 2048,
repository: 'cocoon',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'More than one row was deleted from the database for revert request with pr# 2048 in repository cocoon.',
);
}
expect(hasError, isTrue);
});
}