Add tests

Signed-off-by: Tomas Dabasinskas <tomas@dabasinskas.net>
This commit is contained in:
Tomas Dabasinskas
2023-04-05 16:40:12 +03:00
parent ddfdba3275
commit 96bfe75ac8
13 changed files with 679 additions and 35 deletions
@@ -0,0 +1,206 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed 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 { UrlPatternDiscovery } from '@backstage/core-app-api';
import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { PuppetDbClient } from './PuppetDbClient';
import {
PuppetDbReport,
PuppetDbReportEvent,
PuppetDbReportLog,
} from './types';
const server = setupServer();
const reports: PuppetDbReport[] = [
{
hash: 'hash1',
puppet_version: 'version1',
report_format: 1,
certname: 'node1',
start_time: '2021-01-01T00:00:00Z',
end_time: '2021-01-01T00:00:00Z',
producer_timestamp: '2021-01-01T00:00:00Z',
receive_time: '2021-01-01T00:00:00Z',
producer: 'producer1',
transaction_uuid: 'uuid1',
catalog_uuid: 'uuid1',
code_id: 'id1',
cached_catalog_status: 'status1',
type: 'type1',
corrective_change: false,
configuration_version: 'version1',
environment: 'production',
noop: false,
status: 'changed',
},
{
hash: 'hash2',
puppet_version: 'version2',
report_format: 2,
certname: 'node1',
start_time: '2021-01-01T00:00:00Z',
end_time: '2021-01-01T00:00:00Z',
producer_timestamp: '2021-01-01T00:00:00Z',
receive_time: '2021-01-01T00:00:00Z',
producer: 'producer2',
transaction_uuid: 'uuid2',
catalog_uuid: 'uuid2',
code_id: 'id2',
cached_catalog_status: 'status2',
type: 'type2',
corrective_change: false,
configuration_version: 'version2',
environment: 'production',
noop: false,
status: 'failed',
},
];
const events: PuppetDbReportEvent[] = [
{
name: 'event1',
message: 'message1',
file: 'file1',
line: 1,
certname: 'node1',
report: 'hash1',
timestamp: '2021-01-01T00:00:00Z',
report_receive_time: '2021-01-01T00:00:00Z',
run_start_time: '2021-01-01T00:00:00Z',
containing_class: 'class1',
environment: 'production',
configuration_version: 'version1',
containment_path: ['path1'],
corrective_change: false,
run_end_time: '2021-01-01T00:00:00Z',
resource_type: 'type1',
resource_title: 'title1',
property: 'property1',
old_value: 'old1',
new_value: 'new1',
status: 'changed',
},
{
name: 'event2',
message: 'message2',
file: 'file2',
line: 2,
certname: 'node1',
report: 'hash1',
timestamp: '2021-01-01T00:00:00Z',
report_receive_time: '2021-01-01T00:00:00Z',
run_start_time: '2021-01-01T00:00:00Z',
containing_class: 'class2',
environment: 'production',
configuration_version: 'version2',
containment_path: ['path2'],
corrective_change: false,
run_end_time: '2021-01-01T00:00:00Z',
resource_type: 'type2',
resource_title: 'title2',
property: 'property2',
old_value: 'old2',
new_value: 'new2',
status: 'failed',
},
];
const logs: PuppetDbReportLog[] = [
{
level: 'level1',
file: 'file1',
line: 1,
time: '2021-01-01T00:00:00Z',
message: 'message1',
source: 'source1',
tags: ['tag1'],
},
{
level: 'level2',
file: 'file2',
line: 2,
time: '2021-01-01T00:00:00Z',
message: 'message2',
source: 'source2',
tags: ['tag2'],
},
];
describe('PuppetDbClient', () => {
setupRequestMockHandlers(server);
const puppetDbCertName = 'node1';
const mockBaseUrl = 'http://backstage:9191/api/proxy';
const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl);
let client: PuppetDbClient;
const setupHandlers = () => {
server.use(
rest.get(
`${mockBaseUrl}/puppetdb/pdb/query/v4/reports`,
(req, res, ctx) => {
if (
req.url.searchParams.get('query') !==
`["=","certname","${puppetDbCertName}"]`
) {
return res(ctx.status(400));
}
return res(ctx.status(200), ctx.json(reports));
},
),
rest.get(
`${mockBaseUrl}/puppetdb/pdb/query/v4/reports/hash1/events`,
(_, res, ctx) => {
return res(ctx.status(200), ctx.json(events));
},
),
rest.get(
`${mockBaseUrl}/puppetdb/pdb/query/v4/reports/hash1/logs`,
(_, res, ctx) => {
return res(ctx.status(200), ctx.json(logs));
},
),
);
};
beforeEach(() => {
setupHandlers();
client = new PuppetDbClient({
discoveryApi: discoveryApi,
fetchApi: new MockFetchApi(),
});
});
it('getPuppetDbNodeReports should return reports', async () => {
const got = await client.getPuppetDbNodeReports(puppetDbCertName);
expect(got).toEqual(reports);
});
it('getPuppetDbReportEvents should return events', async () => {
const got = await client.getPuppetDbReportEvents('hash1');
expect(got).toEqual(events);
});
it('getPuppetDbReportLogs should return logs', async () => {
const got = await client.getPuppetDbReportLogs('hash1');
expect(got).toEqual(logs);
});
});
@@ -56,24 +56,6 @@ export class PuppetDbClient implements PuppetDbApi {
throw await ResponseError.fromResponse(response);
}
async getPuppetDbReport(
puppetDbReportHash: string,
): Promise<PuppetDbReport | undefined> {
if (!puppetDbReportHash) {
throw new Error('PuppetDB report hash is required');
}
const reports = (await this.callApi(`/pdb/query/v4/reports`, {
query: `["=","hash","${puppetDbReportHash}"]`,
})) as PuppetDbReport[];
if (!reports || reports.length === 0) {
return undefined;
}
return reports[0];
}
async getPuppetDbReportEvents(
puppetDbReportHash: string,
): Promise<PuppetDbReportEvent[] | undefined> {
+5 -1
View File
@@ -13,6 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type { PuppetDbReport, PuppetDbReportEvent } from './types';
export type {
PuppetDbReport,
PuppetDbReportEvent,
PuppetDbReportLog,
} from './types';
export { puppetDbApiRef } from './types';
export { PuppetDbClient } from './PuppetDbClient';
+1 -11
View File
@@ -62,7 +62,7 @@ export type PuppetDbReport = {
/** A flag indicating whether any of the report's events remediated configuration drift. */
corrective_change: boolean;
/** Report metrics. */
metrics: {
metrics?: {
/** Metrics data. */
data: PuppetDbReportMetric[];
/** Link to the metrics endpoint. */
@@ -176,16 +176,6 @@ export type PuppetDbApi = {
puppetDbCertName: string,
): Promise<PuppetDbReport[] | undefined>;
/**
* Get a specific PuppetDB report.
*
* @param puppetDbReportHash - The ID of the report.
* @returns A specific PuppetDB report.
*/
getPuppetDbReport(
puppetDbReportHash: string,
): Promise<PuppetDbReport | undefined>;
/**
* Get a specific PuppetDB report events.
*
@@ -0,0 +1,105 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed 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 React from 'react';
import { ReportDetailsEventsTable } from './ReportDetailsEventsTable';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { puppetDbApiRef, PuppetDbReportEvent } from '../../api';
import { PuppetDbClient } from '../../api';
import { ApiProvider } from '@backstage/core-app-api';
import { puppetDbRouteRef } from '../../routes';
const events: PuppetDbReportEvent[] = [
{
name: 'event3',
message: 'message1',
file: 'file1',
line: 1,
certname: 'node1',
report: 'hash1',
timestamp: '2021-01-01T00:00:00Z',
report_receive_time: '2021-01-01T00:00:00Z',
run_start_time: '2021-01-01T00:00:00Z',
containing_class: 'class1',
environment: 'production',
configuration_version: 'version1',
containment_path: ['path1'],
corrective_change: false,
run_end_time: '2021-01-01T00:00:00Z',
resource_type: 'type1',
resource_title: 'title1',
property: 'property1',
old_value: 'old1',
new_value: 'new1',
status: 'changed',
},
{
name: 'event2',
message: 'message2',
file: 'file2',
line: 2,
certname: 'node1',
report: 'hash1',
timestamp: '2021-01-01T00:00:00Z',
report_receive_time: '2021-01-01T00:00:00Z',
run_start_time: '2021-01-01T00:00:00Z',
containing_class: 'class2',
environment: 'production',
configuration_version: 'version2',
containment_path: ['path2'],
corrective_change: false,
run_end_time: '2021-01-01T00:00:00Z',
resource_type: 'type2',
resource_title: 'title2',
property: 'property2',
old_value: 'old2',
new_value: 'new2',
status: 'failed',
},
];
const mockPuppetDbApi: Partial<PuppetDbClient> = {
getPuppetDbReportEvents: async () => events,
};
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
const hash = 'hash';
describe('ReportEventsTable', () => {
it('should render', async () => {
const render = await renderInTestApp(
<ApiProvider apis={apis}>
<ReportDetailsEventsTable hash={hash} />
</ApiProvider>,
{
mountedRoutes: {
'/puppetdb/report/:hash': puppetDbRouteRef,
},
},
);
expect(render.getByText('Latest events')).toBeInTheDocument();
expect(render.getByText('Containing Class')).toBeInTheDocument();
expect(render.getByText('Resource')).toBeInTheDocument();
expect(render.getByText('Property')).toBeInTheDocument();
expect(render.getByText('Old Value')).toBeInTheDocument();
expect(render.getByText('New Value')).toBeInTheDocument();
expect(render.getByText('Status')).toBeInTheDocument();
expect(render.getByText('property2')).toBeInTheDocument();
expect(render.getByText('old1')).toBeInTheDocument();
expect(render.getByText('old2')).toBeInTheDocument();
});
});
@@ -15,13 +15,12 @@
*/
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { puppetDbApiRef } from '../../api';
import { puppetDbApiRef, PuppetDbReportEvent } from '../../api';
import {
ResponseErrorPanel,
Table,
TableColumn,
} from '@backstage/core-components';
import { PuppetDbReportEvent } from '../../api/types';
import useAsync from 'react-use/lib/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core/styles';
@@ -0,0 +1,74 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed 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 React from 'react';
import { ReportDetailsLogsTable } from './ReportDetailsLogsTable';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { puppetDbApiRef, PuppetDbReportLog } from '../../api';
import { PuppetDbClient } from '../../api';
import { ApiProvider } from '@backstage/core-app-api';
import { puppetDbRouteRef } from '../../routes';
const logs: PuppetDbReportLog[] = [
{
level: 'level1',
file: 'file1',
line: 1,
time: '2021-01-01T00:00:00Z',
message: 'message1',
source: 'source1',
tags: ['tag1'],
},
{
level: 'level2',
file: 'file2',
line: 2,
time: '2021-01-01T00:00:00Z',
message: 'message2',
source: 'source2',
tags: ['tag2'],
},
];
const mockPuppetDbApi: Partial<PuppetDbClient> = {
getPuppetDbReportLogs: async () => logs,
};
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
const hash = 'hash';
describe('ReportLogsTable', () => {
it('should render', async () => {
const render = await renderInTestApp(
<ApiProvider apis={apis}>
<ReportDetailsLogsTable hash={hash} />
</ApiProvider>,
{
mountedRoutes: {
'/puppetdb/report/:hash': puppetDbRouteRef,
},
},
);
expect(render.getByText('Latest logs')).toBeInTheDocument();
expect(render.getByText('Level')).toBeInTheDocument();
expect(render.getByText('Timestamp')).toBeInTheDocument();
expect(render.getByText('Source')).toBeInTheDocument();
expect(render.getByText('Message')).toBeInTheDocument();
expect(render.getByText('source1')).toBeInTheDocument();
expect(render.getByText('source2')).toBeInTheDocument();
});
});
@@ -15,13 +15,12 @@
*/
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { puppetDbApiRef } from '../../api';
import { puppetDbApiRef, PuppetDbReportLog } from '../../api';
import {
ResponseErrorPanel,
Table,
TableColumn,
} from '@backstage/core-components';
import { PuppetDbReportLog } from '../../api/types';
import useAsync from 'react-use/lib/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { makeStyles } from '@material-ui/core/styles';
@@ -0,0 +1,45 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed 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 React from 'react';
import { ReportDetailsPage } from './ReportDetailsPage';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { puppetDbRouteRef } from '../../routes';
import { puppetDbApiRef, PuppetDbClient } from '../../api';
import { ApiProvider } from '@backstage/core-app-api';
const mockPuppetDbApi: Partial<PuppetDbClient> = {
getPuppetDbReportEvents: async () => [],
};
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
describe('ReportDetailsPage', () => {
it('should render', async () => {
const render = await renderInTestApp(
<ApiProvider apis={apis}>
<ReportDetailsPage />
</ApiProvider>,
{
mountedRoutes: {
'/puppetdb/report': puppetDbRouteRef,
},
},
);
expect(render.getByText('PuppetDB Reports')).toBeInTheDocument();
expect(render.getByText('Events')).toBeInTheDocument();
expect(render.getByText('Logs')).toBeInTheDocument();
});
});
@@ -0,0 +1,85 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed 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 React from 'react';
import { ReportsPage } from './ReportsPage';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { puppetDbApiRef, PuppetDbClient } from '../../api';
import { ANNOTATION_PUPPET_CERTNAME } from '../../constants';
import { Entity } from '@backstage/catalog-model/';
import { isPluginApplicableToEntity } from '../../index';
import { ApiProvider } from '@backstage/core-app-api';
import { puppetDbRouteRef } from '../../routes';
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Resource',
metadata: {
name: 'test',
annotations: {
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
},
},
};
const entityWithoutAnnotations: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Resource',
metadata: {
name: 'test',
annotations: {},
},
};
const mockPuppetDbApi: Partial<PuppetDbClient> = {
getPuppetDbNodeReports: async () => [],
};
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
describe('isPluginApplicableToEntity', () => {
describe('when entity has no annotations', () => {
it('returns false', () => {
expect(isPluginApplicableToEntity(entityWithoutAnnotations)).toBe(false);
});
});
describe('when entity has the puppet annotation', () => {
it('returns true', () => {
expect(isPluginApplicableToEntity(entity)).toBe(true);
});
});
});
describe('ReportsPage', () => {
it('should render', async () => {
const render = await renderInTestApp(
<ApiProvider apis={apis}>
<EntityProvider entity={entity}>
<ReportsPage />
</EntityProvider>
</ApiProvider>,
{
mountedRoutes: {
'/puppetdb': puppetDbRouteRef,
},
},
);
expect(
render.getByText('Latest PuppetDB reports from node node1'),
).toBeInTheDocument();
});
});
@@ -0,0 +1,103 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed 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 React from 'react';
import { ReportsTable } from './ReportsTable';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { puppetDbApiRef, PuppetDbReport, PuppetDbClient } from '../../api';
import { ApiProvider } from '@backstage/core-app-api';
import { puppetDbRouteRef } from '../../routes';
const reports: PuppetDbReport[] = [
{
hash: 'hash1',
puppet_version: 'version1',
report_format: 1,
certname: 'node1',
start_time: '2021-01-01T00:00:00Z',
end_time: '2021-01-01T00:00:00Z',
producer_timestamp: '2021-01-01T00:00:00Z',
receive_time: '2021-01-01T00:00:00Z',
producer: 'producer1',
transaction_uuid: 'uuid1',
catalog_uuid: 'uuid1',
code_id: 'id1',
cached_catalog_status: 'status1',
type: 'type1',
corrective_change: false,
configuration_version: 'version1',
environment: 'production',
noop: true,
status: 'changed',
},
{
hash: 'hash2',
puppet_version: 'version2',
report_format: 2,
certname: 'node1',
start_time: '2021-01-01T00:00:00Z',
end_time: '2021-01-01T00:00:00Z',
producer_timestamp: '2021-01-01T00:00:00Z',
receive_time: '2021-01-01T00:00:00Z',
producer: 'producer2',
transaction_uuid: 'uuid2',
catalog_uuid: 'uuid2',
code_id: 'id2',
cached_catalog_status: 'status2',
type: 'type2',
corrective_change: false,
configuration_version: 'version2',
environment: 'production',
noop: false,
status: 'failed',
},
];
const mockPuppetDbApi: Partial<PuppetDbClient> = {
getPuppetDbNodeReports: async () => reports,
};
const apis = TestApiRegistry.from([puppetDbApiRef, mockPuppetDbApi]);
const certName = 'node1';
describe('ReportsTable', () => {
it('should render', async () => {
const render = await renderInTestApp(
<ApiProvider apis={apis}>
<ReportsTable certName={certName} />
</ApiProvider>,
{
mountedRoutes: {
'/puppetdb': puppetDbRouteRef,
},
},
);
expect(
render.getByText('Latest PuppetDB reports from node node1'),
).toBeInTheDocument();
expect(render.getByText('Configuration Version')).toBeInTheDocument();
expect(render.getByText('Start Time')).toBeInTheDocument();
expect(render.getByText('End Time')).toBeInTheDocument();
expect(render.getByText('Run Duration')).toBeInTheDocument();
expect(render.getByText('Mode')).toBeInTheDocument();
expect(render.getByText('Status')).toBeInTheDocument();
expect(render.getByText('NO-NOOP')).toBeInTheDocument();
expect(render.getByText('NOOP')).toBeInTheDocument();
expect(render.getByText('FAILED')).toBeInTheDocument();
expect(render.getByText('CHANGED')).toBeInTheDocument();
});
});
+1 -1
View File
@@ -21,7 +21,7 @@ import { ANNOTATION_PUPPET_CERTNAME } from '../constants';
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { MissingAnnotationEmptyState } from '@backstage/core-components';
import { ReportsPage } from './ReportsPage/ReportsPage';
import { ReportsPage } from './ReportsPage';
import { ReportDetailsPage } from './ReportDetailsPage';
/**
@@ -0,0 +1,52 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed 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 React from 'react';
import { StatusField } from './StatusField';
import { renderInTestApp } from '@backstage/test-utils';
describe('StatusField', () => {
it('should render failed', async () => {
const render = await renderInTestApp(<StatusField status="failed" />);
expect(render.getByText('FAILED')).toBeInTheDocument();
expect(
render.container.querySelector('span[aria-label="Status error"]'),
).toBeInTheDocument();
});
it('should render changed', async () => {
const render = await renderInTestApp(<StatusField status="changed" />);
expect(render.getByText('CHANGED')).toBeInTheDocument();
expect(
render.container.querySelector('span[aria-label="Status running"]'),
).toBeInTheDocument();
});
it('should render unchanged', async () => {
const render = await renderInTestApp(<StatusField status="unchanged" />);
expect(render.getByText('UNCHANGED')).toBeInTheDocument();
expect(
render.container.querySelector('span[aria-label="Status pending"]'),
).toBeInTheDocument();
});
it('should render default', async () => {
const render = await renderInTestApp(<StatusField status="successful" />);
expect(render.getByText('SUCCESSFUL')).toBeInTheDocument();
expect(
render.container.querySelector('span[aria-label="Status ok"]'),
).toBeInTheDocument();
});
});