Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
34bb576
ee -> events
jloleysens Jan 2, 2025
f50d4cc
added link to router events interface
jloleysens Jan 2, 2025
495b9e4
export http protocol detection logic
jloleysens Jan 2, 2025
f5670a1
wip 1
jloleysens Jan 3, 2025
829f8c8
remove unused import
jloleysens Jan 3, 2025
14bdbcb
wip 2
jloleysens Jan 3, 2025
9b8e30a
some test fixes, mainly for http resources and returning the version …
jloleysens Jan 3, 2025
ebd8add
delete unused code
jloleysens Jan 3, 2025
4da954e
wiiip 3
jloleysens Jan 3, 2025
eec58bd
unrefactor minor test change
jloleysens Jan 3, 2025
b382675
added missing test coverage
jloleysens Jan 3, 2025
cdb39be
remove unnecessary code
jloleysens Jan 3, 2025
6c55327
minor tweaks
jloleysens Jan 3, 2025
43be1ad
remove unused import
jloleysens Jan 3, 2025
97d3375
remove unused type
jloleysens Jan 3, 2025
d6f5e4e
added comment
jloleysens Jan 3, 2025
427b619
remove unused generics
jloleysens Jan 3, 2025
0c4643c
fix types
jloleysens Jan 3, 2025
8a446af
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 6, 2025
c96fd9b
fix validation behaviour on versioned route and added more test coverage
jloleysens Jan 6, 2025
532280b
fix passing through versioned deprecation meta info
jloleysens Jan 6, 2025
bb2bf16
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 7, 2025
0829811
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 7, 2025
ac9c4bb
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 8, 2025
a4618b8
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 9, 2025
2a23a05
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 10, 2025
6010378
Address nit
jloleysens Jan 10, 2025
f71ab61
Merge branch 'main' into http/refactor-versioned-router
elasticmachine Jan 13, 2025
718d77b
Merge branch 'main' into http/refactor-versioned-router
jloleysens Jan 15, 2025
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
2 changes: 1 addition & 1 deletion src/core/packages/http/router-server-internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export {
type HandlerResolutionStrategy,
} from './src/versioned_router';
export { Router } from './src/router';
export type { RouterOptions, InternalRegistrar, InternalRegistrarOptions } from './src/router';
export type { RouterOptions } from './src/router';
export { isKibanaRequest, isRealRequest, ensureRawRequest, CoreKibanaRequest } from './src/request';
export { isSafeMethod } from './src/route';
export { HapiResponseAdapter } from './src/response_adapter';
Expand Down
10 changes: 9 additions & 1 deletion src/core/packages/http/router-server-internal/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class CoreKibanaRequest<
enumerable: false,
});

this.httpVersion = isRealReq ? request.raw.req.httpVersion : '1.0';
this.httpVersion = isRealReq ? getHttpVersionFromRequest(request) : '1.0';
this.apiVersion = undefined;
this.protocol = getProtocolFromHttpVersion(this.httpVersion);

