19231: Add smaller page sizes (10 and 20 items) to load faster
[arvados-workbench2.git] / src / store / auth / auth-action.test.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { getNewExtraToken, initAuth } from "./auth-action";
6 import { API_TOKEN_KEY } from "services/auth-service/auth-service";
7
8 import 'jest-localstorage-mock';
9 import { ServiceRepository, createServices } from "services/services";
10 import { configureStore, RootStore } from "../store";
11 import { createBrowserHistory } from "history";
12 import { mockConfig } from 'common/config';
13 import { ApiActions } from "services/api/api-actions";
14 import { ACCOUNT_LINK_STATUS_KEY } from 'services/link-account-service/link-account-service';
15 import Axios, { AxiosInstance } from "axios";
16 import MockAdapter from "axios-mock-adapter";
17 import { ImportMock } from 'ts-mock-imports';
18 import * as servicesModule from "services/services";
19 import * as authActionSessionModule from "./auth-action-session";
20 import { SessionStatus } from "models/session";
21 import { getRemoteHostConfig } from "./auth-action-session";
22
23 describe('auth-actions', () => {
24     let axiosInst: AxiosInstance;
25     let axiosMock: MockAdapter;
26
27     let store: RootStore;
28     let services: ServiceRepository;
29     const config: any = {};
30     const actions: ApiActions = {
31         progressFn: (id: string, working: boolean) => { },
32         errorFn: (id: string, message: string) => { }
33     };
34     let importMocks: any[];
35
36     beforeEach(() => {
37         axiosInst = Axios.create({ headers: {} });
38         axiosMock = new MockAdapter(axiosInst);
39         services = createServices(mockConfig({}), actions, axiosInst);
40         store = configureStore(createBrowserHistory(), services, config);
41         localStorage.clear();
42         importMocks = [];
43     });
44
45     afterEach(() => {
46         importMocks.map(m => m.restore());
47     });
48
49     it('creates an extra token', async () => {
50         axiosMock
51             .onGet("/users/current")
52             .reply(200, {
53                 email: "test@test.com",
54                 first_name: "John",
55                 last_name: "Doe",
56                 uuid: "zzzzz-tpzed-abcefg",
57                 owner_uuid: "ownerUuid",
58                 is_admin: false,
59                 is_active: true,
60                 username: "jdoe",
61                 prefs: {}
62             })
63             .onGet("/api_client_authorizations/current")
64             .reply(200, {
65                 expires_at: "2140-01-01T00:00:00.000Z",
66                 api_token: 'extra token',
67             })
68             .onPost("/api_client_authorizations")
69             .replyOnce(200, {
70                 uuid: 'zzzzz-gj3su-xxxxxxxxxx',
71                 apiToken: 'extra token',
72             })
73             .onPost("/api_client_authorizations")
74             .reply(200, {
75                 uuid: 'zzzzz-gj3su-xxxxxxxxxx',
76                 apiToken: 'extra additional token',
77             });
78
79         importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
80         sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
81         localStorage.setItem(API_TOKEN_KEY, "token");
82
83         const config: any = {
84             rootUrl: "https://zzzzz.example.com",
85             uuidPrefix: "zzzzz",
86             remoteHosts: { },
87             apiRevision: 12345678,
88             clusterConfig: {
89                 Login: { LoginCluster: "" },
90             },
91         };
92
93         // Set up auth, confirm that no extra token was requested
94         await store.dispatch(initAuth(config))
95         expect(store.getState().auth.apiToken).toBeDefined();
96         expect(store.getState().auth.extraApiToken).toBeUndefined();
97
98         // Ask for an extra token
99         await store.dispatch(getNewExtraToken());
100         expect(store.getState().auth.apiToken).toBeDefined();
101         expect(store.getState().auth.extraApiToken).toBeDefined();
102         const extraToken = store.getState().auth.extraApiToken;
103
104         // Ask for a cached extra token
105         await store.dispatch(getNewExtraToken(true));
106         expect(store.getState().auth.extraApiToken).toBe(extraToken);
107
108         // Ask for a new extra token, should make a second api request
109         await store.dispatch(getNewExtraToken(false));
110         expect(store.getState().auth.extraApiToken).toBeDefined();
111         expect(store.getState().auth.extraApiToken).not.toBe(extraToken);
112     });
113
114     it('requests remote token data to login cluster', async () => {
115         const localClusterTokenExpiration = "2020-01-01T00:00:00.000Z";
116         const loginClusterTokenExpiration = "2140-01-01T00:00:00.000Z";
117         axiosMock
118             .onGet("/users/current")
119             .reply(200, {
120                 email: "test@test.com",
121                 first_name: "John",
122                 last_name: "Doe",
123                 uuid: "zzzz1-tpzed-abcefg",
124                 owner_uuid: "ownerUuid",
125                 is_admin: false,
126                 is_active: true,
127                 username: "jdoe",
128                 prefs: {}
129             })
130             .onGet("https://zzzz1.example.com/discovery/v1/apis/arvados/v1/rest")
131             .reply(200, {
132                 baseUrl: "https://zzzz1.example.com/arvados/v1",
133                 keepWebServiceUrl: "",
134                 keepWebInlineServiceUrl: "",
135                 remoteHosts: {},
136                 rootUrl: "https://zzzz1.example.com",
137                 uuidPrefix: "zzzz1",
138                 websocketUrl: "",
139                 workbenchUrl: "",
140                 workbench2Url: "",
141                 revision: 12345678
142             })
143             // Local cluster -- cached token
144             .onGet("https://zzzzz.example.com/arvados/v1/api_client_authorizations/current")
145             .reply(200, {
146                 uuid: 'zzzz1-gj3su-aaaaaaa',
147                 expires_at: localClusterTokenExpiration,
148                 api_token: 'tokensecret',
149             })
150             // Login cluster -- authoritative token copy
151             .onGet("https://zzzz1.example.com/arvados/v1/api_client_authorizations/current")
152             .reply(200, {
153                 uuid: 'zzzz1-gj3su-aaaaaaa',
154                 expires_at: loginClusterTokenExpiration,
155                 api_token: 'tokensecret',
156             });
157
158         const config: any = {
159             rootUrl: "https://zzzzz.example.com",
160             uuidPrefix: "zzzzz",
161             remoteHosts: { zzzz1: "zzzz1.example.com" },
162             apiRevision: 12345678,
163             clusterConfig: {
164                 Login: { LoginCluster: "zzzz1" },
165             },
166         };
167
168         const remoteHostConfig = await getRemoteHostConfig(config.remoteHosts.zzzz1, axiosInst);
169         expect(remoteHostConfig).not.toBeFalsy;
170         services = createServices(remoteHostConfig!, actions, axiosInst);
171
172         importMocks.push(ImportMock.mockFunction(authActionSessionModule, 'getRemoteHostConfig', remoteHostConfig));
173         importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
174
175         sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
176         localStorage.setItem(API_TOKEN_KEY, "v2/zzzz1-gj3su-aaaaaaa/tokensecret");
177
178         await store.dispatch(initAuth(config));
179         expect(store.getState().auth.apiToken).toBeDefined();
180         expect(localClusterTokenExpiration).not.toBe(loginClusterTokenExpiration);
181         expect(store.getState().auth.apiTokenExpiration).toEqual(new Date(loginClusterTokenExpiration));
182     });
183
184     it('should initialise state with user and api token from local storage', (done) => {
185         axiosMock
186             .onGet("/users/current")
187             .reply(200, {
188                 email: "test@test.com",
189                 first_name: "John",
190                 last_name: "Doe",
191                 uuid: "zzzzz-tpzed-abcefg",
192                 owner_uuid: "ownerUuid",
193                 is_admin: false,
194                 is_active: true,
195                 username: "jdoe",
196                 prefs: {}
197             })
198             .onGet("/api_client_authorizations/current")
199             .reply(200, {
200                 expires_at: "2140-01-01T00:00:00.000Z"
201             })
202             .onGet("https://xc59z.example.com/discovery/v1/apis/arvados/v1/rest")
203             .reply(200, {
204                 baseUrl: "https://xc59z.example.com/arvados/v1",
205                 keepWebServiceUrl: "",
206                 keepWebInlineServiceUrl: "",
207                 remoteHosts: {},
208                 rootUrl: "https://xc59z.example.com",
209                 uuidPrefix: "xc59z",
210                 websocketUrl: "",
211                 workbenchUrl: "",
212                 workbench2Url: "",
213                 revision: 12345678
214             });
215
216         importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
217
218         // Only test the case when a link account operation is not being cancelled
219         sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
220         localStorage.setItem(API_TOKEN_KEY, "token");
221
222         const config: any = {
223             rootUrl: "https://zzzzz.example.com",
224             uuidPrefix: "zzzzz",
225             remoteHosts: { xc59z: "xc59z.example.com" },
226             apiRevision: 12345678,
227             clusterConfig: {
228                 Login: { LoginCluster: "" },
229             },
230         };
231
232         store.dispatch(initAuth(config));
233
234         store.subscribe(() => {
235             const auth = store.getState().auth;
236             if (auth.apiToken === "token" &&
237                 auth.sessions.length === 2 &&
238                 auth.sessions[0].status === SessionStatus.VALIDATED &&
239                 auth.sessions[1].status === SessionStatus.VALIDATED
240             ) {
241                 try {
242                     expect(auth).toEqual({
243                         apiToken: "token",
244                         apiTokenExpiration: new Date("2140-01-01T00:00:00.000Z"),
245                         apiTokenLocation: "localStorage",
246                         config: {
247                             apiRevision: 12345678,
248                             clusterConfig: {
249                                 Login: {
250                                     LoginCluster: "",
251                                 },
252                             },
253                             remoteHosts: {
254                                 "xc59z": "xc59z.example.com",
255                             },
256                             rootUrl: "https://zzzzz.example.com",
257                             uuidPrefix: "zzzzz",
258                         },
259                         sshKeys: [],
260                         extraApiToken: undefined,
261                         extraApiTokenExpiration: undefined,
262                         homeCluster: "zzzzz",
263                         localCluster: "zzzzz",
264                         loginCluster: undefined,
265                         remoteHostsConfig: {
266                             "zzzzz": {
267                                 "apiRevision": 12345678,
268                                 "clusterConfig": {
269                                     "Login": {
270                                         "LoginCluster": "",
271                                     },
272                                 },
273                                 "remoteHosts": {
274                                     "xc59z": "xc59z.example.com",
275                                 },
276                                 "rootUrl": "https://zzzzz.example.com",
277                                 "uuidPrefix": "zzzzz",
278                             },
279                             "xc59z": mockConfig({
280                                 apiRevision: 12345678,
281                                 baseUrl: "https://xc59z.example.com/arvados/v1",
282                                 rootUrl: "https://xc59z.example.com",
283                                 uuidPrefix: "xc59z"
284                             })
285                         },
286                         remoteHosts: {
287                             zzzzz: "zzzzz.example.com",
288                             xc59z: "xc59z.example.com"
289                         },
290                         sessions: [{
291                             "active": true,
292                             "baseUrl": undefined,
293                             "clusterId": "zzzzz",
294                             "email": "test@test.com",
295                             "loggedIn": true,
296                             "remoteHost": "https://zzzzz.example.com",
297                             "status": 2,
298                             "token": "token",
299                             "name": "John Doe",
300                             "apiRevision": 12345678,
301                             "uuid": "zzzzz-tpzed-abcefg",
302                             "userIsActive": true
303                         }, {
304                             "active": false,
305                             "baseUrl": "",
306                             "clusterId": "xc59z",
307                             "email": "",
308                             "loggedIn": false,
309                             "remoteHost": "xc59z.example.com",
310                             "status": 2,
311                             "token": "",
312                             "name": "",
313                             "uuid": "",
314                             "apiRevision": 0,
315                         }],
316                         user: {
317                             email: "test@test.com",
318                             firstName: "John",
319                             lastName: "Doe",
320                             uuid: "zzzzz-tpzed-abcefg",
321                             ownerUuid: "ownerUuid",
322                             username: "jdoe",
323                             prefs: { profile: {} },
324                             isAdmin: false,
325                             isActive: true
326                         }
327                     });
328                     done();
329                 } catch (e) {
330                     fail(e);
331                 }
332             }
333         });
334     });
335
336
337     // TODO: Add remaining action tests
338     /*
339        it('should fire external url to login', () => {
340        const initialState = undefined;
341        window.location.assign = jest.fn();
342        reducer(initialState, authActions.LOGIN());
343        expect(window.location.assign).toBeCalledWith(
344        `/login?return_to=${window.location.protocol}//${window.location.host}/token`
345        );
346        });
347
348        it('should fire external url to logout', () => {
349        const initialState = undefined;
350        window.location.assign = jest.fn();
351        reducer(initialState, authActions.LOGOUT());
352        expect(window.location.assign).toBeCalledWith(
353        `/logout?return_to=${location.protocol}//${location.host}`
354        );
355        });
356      */
357 });