Reporting/improve request handler path testing (#162926)

## Summary

Addresses https://github.com/elastic/kibana/issues/152870

When clients request a report is generated, the JSON payload they
receive in response contains a path string that helps automate
downloading the report job once it is finished. This is an important
piece of our public APIs.

When reviewing some code to research [this
issue](https://github.com/elastic/kibana/issues/152870), I found a few
areas where the validity of this field was not properly documented and
tested. This PR updates types and a test to fix the issue.
This commit is contained in:
Tim Sullivan 2023-08-02 13:23:01 -07:00 committed by GitHub
parent 70676ccbed
commit 787454a146
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 47 deletions

View file

@ -5,6 +5,16 @@
* 2.0.
*/
jest.mock(
'puid',
() =>
class MockPuid {
generate() {
return 'mock-report-id';
}
}
);
import { KibanaRequest, KibanaResponseFactory } from '@kbn/core/server';
import rison from '@kbn/rison';
import { coreMock, httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks';
@ -12,11 +22,13 @@ import { ReportingCore } from '../../..';
import { TaskPayloadPDFV2 } from '../../../../common/types/export_types/printable_pdf_v2';
import { JobParamsPDFDeprecated } from '../../../export_types/printable_pdf/types';
import { Report, ReportingStore } from '../../../lib/store';
import { ReportApiJSON } from '../../../lib/store/report';
import { createMockConfigSchema, createMockReportingCore } from '../../../test_helpers';
import { ReportingRequestHandlerContext, ReportingSetup } from '../../../types';
import {
ReportingJobResponse,
ReportingRequestHandlerContext,
ReportingSetup,
} from '../../../types';
import { RequestHandler } from './request_handler';
jest.mock('../../../lib/crypto', () => ({
cryptoFactory: () => ({
encrypt: () => `hello mock cypher text`,
@ -246,47 +258,12 @@ describe('Handle request to generate', () => {
});
test('generates the download path', async () => {
const response = (await requestHandler.handleGenerateRequest(
const { body } = (await requestHandler.handleGenerateRequest(
'csv_searchsource',
mockJobParams
)) as unknown as { body: { job: ReportApiJSON } };
const { id, created_at: _created_at, ...snapObj } = response.body.job;
expect(snapObj).toMatchInlineSnapshot(`
Object {
"attempts": 0,
"completed_at": undefined,
"created_by": "testymcgee",
"execution_time_ms": undefined,
"index": ".reporting-foo-index-234",
"jobtype": "csv_searchsource",
"kibana_id": undefined,
"kibana_name": undefined,
"max_attempts": undefined,
"meta": Object {
"isDeprecated": undefined,
"layout": "preserve_layout",
"objectType": "cool_object_type",
},
"metrics": undefined,
"migration_version": "7.14.0",
"output": Object {},
"payload": Object {
"browserTimezone": "UTC",
"layout": Object {
"id": "preserve_layout",
},
"objectType": "cool_object_type",
"relativeUrls": Array [],
"spaceId": undefined,
"title": "cool_title",
"version": "7.14.0",
},
"queue_time_ms": undefined,
"started_at": undefined,
"status": "pending",
"timeout": undefined,
}
`);
)) as unknown as { body: ReportingJobResponse };
expect(body.path).toMatch('/mock-server-basepath/api/reporting/jobs/download/mock-report-id');
});
});
});

View file

@ -16,7 +16,12 @@ import type { ReportingCore } from '../../..';
import { PUBLIC_ROUTES } from '../../../../common/constants';
import { checkParamsVersion, cryptoFactory } from '../../../lib';
import { Report } from '../../../lib/store';
import type { BaseParams, ReportingRequestHandlerContext, ReportingUser } from '../../../types';
import type {
BaseParams,
ReportingJobResponse,
ReportingRequestHandlerContext,
ReportingUser,
} from '../../../types';
export const handleUnavailable = (res: KibanaResponseFactory) => {
return res.custom({ statusCode: 503, body: 'Not Available' });
@ -203,7 +208,7 @@ export class RequestHandler {
// return task manager's task information and the download URL
counters.usageCounter();
return this.res.ok({
return this.res.ok<ReportingJobResponse>({
headers: { 'content-type': 'application/json' },
body: {
path: `${publicDownloadPath}/${report._id}`,

View file

@ -11,6 +11,7 @@ import { DiscoverServerPluginStart } from '@kbn/discover-plugin/server';
import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import type { LicensingPluginStart } from '@kbn/licensing-plugin/server';
import type { CancellationToken, TaskRunResult } from '@kbn/reporting-common';
import type { ScreenshotModePluginSetup } from '@kbn/screenshot-mode-plugin/server';
import type {
PdfScreenshotOptions as BasePdfScreenshotOptions,
@ -29,11 +30,10 @@ import type {
} from '@kbn/task-manager-plugin/server';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import type { Writable } from 'stream';
import type { CancellationToken, TaskRunResult } from '@kbn/reporting-common';
import type { BaseParams, BasePayload, UrlOrUrlLocatorTuple } from '../common/types';
import type { BaseParams, BasePayload, ReportApiJSON, UrlOrUrlLocatorTuple } from '../common/types';
import type { ReportingConfigType } from './config';
import { ExportTypesRegistry } from './lib';
import { ReportingCore } from './core';
import { ExportTypesRegistry } from './lib';
/**
* Plugin Setup Contract
@ -56,6 +56,23 @@ export type ReportingUser = { username: AuthenticatedUser['username'] } | false;
export type ScrollConfig = ReportingConfigType['csv']['scroll'];
/**
* Interface of a response to an HTTP request for our plugin to generate a report.
* @public
*/
export interface ReportingJobResponse {
/**
* Contractual field with Watcher: used to automate download of the report once it is finished
* @public
*/
path: string;
/**
* Details of a new report job that was requested
* @public
*/
job: ReportApiJSON;
}
/**
* Internal Types
*/