diff --git a/lib/main_development.dart b/lib/main_development.dart index 268fc3f..e91da0b 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -7,8 +7,6 @@ void main() { () { final apiClient = ApiClient( baseUrl: 'http://localhost:8080', - idTokenStream: const Stream.empty(), - refreshIdToken: () async => Future.value(), ); return App( diff --git a/lib/main_production.dart b/lib/main_production.dart index 42aeb07..b08e7d2 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -7,8 +7,6 @@ void main() { () { final apiClient = ApiClient( baseUrl: 'http://production', - idTokenStream: const Stream.empty(), - refreshIdToken: () async => Future.value(), ); return App( diff --git a/packages/api_client/example/main.dart b/packages/api_client/example/main.dart new file mode 100644 index 0000000..68a2cff --- /dev/null +++ b/packages/api_client/example/main.dart @@ -0,0 +1,11 @@ +import 'package:api_client/api_client.dart'; + +Future main() async { + final apiClient = ApiClient( + baseUrl: '', + ); + final questionsResource = apiClient.questionsResource; + final answer = await questionsResource.getVertexResponse('random'); + // ignore: avoid_print + print(answer); +} diff --git a/packages/api_client/lib/api_client.dart b/packages/api_client/lib/api_client.dart index c2a514e..7fdabeb 100644 --- a/packages/api_client/lib/api_client.dart +++ b/packages/api_client/lib/api_client.dart @@ -2,3 +2,5 @@ library api_client; export 'src/api_client.dart'; +export 'src/models/models.dart'; +export 'src/resources/resources.dart'; diff --git a/packages/api_client/lib/src/api_client.dart b/packages/api_client/lib/src/api_client.dart index b901a43..1702cf6 100644 --- a/packages/api_client/lib/src/api_client.dart +++ b/packages/api_client/lib/src/api_client.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:api_client/api_client.dart'; import 'package:http/http.dart' as http; /// {@template api_client_error} @@ -58,8 +59,6 @@ class ApiClient { /// {@macro api_client} ApiClient({ required String baseUrl, - required Stream idTokenStream, - required Future Function() refreshIdToken, PostCall postCall = http.post, PutCall putCall = http.put, PatchCall patchCall = http.patch, @@ -68,26 +67,16 @@ class ApiClient { _post = postCall, _put = putCall, _patch = patchCall, - _get = getCall, - _refreshIdToken = refreshIdToken { - _idTokenSubscription = idTokenStream.listen((idToken) { - _idToken = idToken; - }); - } + _get = getCall; final Uri _base; final PostCall _post; final PostCall _put; final PatchCall _patch; final GetCall _get; - final Future Function() _refreshIdToken; - late final StreamSubscription _idTokenSubscription; - String? _idToken; - - Map get _headers => { - if (_idToken != null) 'Authorization': 'Bearer $_idToken', - }; + /// Questions resource. + late final QuestionsResource questionsResource = const QuestionsResource(); Future _handleUnauthorized( Future Function() sendRequest, @@ -95,17 +84,11 @@ class ApiClient { final response = await sendRequest(); if (response.statusCode == HttpStatus.unauthorized) { - _idToken = await _refreshIdToken(); return sendRequest(); } return response; } - /// Dispose of resources used by this client. - Future dispose() async { - await _idTokenSubscription.cancel(); - } - /// Sends a POST request to the specified [path] with the given [body]. Future post( String path, { @@ -119,7 +102,6 @@ class ApiClient { queryParameters: queryParameters, ), body: body, - headers: _headers..addContentTypeJson(), ); return response; @@ -139,7 +121,6 @@ class ApiClient { queryParameters: queryParameters, ), body: body, - headers: _headers..addContentTypeJson(), ); return response; @@ -155,7 +136,6 @@ class ApiClient { final response = await _put( _base.replace(path: path), body: body, - headers: _headers..addContentTypeJson(), ); return response; @@ -173,16 +153,9 @@ class ApiClient { path: path, queryParameters: queryParameters, ), - headers: _headers, ); return response; }); } } - -extension on Map { - void addContentTypeJson() { - addAll({HttpHeaders.contentTypeHeader: ContentType.json.value}); - } -} diff --git a/packages/api_client/lib/src/models/models.dart b/packages/api_client/lib/src/models/models.dart new file mode 100644 index 0000000..c23d329 --- /dev/null +++ b/packages/api_client/lib/src/models/models.dart @@ -0,0 +1,3 @@ +export 'vertex_document.dart'; +export 'vertex_metadata.dart'; +export 'vertex_response.dart'; diff --git a/packages/api_client/lib/src/models/vertex_document.dart b/packages/api_client/lib/src/models/vertex_document.dart new file mode 100644 index 0000000..1dc75cf --- /dev/null +++ b/packages/api_client/lib/src/models/vertex_document.dart @@ -0,0 +1,26 @@ +import 'package:api_client/api_client.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'vertex_document.g.dart'; + +/// {@template vertex_document} +/// Vertex document +/// {@endtemplate} +@JsonSerializable(createToJson: false) +class VertexDocument extends Equatable { + /// {@macro vertex_document} + const VertexDocument({ + required this.metadata, + }); + + /// Convert from Map to [VertexDocument] + factory VertexDocument.fromJson(Map json) => + _$VertexDocumentFromJson(json); + + /// Metadata. + final VertexMetadata metadata; + + @override + List get props => [metadata]; +} diff --git a/packages/api_client/lib/src/models/vertex_document.g.dart b/packages/api_client/lib/src/models/vertex_document.g.dart new file mode 100644 index 0000000..569a86f --- /dev/null +++ b/packages/api_client/lib/src/models/vertex_document.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'vertex_document.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VertexDocument _$VertexDocumentFromJson(Map json) => + VertexDocument( + metadata: + VertexMetadata.fromJson(json['metadata'] as Map), + ); diff --git a/packages/api_client/lib/src/models/vertex_metadata.dart b/packages/api_client/lib/src/models/vertex_metadata.dart new file mode 100644 index 0000000..7f15016 --- /dev/null +++ b/packages/api_client/lib/src/models/vertex_metadata.dart @@ -0,0 +1,34 @@ +import 'package:api_client/api_client.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'vertex_metadata.g.dart'; + +/// {@template vertex_metadata} +/// Vertex metadata inside a [VertexResponse] +/// {@endtemplate} +@JsonSerializable(createToJson: false) +class VertexMetadata extends Equatable { + /// {@macro vertex_metadata} + const VertexMetadata({ + required this.url, + required this.title, + required this.description, + }); + + /// Convert from Map to [VertexMetadata] + factory VertexMetadata.fromJson(Map json) => + _$VertexMetadataFromJson(json); + + /// Url + final String url; + + /// Title + final String title; + + /// Description + final String description; + + @override + List get props => [url, title, description]; +} diff --git a/packages/api_client/lib/src/models/vertex_metadata.g.dart b/packages/api_client/lib/src/models/vertex_metadata.g.dart new file mode 100644 index 0000000..668e1db --- /dev/null +++ b/packages/api_client/lib/src/models/vertex_metadata.g.dart @@ -0,0 +1,14 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'vertex_metadata.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VertexMetadata _$VertexMetadataFromJson(Map json) => + VertexMetadata( + url: json['url'] as String, + title: json['title'] as String, + description: json['description'] as String, + ); diff --git a/packages/api_client/lib/src/models/vertex_response.dart b/packages/api_client/lib/src/models/vertex_response.dart new file mode 100644 index 0000000..2e93e97 --- /dev/null +++ b/packages/api_client/lib/src/models/vertex_response.dart @@ -0,0 +1,31 @@ +import 'package:api_client/api_client.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'vertex_response.g.dart'; + +/// {@template vertex_response} +/// Vertex response +/// {@endtemplate} +@JsonSerializable(createToJson: false) +class VertexResponse extends Equatable { + /// {@macro vertex_response} + + const VertexResponse({ + required this.summary, + required this.documents, + }); + + /// Convert from Map to [VertexResponse] + factory VertexResponse.fromJson(Map json) => + _$VertexResponseFromJson(json); + + /// Summary. + final String summary; + + /// Documents. + final List documents; + + @override + List get props => [documents]; +} diff --git a/packages/api_client/lib/src/models/vertex_response.g.dart b/packages/api_client/lib/src/models/vertex_response.g.dart new file mode 100644 index 0000000..3430a3e --- /dev/null +++ b/packages/api_client/lib/src/models/vertex_response.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'vertex_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +VertexResponse _$VertexResponseFromJson(Map json) => + VertexResponse( + summary: json['summary'] as String, + documents: (json['documents'] as List) + .map((e) => VertexDocument.fromJson(e as Map)) + .toList(), + ); diff --git a/packages/api_client/lib/src/resources/fake_response.json b/packages/api_client/lib/src/resources/fake_response.json new file mode 100644 index 0000000..1daee5b --- /dev/null +++ b/packages/api_client/lib/src/resources/fake_response.json @@ -0,0 +1,168 @@ +{ + "summary": "Hot reload loads code changes into the VM and re-builds the widget tree, preserving the app state [1]. Hot restart loads code changes into the VM, and restarts the Flutter app, losing the app state [1].", + "total_size": 701, + "attribution_token": "W_BaCgwIiN6vqgYQ5O6q0AMSJDY1NDNiODJhLTAwMDAtMjA3NS05YmQyLTE0YzE0ZWY0ZWVmMCIHR0VORVJJQyocpovvF8XL8xecho4iooaOIsLwnhXUsp0Vjr6dFQ", + "next_page_token": "AjZlVGNmVGNxMGNx0iMkJWOtUzNwITLwADMw0SOygjYzQTN2QiGCEM9JyMEGo6vHrICMIBMxIgC", + "documents": [ + { + "id": "fee364b1-5b75-4042-9669-2bd123364094", + "metadata": { + "url": "https://docs.flutter.dev/tools/hot-reload", + "title": "Hot reload | Flutter", + "tags": "missing", + "keywords": "", + "description": "Speed up development using Flutter's hot reload feature.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/tools/hot-reload", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-tools-hot-reload.html", + "snippets": [ + "... What is the difference between hot reload, hot restart, and full restart? Hot reload loads code changes into the VM and re-builds the widget tree ..." + ] + }, + { + "id": "8fec71e3-4dbb-4948-b3ca-ec13af65691e", + "metadata": { + "url": "https://docs.flutter.dev/tools/vs-code", + "title": "Visual Studio Code | Flutter", + "tags": "fake", + "keywords": "", + "description": "How to develop Flutter apps in Visual Studio Code.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/tools/vs-code", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-tools-vs-code.html", + "snippets": [ + "... with a placeholder Play ... command from the Command Palette. Hot reload vs. hot restart Hot reload works by injecting updated source code files into the running ..." + ] + }, + { + "id": "e5e6bfd4-6da8-44a9-8ced-5a9493960087", + "metadata": { + "url": "https://docs.flutter.dev/tools/android-studio", + "title": "Android Studio and IntelliJ | Flutter", + "tags": "missing", + "keywords": "", + "description": "How to develop Flutter apps in Android Studio or other IntelliJ products.\n", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/tools/android-studio", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-tools-android-studio.html", + "snippets": [ + "... between Android Studio and IntelliJ. In Android Studio: In the IDE, click ... Hot reload vs. hot restart Hot reload works by injecting updated source code files ..." + ] + }, + { + "id": "b5103539-2e8f-4450-952b-b21428972bb5", + "metadata": { + "url": "https://docs.flutter.dev/platform-integration/web/faq", + "title": "Web FAQ | Flutter", + "tags": "unknown", + "keywords": "", + "description": "Some gotchas and differences when writing or running web apps in Flutter.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/platform-integration/web/faq", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-platform-integration-web-faq.html", + "snippets": [ + "See building a web app with Flutter. Does hot reload work with a web app? No ... feature for Flutter mobile development. The only difference is that hot reload ..." + ] + }, + { + "id": "aa9bb7db-a635-4a85-b7ca-239a9386b53f", + "metadata": { + "url": "https://docs.flutter.dev/add-to-app/debugging", + "title": "Debug your add-to-app module | Flutter", + "tags": "fake", + "keywords": "", + "description": "How to run, debug, and hot reload your add-to-app Flutter module.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/add-to-app/debugging", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-add-to-app-debugging.html", + "snippets": [ + "... with a placeholder Play and pause a video Navigation & routing Overview Add ... Hot reload Profiling Install Flutter DevTools Cookbook Codelabs Get started ..." + ] + }, + { + "id": "b3559279-c9a9-4349-9fa6-5afbb30b572e", + "metadata": { + "url": "https://docs.flutter.dev/testing/build-modes", + "title": "Flutter's build modes | Flutter", + "tags": "missing", + "keywords": "", + "description": "Describes Flutter's build modes and when you should use debug, release, or profile mode.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/testing/build-modes", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-testing-build-modes.html", + "snippets": [ + "... with a placeholder Play and pause a video Navigation & routing ... Hot reload Profiling Install Flutter DevTools Cookbook Codelabs Get started Flutter 3.13 ..." + ] + }, + { + "id": "c0c9df6a-7f18-439b-b55a-269230829dc7", + "metadata": { + "url": "https://docs.flutter.dev/packages-and-plugins/using-packages", + "title": "Using packages | Flutter", + "tags": "unknown", + "keywords": "", + "description": "How to use packages in your Flutter app.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/packages-and-plugins/using-packages", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-packages-and-plugins-using-packages.html", + "snippets": [ + "scratch. What is the difference between a package and a plugin? A plugin is a ... Hot reload and hot restart only update the Dart code, so a full restart of ..." + ] + }, + { + "id": "f1652580-ef94-4620-9b9f-e012cef86977", + "metadata": { + "url": "https://docs.flutter.dev/resources/faq", + "title": "FAQ | Flutter", + "tags": "missing", + "keywords": "", + "description": "Frequently asked questions and answers about Flutter.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/resources/faq", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-resources-faq.html", + "snippets": [ + "between edit and refresh? How is hot reload different from hot restart? Where can I deploy my Flutter app? What devices and OS versions does Flutter run on ..." + ] + }, + { + "id": "9b8c10b0-0d70-4cc8-ba5c-f85237350ab2", + "metadata": { + "url": "https://docs.flutter.dev/get-started/test-drive", + "title": "Test drive | Flutter", + "tags": "missing", + "keywords": "", + "description": "How to create a templated Flutter app and use hot reload.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/get-started/test-drive", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-get-started-test-drive.html", + "snippets": [ + "... How to use \u201chot reload\u201d after you make changes to the app. Details for these ... restart it for VS Code's Flutter plugin to detect the Flutter SDK ..." + ] + }, + { + "id": "407b18f1-c655-438c-a033-2d42df430def", + "metadata": { + "url": "https://docs.flutter.dev/ui/interactivity", + "title": "Add interactivity to your Flutter app | Flutter", + "tags": "missing", + "keywords": "", + "description": "How to implement a stateful widget that responds to taps.", + "image_uri": "/assets/images/shared/brand/flutter/logo+text/horizontal/default.svg" + }, + "link": "https://docs.flutter.dev/ui/interactivity", + "doc_link": "gs://alanblount-sandbox-flutter-docs/docs-flutter-dev-ui-interactivity.html", + "snippets": [ + "Flutter Understanding constraints Flutter's build modes Hot reload ... How to create a custom widget. The difference between stateless and stateful widgets." + ] + } + ] +} \ No newline at end of file diff --git a/packages/api_client/lib/src/resources/questions_resource.dart b/packages/api_client/lib/src/resources/questions_resource.dart new file mode 100644 index 0000000..2850c70 --- /dev/null +++ b/packages/api_client/lib/src/resources/questions_resource.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:api_client/api_client.dart'; + +/// {@template questions_resource} +/// An api resource to get response to question from the VERTEX api +/// {@endtemplate} +class QuestionsResource { + /// {@macro questions_resource} + const QuestionsResource(); + + /// Get /game/prompt/terms + /// + /// Returns a [List]. + Future getVertexResponse(String query) async { + const path = 'lib/src/resources/fake_response.json'; + final fakeResponse = await File(path).readAsString(); + + final json = jsonDecode(fakeResponse) as Map; + return VertexResponse.fromJson(json); + } +} diff --git a/packages/api_client/lib/src/resources/resources.dart b/packages/api_client/lib/src/resources/resources.dart new file mode 100644 index 0000000..bff72f3 --- /dev/null +++ b/packages/api_client/lib/src/resources/resources.dart @@ -0,0 +1 @@ +export 'questions_resource.dart'; diff --git a/packages/api_client/pubspec.yaml b/packages/api_client/pubspec.yaml index 5700f71..c9d52aa 100644 --- a/packages/api_client/pubspec.yaml +++ b/packages/api_client/pubspec.yaml @@ -6,10 +6,14 @@ publish_to: none environment: sdk: ">=3.1.0 <4.0.0" +dependencies: + equatable: ^2.0.5 + http: ^1.1.0 + json_annotation: ^4.8.1 + dev_dependencies: + build_runner: ^2.4.6 + json_serializable: ^6.7.1 mocktail: ^1.0.0 test: ^1.19.2 very_good_analysis: ^5.1.0 - -dependencies: - http: ^1.1.0 diff --git a/packages/api_client/test/src/api_client_test.dart b/packages/api_client/test/src/api_client_test.dart index 69503d7..9f335e3 100644 --- a/packages/api_client/test/src/api_client_test.dart +++ b/packages/api_client/test/src/api_client_test.dart @@ -34,17 +34,12 @@ void main() { group('ApiClient', () { const baseUrl = 'http://baseurl.com'; - const mockIdToken = 'mockIdToken'; - const mockNewIdToken = 'mockNewIdToken'; final testJson = {'data': 'test'}; final expectedResponse = http.Response(testJson.toString(), 200); late ApiClient subject; late _MockHttpClient httpClient; - late StreamController idTokenStreamController; - - Future Function() refreshIdToken = () async => null; setUp(() { httpClient = _MockHttpClient(); @@ -80,16 +75,12 @@ void main() { ), ).thenAnswer((_) async => expectedResponse); - idTokenStreamController = StreamController.broadcast(); - subject = ApiClient( baseUrl: baseUrl, getCall: httpClient.get, postCall: httpClient.post, patchCall: httpClient.patch, putCall: httpClient.put, - idTokenStream: idTokenStreamController.stream, - refreshIdToken: () => refreshIdToken(), ); }); @@ -97,23 +88,11 @@ void main() { expect( ApiClient( baseUrl: 'http://localhost', - idTokenStream: Stream.empty(), - refreshIdToken: () async => null, ), isNotNull, ); }); - group('dispose', () { - test('cancels id token stream subscription', () async { - expect(idTokenStreamController.hasListener, isTrue); - - await subject.dispose(); - - expect(idTokenStreamController.hasListener, isFalse); - }); - }); - group('get', () { setUp(() { when( @@ -146,53 +125,6 @@ void main() { ), ).called(1); }); - - test('sends the authentication and app check token', () async { - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.get('/path/to/endpoint'); - - verify( - () => httpClient.get( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - }, - ), - ).called(1); - }); - - test('refreshes the authentication token when needed', () async { - when( - () => httpClient.get( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer((_) async => http.Response('', 401)); - - refreshIdToken = () async => mockNewIdToken; - - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.get('/path/to/endpoint'); - - verify( - () => httpClient.get( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - }, - ), - ).called(1); - verify( - () => httpClient.get( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockNewIdToken', - }, - ), - ).called(1); - }); }); group('post', () { @@ -218,56 +150,6 @@ void main() { ), ).called(1); }); - - test('sends the authentication and app check token', () async { - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.post('/path/to/endpoint'); - - verify( - () => httpClient.post( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - }); - - test('refreshes the authentication token when needed', () async { - when( - () => httpClient.post( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer((_) async => http.Response('', 401)); - - refreshIdToken = () async => mockNewIdToken; - - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.post('/path/to/endpoint'); - - verify( - () => httpClient.post( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - verify( - () => httpClient.post( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockNewIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - }); }); group('patch', () { @@ -293,56 +175,6 @@ void main() { ), ).called(1); }); - - test('sends the authentication and app check token', () async { - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.patch('/path/to/endpoint'); - - verify( - () => httpClient.patch( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - }); - - test('refreshes the authentication token when needed', () async { - when( - () => httpClient.patch( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer((_) async => http.Response('', 401)); - - refreshIdToken = () async => mockNewIdToken; - - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.patch('/path/to/endpoint'); - - verify( - () => httpClient.patch( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - verify( - () => httpClient.patch( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockNewIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - }); }); group('put', () { @@ -367,56 +199,6 @@ void main() { ), ).called(1); }); - - test('sends the authentication and app check token', () async { - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.put('/path/to/endpoint'); - - verify( - () => httpClient.put( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - }); - - test('refreshes the authentication token when needed', () async { - when( - () => httpClient.put( - any(), - headers: any(named: 'headers'), - ), - ).thenAnswer((_) async => http.Response('', 401)); - - refreshIdToken = () async => mockNewIdToken; - - idTokenStreamController.add(mockIdToken); - await Future.microtask(() {}); - await subject.put('/path/to/endpoint'); - - verify( - () => httpClient.put( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - verify( - () => httpClient.put( - Uri.parse('$baseUrl/path/to/endpoint'), - headers: { - 'Authorization': 'Bearer $mockNewIdToken', - HttpHeaders.contentTypeHeader: ContentType.json.value, - }, - ), - ).called(1); - }); }); group('ApiClientError', () {