|
12 | 12 | // See the License for the specific language governing permissions and |
13 | 13 | // limitations under the License. |
14 | 14 |
|
| 15 | +import 'dart:convert'; |
| 16 | + |
15 | 17 | import 'package:firebase_ai/firebase_ai.dart'; |
16 | 18 | import 'package:firebase_app_check/firebase_app_check.dart'; |
17 | 19 | import 'package:firebase_auth/firebase_auth.dart'; |
18 | 20 | import 'package:firebase_core/firebase_core.dart'; |
| 21 | +import 'package:flutter/services.dart'; |
19 | 22 | import 'package:flutter_test/flutter_test.dart'; |
| 23 | +import 'package:http/http.dart' as http; |
20 | 24 |
|
21 | 25 | import 'mock.dart'; |
22 | 26 |
|
@@ -50,6 +54,16 @@ void main() { |
50 | 54 | customAppCheck = FirebaseAppCheck.instanceFor(app: customApp); |
51 | 55 | limitTokenAppCheck = FirebaseAppCheck.instanceFor(app: limitTokenApp); |
52 | 56 | customAuth = FirebaseAuth.instanceFor(app: customApp); |
| 57 | + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger |
| 58 | + .setMockMessageHandler( |
| 59 | + 'dev.flutter.pigeon.firebase_app_check_platform_interface.' |
| 60 | + 'FirebaseAppCheckHostApi.getToken', |
| 61 | + (_) async { |
| 62 | + return const StandardMessageCodec().encodeMessage( |
| 63 | + <Object?>['app-check-token'], |
| 64 | + ); |
| 65 | + }, |
| 66 | + ); |
53 | 67 | }); |
54 | 68 |
|
55 | 69 | test('Singleton behavior', () { |
@@ -129,5 +143,67 @@ void main() { |
129 | 143 |
|
130 | 144 | expect(model, isA<GenerativeModel>()); |
131 | 145 | }); |
| 146 | + |
| 147 | + test('generativeModel uses provided HTTP client', () async { |
| 148 | + final requests = <http.BaseRequest>[]; |
| 149 | + final client = _RecordingClient((request) { |
| 150 | + requests.add(request); |
| 151 | + |
| 152 | + if (request.url.path.endsWith(':streamGenerateContent')) { |
| 153 | + return http.StreamedResponse( |
| 154 | + Stream.value(utf8.encode('data: ${jsonEncode(_response)}\n\n')), |
| 155 | + 200, |
| 156 | + ); |
| 157 | + } |
| 158 | + |
| 159 | + return http.StreamedResponse( |
| 160 | + Stream.value(utf8.encode(jsonEncode(_response))), |
| 161 | + 200, |
| 162 | + ); |
| 163 | + }); |
| 164 | + final ai = FirebaseAI.googleAI(app: app); |
| 165 | + |
| 166 | + final model = ai.generativeModel( |
| 167 | + model: 'gemini-pro', |
| 168 | + httpClient: client, |
| 169 | + ); |
| 170 | + |
| 171 | + await model.generateContent([Content.text('prompt')]); |
| 172 | + await model.generateContentStream([Content.text('prompt')]).drain<void>(); |
| 173 | + |
| 174 | + expect(requests, hasLength(2)); |
| 175 | + expect( |
| 176 | + requests.first.url.path, |
| 177 | + endsWith('/models/gemini-pro:generateContent'), |
| 178 | + ); |
| 179 | + expect( |
| 180 | + requests.last.url.path, |
| 181 | + endsWith('/models/gemini-pro:streamGenerateContent'), |
| 182 | + ); |
| 183 | + }); |
132 | 184 | }); |
133 | 185 | } |
| 186 | + |
| 187 | +class _RecordingClient extends http.BaseClient { |
| 188 | + _RecordingClient(this._handler); |
| 189 | + |
| 190 | + final http.StreamedResponse Function(http.BaseRequest request) _handler; |
| 191 | + |
| 192 | + @override |
| 193 | + Future<http.StreamedResponse> send(http.BaseRequest request) async { |
| 194 | + return _handler(request); |
| 195 | + } |
| 196 | +} |
| 197 | + |
| 198 | +const _response = { |
| 199 | + 'candidates': [ |
| 200 | + { |
| 201 | + 'content': { |
| 202 | + 'role': 'model', |
| 203 | + 'parts': [ |
| 204 | + {'text': 'Some Response'}, |
| 205 | + ], |
| 206 | + }, |
| 207 | + }, |
| 208 | + ], |
| 209 | +}; |
0 commit comments