Expand Down Expand Up @@ -418,3 +418,11 @@ function sanitizeRequest(req: Request): { query: unknown; params: unknown; body:
function getProtocolFromHttpVersion(httpVersion: string): HttpProtocol {
return httpVersion.split('.')[0] === '2' ? 'http2' : 'http1';
}

function getHttpVersionFromRequest(request: Request) {
return request.raw.req.httpVersion;
}

export function getProtocolFromRequest(request: Request) {
return getProtocolFromHttpVersion(getHttpVersionFromRequest(request));
}
188 changes: 188 additions & 0 deletions src/core/packages/http/router-server-internal/src/route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { hapiMocks } from '@kbn/hapi-mocks';
import { validateHapiRequest, handle } from './route';
import { createRouter } from './versioned_router/mocks';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { Logger } from '@kbn/logging';
import { RouteValidator } from './validator';
import { schema } from '@kbn/config-schema';
import { Router } from './router';
import { RouteAccess } from '@kbn/core-http-server';
import { createRequest } from './versioned_router/core_versioned_route.test.util';
import { kibanaResponseFactory } from './response';

describe('handle', () => {
let handler: jest.Func;
let log: Logger;
let router: Router;
beforeEach(() => {
router = createRouter();
handler = jest.fn(async () => kibanaResponseFactory.ok());
log = loggingSystemMock.createLogger();
});
describe('post validation events', () => {
it('emits with validation schemas provided', async () => {
const validate = { body: schema.object({ foo: schema.number() }) };
await handle(createRequest({ body: { foo: 1 } }), {
router,
handler,
log,
method: 'get',
route: { path: '/test', validate },
routeSchemas: RouteValidator.from(validate),
});
// Failure
await handle(createRequest({ body: { foo: 'bar' } }), {
router,
handler,
log,
method: 'get',
route: {
path: '/test',
validate,
options: {
deprecated: {
severity: 'warning',
reason: { type: 'bump', newApiVersion: '123' },
documentationUrl: 'http://test.foo',
},
},
},
routeSchemas: RouteValidator.from(validate),
});

expect(handler).toHaveBeenCalledTimes(1);
expect(router.emitPostValidate).toHaveBeenCalledTimes(2);

expect(router.emitPostValidate).toHaveBeenNthCalledWith(1, expect.any(Object), {
deprecated: undefined,
isInternalApiRequest: false,
isPublicAccess: false,
});
expect(router.emitPostValidate).toHaveBeenNthCalledWith(2, expect.any(Object), {
deprecated: {
severity: 'warning',
reason: { type: 'bump', newApiVersion: '123' },
documentationUrl: 'http://test.foo',
},
isInternalApiRequest: false,
isPublicAccess: false,
});
});

it('emits with no validation schemas provided', async () => {
await handle(createRequest({ body: { foo: 1 } }), {
router,
handler,
log,
method: 'get',
route: {
path: '/test',
validate: false,
options: {
deprecated: {
severity: 'warning',
reason: { type: 'bump', newApiVersion: '123' },
documentationUrl: 'http://test.foo',
},
},
},
routeSchemas: undefined,
});

expect(handler).toHaveBeenCalledTimes(1);
expect(router.emitPostValidate).toHaveBeenCalledTimes(1);

expect(router.emitPostValidate).toHaveBeenCalledWith(expect.any(Object), {
deprecated: {
severity: 'warning',
reason: { type: 'bump', newApiVersion: '123' },
documentationUrl: 'http://test.foo',
},
isInternalApiRequest: false,
isPublicAccess: false,
});
});
});
});

describe('validateHapiRequest', () => {
let router: Router;
let log: Logger;
beforeEach(() => {
router = createRouter();
log = loggingSystemMock.createLogger();
});
it('validates hapi requests and returns kibana requests: ok case', () => {
const { ok, error } = validateHapiRequest(hapiMocks.createRequest({ payload: { ok: true } }), {
log,
routeInfo: { access: 'public', httpResource: false },
router,
routeSchemas: RouteValidator.from({ body: schema.object({ ok: schema.literal(true) }) }),
});
expect(ok?.body).toEqual({ ok: true });
expect(error).toBeUndefined();
expect(log.error).not.toHaveBeenCalled();
});
it('validates hapi requests and returns kibana requests: error case', () => {
const { ok, error } = validateHapiRequest(hapiMocks.createRequest({ payload: { ok: false } }), {
log,
routeInfo: { access: 'public', httpResource: false },
router,
routeSchemas: RouteValidator.from({ body: schema.object({ ok: schema.literal(true) }) }),
});
expect(ok).toBeUndefined();
expect(error?.status).toEqual(400);
expect(error?.payload).toMatch(/expected value to equal/);
expect(log.error).toHaveBeenCalledTimes(1);
expect(log.error).toHaveBeenCalledWith('400 Bad Request', {
error: { message: '[request body.ok]: expected value to equal [true]' },
http: { request: { method: undefined, path: undefined }, response: { status_code: 400 } },
});
});

it('emits post validation events on the router', () => {
const deps = {
log,
routeInfo: { access: 'public' as RouteAccess, httpResource: false },
router,
routeSchemas: RouteValidator.from({ body: schema.object({ ok: schema.literal(true) }) }),
};
{
const { ok, error } = validateHapiRequest(
hapiMocks.createRequest({ payload: { ok: false } }),
deps
);
expect(ok).toBeUndefined();
expect(error).toBeDefined();
expect(router.emitPostValidate).toHaveBeenCalledTimes(1);
expect(router.emitPostValidate).toHaveBeenCalledWith(expect.any(Object), {
deprecated: undefined,
isInternalApiRequest: false,
isPublicAccess: true,
});
}
{
const { ok, error } = validateHapiRequest(
hapiMocks.createRequest({ payload: { ok: true } }),
deps
);
expect(ok).toBeDefined();
expect(error).toBeUndefined();
expect(router.emitPostValidate).toHaveBeenCalledTimes(2);
expect(router.emitPostValidate).toHaveBeenNthCalledWith(2, expect.any(Object), {
deprecated: undefined,
isInternalApiRequest: false,
isPublicAccess: true,
});
}
});
});
Loading