Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Route tags #37344

Merged
merged 16 commits into from
Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/
import { CapabilitiesService, CapabilitiesStart } from './capabilities_service';
import { deepFreeze } from '../../utils/deep_freeze';
import { deepFreeze } from '../../../utils/';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove trailing slash for import consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's introduce linter rule explicitly if it's important. imo it doesn't make sense to perform manual check on every PR
related #36829 (comment)

import { MixedApp } from '../application_service';

const createStartContractMock = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { deepFreeze, RecursiveReadonly } from '../../utils/deep_freeze';
import { deepFreeze, RecursiveReadonly } from '../../../utils';
import { MixedApp } from '../application_service';
import { InjectedMetadataStart } from '../../injected_metadata';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { get } from 'lodash';
import { DiscoveredPlugin, PluginName } from '../../server';
import { UiSettingsState } from '../ui_settings';
import { deepFreeze } from '../utils/deep_freeze';
import { deepFreeze } from '../../utils/';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove trailing slash for import consistency.

import { Capabilities } from '..';

/** @public */
Expand Down
57 changes: 57 additions & 0 deletions src/core/server/http/http_server.mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Request, ResponseToolkit } from 'hapi';
import { merge } from 'lodash';

type DeepPartial<T> = T extends any[]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elastic/kibana-platform would be useful to declare some type helpers on project level or add a ready toolkit.

? DeepPartialArray<T[number]>
: T extends object
? DeepPartialObject<T>
: T;

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface DeepPartialArray<T> extends Array<DeepPartial<T>> {}

type DeepPartialObject<T> = { [P in keyof T]+?: DeepPartial<T[P]> };

