18d307f52SEvan Baconimport assert from 'assert'; 28d307f52SEvan Baconimport nock from 'nock'; 38d307f52SEvan Baconimport { FetchError } from 'node-fetch'; 48d307f52SEvan Baconimport { URLSearchParams } from 'url'; 58d307f52SEvan Bacon 6*fa5bc561SWill Schurmanimport { asMock } from '../../../__tests__/asMock'; 78d307f52SEvan Baconimport { getExpoApiBaseUrl } from '../../endpoint'; 88d307f52SEvan Baconimport UserSettings from '../../user/UserSettings'; 98d307f52SEvan Baconimport { ApiV2Error, fetchAsync } from '../client'; 108d307f52SEvan Bacon 118d307f52SEvan Baconjest.mock('../../user/UserSettings'); 128d307f52SEvan Bacon 138d307f52SEvan Baconit('converts Expo APIv2 error to ApiV2Error', async () => { 148d307f52SEvan Bacon const scope = nock(getExpoApiBaseUrl()) 158d307f52SEvan Bacon .post('/v2/test') 168d307f52SEvan Bacon .reply(400, { 178d307f52SEvan Bacon errors: [ 188d307f52SEvan Bacon { 198d307f52SEvan Bacon message: 'hellomessage', 208d307f52SEvan Bacon code: 'TEST_CODE', 218d307f52SEvan Bacon stack: 'line 1: hello', 228d307f52SEvan Bacon details: { who: 'world' }, 238d307f52SEvan Bacon metadata: { an: 'object' }, 248d307f52SEvan Bacon }, 258d307f52SEvan Bacon ], 268d307f52SEvan Bacon }); 278d307f52SEvan Bacon 288d307f52SEvan Bacon expect.assertions(6); 298d307f52SEvan Bacon 308d307f52SEvan Bacon try { 318d307f52SEvan Bacon await fetchAsync('test', { 328d307f52SEvan Bacon method: 'POST', 338d307f52SEvan Bacon }); 348d307f52SEvan Bacon } catch (error: any) { 358d307f52SEvan Bacon assert(error instanceof ApiV2Error); 368d307f52SEvan Bacon 378d307f52SEvan Bacon expect(error.message).toEqual('hellomessage'); 388d307f52SEvan Bacon expect(error.expoApiV2ErrorCode).toEqual('TEST_CODE'); 398d307f52SEvan Bacon expect(error.expoApiV2ErrorDetails).toEqual({ who: 'world' }); 408d307f52SEvan Bacon expect(error.expoApiV2ErrorMetadata).toEqual({ an: 'object' }); 418d307f52SEvan Bacon expect(error.expoApiV2ErrorServerStack).toEqual('line 1: hello'); 428d307f52SEvan Bacon } 438d307f52SEvan Bacon expect(scope.isDone()).toBe(true); 448d307f52SEvan Bacon}); 458d307f52SEvan Bacon 468d307f52SEvan Baconit('converts Expo APIv2 error to ApiV2Error (invalid password)', async () => { 478d307f52SEvan Bacon const scope = nock(getExpoApiBaseUrl()) 488d307f52SEvan Bacon .post('/v2/test') 498d307f52SEvan Bacon .reply(401, { 508d307f52SEvan Bacon errors: [ 518d307f52SEvan Bacon { 528d307f52SEvan Bacon code: 'AUTHENTICATION_ERROR', 53b7080537SEvan Bacon message: 'Your username, email, or password was incorrect.', 548d307f52SEvan Bacon isTransient: false, 558d307f52SEvan Bacon }, 568d307f52SEvan Bacon ], 578d307f52SEvan Bacon }); 588d307f52SEvan Bacon 598d307f52SEvan Bacon expect.assertions(3); 608d307f52SEvan Bacon 618d307f52SEvan Bacon try { 628d307f52SEvan Bacon await fetchAsync('test', { 638d307f52SEvan Bacon method: 'POST', 648d307f52SEvan Bacon }); 658d307f52SEvan Bacon } catch (error: any) { 668d307f52SEvan Bacon assert(error instanceof ApiV2Error); 678d307f52SEvan Bacon 68b7080537SEvan Bacon expect(error.message).toEqual('Your username, email, or password was incorrect.'); 698d307f52SEvan Bacon expect(error.expoApiV2ErrorCode).toEqual('AUTHENTICATION_ERROR'); 708d307f52SEvan Bacon } 718d307f52SEvan Bacon expect(scope.isDone()).toBe(true); 728d307f52SEvan Bacon}); 738d307f52SEvan Bacon 748d307f52SEvan Baconit('does not convert non-APIv2 error to ApiV2Error', async () => { 758d307f52SEvan Bacon const scope = nock(getExpoApiBaseUrl()).post('/v2/test').reply(500, 'Something went wrong'); 768d307f52SEvan Bacon 778d307f52SEvan Bacon expect.assertions(1); 788d307f52SEvan Bacon 798d307f52SEvan Bacon try { 808d307f52SEvan Bacon await fetchAsync('test', { 818d307f52SEvan Bacon method: 'POST', 828d307f52SEvan Bacon }); 838d307f52SEvan Bacon } catch (error: any) { 848d307f52SEvan Bacon expect(error).toBeInstanceOf(FetchError); 858d307f52SEvan Bacon expect(error).not.toBeInstanceOf(ApiV2Error); 868d307f52SEvan Bacon } 878d307f52SEvan Bacon expect(scope.isDone()).toBe(true); 888d307f52SEvan Bacon}); 898d307f52SEvan Bacon 908d307f52SEvan Baconit('makes a get request', async () => { 918d307f52SEvan Bacon nock(getExpoApiBaseUrl()).get('/v2/get-me?foo=bar').reply(200, 'Hello World'); 928d307f52SEvan Bacon const res = await fetchAsync('get-me', { 938d307f52SEvan Bacon method: 'GET', 948d307f52SEvan Bacon // Ensure our custom support for URLSearchParams works... 958d307f52SEvan Bacon searchParams: new URLSearchParams({ 968d307f52SEvan Bacon foo: 'bar', 978d307f52SEvan Bacon }), 988d307f52SEvan Bacon }); 998d307f52SEvan Bacon expect(res.status).toEqual(200); 1008d307f52SEvan Bacon expect(await res.text()).toEqual('Hello World'); 1018d307f52SEvan Bacon}); 1028d307f52SEvan Bacon 1038d307f52SEvan Bacon// This test ensures that absolute URLs are allowed with the abstraction. 1048d307f52SEvan Baconit('makes a request using an absolute URL', async () => { 1058d307f52SEvan Bacon nock('http://example').get('/get-me?foo=bar').reply(200, 'Hello World'); 1068d307f52SEvan Bacon const res = await fetchAsync('http://example/get-me', { 1078d307f52SEvan Bacon searchParams: new URLSearchParams({ 1088d307f52SEvan Bacon foo: 'bar', 1098d307f52SEvan Bacon }), 1108d307f52SEvan Bacon }); 1118d307f52SEvan Bacon expect(res.status).toEqual(200); 1128d307f52SEvan Bacon expect(await res.text()).toEqual('Hello World'); 1138d307f52SEvan Bacon}); 1148d307f52SEvan Bacon 1158d307f52SEvan Baconit('makes an authenticated request with access token', async () => { 1168d307f52SEvan Bacon asMock(UserSettings.getAccessToken).mockClear().mockReturnValue('my-access-token'); 1178d307f52SEvan Bacon 1188d307f52SEvan Bacon nock(getExpoApiBaseUrl()) 1198d307f52SEvan Bacon .matchHeader('authorization', (val) => val.length === 1 && val[0] === 'Bearer my-access-token') 1208d307f52SEvan Bacon .get('/v2/get-me') 1218d307f52SEvan Bacon .reply(200, 'Hello World'); 1228d307f52SEvan Bacon const res = await fetchAsync('get-me', { 1238d307f52SEvan Bacon method: 'GET', 1248d307f52SEvan Bacon }); 1258d307f52SEvan Bacon expect(res.status).toEqual(200); 1268d307f52SEvan Bacon}); 1278d307f52SEvan Bacon 1288d307f52SEvan Baconit('makes an authenticated request with session secret', async () => { 129*fa5bc561SWill Schurman asMock(UserSettings.getSession).mockClear().mockReturnValue({ 130*fa5bc561SWill Schurman sessionSecret: 'my-secret-token', 131*fa5bc561SWill Schurman userId: '', 132*fa5bc561SWill Schurman username: '', 133*fa5bc561SWill Schurman currentConnection: 'Username-Password-Authentication', 134*fa5bc561SWill Schurman }); 1358d307f52SEvan Bacon asMock(UserSettings.getAccessToken).mockReturnValue(null); 1368d307f52SEvan Bacon 1378d307f52SEvan Bacon nock(getExpoApiBaseUrl()) 1388d307f52SEvan Bacon .matchHeader('expo-session', (val) => val.length === 1 && val[0] === 'my-secret-token') 1398d307f52SEvan Bacon .get('/v2/get-me') 1408d307f52SEvan Bacon .reply(200, 'Hello World'); 1418d307f52SEvan Bacon const res = await fetchAsync('get-me', { 1428d307f52SEvan Bacon method: 'GET', 1438d307f52SEvan Bacon }); 1448d307f52SEvan Bacon expect(res.status).toEqual(200); 1458d307f52SEvan Bacon}); 1468d307f52SEvan Bacon 1478d307f52SEvan Baconit('only uses access token when both authentication methods are available', async () => { 1488d307f52SEvan Bacon asMock(UserSettings.getAccessToken).mockClear().mockReturnValue('my-access-token'); 149*fa5bc561SWill Schurman asMock(UserSettings.getSession).mockClear().mockReturnValue({ 150*fa5bc561SWill Schurman sessionSecret: 'my-secret-token', 151*fa5bc561SWill Schurman userId: '', 152*fa5bc561SWill Schurman username: '', 153*fa5bc561SWill Schurman currentConnection: 'Username-Password-Authentication', 154*fa5bc561SWill Schurman }); 1558d307f52SEvan Bacon 1568d307f52SEvan Bacon nock(getExpoApiBaseUrl()) 1578d307f52SEvan Bacon .matchHeader('authorization', (val) => val.length === 1 && val[0] === 'Bearer my-access-token') 1588d307f52SEvan Bacon .matchHeader('expo-session', (val) => !val) 1598d307f52SEvan Bacon .get('/v2/get-me') 1608d307f52SEvan Bacon .reply(200, 'Hello World'); 1618d307f52SEvan Bacon const res = await fetchAsync('get-me', { 1628d307f52SEvan Bacon method: 'GET', 1638d307f52SEvan Bacon }); 1648d307f52SEvan Bacon expect(res.status).toEqual(200); 1658d307f52SEvan Bacon}); 166