@@ -27,8 +27,9 @@ import * as mocks from '../../resources/mocks';
2727import { DATA_CONNECT_ERROR_CODE_MAPPING , DataConnectApiClient , FirebaseDataConnectError }
2828 from '../../../src/data-connect/data-connect-api-client-internal' ;
2929import { FirebaseApp } from '../../../src/app/firebase-app' ;
30- import { ConnectorConfig } from '../../../src/data-connect' ;
30+ import { ConnectorConfig , GraphqlOptions } from '../../../src/data-connect' ;
3131import { getMetricsHeader , getSdkVersion } from '../../../src/utils' ;
32+ import { DataConnectService } from '../../../src/data-connect/data-connect' ;
3233
3334describe ( 'DataConnectApiClient' , ( ) => {
3435
@@ -231,11 +232,113 @@ describe('DataConnectApiClient', () => {
231232 } ) ;
232233 } ) ;
233234
234- describe ( 'impersonate' , ( ) => {
235+ describe ( 'impersonateQuery' , ( ) => {
236+ const unauthenticatedOptions : GraphqlOptions < unknown > =
237+ { operationName : 'operationName' , impersonate : { unauthenticated : true } } ;
238+
239+ it ( 'should reject when no operationName is provided' , ( ) => {
240+ apiClient . impersonateQuery ( { impersonate : { unauthenticated : true } } )
241+ . should . eventually . be . rejectedWith ( '`query` must be a non-empty string.' ) ;
242+ apiClient . impersonateQuery ( { operationName : undefined , impersonate : { unauthenticated : true } } )
243+ . should . eventually . be . rejectedWith ( '`query` must be a non-empty string.' ) ;
244+ } ) ;
245+ it ( 'should reject when no impersonate object is provided' , ( ) => {
246+ apiClient . impersonateQuery ( { operationName : 'queryName' } )
247+ . should . eventually . be . rejectedWith ( 'GraphqlOptions must be a non-null object' ) ;
248+ apiClient . impersonateQuery ( { operationName : 'queryName' , impersonate : undefined } )
249+ . should . eventually . be . rejectedWith ( 'GraphqlOptions must be a non-null object' ) ;
250+ } ) ;
235251 it ( 'should reject when project id is not available' , ( ) => {
236- return clientWithoutProjectId . executeGraphql ( 'query' , { } )
252+ clientWithoutProjectId . impersonateQuery ( unauthenticatedOptions )
237253 . should . eventually . be . rejectedWith ( noProjectId ) ;
238254 } ) ;
255+
256+ it ( 'should reject when a full platform error response is received' , ( ) => {
257+ sandbox
258+ . stub ( HttpClient . prototype , 'send' )
259+ . rejects ( utils . errorFrom ( ERROR_RESPONSE , 404 ) ) ;
260+ const expected = new FirebaseDataConnectError ( 'not-found' , 'Requested entity not found' ) ;
261+ return apiClient . impersonateQuery ( unauthenticatedOptions )
262+ . should . eventually . be . rejected . and . deep . include ( expected ) ;
263+ } ) ;
264+
265+ it ( 'should reject with unknown-error when error code is not present' , ( ) => {
266+ sandbox
267+ . stub ( HttpClient . prototype , 'send' )
268+ . rejects ( utils . errorFrom ( { } , 404 ) ) ;
269+ const expected = new FirebaseDataConnectError ( 'unknown-error' , 'Unknown server error: {}' ) ;
270+ return apiClient . impersonateQuery ( unauthenticatedOptions )
271+ . should . eventually . be . rejected . and . deep . include ( expected ) ;
272+ } ) ;
273+
274+ it ( 'should reject with unknown-error for non-json response' , ( ) => {
275+ sandbox
276+ . stub ( HttpClient . prototype , 'send' )
277+ . rejects ( utils . errorFrom ( 'not json' , 404 ) ) ;
278+ const expected = new FirebaseDataConnectError (
279+ 'unknown-error' , 'Unexpected response with status: 404 and body: not json' ) ;
280+ return apiClient . impersonateQuery ( unauthenticatedOptions )
281+ . should . eventually . be . rejected . and . deep . include ( expected ) ;
282+ } ) ;
283+
284+ it ( 'should reject when rejected with a FirebaseDataConnectError' , ( ) => {
285+ const expected = new FirebaseDataConnectError ( 'internal-error' , 'socket hang up' ) ;
286+ sandbox
287+ . stub ( HttpClient . prototype , 'send' )
288+ . rejects ( expected ) ;
289+ return apiClient . impersonateQuery ( unauthenticatedOptions )
290+ . should . eventually . be . rejected . and . deep . include ( expected ) ;
291+ } ) ;
292+
293+ it ( 'should resolve with the GraphQL response on success' , ( ) => {
294+ interface UsersResponse {
295+ users : [
296+ user : {
297+ id : string ;
298+ name : string ;
299+ address : string ;
300+ }
301+ ] ;
302+ }
303+ const stub = sandbox
304+ . stub ( HttpClient . prototype , 'send' )
305+ . resolves ( utils . responseFrom ( TEST_RESPONSE , 200 ) ) ;
306+ return apiClient . impersonateQuery < UsersResponse , unknown > ( unauthenticatedOptions )
307+ . then ( ( resp ) => {
308+ expect ( resp . data . users ) . to . be . not . empty ;
309+ expect ( resp . data . users [ 0 ] . name ) . to . be . not . undefined ;
310+ expect ( resp . data . users [ 0 ] . address ) . to . be . not . undefined ;
311+ expect ( resp . data . users ) . to . deep . equal ( TEST_RESPONSE . data . users ) ;
312+ expect ( stub ) . to . have . been . calledOnce . and . calledWith ( {
313+ method : 'POST' ,
314+ url : `https://firebasedataconnect.googleapis.com/v1alpha/projects/test-project/locations/${ connectorConfig . location } /services/${ connectorConfig . serviceId } /connectors/${ DataConnectService . getId ( connectorConfig ) } :impersonateQuery` ,
315+ headers : EXPECTED_HEADERS ,
316+ data : {
317+ operationName : unauthenticatedOptions . operationName ,
318+ extensions : { impersonate : unauthenticatedOptions . impersonate }
319+ }
320+ } ) ;
321+ } ) ;
322+ } ) ;
323+
324+ it ( 'should use DATA_CONNECT_EMULATOR_HOST if set' , ( ) => {
325+ process . env . DATA_CONNECT_EMULATOR_HOST = 'localhost:9399' ;
326+ const stub = sandbox
327+ . stub ( HttpClient . prototype , 'send' )
328+ . resolves ( utils . responseFrom ( TEST_RESPONSE , 200 ) ) ;
329+ return apiClient . impersonateQuery ( unauthenticatedOptions )
330+ . then ( ( ) => {
331+ expect ( stub ) . to . have . been . calledOnce . and . calledWith ( {
332+ method : 'POST' ,
333+ url : `http://localhost:9399/v1alpha/projects/test-project/locations/${ connectorConfig . location } /services/${ connectorConfig . serviceId } /connectors/${ DataConnectService . getId ( connectorConfig ) } :impersonateQuery` ,
334+ headers : EMULATOR_EXPECTED_HEADERS ,
335+ data : {
336+ operationName : unauthenticatedOptions . operationName ,
337+ extensions : { impersonate : unauthenticatedOptions . impersonate }
338+ }
339+ } ) ;
340+ } ) ;
341+ } ) ;
239342 } ) ;
240343} ) ;
241344
0 commit comments