diff --git a/packages/dart/lib/src/network/options.dart b/packages/dart/lib/src/network/options.dart index 5b07d7303..4b6bb1c62 100644 --- a/packages/dart/lib/src/network/options.dart +++ b/packages/dart/lib/src/network/options.dart @@ -1,9 +1,14 @@ part of '../../parse_server_sdk.dart'; class ParseNetworkOptions { - ParseNetworkOptions({this.headers}); + ParseNetworkOptions({this.headers, this.sendInstallationId}); final Map? headers; + + /// When `false`, the client suppresses the `X-Parse-Installation-Id` + /// header for this request. `null` (the default) lets the client attach + /// the header — matching iOS PFURLSessionCommandRunner behaviour. + final bool? sendInstallationId; // final ParseNetworkResponseType responseType; } diff --git a/packages/dart/lib/src/network/parse_client.dart b/packages/dart/lib/src/network/parse_client.dart index 2104fc20a..a013cd5db 100644 --- a/packages/dart/lib/src/network/parse_client.dart +++ b/packages/dart/lib/src/network/parse_client.dart @@ -69,6 +69,33 @@ abstract class ParseClient { @Deprecated("Use ParseCoreData() instead.") ParseCoreData get data => ParseCoreData(); + + /// Returns `options.headers` with `X-Parse-Installation-Id` attached unless + /// the caller opted out via `ParseNetworkOptions.sendInstallationId = false` + /// or the header is already set. Installation lookup failures fall through + /// silently — a network call should not fail because the install ID could + /// not be read. + @protected + Future?> buildHeaders( + ParseNetworkOptions? options, + ) async { + if (options?.sendInstallationId == false) return options?.headers; + if (options?.headers?[keyHeaderInstallationId] != null) { + return options?.headers; + } + String? installationId; + try { + installationId = + (await ParseInstallation.currentInstallation()).installationId; + } catch (_) { + return options?.headers; + } + if (installationId == null) return options?.headers; + return { + ...?options?.headers, + keyHeaderInstallationId: installationId, + }; + } } /// Callback to listen the progress for sending/receiving data. diff --git a/packages/dart/lib/src/network/parse_dio_client.dart b/packages/dart/lib/src/network/parse_dio_client.dart index fb6072bee..417ba5a37 100644 --- a/packages/dart/lib/src/network/parse_dio_client.dart +++ b/packages/dart/lib/src/network/parse_dio_client.dart @@ -47,12 +47,13 @@ class ParseDioClient extends ParseClient { ParseNetworkOptions? options, ProgressCallback? onReceiveProgress, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( operation: () async { try { final dio.Response dioResponse = await _client.get( path, - options: _Options(headers: options?.headers), + options: _Options(headers: headers), ); return ParseNetworkResponse( @@ -76,6 +77,7 @@ class ParseDioClient extends ParseClient { ProgressCallback? onReceiveProgress, dynamic cancelToken, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( operation: () async { try { @@ -85,7 +87,7 @@ class ParseDioClient extends ParseClient { cancelToken: cancelToken, onReceiveProgress: onReceiveProgress, options: _Options( - headers: options?.headers, + headers: headers, responseType: dio.ResponseType.bytes, ), ); @@ -116,6 +118,7 @@ class ParseDioClient extends ParseClient { String? data, ParseNetworkOptions? options, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( isWriteOperation: true, operation: () async { @@ -123,7 +126,7 @@ class ParseDioClient extends ParseClient { final dio.Response dioResponse = await _client.put( path, data: data, - options: _Options(headers: options?.headers), + options: _Options(headers: headers), ); return ParseNetworkResponse( @@ -146,6 +149,7 @@ class ParseDioClient extends ParseClient { String? data, ParseNetworkOptions? options, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( isWriteOperation: true, operation: () async { @@ -153,7 +157,7 @@ class ParseDioClient extends ParseClient { final dio.Response dioResponse = await _client.post( path, data: data, - options: _Options(headers: options?.headers), + options: _Options(headers: headers), ); return ParseNetworkResponse( @@ -178,6 +182,7 @@ class ParseDioClient extends ParseClient { ProgressCallback? onSendProgress, dynamic cancelToken, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( isWriteOperation: true, operation: () async { @@ -186,7 +191,7 @@ class ParseDioClient extends ParseClient { path, data: data, cancelToken: cancelToken, - options: _Options(headers: options?.headers), + options: _Options(headers: headers), onSendProgress: onSendProgress, ); @@ -235,12 +240,13 @@ class ParseDioClient extends ParseClient { String path, { ParseNetworkOptions? options, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( operation: () async { try { final dio.Response dioResponse = await _client.delete( path, - options: _Options(headers: options?.headers), + options: _Options(headers: headers), ); return ParseNetworkResponse( diff --git a/packages/dart/lib/src/network/parse_http_client.dart b/packages/dart/lib/src/network/parse_http_client.dart index d729733af..c88a78a07 100644 --- a/packages/dart/lib/src/network/parse_http_client.dart +++ b/packages/dart/lib/src/network/parse_http_client.dart @@ -47,12 +47,13 @@ class ParseHTTPClient extends ParseClient { ParseNetworkOptions? options, ProgressCallback? onReceiveProgress, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( operation: () async { try { final http.Response response = await _client.get( Uri.parse(path), - headers: options?.headers, + headers: headers, ); return ParseNetworkResponse( data: response.body, @@ -75,12 +76,13 @@ class ParseHTTPClient extends ParseClient { ProgressCallback? onReceiveProgress, dynamic cancelToken, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( operation: () async { try { final http.Response response = await _client.get( Uri.parse(path), - headers: options?.headers, + headers: headers, ); return ParseNetworkByteResponse( bytes: response.bodyBytes, @@ -102,6 +104,7 @@ class ParseHTTPClient extends ParseClient { String? data, ParseNetworkOptions? options, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( isWriteOperation: true, operation: () async { @@ -109,7 +112,7 @@ class ParseHTTPClient extends ParseClient { final http.Response response = await _client.put( Uri.parse(path), body: data, - headers: options?.headers, + headers: headers, ); return ParseNetworkResponse( data: response.body, @@ -131,6 +134,7 @@ class ParseHTTPClient extends ParseClient { String? data, ParseNetworkOptions? options, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( isWriteOperation: true, operation: () async { @@ -138,7 +142,7 @@ class ParseHTTPClient extends ParseClient { final http.Response response = await _client.post( Uri.parse(path), body: data, - headers: options?.headers, + headers: headers, ); return ParseNetworkResponse( data: response.body, @@ -162,6 +166,7 @@ class ParseHTTPClient extends ParseClient { ProgressCallback? onSendProgress, dynamic cancelToken, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( isWriteOperation: true, operation: () async { @@ -174,7 +179,7 @@ class ParseHTTPClient extends ParseClient { (List previous, List element) => previous..addAll(element), ), - headers: options?.headers, + headers: headers, ); return ParseNetworkResponse( data: response.body, @@ -195,12 +200,13 @@ class ParseHTTPClient extends ParseClient { String path, { ParseNetworkOptions? options, }) async { + final Map? headers = await buildHeaders(options); return executeWithRetry( operation: () async { try { final http.Response response = await _client.delete( Uri.parse(path), - headers: options?.headers, + headers: headers, ); return ParseNetworkResponse( data: response.body, diff --git a/packages/dart/lib/src/objects/parse_user.dart b/packages/dart/lib/src/objects/parse_user.dart index c89a5e87d..f2858a9bd 100644 --- a/packages/dart/lib/src/objects/parse_user.dart +++ b/packages/dart/lib/src/objects/parse_user.dart @@ -216,15 +216,11 @@ class ParseUser extends ParseObject implements ParseCloneable { final Uri url = getSanitisedUri(_client, path); final String body = json.encode(toJson(forApiRQ: true)); _saveChanges(); - final String? installationId = await _getInstallationId(); final ParseNetworkResponse response = await _client.post( url.toString(), options: ParseNetworkOptions( - headers: { - keyHeaderRevocableSession: '1', - if (installationId != null && !doNotSendInstallationID) - keyHeaderInstallationId: installationId, - }, + headers: {keyHeaderRevocableSession: '1'}, + sendInstallationId: !doNotSendInstallationID, ), data: body, ); @@ -255,18 +251,14 @@ class ParseUser extends ParseObject implements ParseCloneable { keyVarUsername: username!, keyVarPassword: password!, }; - final String? installationId = await _getInstallationId(); final Uri url = getSanitisedUri(_client, keyEndPointLogin); _saveChanges(); final ParseNetworkResponse response = await _client.post( url.toString(), data: jsonEncode(queryParams), options: ParseNetworkOptions( - headers: { - keyHeaderRevocableSession: '1', - if (installationId != null && !doNotSendInstallationID) - keyHeaderInstallationId: installationId, - }, + headers: {keyHeaderRevocableSession: '1'}, + sendInstallationId: !doNotSendInstallationID, ), ); @@ -292,16 +284,12 @@ class ParseUser extends ParseObject implements ParseCloneable { try { final Uri url = getSanitisedUri(_client, keyEndPointUsers); const Uuid uuid = Uuid(); - final String? installationId = await _getInstallationId(); final ParseNetworkResponse response = await _client.post( url.toString(), options: ParseNetworkOptions( - headers: { - keyHeaderRevocableSession: '1', - if (installationId != null && !doNotSendInstallationID) - keyHeaderInstallationId: installationId, - }, + headers: {keyHeaderRevocableSession: '1'}, + sendInstallationId: !doNotSendInstallationID, ), data: jsonEncode({ 'authData': { @@ -356,17 +344,13 @@ class ParseUser extends ParseObject implements ParseCloneable { }) async { try { final Uri url = getSanitisedUri(_client, keyEndPointUsers); - final String? installationId = await _getInstallationId(); final Map body = toJson(forApiRQ: true); body['authData'] = {provider: authData}; final ParseNetworkResponse response = await _client.post( url.toString(), options: ParseNetworkOptions( - headers: { - keyHeaderRevocableSession: '1', - if (installationId != null && !doNotSendInstallationID) - keyHeaderInstallationId: installationId, - }, + headers: {keyHeaderRevocableSession: '1'}, + sendInstallationId: !doNotSendInstallationID, ), data: jsonEncode(body), ); @@ -630,10 +614,4 @@ class ParseUser extends ParseObject implements ParseCloneable { static ParseUser _getEmptyUser() => ParseCoreData.instance.createParseUser(null, null, null); - - static Future _getInstallationId() async { - final ParseInstallation parseInstallation = - await ParseInstallation.currentInstallation(); - return parseInstallation.installationId; - } } diff --git a/packages/dart/test/src/network/parse_client_retry_integration_test.mocks.dart b/packages/dart/test/src/network/parse_client_retry_integration_test.mocks.dart index 9bc333c4e..ff801802b 100644 --- a/packages/dart/test/src/network/parse_client_retry_integration_test.mocks.dart +++ b/packages/dart/test/src/network/parse_client_retry_integration_test.mocks.dart @@ -209,4 +209,14 @@ class MockParseClient extends _i1.Mock implements _i2.ParseClient { ), ) as _i3.Future<_i2.ParseNetworkByteResponse>); + + @override + _i3.Future?> buildHeaders( + _i2.ParseNetworkOptions? options, + ) => + (super.noSuchMethod( + Invocation.method(#buildHeaders, [options]), + returnValue: _i3.Future?>.value(), + ) + as _i3.Future?>); } diff --git a/packages/dart/test/src/network/parse_client_test.dart b/packages/dart/test/src/network/parse_client_test.dart new file mode 100644 index 000000000..5ed1e11d8 --- /dev/null +++ b/packages/dart/test/src/network/parse_client_test.dart @@ -0,0 +1,128 @@ +import 'package:parse_server_sdk/parse_server_sdk.dart'; +import 'package:test/test.dart'; + +import '../../test_utils.dart'; + +/// Minimal concrete subclass for exercising methods on the abstract +/// [ParseClient]. All HTTP methods throw — the test suite only targets the +/// inherited helpers (`buildHeaders`) and never dispatches a real request. +class _StubParseClient extends ParseClient { + @override + Future get( + String path, { + ParseNetworkOptions? options, + ProgressCallback? onReceiveProgress, + }) => throw UnimplementedError(); + + @override + Future put( + String path, { + String? data, + ParseNetworkOptions? options, + }) => throw UnimplementedError(); + + @override + Future post( + String path, { + String? data, + ParseNetworkOptions? options, + }) => throw UnimplementedError(); + + @override + Future postBytes( + String path, { + Stream>? data, + ParseNetworkOptions? options, + ProgressCallback? onSendProgress, + dynamic cancelToken, + }) => throw UnimplementedError(); + + @override + Future delete( + String path, { + ParseNetworkOptions? options, + }) => throw UnimplementedError(); + + @override + Future getBytes( + String path, { + ParseNetworkOptions? options, + ProgressCallback? onReceiveProgress, + dynamic cancelToken, + }) => throw UnimplementedError(); + + // Exposes the protected helper so tests can call it without subclass tricks. + Future?> exposedBuildHeaders( + ParseNetworkOptions? options, + ) => buildHeaders(options); +} + +void main() { + setUpAll(() async { + await initializeParse(); + }); + + group('ParseClient.buildHeaders — X-Parse-Installation-Id handling', () { + late _StubParseClient client; + + setUp(() { + client = _StubParseClient(); + }); + + test('attaches X-Parse-Installation-Id by default. Matches iOS ' + 'PFURLSessionCommandRunner behaviour — every request carries the ' + 'install ID so parse-server can bind created _Session rows to the ' + 'right installation and so destroyDuplicatedSessions can clean up ' + 'prior sessions on the same install during login', () async { + final Map? headers = await client.exposedBuildHeaders( + null, + ); + + expect(headers, isNotNull); + expect(headers![keyHeaderInstallationId], isNotEmpty); + }); + + test('omits X-Parse-Installation-Id when caller passes ' + 'sendInstallationId: false. The opt-out is forwarded by methods such ' + 'as ParseUser.signUp(doNotSendInstallationID: true) for callers that ' + 'cannot allow-list the header on their parse-server', () async { + final Map? headers = await client.exposedBuildHeaders( + ParseNetworkOptions(sendInstallationId: false), + ); + + expect( + headers?[keyHeaderInstallationId], + isNull, + reason: + 'sendInstallationId=false must suppress the header even when ' + 'an install ID is available', + ); + }); + + test( + 'preserves a caller-supplied X-Parse-Installation-Id rather than ' + 'overwriting it. Lets advanced callers (tests, multi-tenant proxies) ' + 'inject a specific install ID without the client clobbering it', + () async { + final Map? headers = await client.exposedBuildHeaders( + ParseNetworkOptions( + headers: {keyHeaderInstallationId: 'caller-id'}, + ), + ); + + expect(headers![keyHeaderInstallationId], equals('caller-id')); + }, + ); + + test('merges caller-supplied headers with the install ID. Custom headers ' + 'and the auto-attached install ID must coexist — neither side ' + 'overrides the other', () async { + final Map? headers = await client.exposedBuildHeaders( + ParseNetworkOptions(headers: {'X-Custom': 'value'}), + ); + + expect(headers!['X-Custom'], equals('value')); + expect(headers[keyHeaderInstallationId], isNotEmpty); + }); + }); +} diff --git a/packages/dart/test/src/network/parse_dio_client_test.dart b/packages/dart/test/src/network/parse_dio_client_test.dart index a0723808c..4ff75130e 100644 --- a/packages/dart/test/src/network/parse_dio_client_test.dart +++ b/packages/dart/test/src/network/parse_dio_client_test.dart @@ -4,6 +4,31 @@ import 'package:test/test.dart'; import '../../test_utils.dart'; +/// Records the headers of every request made through it without performing +/// the actual HTTP call. Returns an empty 200 OK so callers can `await`. +class _HeaderCapturingAdapter implements HttpClientAdapter { + final List> requests = []; + + @override + Future fetch( + RequestOptions options, + Stream>? requestStream, + Future? cancelFuture, + ) async { + requests.add(Map.from(options.headers)); + return ResponseBody.fromString( + '{}', + 200, + headers: >{ + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + ); + } + + @override + void close({bool force = false}) {} +} + void main() { setUpAll(() async { await initializeParse(); @@ -57,4 +82,88 @@ void main() { expect(parseDioClient.additionalHeaders, isNull); }); }); + + group('ParseDioClient request pipeline integration', () { + late ParseDioClient parseDioClient; + late _HeaderCapturingAdapter adapter; + + setUp(() async { + parseDioClient = ParseDioClient(); + adapter = _HeaderCapturingAdapter(); + parseDioClient.client.httpClientAdapter = adapter; + }); + + test('headers returned by buildHeaders reach the outgoing request. This is ' + 'the wiring check between the inherited helper (covered in detail by ' + 'parse_client_test.dart) and dio\'s request pipeline — without it, a ' + 'refactor that bypassed buildHeaders would silently drop install IDs ' + 'on every request', () async { + await parseDioClient.put('$serverUrl/classes/_User/abc', data: '{}'); + + expect(adapter.requests, hasLength(1)); + expect( + adapter.requests.first[keyHeaderInstallationId], + isNotEmpty, + reason: 'install ID added by buildHeaders must reach the wire', + ); + }); + + test('get() attaches X-Parse-Installation-Id', () async { + await parseDioClient.get('$serverUrl/classes/Item/abc'); + + expect(adapter.requests, hasLength(1)); + expect(adapter.requests.first[keyHeaderInstallationId], isNotEmpty); + }); + + test('getBytes() attaches X-Parse-Installation-Id', () async { + await parseDioClient.getBytes('$serverUrl/files/abc.bin'); + + expect(adapter.requests, hasLength(1)); + expect(adapter.requests.first[keyHeaderInstallationId], isNotEmpty); + }); + + test('post() attaches X-Parse-Installation-Id', () async { + await parseDioClient.post('$serverUrl/classes/Item', data: '{"k":"v"}'); + + expect(adapter.requests, hasLength(1)); + expect(adapter.requests.first[keyHeaderInstallationId], isNotEmpty); + }); + + test('postBytes() attaches X-Parse-Installation-Id', () async { + await parseDioClient.postBytes( + '$serverUrl/files/abc.bin', + data: Stream>.fromIterable(>[ + [1, 2, 3], + ]), + ); + + expect(adapter.requests, hasLength(1)); + expect(adapter.requests.first[keyHeaderInstallationId], isNotEmpty); + }); + + test('delete() attaches X-Parse-Installation-Id', () async { + await parseDioClient.delete('$serverUrl/classes/Item/abc'); + + expect(adapter.requests, hasLength(1)); + expect(adapter.requests.first[keyHeaderInstallationId], isNotEmpty); + }); + + test('caller can suppress X-Parse-Installation-Id per request. ' + 'sendInstallationId: false matches ParseUser.signUp(doNotSendInstallationID: true) ' + 'and must propagate through every verb', () async { + await parseDioClient.post( + '$serverUrl/classes/Item', + data: '{"k":"v"}', + options: ParseNetworkOptions(sendInstallationId: false), + ); + + expect(adapter.requests, hasLength(1)); + expect( + adapter.requests.first[keyHeaderInstallationId], + isNull, + reason: + 'sendInstallationId: false must suppress the header on the wire', + ); + }); + }); } diff --git a/packages/dart/test/src/network/parse_http_client_test.dart b/packages/dart/test/src/network/parse_http_client_test.dart index 9f0a79751..3a7672767 100644 --- a/packages/dart/test/src/network/parse_http_client_test.dart +++ b/packages/dart/test/src/network/parse_http_client_test.dart @@ -1,3 +1,6 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:http/http.dart' as http; import 'package:parse_server_sdk/parse_server_sdk.dart'; import 'package:test/test.dart'; @@ -9,20 +12,119 @@ void main() { await initializeParse(); }); - group('ParseDioClient Tests', () { + group('ParseHTTPClient Tests', () { late ParseHTTPClient parseHTTPClient; setUp(() async { parseHTTPClient = ParseHTTPClient(); }); - test('should return an instance of Dio from dioClient', () { + test('should return an instance of http.BaseClient from client getter', () { // arrange - final dioClient = parseHTTPClient.client; + final httpClient = parseHTTPClient.client; // assert - expect(dioClient, isNotNull); - expect(dioClient, isA()); + expect(httpClient, isNotNull); + expect(httpClient, isA()); + }); + }); + + /// Verifies headers returned by `buildHeaders` reach the wire through each + /// HTTP verb. ParseHTTPClient has no in-process injection seam for its + /// underlying http.Client, so the test spins up a localhost HttpServer that + /// captures the request headers and replies with a minimal 200 response. + group('ParseHTTPClient request pipeline integration', () { + late ParseHTTPClient parseHTTPClient; + late HttpServer server; + late List captured; + late String baseUrl; + + setUp(() async { + parseHTTPClient = ParseHTTPClient(); + server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + baseUrl = 'http://${server.address.host}:${server.port}'; + captured = []; + server.listen((HttpRequest request) async { + captured.add(request); + // Drain any body so the client receives a complete response. + await request.drain(); + request.response.statusCode = HttpStatus.ok; + request.response.headers.contentType = ContentType.json; + request.response.write('{}'); + await request.response.close(); + }); + }); + + tearDown(() async { + await server.close(force: true); + }); + + String headerOf(HttpRequest request, String name) => + request.headers.value(name) ?? ''; + + test('get() attaches X-Parse-Installation-Id', () async { + await parseHTTPClient.get('$baseUrl/classes/Item/abc'); + + expect(captured, hasLength(1)); + expect(headerOf(captured.first, keyHeaderInstallationId), isNotEmpty); + }); + + test('getBytes() attaches X-Parse-Installation-Id', () async { + await parseHTTPClient.getBytes('$baseUrl/files/abc.bin'); + + expect(captured, hasLength(1)); + expect(headerOf(captured.first, keyHeaderInstallationId), isNotEmpty); + }); + + test('put() attaches X-Parse-Installation-Id', () async { + await parseHTTPClient.put('$baseUrl/classes/Item/abc', data: '{}'); + + expect(captured, hasLength(1)); + expect(headerOf(captured.first, keyHeaderInstallationId), isNotEmpty); + }); + + test('post() attaches X-Parse-Installation-Id', () async { + await parseHTTPClient.post('$baseUrl/classes/Item', data: '{"k":"v"}'); + + expect(captured, hasLength(1)); + expect(headerOf(captured.first, keyHeaderInstallationId), isNotEmpty); + }); + + test('postBytes() attaches X-Parse-Installation-Id', () async { + await parseHTTPClient.postBytes( + '$baseUrl/files/abc.bin', + data: Stream>.fromIterable(>[ + [1, 2, 3], + ]), + ); + + expect(captured, hasLength(1)); + expect(headerOf(captured.first, keyHeaderInstallationId), isNotEmpty); + }); + + test('delete() attaches X-Parse-Installation-Id', () async { + await parseHTTPClient.delete('$baseUrl/classes/Item/abc'); + + expect(captured, hasLength(1)); + expect(headerOf(captured.first, keyHeaderInstallationId), isNotEmpty); + }); + + test('caller can suppress X-Parse-Installation-Id per request. ' + 'sendInstallationId: false matches ParseUser.signUp(doNotSendInstallationID: true) ' + 'and must propagate through every verb', () async { + await parseHTTPClient.post( + '$baseUrl/classes/Item', + data: '{"k":"v"}', + options: ParseNetworkOptions(sendInstallationId: false), + ); + + expect(captured, hasLength(1)); + expect( + captured.first.headers.value(keyHeaderInstallationId), + isNull, + reason: + 'sendInstallationId: false must suppress the header on the wire', + ); }); }); } diff --git a/packages/dart/test/src/network/parse_query_test.mocks.dart b/packages/dart/test/src/network/parse_query_test.mocks.dart index 4cdbf3808..862284629 100644 --- a/packages/dart/test/src/network/parse_query_test.mocks.dart +++ b/packages/dart/test/src/network/parse_query_test.mocks.dart @@ -209,4 +209,14 @@ class MockParseClient extends _i1.Mock implements _i2.ParseClient { ), ) as _i3.Future<_i2.ParseNetworkByteResponse>); + + @override + _i3.Future?> buildHeaders( + _i2.ParseNetworkOptions? options, + ) => + (super.noSuchMethod( + Invocation.method(#buildHeaders, [options]), + returnValue: _i3.Future?>.value(), + ) + as _i3.Future?>); } diff --git a/packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart b/packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart index 39a066aef..21904274f 100644 --- a/packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart +++ b/packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart @@ -209,4 +209,14 @@ class MockParseClient extends _i1.Mock implements _i2.ParseClient { ), ) as _i3.Future<_i2.ParseNetworkByteResponse>); + + @override + _i3.Future?> buildHeaders( + _i2.ParseNetworkOptions? options, + ) => + (super.noSuchMethod( + Invocation.method(#buildHeaders, [options]), + returnValue: _i3.Future?>.value(), + ) + as _i3.Future?>); }