diff --git a/packages/shorebird_code_push_client/lib/src/code_push_client.dart b/packages/shorebird_code_push_client/lib/src/code_push_client.dart index 6367f991f..b10359358 100644 --- a/packages/shorebird_code_push_client/lib/src/code_push_client.dart +++ b/packages/shorebird_code_push_client/lib/src/code_push_client.dart @@ -38,6 +38,26 @@ class CodePushNotFoundException extends CodePushException { CodePushNotFoundException({required super.message, super.details}); } +/// A wrapper around [http.Client] that ensures all outbound requests +/// are consistent. +/// For example, all requests include the standard `x-version` header. +class _CodePushHttpClient extends http.BaseClient { + _CodePushHttpClient(this._client); + + final http.Client _client; + + @override + Future send(http.BaseRequest request) { + return _client.send(request..headers.addAll(CodePushClient.headers)); + } + + @override + void close() { + _client.close(); + super.close(); + } +} + /// {@template code_push_client} /// Dart client for the Shorebird CodePush API. /// {@endtemplate} @@ -46,7 +66,7 @@ class CodePushClient { CodePushClient({ http.Client? httpClient, Uri? hostedUri, - }) : _httpClient = httpClient ?? http.Client(), + }) : _httpClient = _CodePushHttpClient(httpClient ?? http.Client()), hostedUri = hostedUri ?? Uri.https('api.shorebird.dev'); /// The standard headers applied to all requests. @@ -70,7 +90,6 @@ class CodePushClient { }) async { final response = await _httpClient.post( Uri.parse('$_v1/apps/$appId/collaborators'), - headers: headers, body: json.encode(CreateAppCollaboratorRequest(email: email).toJson()), ); @@ -107,7 +126,6 @@ class CodePushClient { Uri.parse('$_v1/patches/$patchId/artifacts'), ); final file = await http.MultipartFile.fromPath('file', artifactPath); - request.headers.addAll(headers); request.fields.addAll({ 'arch': arch, 'platform': platform, @@ -141,7 +159,6 @@ class CodePushClient { Future createPaymentLink() async { final response = await _httpClient.post( Uri.parse('$_v1/subscriptions/payment_link'), - headers: headers, ); if (response.statusCode != HttpStatus.ok) { @@ -166,7 +183,6 @@ class CodePushClient { Uri.parse('$_v1/releases/$releaseId/artifacts'), ); final file = await http.MultipartFile.fromPath('file', artifactPath); - request.headers.addAll(headers); request.fields.addAll({ 'arch': arch, 'platform': platform, @@ -201,7 +217,6 @@ class CodePushClient { Future createApp({required String displayName}) async { final response = await _httpClient.post( Uri.parse('$_v1/apps'), - headers: headers, body: json.encode({'display_name': displayName}), ); @@ -219,7 +234,6 @@ class CodePushClient { }) async { final response = await _httpClient.post( Uri.parse('$_v1/channels'), - headers: headers, body: json.encode({'app_id': appId, 'channel': channel}), ); @@ -234,7 +248,6 @@ class CodePushClient { Future createPatch({required int releaseId}) async { final response = await _httpClient.post( Uri.parse('$_v1/patches'), - headers: headers, body: json.encode({'release_id': releaseId}), ); @@ -255,7 +268,6 @@ class CodePushClient { }) async { final response = await _httpClient.post( Uri.parse('$_v1/releases'), - headers: headers, body: json.encode({ 'app_id': appId, 'version': version, @@ -278,7 +290,6 @@ class CodePushClient { }) async { final response = await _httpClient.delete( Uri.parse('$_v1/apps/$appId/collaborators/$userId'), - headers: headers, ); if (response.statusCode != HttpStatus.noContent) { @@ -290,7 +301,6 @@ class CodePushClient { Future deleteRelease({required int releaseId}) async { final response = await _httpClient.delete( Uri.parse('$_v1/releases/$releaseId'), - headers: headers, ); if (response.statusCode != HttpStatus.noContent) { @@ -306,7 +316,6 @@ class CodePushClient { }) async { final response = await _httpClient.post( Uri.parse('$_v1/users'), - headers: headers, body: jsonEncode(CreateUserRequest(name: name).toJson()), ); @@ -322,7 +331,6 @@ class CodePushClient { Future deleteApp({required String appId}) async { final response = await _httpClient.delete( Uri.parse('$_v1/apps/$appId'), - headers: headers, ); if (response.statusCode != HttpStatus.noContent) { @@ -334,7 +342,6 @@ class CodePushClient { Future> getApps() async { final response = await _httpClient.get( Uri.parse('$_v1/apps'), - headers: headers, ); if (response.statusCode != HttpStatus.ok) { @@ -353,7 +360,6 @@ class CodePushClient { Uri.parse('$_v1/channels').replace( queryParameters: {'appId': appId}, ), - headers: headers, ); if (response.statusCode != HttpStatus.ok) { @@ -370,7 +376,6 @@ class CodePushClient { Future> getCollaborators({required String appId}) async { final response = await _httpClient.get( Uri.parse('$_v1/apps/$appId/collaborators'), - headers: headers, ); if (response.statusCode != HttpStatus.ok) { @@ -391,7 +396,6 @@ class CodePushClient { Uri.parse('$_v1/releases').replace( queryParameters: {'appId': appId}, ), - headers: headers, ); if (response.statusCode != HttpStatus.ok) { @@ -417,7 +421,6 @@ class CodePushClient { 'platform': platform, }, ), - headers: headers, ); if (response.statusCode != HttpStatus.ok) { @@ -435,7 +438,6 @@ class CodePushClient { }) async { final response = await _httpClient.post( Uri.parse('$_v1/patches/promote'), - headers: headers, body: json.encode({'patch_id': patchId, 'channel_id': channelId}), ); @@ -446,10 +448,7 @@ class CodePushClient { /// Cancels the current user's subscription. Future cancelSubscription() async { - final response = await _httpClient.delete( - Uri.parse('$_v1/subscriptions'), - headers: headers, - ); + final response = await _httpClient.delete(Uri.parse('$_v1/subscriptions')); if (response.statusCode != HttpStatus.ok) { throw _parseErrorResponse(response.statusCode, response.body); diff --git a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart index e49530716..5f13c7be3 100644 --- a/packages/shorebird_code_push_client/test/src/code_push_client_test.dart +++ b/packages/shorebird_code_push_client/test/src/code_push_client_test.dart @@ -26,6 +26,10 @@ void main() { late http.Client httpClient; late CodePushClient codePushClient; + Uri v1(String endpoint) { + return Uri.parse('${codePushClient.hostedUri}/api/v1/$endpoint'); + } + setUpAll(() { registerFallbackValue(_FakeBaseRequest()); registerFallbackValue(Uri()); @@ -34,6 +38,9 @@ void main() { setUp(() { httpClient = _MockHttpClient(); codePushClient = CodePushClient(httpClient: httpClient); + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse(Stream.empty(), HttpStatus.ok), + ); }); test('can be instantiated', () { @@ -57,15 +64,22 @@ void main() { const appId = 'test-app-id'; const email = 'jane.doe@shorebird.dev'; + test('makes the correct request', () async { + codePushClient.createCollaborator(appId: appId, email: email).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('apps/$appId/collaborators'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.failedDependency, ), - ).thenAnswer( - (_) async => http.Response('', HttpStatus.failedDependency), ); expect( @@ -81,15 +95,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -107,29 +115,16 @@ void main() { }); test('completes when request succeeds', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer((_) async => http.Response('', HttpStatus.created)); - - await codePushClient.createCollaborator(appId: appId, email: email); - - final uri = verify( - () => httpClient.post( - captureAny(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.created, ), - ).captured.single as Uri; + ); - expect( - uri, - codePushClient.hostedUri.replace( - path: '/api/v1/apps/$appId/collaborators', - ), + await expectLater( + codePushClient.createCollaborator(appId: appId, email: email), + completes, ); }); }); @@ -137,20 +132,33 @@ void main() { group('getCurrentUser', () { const user = User(id: 123, email: 'tester@shorebird.dev'); - late Uri uri; - setUp(() { - uri = Uri.parse('${codePushClient.hostedUri}/api/v1/users/me'); + test('makes the correct request', () async { + codePushClient.getCurrentUser().ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('GET')); + expect(request.url, equals(v1('users/me'))); + expect(request.hasStandardHeaders, isTrue); }); test('returns null if reponse is a 404', () async { - when(() => httpClient.get(uri)) - .thenAnswer((_) async => http.Response('', HttpStatus.notFound)); + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.notFound, + ), + ); expect(await codePushClient.getCurrentUser(), isNull); }); test('throws exception if the http request fails', () { - when(() => httpClient.get(uri)) - .thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, + ), + ); expect( codePushClient.getCurrentUser(), @@ -165,8 +173,11 @@ void main() { }); test('returns a deserialize user if the request succeeds', () async { - when(() => httpClient.get(uri)).thenAnswer( - (_) async => http.Response(jsonEncode(user.toJson()), HttpStatus.ok), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(user.toJson()))), + HttpStatus.ok, + ), ); final responseUser = await codePushClient.getCurrentUser(); @@ -184,10 +195,33 @@ void main() { const hash = 'test-hash'; const size = 42; + test('makes the correct request', () async { + final tempDir = Directory.systemTemp.createTempSync(); + final fixture = File(path.join(tempDir.path, 'release.txt')) + ..createSync(); + + try { + await codePushClient.createPatchArtifact( + artifactPath: fixture.path, + patchId: patchId, + arch: arch, + platform: platform, + hash: hash, + ); + } catch (_) {} + + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('patches/$patchId/artifacts'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { when(() => httpClient.send(any())).thenAnswer((_) async { return http.StreamedResponse( - Stream.empty(), + const Stream.empty(), HttpStatus.failedDependency, ); }); @@ -247,31 +281,35 @@ void main() { test('throws an exception if the upload fails', () async { const artifactId = 42; const uploadUrl = 'https://example.com'; - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.value( - utf8.encode( - json.encode( - CreatePatchArtifactResponse( - id: artifactId, - patchId: patchId, - arch: arch, - platform: platform, - hash: hash, - size: size, - url: uploadUrl, + when(() => httpClient.send(any())).thenAnswer((invocation) async { + final request = + invocation.positionalArguments.first as http.BaseRequest; + if (request.method == 'POST') { + return http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + CreatePatchArtifactResponse( + id: artifactId, + patchId: patchId, + arch: arch, + platform: platform, + hash: hash, + size: size, + url: uploadUrl, + ), ), ), ), - ), - HttpStatus.ok, + HttpStatus.ok, + ); + } + return http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ); }); - when( - () => httpClient.put(any(), body: any(named: 'body')), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); - final tempDir = Directory.systemTemp.createTempSync(); final fixture = File(path.join(tempDir.path, 'release.txt')) ..createSync(); @@ -292,42 +330,48 @@ void main() { ), ), ); - verify( - () => httpClient.put( - Uri.parse(uploadUrl), - body: fixture.readAsBytesSync(), - ), - ).called(1); + final request = verify(() => httpClient.send(captureAny())) + .captured + .last as http.BaseRequest; + expect(request.url, equals(Uri.parse(uploadUrl))); + expect( + request.contentLength, + equals(fixture.readAsBytesSync().lengthInBytes), + ); }); test('completes when request succeeds', () async { const artifactId = 42; const uploadUrl = 'https://example.com'; - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.value( - utf8.encode( - json.encode( - CreatePatchArtifactResponse( - id: artifactId, - patchId: patchId, - arch: arch, - platform: platform, - hash: hash, - size: size, - url: uploadUrl, + when(() => httpClient.send(any())).thenAnswer((invocation) async { + final request = + invocation.positionalArguments.first as http.BaseRequest; + if (request.method == 'POST') { + return http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + CreatePatchArtifactResponse( + id: artifactId, + patchId: patchId, + arch: arch, + platform: platform, + hash: hash, + size: size, + url: uploadUrl, + ), ), ), ), - ), + HttpStatus.ok, + ); + } + return http.StreamedResponse( + const Stream.empty(), HttpStatus.ok, ); }); - when( - () => httpClient.put(any(), body: any(named: 'body')), - ).thenAnswer((_) async => http.Response('', HttpStatus.ok)); - final tempDir = Directory.systemTemp.createTempSync(); final fixture = File(path.join(tempDir.path, 'release.txt')) ..createSync(); @@ -345,7 +389,7 @@ void main() { final request = verify(() => httpClient.send(captureAny())) .captured - .single as http.MultipartRequest; + .first as http.MultipartRequest; expect( request.url, @@ -357,13 +401,23 @@ void main() { }); group('createPaymentLink', () { + test('makes the correct request', () async { + codePushClient.createPaymentLink().ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('subscriptions/payment_link'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails', () { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, + ); + }); expect( codePushClient.createPaymentLink(), @@ -379,18 +433,18 @@ void main() { test('returns a payment link if the http request succeeds', () { final link = Uri.parse('http://test.com'); - - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - jsonEncode(CreatePaymentLinkResponse(paymentLink: link).toJson()), + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + CreatePaymentLinkResponse(paymentLink: link).toJson(), + ), + ), + ), HttpStatus.ok, - ), - ); + ); + }); expect( codePushClient.createPaymentLink(), @@ -406,6 +460,29 @@ void main() { const hash = 'test-hash'; const size = 42; + test('makes the correct request', () async { + final tempDir = Directory.systemTemp.createTempSync(); + final fixture = File(path.join(tempDir.path, 'release.txt')) + ..createSync(); + + try { + await codePushClient.createReleaseArtifact( + artifactPath: fixture.path, + releaseId: releaseId, + arch: arch, + platform: platform, + hash: hash, + ); + } catch (_) {} + + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('releases/$releaseId/artifacts'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { when(() => httpClient.send(any())).thenAnswer((_) async { return http.StreamedResponse( @@ -521,31 +598,35 @@ void main() { test('throws an exception if the upload fails', () async { const artifactId = 42; const uploadUrl = 'https://example.com'; - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.value( - utf8.encode( - json.encode( - CreateReleaseArtifactResponse( - id: artifactId, - releaseId: releaseId, - arch: arch, - platform: platform, - hash: hash, - size: size, - url: uploadUrl, + when(() => httpClient.send(any())).thenAnswer((invocation) async { + final request = + invocation.positionalArguments.first as http.BaseRequest; + if (request.method == 'POST') { + return http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + CreateReleaseArtifactResponse( + id: artifactId, + releaseId: releaseId, + arch: arch, + platform: platform, + hash: hash, + size: size, + url: uploadUrl, + ), ), ), ), - ), - HttpStatus.ok, + HttpStatus.ok, + ); + } + return http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ); }); - when( - () => httpClient.put(any(), body: any(named: 'body')), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); - final tempDir = Directory.systemTemp.createTempSync(); final fixture = File(path.join(tempDir.path, 'release.txt')) ..createSync(); @@ -566,42 +647,48 @@ void main() { ), ), ); - verify( - () => httpClient.put( - Uri.parse(uploadUrl), - body: fixture.readAsBytesSync(), - ), - ).called(1); + final request = verify( + () => httpClient.send(captureAny()), + ).captured.last as http.BaseRequest; + expect(request.url, equals(Uri.parse(uploadUrl))); + expect( + request.contentLength, + equals(fixture.readAsBytesSync().lengthInBytes), + ); }); test('completes when request succeeds', () async { const artifactId = 42; const uploadUrl = 'https://example.com'; - when(() => httpClient.send(any())).thenAnswer((_) async { - return http.StreamedResponse( - Stream.value( - utf8.encode( - json.encode( - CreateReleaseArtifactResponse( - id: artifactId, - releaseId: releaseId, - arch: arch, - platform: platform, - hash: hash, - size: size, - url: uploadUrl, + when(() => httpClient.send(any())).thenAnswer((invocation) async { + final request = + invocation.positionalArguments.first as http.BaseRequest; + if (request.method == 'POST') { + return http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + CreateReleaseArtifactResponse( + id: artifactId, + releaseId: releaseId, + arch: arch, + platform: platform, + hash: hash, + size: size, + url: uploadUrl, + ), ), ), ), - ), + HttpStatus.ok, + ); + } + return http.StreamedResponse( + const Stream.empty(), HttpStatus.ok, ); }); - when( - () => httpClient.put(any(), body: any(named: 'body')), - ).thenAnswer((_) async => http.Response('', HttpStatus.ok)); - final tempDir = Directory.systemTemp.createTempSync(); final fixture = File(path.join(tempDir.path, 'release.txt')) ..createSync(); @@ -619,7 +706,7 @@ void main() { final request = verify(() => httpClient.send(captureAny())) .captured - .single as http.MultipartRequest; + .first as http.MultipartRequest; expect( request.url, @@ -631,14 +718,23 @@ void main() { }); group('createApp', () { + test('makes the correct request', () async { + codePushClient.createApp(displayName: displayName).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('apps'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + ); expect( codePushClient.createApp(displayName: displayName), @@ -653,15 +749,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -679,15 +769,13 @@ void main() { }); test('completes when request succeeds', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(App(id: appId, displayName: displayName)), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode(App(id: appId, displayName: displayName)), + ), + ), HttpStatus.ok, ), ); @@ -703,16 +791,12 @@ void main() { ), ); - final uri = verify( - () => httpClient.post( - captureAny(), - headers: CodePushClient.headers, - body: any(named: 'body'), - ), - ).captured.single as Uri; + final request = verify( + () => httpClient.send(captureAny()), + ).captured.single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace(path: '/api/v1/apps'), ); }); @@ -720,14 +804,24 @@ void main() { group('createChannel', () { const channel = 'stable'; + + test('makes the correct request', () async { + codePushClient.createChannel(appId: appId, channel: channel).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('channels'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + ); expect( codePushClient.createChannel(appId: appId, channel: channel), @@ -742,15 +836,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -769,15 +857,15 @@ void main() { test('completes when request succeeds', () async { const channelId = 0; - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(Channel(id: channelId, appId: appId, name: channel)), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + Channel(id: channelId, appId: appId, name: channel), + ), + ), + ), HttpStatus.ok, ), ); @@ -794,16 +882,12 @@ void main() { ), ); - final uri = verify( - () => httpClient.post( - captureAny(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).captured.single as Uri; + final request = verify( + () => httpClient.send(captureAny()), + ).captured.single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace(path: '/api/v1/channels'), ); }); @@ -811,14 +895,24 @@ void main() { group('createPatch', () { const releaseId = 0; + + test('makes the correct request', () async { + codePushClient.createPatch(releaseId: releaseId).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('patches'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + ); expect( codePushClient.createPatch(releaseId: releaseId), @@ -833,15 +927,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -861,15 +949,13 @@ void main() { test('completes when request succeeds', () async { const patchId = 0; const patchNumber = 1; - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(Patch(id: patchId, number: patchNumber)), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode(Patch(id: patchId, number: patchNumber)), + ), + ), HttpStatus.ok, ), ); @@ -885,16 +971,12 @@ void main() { ), ); - final uri = verify( - () => httpClient.post( - captureAny(), - headers: CodePushClient.headers, - body: any(named: 'body'), - ), - ).captured.single as Uri; + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace(path: '/api/v1/patches'), ); }); @@ -902,14 +984,31 @@ void main() { group('createRelease', () { const version = '1.0.0'; + + test('makes the correct request', () async { + codePushClient + .createRelease( + appId: appId, + version: version, + flutterRevision: flutterRevision, + displayName: displayName, + ) + .ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('releases'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + ); expect( codePushClient.createRelease( @@ -929,15 +1028,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -961,21 +1054,19 @@ void main() { test('completes when request succeeds', () async { const releaseId = 0; - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode( - Release( - id: releaseId, - appId: appId, - version: version, - flutterRevision: flutterRevision, - displayName: displayName, + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value( + utf8.encode( + json.encode( + Release( + id: releaseId, + appId: appId, + version: version, + flutterRevision: flutterRevision, + displayName: displayName, + ), + ), ), ), HttpStatus.ok, @@ -1005,16 +1096,12 @@ void main() { ), ); - final uri = verify( - () => httpClient.post( - captureAny(), - headers: CodePushClient.headers, - body: any(named: 'body'), - ), - ).captured.single as Uri; + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace(path: '/api/v1/releases'), ); }); @@ -1024,11 +1111,24 @@ void main() { const appId = 'test-app-id'; const userId = 42; + test('makes the correct request', () async { + codePushClient + .deleteCollaborator(appId: appId, userId: userId) + .ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('DELETE')); + expect(request.url, equals(v1('apps/$appId/collaborators/$userId'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response('', HttpStatus.failedDependency), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, + ), ); expect( @@ -1044,11 +1144,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1066,27 +1164,24 @@ void main() { }); test('completes when request succeeds', () async { - when( - () => httpClient.delete( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.noContent, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.noContent)); + ); await codePushClient.deleteCollaborator( appId: appId, userId: userId, ); - final uri = verify( - () => httpClient.delete( - captureAny(), - headers: CodePushClient.headers, - ), - ).captured.single as Uri; + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace( path: '/api/v1/apps/$appId/collaborators/$userId', ), @@ -1097,11 +1192,22 @@ void main() { group('deleteRelease', () { const releaseId = 42; + test('makes the correct request', () async { + codePushClient.deleteRelease(releaseId: releaseId).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('DELETE')); + expect(request.url, equals(v1('releases/$releaseId'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response('', HttpStatus.failedDependency), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, + ), ); expect( @@ -1117,11 +1223,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1139,24 +1243,21 @@ void main() { }); test('completes when request succeeds', () async { - when( - () => httpClient.delete( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.noContent, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.noContent)); + ); await codePushClient.deleteRelease(releaseId: releaseId); - final uri = verify( - () => httpClient.delete( - captureAny(), - headers: CodePushClient.headers, - ), - ).captured.single as Uri; + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace( path: '/api/v1/releases/$releaseId', ), @@ -1172,15 +1273,22 @@ void main() { displayName: userName, ); + test('makes the correct request', () async { + codePushClient.createUser(name: userName).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('users'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails', () { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.failedDependency, ), - ).thenAnswer( - (_) async => http.Response('', HttpStatus.failedDependency), ); expect( @@ -1196,15 +1304,9 @@ void main() { }); test('returns a User when the http request succeeds', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - jsonEncode(user.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(user.toJson()))), HttpStatus.created, ), ); @@ -1216,11 +1318,22 @@ void main() { }); group('deleteApp', () { + test('makes the correct request', () async { + codePushClient.deleteApp(appId: appId).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('DELETE')); + expect(request.url, equals(v1('apps/$appId'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response('', HttpStatus.failedDependency), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.failedDependency, + ), ); expect( @@ -1236,11 +1349,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.delete(any(), headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1258,24 +1369,21 @@ void main() { }); test('completes when request succeeds', () async { - when( - () => httpClient.delete( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.noContent, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.noContent)); + ); await codePushClient.deleteApp(appId: appId); - final uri = verify( - () => httpClient.delete( - captureAny(), - headers: any(named: 'headers'), - ), - ).captured.single as Uri; + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace( path: '/api/v1/apps/$appId', ), @@ -1284,15 +1392,20 @@ void main() { }); group('getApps', () { + test('makes the correct request', () async { + codePushClient.getApps().ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('GET')); + expect(request.url, equals(v1('apps'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - '', + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), HttpStatus.failedDependency, ), ); @@ -1310,15 +1423,10 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), - HttpStatus.failedDependency, + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), + HttpStatus.noContent, ), ); @@ -1335,13 +1443,11 @@ void main() { }); test('completes when request succeeds (empty)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode([]))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode([]), HttpStatus.ok), ); final apps = await codePushClient.getApps(); @@ -1354,13 +1460,11 @@ void main() { AppMetadata(appId: '2', displayName: 'Shorebird Clock'), ]; - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(expected))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode(expected), HttpStatus.ok), ); final actual = await codePushClient.getApps(); @@ -1370,15 +1474,21 @@ void main() { group('getChannels', () { const appId = 'test-app-id'; + + test('makes the correct request', () async { + codePushClient.getChannels(appId: appId).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('GET')); + expect(request.url, equals(v1('channels?appId=$appId'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - '', + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), HttpStatus.failedDependency, ), ); @@ -1396,14 +1506,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1421,13 +1526,11 @@ void main() { }); test('completes when request succeeds (empty)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode([]))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode([]), HttpStatus.ok), ); final apps = await codePushClient.getChannels(appId: appId); @@ -1440,13 +1543,11 @@ void main() { Channel(id: 1, appId: '2', name: 'development'), ]; - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(expected))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode(expected), HttpStatus.ok), ); final actual = await codePushClient.getChannels(appId: appId); @@ -1456,15 +1557,21 @@ void main() { group('getCollaborators', () { const appId = 'test-app-id'; + + test('makes the correct request', () async { + codePushClient.getCollaborators(appId: appId).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('GET')); + expect(request.url, equals(v1('apps/$appId/collaborators'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - '', + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), HttpStatus.failedDependency, ), ); @@ -1482,14 +1589,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1507,13 +1609,11 @@ void main() { }); test('completes when request succeeds (empty)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode([]))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode([]), HttpStatus.ok), ); final apps = await codePushClient.getCollaborators(appId: appId); @@ -1532,13 +1632,11 @@ void main() { ), ]; - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(expected))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode(expected), HttpStatus.ok), ); final actual = await codePushClient.getCollaborators(appId: appId); @@ -1548,15 +1646,21 @@ void main() { group('getReleases', () { const appId = 'test-app-id'; + + test('makes the correct request', () async { + codePushClient.getReleases(appId: appId).ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('GET')); + expect(request.url, equals(v1('releases?appId=$appId'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - '', + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), HttpStatus.failedDependency, ), ); @@ -1574,14 +1678,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1599,13 +1698,11 @@ void main() { }); test('completes when request succeeds (empty)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode([]))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode([]), HttpStatus.ok), ); final apps = await codePushClient.getReleases(appId: appId); @@ -1630,13 +1727,11 @@ void main() { ), ]; - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(expected))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode(expected), HttpStatus.ok), ); final actual = await codePushClient.getReleases(appId: appId); @@ -1649,15 +1744,31 @@ void main() { const arch = 'aarch64'; const platform = 'android'; + test('makes the correct request', () async { + codePushClient + .getReleaseArtifact( + releaseId: releaseId, + arch: arch, + platform: platform, + ) + .ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('GET')); + expect( + request.url, + equals( + v1('releases/$releaseId/artifacts?arch=$arch&platform=$platform'), + ), + ); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - '', + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), HttpStatus.failedDependency, ), ); @@ -1679,14 +1790,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1718,13 +1824,11 @@ void main() { size: 42, ); - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(expected))), + HttpStatus.ok, ), - ).thenAnswer( - (_) async => http.Response(json.encode(expected), HttpStatus.ok), ); final actual = await codePushClient.getReleaseArtifact( @@ -1739,14 +1843,26 @@ void main() { group('promotePatch', () { const patchId = 0; const channelId = 0; + + test('makes the correct request', () async { + codePushClient + .promotePatch(patchId: patchId, channelId: channelId) + .ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('POST')); + expect(request.url, equals(v1('patches/promote'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails (unknown)', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, ), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + ); expect( codePushClient.promotePatch(patchId: patchId, channelId: channelId), @@ -1761,15 +1877,9 @@ void main() { }); test('throws an exception if the http request fails', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), - ), - ).thenAnswer( - (_) async => http.Response( - json.encode(errorResponse.toJson()), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value(utf8.encode(json.encode(errorResponse.toJson()))), HttpStatus.failedDependency, ), ); @@ -1787,14 +1897,11 @@ void main() { }); test('completes when request succeeds', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - body: any(named: 'body'), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.created, ), - ).thenAnswer( - (_) async => http.Response('', HttpStatus.created), ); await expectLater( @@ -1802,16 +1909,12 @@ void main() { completes, ); - final uri = verify( - () => httpClient.post( - captureAny(), - headers: CodePushClient.headers, - body: any(named: 'body'), - ), - ).captured.single as Uri; + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; expect( - uri, + request.url, codePushClient.hostedUri.replace(path: '/api/v1/patches/promote'), ); }); @@ -1824,10 +1927,23 @@ void main() { uri = Uri.parse('${codePushClient.hostedUri}/api/v1/subscriptions'); }); + test('makes the correct request', () async { + codePushClient.cancelSubscription().ignore(); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.method, equals('DELETE')); + expect(request.url, equals(v1('subscriptions'))); + expect(request.hasStandardHeaders, isTrue); + }); + test('throws an exception if the http request fails', () { - when( - () => httpClient.delete(uri, headers: any(named: 'headers')), - ).thenAnswer((_) async => http.Response('', HttpStatus.badRequest)); + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + const Stream.empty(), + HttpStatus.badRequest, + ), + ); expect( codePushClient.cancelSubscription(), @@ -1843,12 +1959,11 @@ void main() { test('completes when request succeeds', () async { const timestamp = 1681455600; - - when( - () => httpClient.delete(uri, headers: any(named: 'headers')), - ).thenAnswer( - (_) async => http.Response( - jsonEncode({'expiration_date': 1681455600}), + when(() => httpClient.send(any())).thenAnswer( + (_) async => http.StreamedResponse( + Stream.value( + utf8.encode(json.encode({'expiration_date': 1681455600})), + ), HttpStatus.ok, ), ); @@ -1856,9 +1971,10 @@ void main() { final response = await codePushClient.cancelSubscription(); expect(response.millisecondsSinceEpoch, timestamp * 1000); - verify( - () => httpClient.delete(uri, headers: CodePushClient.headers), - ).called(1); + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.BaseRequest; + expect(request.url, equals(uri)); }); }); @@ -1870,3 +1986,11 @@ void main() { }); }); } + +extension on http.BaseRequest { + bool get hasStandardHeaders { + return CodePushClient.headers.entries.every( + (entry) => headers[entry.key] == entry.value, + ); + } +}