1. Use File.exists and fs.access to check if file exists instead of reading file

2. Clarify name of extension types which need special headers
3. More tests
This commit is contained in:
Himanshu Mishra
2020-12-10 22:55:53 +01:00
parent 15d23c004e
commit 320bb9c671
6 changed files with 86 additions and 33 deletions
@@ -61,7 +61,7 @@ beforeEach(() => {
});
describe('GoogleGCSPublish', () => {
it('should publish a directory', () => {
it('should publish a directory', async () => {
mockFs({
'/path/to/generatedDirectory': {
'index.html': '',
@@ -73,8 +73,12 @@ describe('GoogleGCSPublish', () => {
});
const entity = createMockEntity();
return expect(
publisher.publish({ entity, directory: '/path/to/generatedDirectory' }),
).resolves.toBeUndefined();
expect(
await publisher.publish({
entity,
directory: '/path/to/generatedDirectory',
}),
).toBeUndefined();
mockFs.restore();
});
});
@@ -14,15 +14,15 @@
* limitations under the License.
*/
import express from 'express';
import { Storage, UploadResponse } from '@google-cloud/storage';
import {
Storage,
UploadResponse,
FileExistsResponse,
} from '@google-cloud/storage';
import { Logger } from 'winston';
import { Entity, EntityName } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import {
getHeadersForFileExtension,
supportedFileType,
getFileTreeRecursively,
} from './helpers';
import { getHeadersForFileExtension, getFileTreeRecursively } from './helpers';
import { PublisherBase, PublishRequest } from './types';
export class GoogleGCSPublish implements PublisherBase {
@@ -168,9 +168,7 @@ export class GoogleGCSPublish implements PublisherBase {
// Files with different extensions (CSS, HTML) need to be served with different headers
const fileExtension = filePath.split('.')[filePath.split('.').length - 1];
const responseHeaders = getHeadersForFileExtension(
fileExtension as supportedFileType,
);
const responseHeaders = getHeadersForFileExtension(fileExtension);
const fileStreamChunks: Array<any> = [];
this.storageClient
@@ -208,12 +206,12 @@ export class GoogleGCSPublish implements PublisherBase {
this.storageClient
.bucket(this.bucketName)
.file(`${entityRootDir}/index.html`)
.createReadStream()
.on('error', () => {
resolve(false);
.exists()
.then((response: FileExistsResponse) => {
resolve(response[0]);
})
.on('data', () => {
resolve(true);
.catch(() => {
resolve(false);
});
});
}
@@ -14,7 +14,33 @@
* limitations under the License.
*/
import mockFs from 'mock-fs';
import { getFileTreeRecursively } from './helpers';
import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
describe('getHeadersForFileExtension', () => {
it('returns correct header for default extensions', () => {
const headers = getHeadersForFileExtension('xyz');
const expectedHeaders = {
'Content-Type': 'text/plain',
};
expect(headers).toEqual(expectedHeaders);
});
it('returns correct header for html', () => {
const headers = getHeadersForFileExtension('html');
const expectedHeaders = {
'Content-Type': 'text/html; charset=UTF-8',
};
expect(headers).toEqual(expectedHeaders);
});
it('returns correct header for css', () => {
const headers = getHeadersForFileExtension('css');
const expectedHeaders = {
'Content-Type': 'text/css; charset=UTF-8',
};
expect(headers).toEqual(expectedHeaders);
});
});
describe('getFileTreeRecursively', () => {
beforeEach(() => {
@@ -15,14 +15,17 @@
*/
import recursiveReadDir from 'recursive-readdir';
export type supportedFileType = 'html' | 'css';
export type responseHeadersType = {
'Content-Type': string;
};
/**
* Some files need special headers to be used correctly by the frontend. This function
* generates headers in the response to those file requests.
* @param {string} fileExtension html, css, js etc.
*/
export const getHeadersForFileExtension = (
fileType: supportedFileType,
fileExtension: string,
): responseHeadersType => {
const headersCommon = {
'Content-Type': 'text/plain',
@@ -37,7 +40,7 @@ export const getHeadersForFileExtension = (
'Content-Type': 'text/css; charset=UTF-8',
};
switch (fileType) {
switch (fileExtension) {
case 'html':
return headersHTML;
case 'css':
@@ -24,6 +24,23 @@ import {
import { ConfigReader } from '@backstage/config';
import { LocalPublish } from './local';
jest.mock('fs-extra', () => {
const fsOriginal = jest.requireActual('fs-extra');
return {
...fsOriginal,
access: jest.fn().mockImplementation((path, checkType, callback) => {
if (
path.includes('http://localhost:7000/static') &&
checkType === fs.constants.F_OK
) {
callback();
} else {
callback(new Error());
}
}),
};
});
const createMockEntity = (annotations = {}) => {
return {
apiVersion: 'version',
@@ -42,7 +59,7 @@ const logger = getVoidLogger();
describe('local publisher', () => {
it('should publish generated documentation dir', async () => {
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7000'),
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000'),
getExternalBaseUrl: jest.fn(),
};
@@ -52,27 +69,24 @@ describe('local publisher', () => {
data: {
techdocs: {
requestUrl: 'http://localhost:7000',
storageUrl: 'http://localhost:7000/static/docs',
},
},
},
]);
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
const mockEntity = createMockEntity();
const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`);
expect(tempDir).toBeTruthy();
fs.closeSync(fs.openSync(path.join(tempDir, '/mock-file'), 'w'));
await publisher.publish({ entity: mockEntity, directory: tempDir });
const publishDir = path.resolve(
__dirname,
`../../../../../plugins/techdocs-backend/static/docs/${mockEntity.metadata.name}`,
);
const resultDir = path.resolve(
__dirname,
`../../../../../plugins/techdocs-backend/static/docs/default/${mockEntity.kind}/${mockEntity.metadata.name}`,
@@ -81,6 +95,8 @@ describe('local publisher', () => {
expect(fs.existsSync(resultDir)).toBeTruthy();
expect(fs.existsSync(path.join(resultDir, '/mock-file'))).toBeTruthy();
expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true);
fs.removeSync(publishDir);
fs.removeSync(tempDir);
});
@@ -123,6 +123,7 @@ export class LocalPublish implements PublisherBase {
}
async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {
const namespace = entity.metadata.namespace ?? 'default';
return new Promise(resolve => {
this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => {
const storageUrl = new URL(
@@ -130,11 +131,16 @@ export class LocalPublish implements PublisherBase {
techdocsApiUrl,
).toString();
const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
const entityRootDir = `${namespace}/${entity.kind}/${entity.metadata.name}`;
const indexHtmlUrl = `${storageUrl}/${entityRootDir}/index.html`;
fetch(indexHtmlUrl)
.then(() => resolve(true))
.catch(() => resolve(false));
// Check if the file exists
fs.access(indexHtmlUrl, fs.constants.F_OK, err => {
if (err) {
resolve(false);
} else {
resolve(true);
}
});
});
});
}