1import { ExecutionEnvironment } from 'expo-constants';
2import { mockLinking, mockProperty, unmockAllProperties } from 'jest-expo';
3
4import { describeManifestTypes, mockConstants } from './ManifestTestUtils';
5
6function mockOpenAuthSessionAsync(webBrowserModule, openBrowserFn) {
7  mockProperty(webBrowserModule, 'openAuthSessionAsync', jest.fn(openBrowserFn));
8}
9
10beforeEach(() => {
11  unmockAllProperties();
12  jest.resetModules();
13  jest.resetAllMocks();
14});
15
16describeManifestTypes(
17  {
18    id: '@example/abc',
19    originalFullName: '@example/abc',
20  },
21  {
22    extra: {
23      expoClient: {
24        name: 'wat',
25        slug: 'wat',
26        originalFullName: '@example/abc',
27      },
28    },
29  }
30)((manifestObj) => {
31  beforeEach(() => {
32    mockConstants(
33      {
34        executionEnvironment: ExecutionEnvironment.Standalone,
35      },
36      manifestObj
37    );
38  });
39
40  it(`returns correct redirect URL from getRedirectUrl`, () => {
41    const { getRedirectUrl } = require('../AuthSession');
42    expect(getRedirectUrl()).toEqual('https://auth.expo.io/@example/abc');
43  });
44
45  it(`opens WebBrowser startAsync to the start URL`, async () => {
46    const authUrl = 'abcd.com';
47    const returnUrl = 'efgh.com';
48
49    const { startAsync } = require('../AuthSession');
50    const WebBrowser = require('expo-web-browser');
51
52    mockOpenAuthSessionAsync(WebBrowser, async () => ({ type: 'cancel' }));
53    await startAsync({ authUrl, returnUrl });
54
55    const sessionUrlProvider = require('../SessionUrlProvider').default;
56    expect(WebBrowser.openAuthSessionAsync).toHaveBeenCalledWith(
57      sessionUrlProvider.getStartUrl(authUrl, returnUrl),
58      returnUrl,
59      { showInRecents: false }
60    );
61  });
62
63  it(`only lets you call startAsync once at a time`, async () => {
64    const authUrl = 'abcd.com';
65    const returnUrl = 'efgh.com';
66    const normalResponse = { type: 'cancel' };
67    const lockedResponse = { type: 'locked' };
68
69    mockProperty(console, 'warn', jest.fn());
70
71    const { startAsync } = require('../AuthSession');
72    const WebBrowser = require('expo-web-browser');
73
74    mockOpenAuthSessionAsync(WebBrowser, () => {
75      return new Promise((resolve) => {
76        setTimeout(() => resolve(normalResponse), 0);
77      });
78    });
79    const parallelStartAsyncCalls = Promise.all([
80      startAsync({ authUrl, returnUrl }),
81      startAsync({ authUrl, returnUrl }),
82    ]);
83
84    const [first, second] = await parallelStartAsyncCalls;
85    expect(first).toEqual(normalResponse);
86    expect(second).toEqual(lockedResponse);
87    expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(/Only one AuthSession/));
88  });
89
90  it(`returns success with params on redirect`, async () => {
91    const emitLinkingEvent = mockLinking();
92    const authUrl = 'http://example.io';
93    const returnUrl = 'https://example-return.io/+';
94    const returnUrlWithParams = `${returnUrl}?id=42#token=abc123`;
95
96    const { startAsync } = require('../AuthSession');
97    const authSessionPromise = startAsync({
98      authUrl,
99      returnUrl,
100    });
101    emitLinkingEvent('url', { url: returnUrlWithParams });
102    const result = await authSessionPromise;
103
104    expect(result.type).toEqual('success');
105    expect((result as any).params).toEqual({ token: 'abc123', id: '42' });
106  });
107
108  it(`returns error when errorCode is present`, async () => {
109    const emitLinkingEvent = mockLinking();
110
111    const authUrl = 'http://example.io';
112    const returnUrl = 'https://example-return.io/+';
113
114    const { startAsync } = require('../AuthSession');
115    const authSessionPromise = startAsync({
116      authUrl,
117      returnUrl,
118    });
119
120    const returnUrlWithParams = `${returnUrl}?errorCode=nope&id=42`;
121    emitLinkingEvent('url', { url: returnUrlWithParams });
122
123    const result = await authSessionPromise;
124    expect(result.type).toEqual('error');
125    expect((result as any).errorCode).toEqual('nope');
126    expect((result as any).params).toEqual({ id: '42' });
127  });
128
129  it(`throws from AuthSession.startAsync if authUrl is falsy`, async () => {
130    expect.assertions(1);
131    try {
132      const { startAsync } = require('../AuthSession');
133      await startAsync({
134        authUrl: null as any,
135      });
136    } catch (e) {
137      expect(e).toMatchSnapshot();
138    }
139  });
140
141  it(`lets us call AuthSession.startAsync after param validation throws`, async () => {
142    const { startAsync } = require('../AuthSession');
143    try {
144      await startAsync({ authUrl: null as any });
145    } catch {}
146
147    const emitLinkingEvent = mockLinking();
148    const authUrl = 'http://example.io';
149    const returnUrl = 'https://example-return.io/+';
150    const returnUrlWithParams = `${returnUrl}?id=42#token=abc123`;
151
152    const authSessionPromise = startAsync({
153      authUrl,
154      returnUrl,
155    });
156    emitLinkingEvent('url', { url: returnUrlWithParams });
157
158    const result = await authSessionPromise;
159
160    expect(result.type).not.toEqual('locked');
161  });
162});
163
164describeManifestTypes(
165  {
166    id: '@anonymous/abc',
167    originalFullName: undefined,
168  },
169  {
170    extra: {
171      expoClient: {
172        name: 'wat',
173        slug: 'wat',
174        originalFullName: '@anonymous/abc',
175      },
176    },
177  }
178)((manifestObj) => {
179  it(`warns if user is @anonymous in getRedirectUrl`, () => {
180    mockConstants(
181      {
182        executionEnvironment: ExecutionEnvironment.Standalone,
183      },
184      manifestObj
185    );
186
187    mockProperty(console, 'warn', jest.fn());
188    const { getRedirectUrl } = require('../AuthSession');
189    getRedirectUrl();
190    expect((console.warn as jest.Mock).mock.calls).toMatchSnapshot();
191  });
192});
193