techdocs-common: Add tests for google publisher and some more

This commit is contained in:
Himanshu Mishra
2020-12-05 17:42:21 +01:00
parent ff3dd9c02b
commit 17cab77fd2
6 changed files with 371 additions and 23 deletions
@@ -0,0 +1,53 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
type storageOptions = {
projectId?: string;
keyFilename?: string;
};
class Bucket {
private readonly bucketName;
constructor(bucketName: string) {
this.bucketName = bucketName;
}
getMetadata() {
return new Promise(resolve => {
resolve('');
});
}
upload(source: string, { destination }) {
return new Promise(resolve => {
resolve({ source, destination });
});
}
}
export class Storage {
private readonly projectId;
private readonly keyFilename;
constructor(options: storageOptions) {
this.projectId = options.projectId;
this.keyFilename = options.keyFilename;
}
bucket(bucketName) {
return new Bucket(bucketName);
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { EventEmitter } from 'events';
const walk = () => {
const emitter = new EventEmitter();
setTimeout(() => {
emitter.emit('end');
}, 10);
return emitter;
};
export default walk;
+7 -2
View File
@@ -54,7 +54,12 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@types/klaw": "^3.0.1",
"@backstage/cli": "^0.4.0"
"@backstage/cli": "^0.4.0",
"@types/klaw": "^3.0.1"
},
"jest": {
"roots": [
".."
]
}
}
+100 -21
View File
@@ -15,10 +15,108 @@
*/
import { Readable } from 'stream';
import { getDocFilesFromRepository } from './helpers';
import {
getDocFilesFromRepository,
getLocationForEntity,
parseReferenceAnnotation,
} from './helpers';
import { UrlReader, ReadTreeResponse } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
const entityBase: Entity = {
metadata: {
namespace: 'default',
name: 'mytestcomponent',
description: 'A component for testing',
},
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
spec: {
type: 'documentation',
lifecycle: 'experimental',
owner: 'testuser',
},
};
const metadataBase = {
namespace: 'default',
name: 'mytestcomponent',
description: 'A component for testing',
};
const goodAnnotation = {
annotations: {
'backstage.io/techdocs-ref':
'url:https://github.com/backstage/backstage/blob/master/subfolder/',
},
};
const mockEntityWithAnnotation: Entity = {
...entityBase,
...{
metadata: {
...metadataBase,
...goodAnnotation,
},
},
};
const badAnnotation = {
annotations: {
'backstage.io/techdocs-ref': 'bad-annotation',
},
};
const mockEntityWithBadAnnotation: Entity = {
...entityBase,
...{
metadata: {
...metadataBase,
...badAnnotation,
},
},
};
describe('parseReferenceAnnotation', () => {
it('should parse annotation', () => {
const parsedLocationAnnotation = parseReferenceAnnotation(
'backstage.io/techdocs-ref',
mockEntityWithAnnotation,
);
expect(parsedLocationAnnotation.type).toBe('url');
expect(parsedLocationAnnotation.target).toBe(
'https://github.com/backstage/backstage/blob/master/subfolder/',
);
});
it('should throw error without annotation', () => {
expect(() => {
parseReferenceAnnotation('backstage.io/techdocs-ref', entityBase);
}).toThrow(/No location annotation/);
});
it('should throw error with bad annotation', () => {
expect(() => {
parseReferenceAnnotation(
'backstage.io/techdocs-ref',
mockEntityWithBadAnnotation,
);
}).toThrow(/Failure to parse/);
});
});
describe('getLocationForEntity', () => {
it('should get location for entity', () => {
const parsedLocationAnnotation = getLocationForEntity(
mockEntityWithAnnotation,
);
expect(parsedLocationAnnotation.type).toBe('url');
expect(parsedLocationAnnotation.target).toBe(
'https://github.com/backstage/backstage/blob/master/subfolder/',
);
});
});
describe('getDocFilesFromRepository', () => {
it('should read a remote directory using UrlReader.readTree', async () => {
class MockUrlReader implements UrlReader {
@@ -41,28 +139,9 @@ describe('getDocFilesFromRepository', () => {
}
}
const mockEntity: Entity = {
metadata: {
namespace: 'default',
annotations: {
'backstage.io/techdocs-ref':
'url:https://github.com/backstage/backstage/blob/master/subfolder/',
},
name: 'mytestcomponent',
description: 'A component for testing',
},
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
spec: {
type: 'documentation',
lifecycle: 'experimental',
owner: 'testuser',
},
};
const output = await getDocFilesFromRepository(
new MockUrlReader(),
mockEntity,
mockEntityWithAnnotation,
);
expect(output).toBe('/tmp/testfolder');
@@ -0,0 +1,88 @@
/*
* Copyright 2020 Spotify AB
*
* 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 mockFs from 'mock-fs';
import * as winston from 'winston';
import { ConfigReader } from '@backstage/config';
import { GoogleGCSPublish } from './googleStorage';
import { PublisherBase } from './types';
const createMockEntity = (annotations = {}) => {
return {
apiVersion: 'version',
kind: 'TestKind',
metadata: {
name: 'test-component-name',
annotations: {
...annotations,
},
},
};
};
const logger = winston.createLogger();
jest.spyOn(logger, 'info').mockReturnValue(logger);
let publisher: PublisherBase;
beforeEach(() => {
mockFs({
'/path/to/google-application-credentials.json': '{}',
});
const mockConfig = ConfigReader.fromConfigs([
{
context: '',
data: {
techdocs: {
requestUrl: 'http://localhost:7000',
publisher: {
type: 'google_gcs',
google: {
pathToKey: '/path/to/google-application-credentials.json',
projectId: 'gcp-project-id',
bucketName: 'bucketName',
},
},
},
},
},
]);
publisher = GoogleGCSPublish.fromConfig(mockConfig, logger);
});
afterEach(() => {
mockFs.restore();
});
describe('GoogleGCSPublish', () => {
it('should publish a directory', () => {
mockFs({
'/path/to/generatedDirectory': {
'index.html': '',
'404.html': '',
assets: {
'main.css': '',
},
},
});
const entity = createMockEntity();
return expect(
publisher.publish({ entity, directory: '/path/to/generatedDirectory' }),
).resolves.toStrictEqual({});
});
});
@@ -0,0 +1,97 @@
/*
* Copyright 2020 Spotify AB
*
* 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 mockFs from 'mock-fs';
import {
getVoidLogger,
PluginEndpointDiscovery,
} from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { Publisher } from './publish';
import { LocalPublish } from './local';
import { GoogleGCSPublish } from './googleStorage';
const logger = getVoidLogger();
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7000'),
getExternalBaseUrl: jest.fn(),
};
describe('Publisher', () => {
it('should create local publisher by default', () => {
const mockConfig = ConfigReader.fromConfigs([
{
context: '',
data: {
techdocs: {
requestUrl: 'http://localhost:7000',
},
},
},
]);
const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery);
expect(publisher).toBeInstanceOf(LocalPublish);
});
it('should create local publisher from config', () => {
const mockConfig = ConfigReader.fromConfigs([
{
context: '',
data: {
techdocs: {
requestUrl: 'http://localhost:7000',
publisher: {
type: 'local',
},
},
},
},
]);
const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery);
expect(publisher).toBeInstanceOf(LocalPublish);
});
it('should create google gcs publisher from config', () => {
mockFs({
'/path/to/google-application-credentials.json': '{}',
});
const mockConfig = ConfigReader.fromConfigs([
{
context: '',
data: {
techdocs: {
requestUrl: 'http://localhost:7000',
publisher: {
type: 'google_gcs',
google: {
pathToKey: '/path/to/google-application-credentials.json',
projectId: 'gcp-project-id',
bucketName: 'bucketName',
},
},
},
},
},
]);
const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery);
expect(publisher).toBeInstanceOf(GoogleGCSPublish);
mockFs.restore();
});
});