function createRawRequestMock(customization: DeepPartial<Request> = {}) {
return merge(
{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using 2 anonymous objects here is redundant, so you can remove the first one:

return merge(
  {
    headers: {},
    path: '/',
    route: { settings: {} },
    raw: {
      req: {
        url: '/',
      },
    },
  },
  customization
) as Request;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge mutates the destination object https://lodash.com/docs/3.10.1#merge
if we mutate default value, the next test could be affected by the result of the previous createRawRequestMock call

{
headers: {},
path: '/',
route: { settings: {} },
raw: {
req: {
url: '/',
},
},
},
customization
) as Request;
}

function createRawResponseToolkitMock(customization: DeepPartial<ResponseToolkit> = {}) {
return merge({}, customization) as ResponseToolkit;
}

export const httpServerMock = {
createRawRequest: createRawRequestMock,
createRawResponseToolkit: createRawResponseToolkitMock,
};
29 changes: 10 additions & 19 deletions src/core/server/http/http_server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { HttpConfig, Router } from '.';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { HttpServer } from './http_server';
import { KibanaRequest } from './router';
import { httpServerMock } from './http_server.mocks';

const chance = new Chance();

Expand Down Expand Up @@ -678,30 +679,18 @@ test('#getBasePathFor() is based on server base path', async () => {
});

test('#setBasePathFor() cannot be set twice for one request', async () => {
const incomingMessage = {
url: '/',
};
const kibanaRequestFactory = {
from() {
return KibanaRequest.from(
{
headers: {},
path: '/',
raw: {
req: incomingMessage,
},
} as any,
undefined
);
return KibanaRequest.from(httpServerMock.createRawRequest(), undefined);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically, passing undefined to a function is redundant, can this just be:

return KibanaRequest.from(httpServerMock.createRawRequest());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KibanaRequest.from declaration marks the second parameter as required
https://github.com/restrry/kibana/blob/5128c9bf3d2fc508d95d27414b912e1d12be5a55/src/core/server/http/router/request.ts#L49
I suspect it was done to force the caller to specify route schema. Although this method is used only by Kibana, so probably we can make it optional. @azasypkin

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this method is used only by Kibana, so probably we can make it optional.

Yeah, it seems we should do that. And probably mark it as @internal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no way to make a method as truly @internal on @public class. instead we can declare from method as an internal function, not a class static method

},
};
jest.doMock('./router/request', () => ({
KibanaRequest: jest.fn(() => kibanaRequestFactory),
}));

const { setBasePathFor } = await server.setup(config);

const setPath = () => setBasePathFor(kibanaRequestFactory.from(), '/path');
const req = kibanaRequestFactory.from();
const setPath = () => setBasePathFor(req, '/path');

setPath();
expect(setPath).toThrowErrorMatchingInlineSnapshot(
Expand Down Expand Up @@ -739,7 +728,9 @@ test('Should support disabling auth for a route', async () => {
const { registerAuth, registerRouter, server: innerServer } = await server.setup(config);

const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) => res.ok({}));
router.get({ path: '/', validate: false, options: { authRequired: false } }, async (req, res) =>
mshustov marked this conversation as resolved.
Show resolved Hide resolved
res.ok({})
);
registerRouter(router);
const authenticate = jest.fn();
await registerAuth(authenticate, cookieOptions);
Expand Down Expand Up @@ -774,7 +765,7 @@ describe('#auth.isAuthenticated()', () => {
const { registerAuth, registerRouter, server: innerServer, auth } = await server.setup(config);

const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) =>
router.get({ path: '/', validate: false, options: { authRequired: false } }, async (req, res) =>
mshustov marked this conversation as resolved.
Show resolved Hide resolved
res.ok({ isAuthenticated: auth.isAuthenticated(req) })
);
registerRouter(router);
Expand All @@ -791,7 +782,7 @@ describe('#auth.isAuthenticated()', () => {
const { registerRouter, server: innerServer, auth } = await server.setup(config);

const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) =>
router.get({ path: '/', validate: false, options: { authRequired: false } }, async (req, res) =>
mshustov marked this conversation as resolved.
Show resolved Hide resolved
res.ok({ isAuthenticated: auth.isAuthenticated(req) })
);
registerRouter(router);
Expand Down Expand Up @@ -840,7 +831,7 @@ describe('#auth.get()', () => {
const { registerRouter, registerAuth, server: innerServer, auth } = await server.setup(config);
await registerAuth(authenticate, cookieOptions);
const router = new Router('');
router.get({ path: '/', validate: false, authRequired: false }, async (req, res) =>
router.get({ path: '/', validate: false, options: { authRequired: false } }, async (req, res) =>
mshustov marked this conversation as resolved.
Show resolved Hide resolved
res.ok(auth.get(req))
);

Expand Down
5 changes: 3 additions & 2 deletions src/core/server/http/http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,14 @@ export class HttpServer {

for (const router of this.registeredRouters) {
for (const route of router.getRoutes()) {
const isAuthRequired = Boolean(this.authRegistered && route.authRequired);
const { authRequired = true, tags } = route.options;
this.server.route({
handler: route.handler,
method: route.method,
path: this.getRouteFullPath(router.path, route.path),
options: {
auth: isAuthRequired ? undefined : false,
auth: this.authRegistered && authRequired ? undefined : false,
tags: tags ? Array.from(tags) : undefined,
},
});
}
Expand Down
26 changes: 17 additions & 9 deletions src/core/server/http/lifecycle/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@

import Boom from 'boom';
import { adoptToHapiAuthFormat } from './auth';
import { httpServerMock } from '../http_server.mocks';

const SessionStorageMock = {
asScoped: () => null as any,
};
const requestMock = {} as any;
const createResponseToolkit = (customization = {}): any => ({ ...customization });

describe('adoptToHapiAuthFormat', () => {
it('Should allow authenticating a user identity with given credentials', async () => {
Expand All @@ -35,8 +34,8 @@ describe('adoptToHapiAuthFormat', () => {
SessionStorageMock
);
await onAuth(
requestMock,
createResponseToolkit({
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit({
authenticated: authenticatedMock,
})
);
Expand All @@ -54,8 +53,8 @@ describe('adoptToHapiAuthFormat', () => {
const takeoverSymbol = {};
const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol }));
const result = await onAuth(
requestMock,
createResponseToolkit({
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit({
redirect: redirectMock,
})
);
Expand All @@ -69,7 +68,10 @@ describe('adoptToHapiAuthFormat', () => {
async (req, sessionStorage, t) => t.rejected(new Error('not found'), { statusCode: 404 }),
SessionStorageMock
);
const result = (await onAuth(requestMock, createResponseToolkit())) as Boom;
const result = (await onAuth(
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit()
)) as Boom;

expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('not found');
Expand All @@ -80,7 +82,10 @@ describe('adoptToHapiAuthFormat', () => {
const onAuth = adoptToHapiAuthFormat(async (req, sessionStorage, t) => {
throw new Error('unknown error');
}, SessionStorageMock);
const result = (await onAuth(requestMock, createResponseToolkit())) as Boom;
const result = (await onAuth(
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit()
)) as Boom;

expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unknown error');
Expand All @@ -92,7 +97,10 @@ describe('adoptToHapiAuthFormat', () => {
async (req, sessionStorage, t) => undefined as any,
SessionStorageMock
);
const result = (await onAuth(requestMock, createResponseToolkit())) as Boom;
const result = (await onAuth(
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit()
)) as Boom;

expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe(
Expand Down
29 changes: 18 additions & 11 deletions src/core/server/http/lifecycle/on_post_auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@

import Boom from 'boom';
import { adoptToHapiOnPostAuthFormat } from './on_post_auth';

const requestMock = {} as any;
const createResponseToolkit = (customization = {}): any => ({ ...customization });
import { httpServerMock } from '../http_server.mocks';

describe('adoptToHapiOnPostAuthFormat', () => {
it('Should allow passing request to the next handler', async () => {
const continueSymbol = {};
const continueSymbol = Symbol();
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => t.next());
const result = await onPostAuth(
requestMock,
createResponseToolkit({
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit({
['continue']: continueSymbol,
})
);
Expand All @@ -43,8 +41,8 @@ describe('adoptToHapiOnPostAuthFormat', () => {
const takeoverSymbol = {};
const redirectMock = jest.fn(() => ({ takeover: () => takeoverSymbol }));
const result = await onPostAuth(
requestMock,
createResponseToolkit({
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit({
redirect: redirectMock,
})
);
Expand All @@ -57,7 +55,10 @@ describe('adoptToHapiOnPostAuthFormat', () => {
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => {
return t.rejected(new Error('unexpected result'), { statusCode: 501 });
});
const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom;
const result = (await onPostAuth(
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit()
)) as Boom;

expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unexpected result');
Expand All @@ -68,7 +69,10 @@ describe('adoptToHapiOnPostAuthFormat', () => {
const onPostAuth = adoptToHapiOnPostAuthFormat((req, t) => {
throw new Error('unknown error');
});
const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom;
const result = (await onPostAuth(
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit()
)) as Boom;

expect(result).toBeInstanceOf(Boom);
expect(result.message).toBe('unknown error');
Expand All @@ -77,7 +81,10 @@ describe('adoptToHapiOnPostAuthFormat', () => {

it('Should return Boom.internal error if interceptor returns unexpected result', async () => {
const onPostAuth = adoptToHapiOnPostAuthFormat((req, toolkit) => undefined as any);
const result = (await onPostAuth(requestMock, createResponseToolkit())) as Boom;
const result = (await onPostAuth(
httpServerMock.createRawRequest(),
httpServerMock.createRawResponseToolkit()
)) as Boom;

expect(result).toBeInstanceOf(Boom);
expect(result.message).toMatchInlineSnapshot(
Expand Down
Loading