blob: ae67c4cb6d64c9030ba89945841b81183fce2c83 [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: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": "0" },
{ "v": "" }
]
}
]
}
''';
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 insertDeleteUpdateSuccessResponse = '''
{
"jobComplete": true,
"numDmlAffectedRows": "1"
}
''';
const String insertDeleteUpdateSuccessTooManyRows = '''
{
"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 selectReviewRequestRecordsResponse = '''
{
"jobComplete": true,
"numDmlAffectedRows": "2",
"rows": [
{ "f": [
{ "v": "Keyonghan" },
{ "v": "2048" },
{ "v": "234567890" },
{ "v": "0" },
{ "v": "" }
]
},
{ "f": [
{ "v": "caseyhillers" },
{ "v": "2049" },
{ "v": "234567890" },
{ "v": "0" },
{ "v": "" }
]
}
]
}
''';
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(insertDeleteUpdateSuccessResponse) as Map<dynamic, dynamic>),
);
});
final 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;
final 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;
final 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>),
);
});
final 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(insertDeleteUpdateSuccessTooManyRows) 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('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(insertDeleteUpdateSuccessTooManyRows) 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);
});
test('Update record is successful.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(insertDeleteUpdateSuccessResponse) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.updateReviewRequestIssue(
projectId: expectedProjectId,
reviewIssueLandedTimestamp: DateTime.now(),
reviewIssueNumber: 2048,
reviewIssueClosedBy: 'ricardoamador',
);
} on BigQueryException {
hasError = true;
}
expect(hasError, isFalse);
});
test('Update revert request review record is successful but wrong number of rows is updated.', () async {
when(jobsResource.query(captureAny, expectedProjectId)).thenAnswer((Invocation invocation) {
return Future<QueryResponse>.value(
QueryResponse.fromJson(jsonDecode(insertDeleteUpdateSuccessTooManyRows) as Map<dynamic, dynamic>),
);
});
bool hasError = false;
try {
await service.updateReviewRequestIssue(
projectId: expectedProjectId,
reviewIssueLandedTimestamp: DateTime.now(),
reviewIssueNumber: 2048,
reviewIssueClosedBy: 'ricardoamador',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(
exception.cause,
'There was an error updating revert request record review issue landed timestamp with review issue number 2048.',
);
}
expect(hasError, isTrue);
});
test('Update revert request review record does not complete successfully.', () 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.updateReviewRequestIssue(
projectId: expectedProjectId,
reviewIssueLandedTimestamp: DateTime.now(),
reviewIssueNumber: 2048,
reviewIssueClosedBy: 'ricardoamador',
);
} on BigQueryException catch (exception) {
hasError = true;
expect(exception.cause, 'Update of review issue 2048 did not complete.');
}
expect(hasError, isTrue);
});
}