From dd0930342263e891edd7034f0cc1871ca2cf0f80 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 28 Jan 2020 07:00:24 -0800 Subject: [PATCH 1/3] Consistent timeouts for the Space onPostAuth interceptor tests --- .../on_post_auth_interceptor.test.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index c1f557f164ad6..776275715921b 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -32,6 +32,7 @@ import { securityMock } from '../../../../security/server/mocks'; describe('onPostAuthInterceptor', () => { let root: ReturnType; + jest.setTimeout(30000); const headers = { authorization: `Basic ${Buffer.from( @@ -41,7 +42,7 @@ describe('onPostAuthInterceptor', () => { beforeEach(async () => { root = kbnTestServer.createRoot(); - }, 30000); + }); afterEach(async () => await root.shutdown()); @@ -241,7 +242,7 @@ describe('onPostAuthInterceptor', () => { expect(response.status).toEqual(302); expect(response.header.location).toEqual(`/spaces/space_selector`); - }, 30000); + }); it('when accessing the kibana app it always allows the request to continue', async () => { const spaces = [ @@ -258,7 +259,7 @@ describe('onPostAuthInterceptor', () => { const { response } = await request('/s/a-space/app/kibana', spaces); expect(response.status).toEqual(200); - }, 30000); + }); it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { const spaces = [ @@ -274,7 +275,7 @@ describe('onPostAuthInterceptor', () => { const { response } = await request('/s/not-found/api/test/foo', spaces); expect(response.status).toEqual(200); - }, 30000); + }); }); describe('requests handled completely in the new platform', () => { @@ -293,7 +294,7 @@ describe('onPostAuthInterceptor', () => { expect(response.status).toEqual(302); expect(response.header.location).toEqual(`/spaces/space_selector`); - }, 30000); + }); it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { const spaces = [ @@ -309,7 +310,7 @@ describe('onPostAuthInterceptor', () => { const { response } = await request('/s/not-found/api/np_test/foo', spaces); expect(response.status).toEqual(200); - }, 30000); + }); }); it('handles space retrieval errors gracefully when requesting the root, responding with headers returned from ES', async () => { @@ -421,7 +422,7 @@ describe('onPostAuthInterceptor', () => { }), }) ); - }, 30000); + }); it('redirects to the "enter space" endpoint when accessing the root of a non-default space', async () => { const spaces = [ @@ -454,7 +455,7 @@ describe('onPostAuthInterceptor', () => { }), }) ); - }, 30000); + }); describe('with a single available space', () => { it('it redirects to the "enter space" endpoint within the context of the single Space when navigating to Kibana root', async () => { From 6054ac462e68643e453585e60b22d476d671f4a9 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 28 Jan 2020 09:45:48 -0800 Subject: [PATCH 2/3] Run 100 times --- .../on_post_auth_interceptor.test.ts | 836 +++++++++--------- 1 file changed, 421 insertions(+), 415 deletions(-) diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index 776275715921b..1cbca6c59de3e 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -30,273 +30,295 @@ import { Feature } from '../../../../features/server'; import { spacesConfig } from '../__fixtures__'; import { securityMock } from '../../../../security/server/mocks'; -describe('onPostAuthInterceptor', () => { - let root: ReturnType; - jest.setTimeout(30000); - - const headers = { - authorization: `Basic ${Buffer.from( - `${kibanaTestUser.username}:${kibanaTestUser.password}` - ).toString('base64')}`, - }; - - beforeEach(async () => { - root = kbnTestServer.createRoot(); - }); +for (let i = 0; i < 100; ++i) { + describe('onPostAuthInterceptor', () => { + let root: ReturnType; + jest.setTimeout(30000); + + const headers = { + authorization: `Basic ${Buffer.from( + `${kibanaTestUser.username}:${kibanaTestUser.password}` + ).toString('base64')}`, + }; - afterEach(async () => await root.shutdown()); + beforeEach(async () => { + root = kbnTestServer.createRoot(); + }); - function initKbnServer(router: IRouter, basePath: IBasePath, routes: 'legacy' | 'new-platform') { - const kbnServer = kbnTestServer.getKbnServer(root); + afterEach(async () => await root.shutdown()); - if (routes === 'legacy') { - kbnServer.server.route([ - { - method: 'GET', - path: '/foo', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, - }, - { - method: 'GET', - path: '/app/kibana', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, - }, - { - method: 'GET', - path: '/app/app-1', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, - }, - { - method: 'GET', - path: '/app/app-2', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, - }, - { - method: 'GET', - path: '/api/test/foo', - handler: (req: Legacy.Request) => { - return { path: req.path, basePath: basePath.get(req) }; + function initKbnServer( + router: IRouter, + basePath: IBasePath, + routes: 'legacy' | 'new-platform' + ) { + const kbnServer = kbnTestServer.getKbnServer(root); + + if (routes === 'legacy') { + kbnServer.server.route([ + { + method: 'GET', + path: '/foo', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, }, - }, - { - method: 'GET', - path: '/some/path/s/foo/bar', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); + { + method: 'GET', + path: '/app/kibana', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, }, - }, - ]); - } - - if (routes === 'new-platform') { - router.get({ path: '/api/np_test/foo', validate: false }, (context, req, h) => { - return h.ok({ body: { path: req.url.pathname, basePath: basePath.get(req) } }); - }); - } - } - - async function request( - path: string, - availableSpaces: any[], - testOptions = { simulateGetSpacesFailure: false, simulateGetSingleSpaceFailure: false } - ) { - const { http } = await root.setup(); - - const loggingMock = loggingServiceMock - .create() - .asLoggerFactory() - .get('xpack', 'spaces'); - - const featuresPlugin = { - getFeatures: () => - [ { - id: 'feature-1', - name: 'feature 1', - app: ['app-1'], + method: 'GET', + path: '/app/app-1', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, }, { - id: 'feature-2', - name: 'feature 2', - app: ['app-2'], + method: 'GET', + path: '/app/app-2', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, }, { - id: 'feature-4', - name: 'feature 4', - app: ['app-1', 'app-4'], + method: 'GET', + path: '/api/test/foo', + handler: (req: Legacy.Request) => { + return { path: req.path, basePath: basePath.get(req) }; + }, }, { - id: 'feature-5', - name: 'feature 4', - app: ['kibana'], + method: 'GET', + path: '/some/path/s/foo/bar', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, }, - ] as Feature[], - } as PluginsSetup['features']; - - const savedObjectsService = { - SavedObjectsClient: { - errors: SavedObjectsErrorHelpers, - }, - getSavedObjectsRepository: jest.fn().mockImplementation(() => { - return { - get: (type: string, id: string) => { - if (type === 'space') { - const space = availableSpaces.find(s => s.id === id); - if (space) { - return space; + ]); + } + + if (routes === 'new-platform') { + router.get({ path: '/api/np_test/foo', validate: false }, (context, req, h) => { + return h.ok({ body: { path: req.url.pathname, basePath: basePath.get(req) } }); + }); + } + } + + async function request( + path: string, + availableSpaces: any[], + testOptions = { simulateGetSpacesFailure: false, simulateGetSingleSpaceFailure: false } + ) { + const { http } = await root.setup(); + + const loggingMock = loggingServiceMock + .create() + .asLoggerFactory() + .get('xpack', 'spaces'); + + const featuresPlugin = { + getFeatures: () => + [ + { + id: 'feature-1', + name: 'feature 1', + app: ['app-1'], + }, + { + id: 'feature-2', + name: 'feature 2', + app: ['app-2'], + }, + { + id: 'feature-4', + name: 'feature 4', + app: ['app-1', 'app-4'], + }, + { + id: 'feature-5', + name: 'feature 4', + app: ['kibana'], + }, + ] as Feature[], + } as PluginsSetup['features']; + + const savedObjectsService = { + SavedObjectsClient: { + errors: SavedObjectsErrorHelpers, + }, + getSavedObjectsRepository: jest.fn().mockImplementation(() => { + return { + get: (type: string, id: string) => { + if (type === 'space') { + const space = availableSpaces.find(s => s.id === id); + if (space) { + return space; + } + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); - } - }, - create: () => null, - }; - }), - }; + }, + create: () => null, + }; + }), + }; - const legacyAPI = { - savedObjects: (savedObjectsService as unknown) as SavedObjectsLegacyService, - } as LegacyAPI; + const legacyAPI = { + savedObjects: (savedObjectsService as unknown) as SavedObjectsLegacyService, + } as LegacyAPI; - const service = new SpacesService(loggingMock, () => legacyAPI); + const service = new SpacesService(loggingMock, () => legacyAPI); - const spacesService = await service.setup({ - http: (http as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetup(), - authorization: securityMock.createSetup().authz, - getSpacesAuditLogger: () => ({} as SpacesAuditLogger), - config$: Rx.of(spacesConfig), - }); + const spacesService = await service.setup({ + http: (http as unknown) as CoreSetup['http'], + elasticsearch: elasticsearchServiceMock.createSetup(), + authorization: securityMock.createSetup().authz, + getSpacesAuditLogger: () => ({} as SpacesAuditLogger), + config$: Rx.of(spacesConfig), + }); - spacesService.scopedClient = jest.fn().mockResolvedValue({ - getAll() { - if (testOptions.simulateGetSpacesFailure) { - throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); - } - return Promise.resolve(availableSpaces.map(convertSavedObjectToSpace)); - }, - get(spaceId: string) { - if (testOptions.simulateGetSingleSpaceFailure) { - throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); - } - const space = availableSpaces.find(s => s.id === spaceId); - if (!space) { - throw SavedObjectsErrorHelpers.createGenericNotFoundError('space', spaceId); - } - return Promise.resolve(convertSavedObjectToSpace(space)); - }, - }); + spacesService.scopedClient = jest.fn().mockResolvedValue({ + getAll() { + if (testOptions.simulateGetSpacesFailure) { + throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); + } + return Promise.resolve(availableSpaces.map(convertSavedObjectToSpace)); + }, + get(spaceId: string) { + if (testOptions.simulateGetSingleSpaceFailure) { + throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); + } + const space = availableSpaces.find(s => s.id === spaceId); + if (!space) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('space', spaceId); + } + return Promise.resolve(convertSavedObjectToSpace(space)); + }, + }); - // The onRequest interceptor is also included here because the onPostAuth interceptor requires the onRequest - // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, - // we are including the already tested interceptor here in the test chain. - initSpacesOnRequestInterceptor({ - getLegacyAPI: () => legacyAPI, - http: (http as unknown) as CoreSetup['http'], - }); + // The onRequest interceptor is also included here because the onPostAuth interceptor requires the onRequest + // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, + // we are including the already tested interceptor here in the test chain. + initSpacesOnRequestInterceptor({ + getLegacyAPI: () => legacyAPI, + http: (http as unknown) as CoreSetup['http'], + }); - initSpacesOnPostAuthRequestInterceptor({ - getLegacyAPI: () => legacyAPI, - http: (http as unknown) as CoreSetup['http'], - log: loggingMock, - features: featuresPlugin, - spacesService, - }); + initSpacesOnPostAuthRequestInterceptor({ + getLegacyAPI: () => legacyAPI, + http: (http as unknown) as CoreSetup['http'], + log: loggingMock, + features: featuresPlugin, + spacesService, + }); - const router = http.createRouter('/'); + const router = http.createRouter('/'); - initKbnServer(router, http.basePath, 'new-platform'); + initKbnServer(router, http.basePath, 'new-platform'); - await root.start(); + await root.start(); - initKbnServer(router, http.basePath, 'legacy'); + initKbnServer(router, http.basePath, 'legacy'); - const response = await kbnTestServer.request.get(root, path); + const response = await kbnTestServer.request.get(root, path); - return { - response, - spacesService, - }; - } + return { + response, + spacesService, + }; + } - describe('requests proxied to the legacy platform', () => { - it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', + describe('requests proxied to the legacy platform', () => { + it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, }, - }, - ]; + ]; - const { response } = await request('/s/not-found/app/kibana', spaces); + const { response } = await request('/s/not-found/app/kibana', spaces); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/spaces/space_selector`); - }); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); + }); - it('when accessing the kibana app it always allows the request to continue', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: ['feature-1', 'feature-2', 'feature-4', 'feature-5'], + it('when accessing the kibana app it always allows the request to continue', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: ['feature-1', 'feature-2', 'feature-4', 'feature-5'], + }, }, - }, - ]; + ]; - const { response } = await request('/s/a-space/app/kibana', spaces); + const { response } = await request('/s/a-space/app/kibana', spaces); - expect(response.status).toEqual(200); - }); + expect(response.status).toEqual(200); + }); - it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', + it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, }, - }, - ]; + ]; - const { response } = await request('/s/not-found/api/test/foo', spaces); + const { response } = await request('/s/not-found/api/test/foo', spaces); - expect(response.status).toEqual(200); + expect(response.status).toEqual(200); + }); }); - }); - describe('requests handled completely in the new platform', () => { - it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', + describe('requests handled completely in the new platform', () => { + it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, }, - }, - ]; + ]; - const { response } = await request('/s/not-found/app/np_kibana', spaces); + const { response } = await request('/s/not-found/app/np_kibana', spaces); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/spaces/space_selector`); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); + }); + + it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response } = await request('/s/not-found/api/np_test/foo', spaces); + + expect(response.status).toEqual(200); + }); }); - it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { + it('handles space retrieval errors gracefully when requesting the root, responding with headers returned from ES', async () => { const spaces = [ { id: 'a-space', @@ -307,35 +329,18 @@ describe('onPostAuthInterceptor', () => { }, ]; - const { response } = await request('/s/not-found/api/np_test/foo', spaces); - - expect(response.status).toEqual(200); - }); - }); - - it('handles space retrieval errors gracefully when requesting the root, responding with headers returned from ES', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, - }, - ]; - - const { response, spacesService } = await request('/', spaces, { - simulateGetSpacesFailure: true, - simulateGetSingleSpaceFailure: false, - }); + const { response, spacesService } = await request('/', spaces, { + simulateGetSpacesFailure: true, + simulateGetSingleSpaceFailure: false, + }); - expect(response.status).toEqual(401); + expect(response.status).toEqual(401); - expect(response.header).toMatchObject({ - 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, - }); + expect(response.header).toMatchObject({ + 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, + }); - expect(response.body).toMatchInlineSnapshot(` + expect(response.body).toMatchInlineSnapshot(` Object { "error": "Unauthorized", "message": "missing credendials", @@ -343,122 +348,16 @@ describe('onPostAuthInterceptor', () => { } `); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('handles space retrieval errors gracefully when requesting an app, responding with headers returned from ES', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, - }, - ]; - - const { response, spacesService } = await request('/app/kibana', spaces, { - simulateGetSpacesFailure: false, - simulateGetSingleSpaceFailure: true, - }); - - expect(response.status).toEqual(401); - - expect(response.header).toMatchObject({ - 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); }); - expect(response.body).toMatchInlineSnapshot(` - Object { - "error": "Unauthorized", - "message": "missing credendials", - "statusCode": 401, - } - `); - - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('redirects to the space selector when accessing the root of the default space', async () => { - const spaces = [ - { - id: 'default', - type: 'space', - attributes: { - name: 'Default space', - _reserved: true, - }, - }, - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, - }, - ]; - - const { response, spacesService } = await request('/', spaces); - - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/spaces/space_selector`); - - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('redirects to the "enter space" endpoint when accessing the root of a non-default space', async () => { - const spaces = [ - { - id: 'default', - type: 'space', - attributes: { - name: 'Default space', - _reserved: true, - }, - }, - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, - }, - ]; - - const { response, spacesService } = await request('/s/a-space', spaces); - - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); - - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - describe('with a single available space', () => { - it('it redirects to the "enter space" endpoint within the context of the single Space when navigating to Kibana root', async () => { + it('handles space retrieval errors gracefully when requesting an app, responding with headers returned from ES', async () => { const spaces = [ { id: 'a-space', @@ -469,10 +368,24 @@ describe('onPostAuthInterceptor', () => { }, ]; - const { response, spacesService } = await request('/', spaces); + const { response, spacesService } = await request('/app/kibana', spaces, { + simulateGetSpacesFailure: false, + simulateGetSingleSpaceFailure: true, + }); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); + expect(response.status).toEqual(401); + + expect(response.header).toMatchObject({ + 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, + }); + + expect(response.body).toMatchInlineSnapshot(` + Object { + "error": "Unauthorized", + "message": "missing credendials", + "statusCode": 401, + } + `); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -483,49 +396,29 @@ describe('onPostAuthInterceptor', () => { ); }); - it('it redirects to the "enter space" endpoint within the context of the Default Space when navigating to Kibana root', async () => { - // This is very similar to the test above, but this handles the condition where the only available space is the Default Space, - // which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user - // is redirected to does not contain a space identifier (e.g., /s/foo) - + it('redirects to the space selector when accessing the root of the default space', async () => { const spaces = [ { id: 'default', type: 'space', attributes: { - name: 'Default Space', + name: 'Default space', + _reserved: true, }, }, - ]; - - const { response, spacesService } = await request('/', spaces); - - expect(response.status).toEqual(302); - expect(response.header.location).toEqual('/spaces/enter'); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('it allows navigation to apps when none are disabled', async () => { - const spaces = [ { id: 'a-space', type: 'space', attributes: { name: 'a space', - disabledFeatures: [], }, }, ]; - const { response, spacesService } = await request('/s/a-space/app/kibana', spaces); + const { response, spacesService } = await request('/', spaces); - expect(response.status).toEqual(200); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -536,21 +429,29 @@ describe('onPostAuthInterceptor', () => { ); }); - it('allows navigation to app that is granted by multiple features, when only one of those features is disabled', async () => { + it('redirects to the "enter space" endpoint when accessing the root of a non-default space', async () => { const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default space', + _reserved: true, + }, + }, { id: 'a-space', type: 'space', attributes: { name: 'a space', - disabledFeatures: ['feature-1'], }, }, ]; - const { response, spacesService } = await request('/s/a-space/app/app-1', spaces); + const { response, spacesService } = await request('/s/a-space', spaces); - expect(response.status).toEqual(200); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -561,29 +462,134 @@ describe('onPostAuthInterceptor', () => { ); }); - it('does not allow navigation to apps that are only provided by a disabled feature', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: ['feature-2'] as any, + describe('with a single available space', () => { + it('it redirects to the "enter space" endpoint within the context of the single Space when navigating to Kibana root', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, }, - }, - ]; + ]; - const { response, spacesService } = await request('/s/a-space/app/app-2', spaces); + const { response, spacesService } = await request('/', spaces); - expect(response.status).toEqual(404); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('it redirects to the "enter space" endpoint within the context of the Default Space when navigating to Kibana root', async () => { + // This is very similar to the test above, but this handles the condition where the only available space is the Default Space, + // which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user + // is redirected to does not contain a space identifier (e.g., /s/foo) + + const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default Space', + }, + }, + ]; + + const { response, spacesService } = await request('/', spaces); + + expect(response.status).toEqual(302); + expect(response.header.location).toEqual('/spaces/enter'); + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('it allows navigation to apps when none are disabled', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: [], + }, + }, + ]; + + const { response, spacesService } = await request('/s/a-space/app/kibana', spaces); + + expect(response.status).toEqual(200); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('allows navigation to app that is granted by multiple features, when only one of those features is disabled', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: ['feature-1'], + }, + }, + ]; + + const { response, spacesService } = await request('/s/a-space/app/app-1', spaces); + + expect(response.status).toEqual(200); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('does not allow navigation to apps that are only provided by a disabled feature', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: ['feature-2'] as any, + }, + }, + ]; + + const { response, spacesService } = await request('/s/a-space/app/app-2', spaces); + + expect(response.status).toEqual(404); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); }); }); -}); +} From 478adba7c294d9d3ee2d4b6d4ac0e341461f7fb8 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 28 Jan 2020 12:58:45 -0800 Subject: [PATCH 3/3] Revert "Run 100 times" This reverts commit 6054ac462e68643e453585e60b22d476d671f4a9. --- .../on_post_auth_interceptor.test.ts | 836 +++++++++--------- 1 file changed, 415 insertions(+), 421 deletions(-) diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index 1cbca6c59de3e..776275715921b 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -30,295 +30,273 @@ import { Feature } from '../../../../features/server'; import { spacesConfig } from '../__fixtures__'; import { securityMock } from '../../../../security/server/mocks'; -for (let i = 0; i < 100; ++i) { - describe('onPostAuthInterceptor', () => { - let root: ReturnType; - jest.setTimeout(30000); - - const headers = { - authorization: `Basic ${Buffer.from( - `${kibanaTestUser.username}:${kibanaTestUser.password}` - ).toString('base64')}`, - }; - - beforeEach(async () => { - root = kbnTestServer.createRoot(); - }); +describe('onPostAuthInterceptor', () => { + let root: ReturnType; + jest.setTimeout(30000); + + const headers = { + authorization: `Basic ${Buffer.from( + `${kibanaTestUser.username}:${kibanaTestUser.password}` + ).toString('base64')}`, + }; + + beforeEach(async () => { + root = kbnTestServer.createRoot(); + }); - afterEach(async () => await root.shutdown()); + afterEach(async () => await root.shutdown()); - function initKbnServer( - router: IRouter, - basePath: IBasePath, - routes: 'legacy' | 'new-platform' - ) { - const kbnServer = kbnTestServer.getKbnServer(root); + function initKbnServer(router: IRouter, basePath: IBasePath, routes: 'legacy' | 'new-platform') { + const kbnServer = kbnTestServer.getKbnServer(root); - if (routes === 'legacy') { - kbnServer.server.route([ - { - method: 'GET', - path: '/foo', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, + if (routes === 'legacy') { + kbnServer.server.route([ + { + method: 'GET', + path: '/foo', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); }, - { - method: 'GET', - path: '/app/kibana', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, + }, + { + method: 'GET', + path: '/app/kibana', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, + }, + { + method: 'GET', + path: '/app/app-1', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, + }, + { + method: 'GET', + path: '/app/app-2', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); }, + }, + { + method: 'GET', + path: '/api/test/foo', + handler: (req: Legacy.Request) => { + return { path: req.path, basePath: basePath.get(req) }; + }, + }, + { + method: 'GET', + path: '/some/path/s/foo/bar', + handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { + return h.response({ path: req.path, basePath: basePath.get(req) }); + }, + }, + ]); + } + + if (routes === 'new-platform') { + router.get({ path: '/api/np_test/foo', validate: false }, (context, req, h) => { + return h.ok({ body: { path: req.url.pathname, basePath: basePath.get(req) } }); + }); + } + } + + async function request( + path: string, + availableSpaces: any[], + testOptions = { simulateGetSpacesFailure: false, simulateGetSingleSpaceFailure: false } + ) { + const { http } = await root.setup(); + + const loggingMock = loggingServiceMock + .create() + .asLoggerFactory() + .get('xpack', 'spaces'); + + const featuresPlugin = { + getFeatures: () => + [ { - method: 'GET', - path: '/app/app-1', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, + id: 'feature-1', + name: 'feature 1', + app: ['app-1'], }, { - method: 'GET', - path: '/app/app-2', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, + id: 'feature-2', + name: 'feature 2', + app: ['app-2'], }, { - method: 'GET', - path: '/api/test/foo', - handler: (req: Legacy.Request) => { - return { path: req.path, basePath: basePath.get(req) }; - }, + id: 'feature-4', + name: 'feature 4', + app: ['app-1', 'app-4'], }, { - method: 'GET', - path: '/some/path/s/foo/bar', - handler: (req: Legacy.Request, h: Legacy.ResponseToolkit) => { - return h.response({ path: req.path, basePath: basePath.get(req) }); - }, + id: 'feature-5', + name: 'feature 4', + app: ['kibana'], }, - ]); - } - - if (routes === 'new-platform') { - router.get({ path: '/api/np_test/foo', validate: false }, (context, req, h) => { - return h.ok({ body: { path: req.url.pathname, basePath: basePath.get(req) } }); - }); - } - } - - async function request( - path: string, - availableSpaces: any[], - testOptions = { simulateGetSpacesFailure: false, simulateGetSingleSpaceFailure: false } - ) { - const { http } = await root.setup(); - - const loggingMock = loggingServiceMock - .create() - .asLoggerFactory() - .get('xpack', 'spaces'); - - const featuresPlugin = { - getFeatures: () => - [ - { - id: 'feature-1', - name: 'feature 1', - app: ['app-1'], - }, - { - id: 'feature-2', - name: 'feature 2', - app: ['app-2'], - }, - { - id: 'feature-4', - name: 'feature 4', - app: ['app-1', 'app-4'], - }, - { - id: 'feature-5', - name: 'feature 4', - app: ['kibana'], - }, - ] as Feature[], - } as PluginsSetup['features']; - - const savedObjectsService = { - SavedObjectsClient: { - errors: SavedObjectsErrorHelpers, - }, - getSavedObjectsRepository: jest.fn().mockImplementation(() => { - return { - get: (type: string, id: string) => { - if (type === 'space') { - const space = availableSpaces.find(s => s.id === id); - if (space) { - return space; - } - throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + ] as Feature[], + } as PluginsSetup['features']; + + const savedObjectsService = { + SavedObjectsClient: { + errors: SavedObjectsErrorHelpers, + }, + getSavedObjectsRepository: jest.fn().mockImplementation(() => { + return { + get: (type: string, id: string) => { + if (type === 'space') { + const space = availableSpaces.find(s => s.id === id); + if (space) { + return space; } - }, - create: () => null, - }; - }), - }; - - const legacyAPI = { - savedObjects: (savedObjectsService as unknown) as SavedObjectsLegacyService, - } as LegacyAPI; - - const service = new SpacesService(loggingMock, () => legacyAPI); - - const spacesService = await service.setup({ - http: (http as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetup(), - authorization: securityMock.createSetup().authz, - getSpacesAuditLogger: () => ({} as SpacesAuditLogger), - config$: Rx.of(spacesConfig), - }); + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + }, + create: () => null, + }; + }), + }; - spacesService.scopedClient = jest.fn().mockResolvedValue({ - getAll() { - if (testOptions.simulateGetSpacesFailure) { - throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); - } - return Promise.resolve(availableSpaces.map(convertSavedObjectToSpace)); - }, - get(spaceId: string) { - if (testOptions.simulateGetSingleSpaceFailure) { - throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); - } - const space = availableSpaces.find(s => s.id === spaceId); - if (!space) { - throw SavedObjectsErrorHelpers.createGenericNotFoundError('space', spaceId); - } - return Promise.resolve(convertSavedObjectToSpace(space)); - }, - }); + const legacyAPI = { + savedObjects: (savedObjectsService as unknown) as SavedObjectsLegacyService, + } as LegacyAPI; - // The onRequest interceptor is also included here because the onPostAuth interceptor requires the onRequest - // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, - // we are including the already tested interceptor here in the test chain. - initSpacesOnRequestInterceptor({ - getLegacyAPI: () => legacyAPI, - http: (http as unknown) as CoreSetup['http'], - }); + const service = new SpacesService(loggingMock, () => legacyAPI); - initSpacesOnPostAuthRequestInterceptor({ - getLegacyAPI: () => legacyAPI, - http: (http as unknown) as CoreSetup['http'], - log: loggingMock, - features: featuresPlugin, - spacesService, - }); + const spacesService = await service.setup({ + http: (http as unknown) as CoreSetup['http'], + elasticsearch: elasticsearchServiceMock.createSetup(), + authorization: securityMock.createSetup().authz, + getSpacesAuditLogger: () => ({} as SpacesAuditLogger), + config$: Rx.of(spacesConfig), + }); - const router = http.createRouter('/'); + spacesService.scopedClient = jest.fn().mockResolvedValue({ + getAll() { + if (testOptions.simulateGetSpacesFailure) { + throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); + } + return Promise.resolve(availableSpaces.map(convertSavedObjectToSpace)); + }, + get(spaceId: string) { + if (testOptions.simulateGetSingleSpaceFailure) { + throw Boom.unauthorized('missing credendials', 'Protected Elasticsearch'); + } + const space = availableSpaces.find(s => s.id === spaceId); + if (!space) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError('space', spaceId); + } + return Promise.resolve(convertSavedObjectToSpace(space)); + }, + }); - initKbnServer(router, http.basePath, 'new-platform'); + // The onRequest interceptor is also included here because the onPostAuth interceptor requires the onRequest + // interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic, + // we are including the already tested interceptor here in the test chain. + initSpacesOnRequestInterceptor({ + getLegacyAPI: () => legacyAPI, + http: (http as unknown) as CoreSetup['http'], + }); - await root.start(); + initSpacesOnPostAuthRequestInterceptor({ + getLegacyAPI: () => legacyAPI, + http: (http as unknown) as CoreSetup['http'], + log: loggingMock, + features: featuresPlugin, + spacesService, + }); - initKbnServer(router, http.basePath, 'legacy'); + const router = http.createRouter('/'); - const response = await kbnTestServer.request.get(root, path); + initKbnServer(router, http.basePath, 'new-platform'); - return { - response, - spacesService, - }; - } + await root.start(); - describe('requests proxied to the legacy platform', () => { - it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, - }, - ]; + initKbnServer(router, http.basePath, 'legacy'); - const { response } = await request('/s/not-found/app/kibana', spaces); + const response = await kbnTestServer.request.get(root, path); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/spaces/space_selector`); - }); + return { + response, + spacesService, + }; + } - it('when accessing the kibana app it always allows the request to continue', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: ['feature-1', 'feature-2', 'feature-4', 'feature-5'], - }, + describe('requests proxied to the legacy platform', () => { + it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', }, - ]; + }, + ]; - const { response } = await request('/s/a-space/app/kibana', spaces); + const { response } = await request('/s/not-found/app/kibana', spaces); - expect(response.status).toEqual(200); - }); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); + }); - it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, + it('when accessing the kibana app it always allows the request to continue', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: ['feature-1', 'feature-2', 'feature-4', 'feature-5'], }, - ]; + }, + ]; - const { response } = await request('/s/not-found/api/test/foo', spaces); + const { response } = await request('/s/a-space/app/kibana', spaces); - expect(response.status).toEqual(200); - }); + expect(response.status).toEqual(200); }); - describe('requests handled completely in the new platform', () => { - it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, + it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', }, - ]; + }, + ]; - const { response } = await request('/s/not-found/app/np_kibana', spaces); + const { response } = await request('/s/not-found/api/test/foo', spaces); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/spaces/space_selector`); - }); + expect(response.status).toEqual(200); + }); + }); - it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, + describe('requests handled completely in the new platform', () => { + it('redirects to the space selector screen when accessing an app within a non-existent space', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', }, - ]; + }, + ]; - const { response } = await request('/s/not-found/api/np_test/foo', spaces); + const { response } = await request('/s/not-found/app/np_kibana', spaces); - expect(response.status).toEqual(200); - }); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); }); - it('handles space retrieval errors gracefully when requesting the root, responding with headers returned from ES', async () => { + it('allows the request to continue when accessing an API endpoint within a non-existent space', async () => { const spaces = [ { id: 'a-space', @@ -329,18 +307,35 @@ for (let i = 0; i < 100; ++i) { }, ]; - const { response, spacesService } = await request('/', spaces, { - simulateGetSpacesFailure: true, - simulateGetSingleSpaceFailure: false, - }); + const { response } = await request('/s/not-found/api/np_test/foo', spaces); - expect(response.status).toEqual(401); + expect(response.status).toEqual(200); + }); + }); - expect(response.header).toMatchObject({ - 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, - }); + it('handles space retrieval errors gracefully when requesting the root, responding with headers returned from ES', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/', spaces, { + simulateGetSpacesFailure: true, + simulateGetSingleSpaceFailure: false, + }); + + expect(response.status).toEqual(401); + + expect(response.header).toMatchObject({ + 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, + }); - expect(response.body).toMatchInlineSnapshot(` + expect(response.body).toMatchInlineSnapshot(` Object { "error": "Unauthorized", "message": "missing credendials", @@ -348,16 +343,122 @@ for (let i = 0; i < 100; ++i) { } `); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('handles space retrieval errors gracefully when requesting an app, responding with headers returned from ES', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/app/kibana', spaces, { + simulateGetSpacesFailure: false, + simulateGetSingleSpaceFailure: true, + }); + + expect(response.status).toEqual(401); + + expect(response.header).toMatchObject({ + 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, }); - it('handles space retrieval errors gracefully when requesting an app, responding with headers returned from ES', async () => { + expect(response.body).toMatchInlineSnapshot(` + Object { + "error": "Unauthorized", + "message": "missing credendials", + "statusCode": 401, + } + `); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('redirects to the space selector when accessing the root of the default space', async () => { + const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default space', + _reserved: true, + }, + }, + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/', spaces); + + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/spaces/space_selector`); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('redirects to the "enter space" endpoint when accessing the root of a non-default space', async () => { + const spaces = [ + { + id: 'default', + type: 'space', + attributes: { + name: 'Default space', + _reserved: true, + }, + }, + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + }, + }, + ]; + + const { response, spacesService } = await request('/s/a-space', spaces); + + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); + + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + describe('with a single available space', () => { + it('it redirects to the "enter space" endpoint within the context of the single Space when navigating to Kibana root', async () => { const spaces = [ { id: 'a-space', @@ -368,24 +469,10 @@ for (let i = 0; i < 100; ++i) { }, ]; - const { response, spacesService } = await request('/app/kibana', spaces, { - simulateGetSpacesFailure: false, - simulateGetSingleSpaceFailure: true, - }); - - expect(response.status).toEqual(401); - - expect(response.header).toMatchObject({ - 'www-authenticate': `Protected Elasticsearch error="missing credendials"`, - }); + const { response, spacesService } = await request('/', spaces); - expect(response.body).toMatchInlineSnapshot(` - Object { - "error": "Unauthorized", - "message": "missing credendials", - "statusCode": 401, - } - `); + expect(response.status).toEqual(302); + expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -396,29 +483,49 @@ for (let i = 0; i < 100; ++i) { ); }); - it('redirects to the space selector when accessing the root of the default space', async () => { + it('it redirects to the "enter space" endpoint within the context of the Default Space when navigating to Kibana root', async () => { + // This is very similar to the test above, but this handles the condition where the only available space is the Default Space, + // which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user + // is redirected to does not contain a space identifier (e.g., /s/foo) + const spaces = [ { id: 'default', type: 'space', attributes: { - name: 'Default space', - _reserved: true, + name: 'Default Space', }, }, + ]; + + const { response, spacesService } = await request('/', spaces); + + expect(response.status).toEqual(302); + expect(response.header.location).toEqual('/spaces/enter'); + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); + }); + + it('it allows navigation to apps when none are disabled', async () => { + const spaces = [ { id: 'a-space', type: 'space', attributes: { name: 'a space', + disabledFeatures: [], }, }, ]; - const { response, spacesService } = await request('/', spaces); + const { response, spacesService } = await request('/s/a-space/app/kibana', spaces); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/spaces/space_selector`); + expect(response.status).toEqual(200); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -429,29 +536,21 @@ for (let i = 0; i < 100; ++i) { ); }); - it('redirects to the "enter space" endpoint when accessing the root of a non-default space', async () => { + it('allows navigation to app that is granted by multiple features, when only one of those features is disabled', async () => { const spaces = [ - { - id: 'default', - type: 'space', - attributes: { - name: 'Default space', - _reserved: true, - }, - }, { id: 'a-space', type: 'space', attributes: { name: 'a space', + disabledFeatures: ['feature-1'], }, }, ]; - const { response, spacesService } = await request('/s/a-space', spaces); + const { response, spacesService } = await request('/s/a-space/app/app-1', spaces); - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); + expect(response.status).toEqual(200); expect(spacesService.scopedClient).toHaveBeenCalledWith( expect.objectContaining({ @@ -462,134 +561,29 @@ for (let i = 0; i < 100; ++i) { ); }); - describe('with a single available space', () => { - it('it redirects to the "enter space" endpoint within the context of the single Space when navigating to Kibana root', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - }, - }, - ]; - - const { response, spacesService } = await request('/', spaces); - - expect(response.status).toEqual(302); - expect(response.header.location).toEqual(`/s/a-space/spaces/enter`); - - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('it redirects to the "enter space" endpoint within the context of the Default Space when navigating to Kibana root', async () => { - // This is very similar to the test above, but this handles the condition where the only available space is the Default Space, - // which does not have a URL Context. In this scenario, the end result is the same as the other test, but the final URL the user - // is redirected to does not contain a space identifier (e.g., /s/foo) - - const spaces = [ - { - id: 'default', - type: 'space', - attributes: { - name: 'Default Space', - }, - }, - ]; - - const { response, spacesService } = await request('/', spaces); - - expect(response.status).toEqual(302); - expect(response.header.location).toEqual('/spaces/enter'); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('it allows navigation to apps when none are disabled', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: [], - }, - }, - ]; - - const { response, spacesService } = await request('/s/a-space/app/kibana', spaces); - - expect(response.status).toEqual(200); - - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('allows navigation to app that is granted by multiple features, when only one of those features is disabled', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: ['feature-1'], - }, - }, - ]; - - const { response, spacesService } = await request('/s/a-space/app/app-1', spaces); - - expect(response.status).toEqual(200); - - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); - - it('does not allow navigation to apps that are only provided by a disabled feature', async () => { - const spaces = [ - { - id: 'a-space', - type: 'space', - attributes: { - name: 'a space', - disabledFeatures: ['feature-2'] as any, - }, + it('does not allow navigation to apps that are only provided by a disabled feature', async () => { + const spaces = [ + { + id: 'a-space', + type: 'space', + attributes: { + name: 'a space', + disabledFeatures: ['feature-2'] as any, }, - ]; + }, + ]; - const { response, spacesService } = await request('/s/a-space/app/app-2', spaces); + const { response, spacesService } = await request('/s/a-space/app/app-2', spaces); - expect(response.status).toEqual(404); + expect(response.status).toEqual(404); - expect(spacesService.scopedClient).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }) - ); - }); + expect(spacesService.scopedClient).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ + authorization: headers.authorization, + }), + }) + ); }); }); -} +});