Add tests

Signed-off-by: Tomas Dabasinskas <tomas@dabasinskas.net>
This commit is contained in:
Tomas Dabasinskas
2023-02-05 19:49:23 +02:00
parent 9bb20d2b0d
commit 30483f692d
4 changed files with 676 additions and 0 deletions
@@ -0,0 +1,218 @@
/*
* Copyright 2021 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 { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks';
import { ConfigReader } from '@backstage/config';
import { getVoidLogger } from '@backstage/backend-common';
import { PuppetDBEntityProvider } from './PuppetDBEntityProvider';
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
import * as p from '../puppet/read';
import { DEFAULT_OWNER } from './constants';
import { ANNOTATION_PUPPET_CERTNAME } from '../puppet';
import {
ANNOTATION_LOCATION,
ANNOTATION_ORIGIN_LOCATION,
} from '@backstage/catalog-model/';
import { ENDPOINT_NODES } from '../puppet/constants';
const logger = getVoidLogger();
jest.mock('../puppet/read', () => ({
__esModule: true,
default: jest.fn(),
readPuppetNodes: null,
}));
class PersistingTaskRunner implements TaskRunner {
private tasks: TaskInvocationDefinition[] = [];
getTasks() {
return this.tasks;
}
run(task: TaskInvocationDefinition): Promise<void> {
this.tasks.push(task);
return Promise.resolve(undefined);
}
}
describe('PuppetEntityProvider', () => {
const config = new ConfigReader({
catalog: {
providers: {
puppetdb: {
host: 'http://puppetdb:8080',
schedule: {
frequency: {
minutes: 10,
},
timeout: {
minutes: 10,
},
},
},
},
},
});
describe('where there are no nodes', () => {
beforeEach(() => {
// @ts-ignore
p.readPuppetNodes = jest.fn().mockResolvedValue([]);
});
it('creates no entities', async () => {
const connection: EntityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
const providers = PuppetDBEntityProvider.fromConfig(config, {
logger,
schedule: new PersistingTaskRunner(),
});
await providers[0].connect(connection);
await providers[0].refresh(logger);
expect(connection.applyMutation).toHaveBeenCalledWith({
type: 'full',
entities: [],
});
});
});
describe('where there are nodes', () => {
beforeEach(() => {
// @ts-ignore
p.readPuppetNodes = jest.fn().mockResolvedValue([
{
api_version: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'node1',
namespace: 'default',
annotations: {
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
},
tags: ['windows'],
description: 'Description 1',
spec: {
type: 'virtual-machine',
owner: DEFAULT_OWNER,
dependsOn: [],
dependencyOf: [],
},
},
},
{
api_version: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'node2',
namespace: 'default',
annotations: {
[ANNOTATION_PUPPET_CERTNAME]: 'node2',
},
tags: ['linux'],
description: 'Description 2',
spec: {
type: 'physical-server',
owner: DEFAULT_OWNER,
dependsOn: [],
dependencyOf: [],
},
},
},
]);
});
it('creates entities', async () => {
const connection: EntityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
const providers = PuppetDBEntityProvider.fromConfig(config, {
logger,
schedule: new PersistingTaskRunner(),
});
await providers[0].connect(connection);
await providers[0].refresh(logger);
expect(connection.applyMutation).toHaveBeenCalledWith({
type: 'full',
entities: [
{
locationKey: providers[0].getProviderName(),
entity: {
api_version: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'node1',
namespace: 'default',
annotations: {
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
[ANNOTATION_LOCATION]: `url:${config.getString(
'catalog.providers.puppetdb.host',
)}${ENDPOINT_NODES}/node1`,
[ANNOTATION_ORIGIN_LOCATION]: `url:${config.getString(
'catalog.providers.puppetdb.host',
)}${ENDPOINT_NODES}/node1`,
},
tags: ['windows'],
description: 'Description 1',
spec: {
type: 'virtual-machine',
owner: DEFAULT_OWNER,
dependsOn: [],
dependencyOf: [],
},
},
},
},
{
locationKey: providers[0].getProviderName(),
entity: {
api_version: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'node2',
namespace: 'default',
annotations: {
[ANNOTATION_PUPPET_CERTNAME]: 'node2',
[ANNOTATION_LOCATION]: `url:${config.getString(
'catalog.providers.puppetdb.host',
)}${ENDPOINT_NODES}/node2`,
[ANNOTATION_ORIGIN_LOCATION]: `url:${config.getString(
'catalog.providers.puppetdb.host',
)}${ENDPOINT_NODES}/node2`,
},
tags: ['linux'],
description: 'Description 2',
spec: {
type: 'physical-server',
owner: DEFAULT_OWNER,
dependsOn: [],
dependencyOf: [],
},
},
},
},
],
});
});
});
});
@@ -0,0 +1,129 @@
/*
* Copyright 2021 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 { ConfigReader } from '@backstage/config';
import { readProviderConfigs } from './PuppetDBEntityProviderConfig';
import { Duration } from 'luxon';
describe('readProviderConfigs', () => {
afterEach(() => jest.resetAllMocks());
it('no provider config', () => {
const config = new ConfigReader({});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(0);
});
it('single simple provider config', () => {
const config = new ConfigReader({
catalog: {
providers: {
puppetdb: {
host: 'https://puppetdb',
},
},
},
});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(1);
expect(providerConfigs[0].id).toEqual('default');
expect(providerConfigs[0].host).toEqual('https://puppetdb');
});
it('single specific provider config', () => {
const config = new ConfigReader({
catalog: {
providers: {
puppetdb: {
'my-provider': {
host: 'https://puppetdb',
},
},
},
},
});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(1);
expect(providerConfigs[0].id).toEqual('my-provider');
expect(providerConfigs[0].host).toEqual('https://puppetdb');
});
it('multiple provider configs', () => {
const config = new ConfigReader({
catalog: {
providers: {
puppetdb: {
'my-provider': {
host: 'https://my-puppet/',
query: 'my-query',
},
'your-provider': {
host: 'https://your-puppet',
query: 'your-query',
},
},
},
},
});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(2);
expect(providerConfigs[0]).toEqual({
id: 'my-provider',
host: 'https://my-puppet',
query: 'my-query',
});
expect(providerConfigs[1]).toEqual({
id: 'your-provider',
host: 'https://your-puppet',
query: 'your-query',
});
});
it('provider config with schedule', () => {
const config = new ConfigReader({
catalog: {
providers: {
puppetdb: {
host: 'https://puppetdb',
schedule: {
frequency: 'PT30M',
timeout: {
minutes: 10,
},
},
},
},
},
});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(1);
expect(providerConfigs[0].schedule).toEqual({
frequency: Duration.fromISO('PT30M'),
timeout: {
minutes: 10,
},
});
});
});
@@ -0,0 +1,244 @@
/*
* 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 { readPuppetNodes } from './read';
import {
DEFAULT_PROVIDER_ID,
PuppetDBEntityProviderConfig,
} from '../providers';
import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import fetch from 'node-fetch';
import { ANNOTATION_PUPPET_CERTNAME, ENDPOINT_FACTSETS } from './constants';
jest.mock('node-fetch', () => {
const original = jest.requireActual('node-fetch');
return {
__esModule: true,
default: jest.fn(),
Headers: original.Headers,
};
});
(global as any).fetch = fetch;
describe('readPuppetNodes', () => {
const mockFetch = fetch as unknown as jest.Mocked<any>;
describe('where no query is specified', () => {
const config: PuppetDBEntityProviderConfig = {
host: 'https://puppetdb',
id: DEFAULT_PROVIDER_ID,
};
beforeEach(async () => {
mockFetch.mockReturnValueOnce(
Promise.resolve(
new Response(
JSON.stringify([
{
certname: 'node1',
timestamp: 'time1',
hash: 'hash1',
producer_timestamp: 'producer_time1',
producer: 'producer1',
environment: 'environment1',
facts: {
data: [
{
name: 'is_virtual',
value: true,
},
{
name: 'kernel',
value: 'Linux',
},
{
name: 'ipaddress',
value: 'ipaddress1',
},
{
name: 'clientnoop',
value: true,
},
{
name: 'clientversion',
value: 'clientversion1',
},
],
},
},
{
certname: 'node2',
timestamp: 'time2',
hash: 'hash2',
producer_timestamp: 'producer_time2',
producer: 'producer2',
environment: 'environment2',
facts: {
data: [
{
name: 'is_virtual',
value: false,
},
{
name: 'kernel',
value: 'Windows',
},
{
name: 'ipaddress',
value: 'ipaddress2',
},
{
name: 'clientnoop',
value: false,
},
{
name: 'clientversion',
value: 'clientversion2',
},
],
},
},
]),
),
),
);
});
describe('where custom transformer is used', () => {
it('should use it for transforming puppet nodes', async () => {
const entities = await readPuppetNodes(config, {
transformer: async (node, _config) => {
return {
apiVersion: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: `custom-${node.certname}`,
namespace: DEFAULT_NAMESPACE,
},
spec: {
type: 'Custom',
owner: 'Custom',
dependsOn: [],
dependencyOf: [],
},
};
},
});
expect(entities).toHaveLength(2);
expect(entities[0]).toEqual({
apiVersion: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'custom-node1',
namespace: DEFAULT_NAMESPACE,
},
spec: {
type: 'Custom',
owner: 'Custom',
dependsOn: [],
dependencyOf: [],
},
});
expect(entities[1]).toEqual({
apiVersion: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'custom-node2',
namespace: DEFAULT_NAMESPACE,
},
spec: {
type: 'Custom',
owner: 'Custom',
dependsOn: [],
dependencyOf: [],
},
});
});
});
describe('where default transformer is used', () => {
it('should use it for transforming puppet nodes', async () => {
const entities = await readPuppetNodes(config);
expect(entities).toHaveLength(2);
expect(entities[0].metadata.annotations).toEqual({
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
});
expect(entities[1].metadata.annotations).toEqual({
[ANNOTATION_PUPPET_CERTNAME]: 'node2',
});
});
});
});
describe('where query is specified', () => {
const config: PuppetDBEntityProviderConfig = {
host: 'https://puppetdb',
id: DEFAULT_PROVIDER_ID,
query: '["=", "certname", "node1"]',
};
describe('where no results are matched', () => {
beforeEach(async () => {
mockFetch.mockReturnValueOnce(
Promise.resolve(new Response(JSON.stringify([]))),
);
});
it('should return empty array', async () => {
const entities = await readPuppetNodes(config);
expect(entities).toHaveLength(0);
});
});
describe('where results are matched', () => {
beforeEach(async () => {
mockFetch.mockReturnValueOnce(
Promise.resolve(
new Response(
JSON.stringify([
{
certname: 'node1',
timestamp: 'time1',
hash: 'hash1',
producer_timestamp: 'producer_time1',
producer: 'producer1',
environment: 'environment1',
},
]),
),
),
);
});
it('should return matched results', async () => {
const entities = await readPuppetNodes(config);
expect(mockFetch).toHaveBeenCalledWith(
`${config.host}${ENDPOINT_FACTSETS}?query=%5B%22%3D%22%2C+%22certname%22%2C+%22node1%22%5D`,
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'GET',
},
);
expect(entities).toHaveLength(1);
});
});
});
});
@@ -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 { PuppetDBEntityProviderConfig } from '../providers';
import { PuppetNode } from './types';
import { defaultResourceTransformer } from './transformers';
import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { DEFAULT_OWNER } from '../providers';
import { ANNOTATION_PUPPET_CERTNAME } from './constants';
describe('defaultResourceTransformer', () => {
it('should transform a puppet node to a resource entity', async () => {
const config: PuppetDBEntityProviderConfig = {
host: '',
id: '',
};
const node: PuppetNode = {
certname: 'node1',
timestamp: 'time1',
hash: 'hash1',
producer_timestamp: 'producer_time1',
producer: 'producer1',
environment: 'environment1',
facts: {
href: 'facts1',
data: [
{
name: 'kernel',
value: 'Linux',
},
{
name: 'ipaddress',
value: 'ipaddress1',
},
{
name: 'is_virtual',
value: true,
},
{
name: 'clientnoop',
value: true,
},
{
name: 'clientversion',
value: 'clientversion1',
},
],
},
};
const entity = await defaultResourceTransformer(node, config);
expect(entity).toEqual({
apiVersion: 'backstage.io/v1beta1',
kind: 'Resource',
metadata: {
name: 'node1',
namespace: DEFAULT_NAMESPACE,
annotations: {
[ANNOTATION_PUPPET_CERTNAME]: 'node1',
},
description: 'ipaddress1',
tags: ['linux'],
},
spec: {
type: 'virtual-machine',
owner: DEFAULT_OWNER,
dependsOn: [],
dependencyOf: [],
},
});
});
});