Merge pull request #20378 from backstage/rugvip/20-fixes
More fixes for node 20 test breakage + overridePackagePathResolution
This commit is contained in:
@@ -63,17 +63,16 @@
|
||||
"js-yaml": "^4.0.0",
|
||||
"json5": "^2.1.3",
|
||||
"mime-types": "^2.1.27",
|
||||
"mock-fs": "^5.2.0",
|
||||
"p-limit": "^3.1.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/fs-extra": "^9.0.5",
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"aws-sdk-client-mock": "^2.0.0",
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import fs from 'fs-extra';
|
||||
import mockFs from 'mock-fs';
|
||||
import os from 'os';
|
||||
import path, { resolve as resolvePath } from 'path';
|
||||
import { ParsedLocationAnnotation } from '../../helpers';
|
||||
import {
|
||||
@@ -88,14 +87,12 @@ const mkdocsYmlWithEnvTag = fs.readFileSync(
|
||||
const mockLogger = getVoidLogger();
|
||||
const warn = jest.spyOn(mockLogger, 'warn');
|
||||
|
||||
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
const scmIntegrations = ScmIntegrations.fromConfig(new ConfigReader({}));
|
||||
|
||||
describe('helpers', () => {
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
afterEach(mockDir.clear);
|
||||
|
||||
describe('getGeneratorKey', () => {
|
||||
it('should return techdocs as the only generator key', () => {
|
||||
@@ -188,13 +185,13 @@ describe('helpers', () => {
|
||||
|
||||
describe('patchMkdocsYmlPreBuild', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/mkdocs.yml': mkdocsYml,
|
||||
'/mkdocs_default.yml': mkdocsDefaultYml,
|
||||
'/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl,
|
||||
'/mkdocs_with_edit_uri.yml': mkdocsYmlWithEditUri,
|
||||
'/mkdocs_with_extensions.yml': mkdocsYmlWithExtensions,
|
||||
'/mkdocs_with_comments.yml': mkdocsYmlWithComments,
|
||||
mockDir.setContent({
|
||||
'mkdocs.yml': mkdocsYml,
|
||||
'mkdocs_default.yml': mkdocsDefaultYml,
|
||||
'mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl,
|
||||
'mkdocs_with_edit_uri.yml': mkdocsYmlWithEditUri,
|
||||
'mkdocs_with_extensions.yml': mkdocsYmlWithExtensions,
|
||||
'mkdocs_with_comments.yml': mkdocsYmlWithComments,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -205,13 +202,13 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs.yml',
|
||||
mockDir.resolve('mkdocs.yml'),
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs.yml');
|
||||
const updatedMkdocsYml = await fs.readFile(mockDir.resolve('mkdocs.yml'));
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
'repo_url: https://github.com/backstage/backstage',
|
||||
@@ -225,13 +222,15 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs_with_extensions.yml',
|
||||
mockDir.resolve('mkdocs_with_extensions.yml'),
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_extensions.yml');
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
mockDir.resolve('mkdocs_with_extensions.yml'),
|
||||
);
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
'repo_url: https://github.com/backstage/backstage',
|
||||
@@ -248,13 +247,15 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs_with_repo_url.yml',
|
||||
mockDir.resolve('mkdocs_with_repo_url.yml'),
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_repo_url.yml');
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
mockDir.resolve('mkdocs_with_repo_url.yml'),
|
||||
);
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
'repo_url: https://github.com/backstage/backstage',
|
||||
@@ -271,13 +272,15 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs_with_edit_uri.yml',
|
||||
mockDir.resolve('mkdocs_with_edit_uri.yml'),
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_edit_uri.yml');
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
mockDir.resolve('mkdocs_with_edit_uri.yml'),
|
||||
);
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
'edit_uri: https://github.com/backstage/backstage/edit/main/docs',
|
||||
@@ -294,13 +297,15 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
await patchMkdocsYmlPreBuild(
|
||||
'/mkdocs_with_comments.yml',
|
||||
mockDir.resolve('mkdocs_with_comments.yml'),
|
||||
mockLogger,
|
||||
parsedLocationAnnotation,
|
||||
scmIntegrations,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_comments.yml');
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
mockDir.resolve('mkdocs_with_comments.yml'),
|
||||
);
|
||||
|
||||
expect(updatedMkdocsYml.toString()).toContain(
|
||||
'# This is a comment that is removed after editing',
|
||||
@@ -312,20 +317,20 @@ describe('helpers', () => {
|
||||
|
||||
describe('patchMkdocsYmlWithPlugins', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'/mkdocs_with_techdocs_plugin.yml': mkdocsYmlWithTechdocsPlugins,
|
||||
'/mkdocs_without_plugins.yml': mkdocsYmlWithoutPlugins,
|
||||
'/mkdocs_with_additional_plugins.yml': mkdocsYmlWithAdditionalPlugins,
|
||||
mockDir.setContent({
|
||||
'mkdocs_with_techdocs_plugin.yml': mkdocsYmlWithTechdocsPlugins,
|
||||
'mkdocs_without_plugins.yml': mkdocsYmlWithoutPlugins,
|
||||
'mkdocs_with_additional_plugins.yml': mkdocsYmlWithAdditionalPlugins,
|
||||
});
|
||||
});
|
||||
it('should not add additional plugins if techdocs exists already in mkdocs file', async () => {
|
||||
await patchMkdocsYmlWithPlugins(
|
||||
'/mkdocs_with_techdocs_plugin.yml',
|
||||
mockDir.resolve('mkdocs_with_techdocs_plugin.yml'),
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
'/mkdocs_with_techdocs_plugin.yml',
|
||||
mockDir.resolve('mkdocs_with_techdocs_plugin.yml'),
|
||||
);
|
||||
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
|
||||
plugins: string[];
|
||||
@@ -335,11 +340,13 @@ describe('helpers', () => {
|
||||
});
|
||||
it("should add the needed plugin if it doesn't exist in mkdocs file", async () => {
|
||||
await patchMkdocsYmlWithPlugins(
|
||||
'/mkdocs_without_plugins.yml',
|
||||
mockDir.resolve('mkdocs_without_plugins.yml'),
|
||||
mockLogger,
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile('/mkdocs_without_plugins.yml');
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
mockDir.resolve('mkdocs_without_plugins.yml'),
|
||||
);
|
||||
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
|
||||
plugins: string[];
|
||||
};
|
||||
@@ -348,11 +355,11 @@ describe('helpers', () => {
|
||||
});
|
||||
it('should not override existing plugins', async () => {
|
||||
await patchMkdocsYmlWithPlugins(
|
||||
'/mkdocs_with_additional_plugins.yml',
|
||||
mockDir.resolve('mkdocs_with_additional_plugins.yml'),
|
||||
mockLogger,
|
||||
);
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
'/mkdocs_with_additional_plugins.yml',
|
||||
mockDir.resolve('mkdocs_with_additional_plugins.yml'),
|
||||
);
|
||||
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
|
||||
plugins: string[];
|
||||
@@ -364,13 +371,13 @@ describe('helpers', () => {
|
||||
});
|
||||
it('should add all provided default plugins', async () => {
|
||||
await patchMkdocsYmlWithPlugins(
|
||||
'/mkdocs_with_additional_plugins.yml',
|
||||
mockDir.resolve('mkdocs_with_additional_plugins.yml'),
|
||||
mockLogger,
|
||||
['techdocs-core', 'custom-plugin'],
|
||||
);
|
||||
|
||||
const updatedMkdocsYml = await fs.readFile(
|
||||
'/mkdocs_with_additional_plugins.yml',
|
||||
mockDir.resolve('mkdocs_with_additional_plugins.yml'),
|
||||
);
|
||||
const parsedYml = yaml.load(updatedMkdocsYml.toString()) as {
|
||||
plugins: string[];
|
||||
@@ -386,45 +393,45 @@ describe('helpers', () => {
|
||||
warn.mockClear();
|
||||
});
|
||||
it('should have no effect if docs/index.md exists', async () => {
|
||||
mockFs({
|
||||
'/docs/index.md': 'index.md content',
|
||||
'/docs/README.md': 'docs/README.md content',
|
||||
mockDir.setContent({
|
||||
'docs/index.md': 'index.md content',
|
||||
'docs/README.md': 'docs/README.md content',
|
||||
});
|
||||
|
||||
await patchIndexPreBuild({ inputDir: '/', logger: mockLogger });
|
||||
await patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger });
|
||||
|
||||
await expect(fs.readFile('/docs/index.md', 'utf-8')).resolves.toEqual(
|
||||
'index.md content',
|
||||
);
|
||||
await expect(
|
||||
fs.readFile(mockDir.resolve('docs/index.md'), 'utf-8'),
|
||||
).resolves.toEqual('index.md content');
|
||||
expect(warn).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it("should use docs/README.md if docs/index.md doesn't exists", async () => {
|
||||
mockFs({
|
||||
'/docs/README.md': 'docs/README.md content',
|
||||
'/README.md': 'main README.md content',
|
||||
mockDir.setContent({
|
||||
'docs/README.md': 'docs/README.md content',
|
||||
'README.md': 'main README.md content',
|
||||
});
|
||||
|
||||
await patchIndexPreBuild({ inputDir: '/', logger: mockLogger });
|
||||
await patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger });
|
||||
|
||||
await expect(fs.readFile('/docs/index.md', 'utf-8')).resolves.toEqual(
|
||||
'docs/README.md content',
|
||||
);
|
||||
await expect(
|
||||
fs.readFile(mockDir.resolve('docs/index.md'), 'utf-8'),
|
||||
).resolves.toEqual('docs/README.md content');
|
||||
expect(warn.mock.calls).toEqual([
|
||||
[`${path.normalize('docs/index.md')} not found.`],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should use README.md if neither docs/index.md or docs/README.md exist', async () => {
|
||||
mockFs({
|
||||
'/README.md': 'main README.md content',
|
||||
mockDir.setContent({
|
||||
'README.md': 'main README.md content',
|
||||
});
|
||||
|
||||
await patchIndexPreBuild({ inputDir: '/', logger: mockLogger });
|
||||
await patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger });
|
||||
|
||||
await expect(fs.readFile('/docs/index.md', 'utf-8')).resolves.toEqual(
|
||||
'main README.md content',
|
||||
);
|
||||
await expect(
|
||||
fs.readFile(mockDir.resolve('docs/index.md'), 'utf-8'),
|
||||
).resolves.toEqual('main README.md content');
|
||||
expect(warn.mock.calls).toEqual([
|
||||
[`${path.normalize('docs/index.md')} not found.`],
|
||||
[`${path.normalize('docs/README.md')} not found.`],
|
||||
@@ -433,11 +440,13 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
it('should not use any file as index.md if no one matches the requirements', async () => {
|
||||
mockFs({});
|
||||
mockDir.setContent({});
|
||||
|
||||
await patchIndexPreBuild({ inputDir: '/', logger: mockLogger });
|
||||
await patchIndexPreBuild({ inputDir: mockDir.path, logger: mockLogger });
|
||||
|
||||
await expect(fs.readFile('/docs/index.md', 'utf-8')).rejects.toThrow();
|
||||
await expect(
|
||||
fs.readFile(mockDir.resolve('docs/index.md'), 'utf-8'),
|
||||
).rejects.toThrow();
|
||||
const paths = [
|
||||
path.normalize('docs/index.md'),
|
||||
path.normalize('docs/README.md'),
|
||||
@@ -449,7 +458,7 @@ describe('helpers', () => {
|
||||
...paths.map(p => [`${p} not found.`]),
|
||||
[
|
||||
`Could not find any techdocs' index file. Please make sure at least one of ${paths
|
||||
.map(p => path.sep + p)
|
||||
.map(p => mockDir.resolve(p))
|
||||
.join(' ')} exists.`,
|
||||
],
|
||||
]);
|
||||
@@ -463,13 +472,11 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[rootDir]: mockFiles,
|
||||
});
|
||||
mockDir.setContent(mockFiles);
|
||||
});
|
||||
|
||||
it('should create the file if it does not exist', async () => {
|
||||
const filePath = path.join(rootDir, 'wrong_techdocs_metadata.json');
|
||||
const filePath = mockDir.resolve('wrong_techdocs_metadata.json');
|
||||
await createOrUpdateMetadata(filePath, mockLogger);
|
||||
|
||||
// Check if the file exists
|
||||
@@ -479,7 +486,7 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
it('should throw error when the JSON is invalid', async () => {
|
||||
const filePath = path.join(rootDir, 'invalid_techdocs_metadata.json');
|
||||
const filePath = mockDir.resolve('invalid_techdocs_metadata.json');
|
||||
|
||||
await expect(
|
||||
createOrUpdateMetadata(filePath, mockLogger),
|
||||
@@ -487,7 +494,7 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
it('should add build timestamp to the metadata json', async () => {
|
||||
const filePath = path.join(rootDir, 'techdocs_metadata.json');
|
||||
const filePath = mockDir.resolve('techdocs_metadata.json');
|
||||
|
||||
await createOrUpdateMetadata(filePath, mockLogger);
|
||||
|
||||
@@ -496,7 +503,7 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
it('should add list of files to the metadata json', async () => {
|
||||
const filePath = path.join(rootDir, 'techdocs_metadata.json');
|
||||
const filePath = mockDir.resolve('techdocs_metadata.json');
|
||||
|
||||
await createOrUpdateMetadata(filePath, mockLogger);
|
||||
|
||||
@@ -508,16 +515,14 @@ describe('helpers', () => {
|
||||
|
||||
describe('storeEtagMetadata', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[rootDir]: {
|
||||
'invalid_techdocs_metadata.json': 'dsds',
|
||||
'techdocs_metadata.json': '{"site_name": "Tech Docs"}',
|
||||
},
|
||||
mockDir.setContent({
|
||||
'invalid_techdocs_metadata.json': 'dsds',
|
||||
'techdocs_metadata.json': '{"site_name": "Tech Docs"}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when the JSON is invalid', async () => {
|
||||
const filePath = path.join(rootDir, 'invalid_techdocs_metadata.json');
|
||||
const filePath = mockDir.resolve('invalid_techdocs_metadata.json');
|
||||
|
||||
await expect(storeEtagMetadata(filePath, 'etag123abc')).rejects.toThrow(
|
||||
'Unexpected token',
|
||||
@@ -525,7 +530,7 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
it('should add etag to the metadata json', async () => {
|
||||
const filePath = path.join(rootDir, 'techdocs_metadata.json');
|
||||
const filePath = mockDir.resolve('techdocs_metadata.json');
|
||||
|
||||
await storeEtagMetadata(filePath, 'etag123abc');
|
||||
|
||||
@@ -535,34 +540,31 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
describe('getMkdocsYml', () => {
|
||||
const inputDir = resolvePath(__filename, '../__fixtures__/');
|
||||
const siteOptions = {
|
||||
name: mockEntity.metadata.title,
|
||||
};
|
||||
|
||||
it('returns expected contents when .yml file is present', async () => {
|
||||
const key = path.join(inputDir, 'mkdocs.yml');
|
||||
mockFs({ [key]: mkdocsYml });
|
||||
mockDir.setContent({ 'mkdocs.yml': mkdocsYml });
|
||||
const {
|
||||
path: mkdocsPath,
|
||||
content,
|
||||
configIsTemporary,
|
||||
} = await getMkdocsYml(inputDir, siteOptions);
|
||||
} = await getMkdocsYml(mockDir.path, siteOptions);
|
||||
|
||||
expect(mkdocsPath).toBe(key);
|
||||
expect(mkdocsPath).toBe(mockDir.resolve('mkdocs.yml'));
|
||||
expect(content).toBe(mkdocsYml.toString());
|
||||
expect(configIsTemporary).toBe(false);
|
||||
});
|
||||
|
||||
it('returns expected contents when .yaml file is present', async () => {
|
||||
const key = path.join(inputDir, 'mkdocs.yaml');
|
||||
mockFs({ [key]: mkdocsYml });
|
||||
mockDir.setContent({ 'mkdocs.yaml': mkdocsYml });
|
||||
const {
|
||||
path: mkdocsPath,
|
||||
content,
|
||||
configIsTemporary,
|
||||
} = await getMkdocsYml(inputDir, siteOptions);
|
||||
expect(mkdocsPath).toBe(key);
|
||||
} = await getMkdocsYml(mockDir.path, siteOptions);
|
||||
expect(mkdocsPath).toBe(mockDir.resolve('mkdocs.yaml'));
|
||||
expect(content).toBe(mkdocsYml.toString());
|
||||
expect(configIsTemporary).toBe(false);
|
||||
});
|
||||
@@ -571,17 +573,16 @@ describe('helpers', () => {
|
||||
const defaultSiteOptions = {
|
||||
name: 'Default Test site name',
|
||||
};
|
||||
const key = path.join(inputDir, 'mkdocs.yml');
|
||||
const mockPathExists = jest.spyOn(fs, 'pathExists');
|
||||
mockPathExists.mockImplementation(() => Promise.resolve(false));
|
||||
mockFs({ [key]: mkdocsDefaultYml });
|
||||
mockDir.setContent({ 'mkdocs.yml': mkdocsDefaultYml });
|
||||
const {
|
||||
path: mkdocsPath,
|
||||
content,
|
||||
configIsTemporary,
|
||||
} = await getMkdocsYml(inputDir, defaultSiteOptions);
|
||||
} = await getMkdocsYml(mockDir.path, defaultSiteOptions);
|
||||
|
||||
expect(mkdocsPath).toBe(key);
|
||||
expect(mkdocsPath).toBe(mockDir.resolve('mkdocs.yml'));
|
||||
expect(content.split(/[\r\n]+/g)).toEqual(
|
||||
mkdocsDefaultYml.toString().split(/[\r\n]+/g),
|
||||
);
|
||||
|
||||
@@ -35,16 +35,17 @@ import {
|
||||
import { mockClient, AwsClientStub } from 'aws-sdk-client-mock';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import mockFs from 'mock-fs';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { AwsS3Publish } from './awsS3';
|
||||
import { storageRootDir } from '../../testUtils/StorageFilesMock';
|
||||
import { Readable } from 'stream';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const env = process.env;
|
||||
let s3Mock: AwsClientStub<S3Client>;
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
function getMockCredentialProvider(): Promise<AwsCredentialProvider> {
|
||||
return Promise.resolve({
|
||||
sdkCredentialProvider: async () => {
|
||||
@@ -66,7 +67,7 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
metadata: { namespace, name },
|
||||
} = entity;
|
||||
|
||||
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
return mockDir.resolve(namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
class ErrorReadable extends Readable {
|
||||
@@ -182,27 +183,23 @@ describe('AwsS3Publish', () => {
|
||||
getMockCredentialProvider(),
|
||||
);
|
||||
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[directory]: files,
|
||||
});
|
||||
|
||||
const { StorageFilesMock } = require('../../testUtils/StorageFilesMock');
|
||||
const storage = new StorageFilesMock();
|
||||
storage.emptyFiles();
|
||||
|
||||
s3Mock = mockClient(S3Client);
|
||||
|
||||
s3Mock.on(HeadObjectCommand).callsFake(input => {
|
||||
if (!storage.fileExists(input.Key)) {
|
||||
if (!fs.pathExistsSync(mockDir.resolve(input.Key))) {
|
||||
throw new Error('File does not exist');
|
||||
}
|
||||
return {};
|
||||
});
|
||||
|
||||
s3Mock.on(GetObjectCommand).callsFake(input => {
|
||||
if (storage.fileExists(input.Key)) {
|
||||
if (fs.pathExistsSync(mockDir.resolve(input.Key))) {
|
||||
return {
|
||||
Body: Readable.from(storage.readFile(input.Key)),
|
||||
Body: Readable.from(fs.readFileSync(mockDir.resolve(input.Key))),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -237,12 +234,11 @@ describe('AwsS3Publish', () => {
|
||||
|
||||
s3Mock.on(UploadPartCommand).rejects();
|
||||
s3Mock.on(PutObjectCommand).callsFake(input => {
|
||||
storage.writeFile(input.Key, input.Body);
|
||||
mockDir.addContent({ [input.Key]: input.Body });
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
process.env = env;
|
||||
});
|
||||
|
||||
@@ -378,8 +374,7 @@ describe('AwsS3Publish', () => {
|
||||
});
|
||||
|
||||
it('should fail to publish a directory', async () => {
|
||||
const wrongPathToGeneratedDirectory = path.join(
|
||||
storageRootDir,
|
||||
const wrongPathToGeneratedDirectory = mockDir.resolve(
|
||||
'wrong',
|
||||
'path',
|
||||
'to',
|
||||
@@ -393,11 +388,9 @@ describe('AwsS3Publish', () => {
|
||||
directory: wrongPathToGeneratedDirectory,
|
||||
});
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
'Unable to upload file(s) to AWS S3. Error: Failed to read template directory: ENOENT, no such file or directory',
|
||||
),
|
||||
});
|
||||
await expect(fails).rejects.toThrow(
|
||||
`Unable to upload file(s) to AWS S3. Error: Failed to read template directory: ENOENT: no such file or directory, scandir '${wrongPathToGeneratedDirectory}'`,
|
||||
);
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(wrongPathToGeneratedDirectory),
|
||||
|
||||
@@ -19,7 +19,6 @@ import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import mockFs from 'mock-fs';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { AzureBlobStoragePublish } from './azureBlobStorage';
|
||||
@@ -28,10 +27,9 @@ import {
|
||||
BlobUploadCommonResponse,
|
||||
ContainerGetPropertiesResponse,
|
||||
} from '@azure/storage-blob';
|
||||
import {
|
||||
storageRootDir,
|
||||
StorageFilesMock,
|
||||
} from '../../testUtils/StorageFilesMock';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
jest.mock('@azure/identity', () => ({
|
||||
__esModule: true,
|
||||
@@ -40,13 +38,12 @@ jest.mock('@azure/identity', () => ({
|
||||
|
||||
jest.mock('@azure/storage-blob', () => {
|
||||
class BlockBlobClient {
|
||||
constructor(
|
||||
private readonly blobName: string,
|
||||
private readonly storage: StorageFilesMock,
|
||||
) {}
|
||||
constructor(private readonly blobName: string) {}
|
||||
|
||||
uploadFile(source: string): Promise<BlobUploadCommonResponse> {
|
||||
this.storage.writeFile(this.blobName, source);
|
||||
mockDir.addContent({
|
||||
[this.blobName]: fs.readFileSync(source, 'utf8'),
|
||||
});
|
||||
return Promise.resolve({
|
||||
_response: {
|
||||
request: {
|
||||
@@ -59,14 +56,14 @@ jest.mock('@azure/storage-blob', () => {
|
||||
}
|
||||
|
||||
exists() {
|
||||
return this.storage.fileExists(this.blobName);
|
||||
return fs.pathExistsSync(mockDir.resolve(this.blobName));
|
||||
}
|
||||
|
||||
download() {
|
||||
const emitter = new EventEmitter();
|
||||
setTimeout(() => {
|
||||
if (this.storage.fileExists(this.blobName)) {
|
||||
emitter.emit('data', this.storage.readFile(this.blobName));
|
||||
if (fs.pathExistsSync(mockDir.resolve(this.blobName))) {
|
||||
emitter.emit('data', fs.readFileSync(mockDir.resolve(this.blobName)));
|
||||
emitter.emit('end');
|
||||
} else {
|
||||
emitter.emit(
|
||||
@@ -126,10 +123,7 @@ jest.mock('@azure/storage-blob', () => {
|
||||
}
|
||||
|
||||
class ContainerClient {
|
||||
constructor(
|
||||
private readonly containerName: string,
|
||||
protected readonly storage: StorageFilesMock,
|
||||
) {}
|
||||
constructor(private readonly containerName: string) {}
|
||||
|
||||
getProperties(): Promise<ContainerGetPropertiesResponse> {
|
||||
return Promise.resolve({
|
||||
@@ -145,7 +139,7 @@ jest.mock('@azure/storage-blob', () => {
|
||||
}
|
||||
|
||||
getBlockBlobClient(blobName: string) {
|
||||
return new BlockBlobClient(blobName, this.storage);
|
||||
return new BlockBlobClient(blobName);
|
||||
}
|
||||
|
||||
listBlobsFlat() {
|
||||
@@ -180,31 +174,24 @@ jest.mock('@azure/storage-blob', () => {
|
||||
|
||||
class ContainerClientFailUpload extends ContainerClient {
|
||||
getBlockBlobClient(blobName: string) {
|
||||
return new BlockBlobClientFailUpload(blobName, this.storage);
|
||||
return new BlockBlobClientFailUpload(blobName);
|
||||
}
|
||||
}
|
||||
|
||||
class BlobServiceClient {
|
||||
storage = new StorageFilesMock();
|
||||
|
||||
constructor(
|
||||
public readonly url: string,
|
||||
private readonly credential?: StorageSharedKeyCredential,
|
||||
) {
|
||||
this.storage.emptyFiles();
|
||||
}
|
||||
) {}
|
||||
|
||||
getContainerClient(containerName: string) {
|
||||
if (containerName === 'bad_container') {
|
||||
return new ContainerClientFailGetProperties(
|
||||
containerName,
|
||||
this.storage,
|
||||
);
|
||||
return new ContainerClientFailGetProperties(containerName);
|
||||
}
|
||||
if (this.credential?.accountName === 'bad_account_credentials') {
|
||||
return new ContainerClientFailUpload(containerName, this.storage);
|
||||
return new ContainerClientFailUpload(containerName);
|
||||
}
|
||||
return new ContainerClient(containerName, this.storage);
|
||||
return new ContainerClient(containerName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +218,7 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
metadata: { namespace, name },
|
||||
} = entity;
|
||||
|
||||
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
return mockDir.resolve(namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
const logger = getVoidLogger();
|
||||
@@ -314,15 +301,11 @@ describe('AzureBlobStoragePublish', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[directory]: files,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
const publisher = createPublisherFromConfig();
|
||||
@@ -374,8 +357,7 @@ describe('AzureBlobStoragePublish', () => {
|
||||
});
|
||||
|
||||
it('should fail to publish a directory', async () => {
|
||||
const wrongPathToGeneratedDirectory = path.join(
|
||||
storageRootDir,
|
||||
const wrongPathToGeneratedDirectory = mockDir.resolve(
|
||||
'wrong',
|
||||
'path',
|
||||
'to',
|
||||
@@ -391,11 +373,9 @@ describe('AzureBlobStoragePublish', () => {
|
||||
directory: wrongPathToGeneratedDirectory,
|
||||
});
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
'Unable to upload file(s) to Azure. Error: Failed to read template directory: ENOENT, no such file or directory',
|
||||
),
|
||||
});
|
||||
await expect(fails).rejects.toThrow(
|
||||
`Unable to upload file(s) to Azure. Error: Failed to read template directory: ENOENT: no such file or directory, scandir '${wrongPathToGeneratedDirectory}'`,
|
||||
);
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(wrongPathToGeneratedDirectory),
|
||||
|
||||
@@ -19,26 +19,21 @@ import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import mockFs from 'mock-fs';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { Readable } from 'stream';
|
||||
import { GoogleGCSPublish } from './googleStorage';
|
||||
import {
|
||||
storageRootDir,
|
||||
StorageFilesMock,
|
||||
} from '../../testUtils/StorageFilesMock';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
jest.mock('@google-cloud/storage', () => {
|
||||
class GCSFile {
|
||||
constructor(
|
||||
private readonly filePath: string,
|
||||
private readonly storage: StorageFilesMock,
|
||||
) {}
|
||||
constructor(private readonly filePath: string) {}
|
||||
|
||||
exists() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (this.storage.fileExists(this.filePath)) {
|
||||
if (fs.pathExistsSync(mockDir.resolve(this.filePath))) {
|
||||
resolve([true]);
|
||||
} else {
|
||||
reject();
|
||||
@@ -51,11 +46,14 @@ jest.mock('@google-cloud/storage', () => {
|
||||
readable._read = () => {};
|
||||
|
||||
process.nextTick(() => {
|
||||
if (this.storage.fileExists(this.filePath)) {
|
||||
if (fs.pathExistsSync(mockDir.resolve(this.filePath))) {
|
||||
if (readable.eventNames().includes('pipe')) {
|
||||
readable.emit('pipe');
|
||||
}
|
||||
readable.emit('data', this.storage.readFile(this.filePath));
|
||||
readable.emit(
|
||||
'data',
|
||||
fs.readFileSync(mockDir.resolve(this.filePath)),
|
||||
);
|
||||
readable.emit('end');
|
||||
} else {
|
||||
readable.emit(
|
||||
@@ -74,10 +72,7 @@ jest.mock('@google-cloud/storage', () => {
|
||||
}
|
||||
|
||||
class Bucket {
|
||||
constructor(
|
||||
private readonly bucketName: string,
|
||||
private readonly storage: StorageFilesMock,
|
||||
) {}
|
||||
constructor(private readonly bucketName: string) {}
|
||||
|
||||
async getMetadata() {
|
||||
if (this.bucketName === 'bad_bucket_name') {
|
||||
@@ -88,7 +83,9 @@ jest.mock('@google-cloud/storage', () => {
|
||||
|
||||
upload(source: string, { destination }: { destination: string }) {
|
||||
return new Promise(async resolve => {
|
||||
this.storage.writeFile(destination, source);
|
||||
mockDir.addContent({
|
||||
[destination]: fs.readFileSync(source, 'utf8'),
|
||||
});
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
@@ -97,7 +94,7 @@ jest.mock('@google-cloud/storage', () => {
|
||||
if (this.bucketName === 'delete_stale_files_error') {
|
||||
throw Error('Message');
|
||||
}
|
||||
return new GCSFile(destinationFilePath, this.storage);
|
||||
return new GCSFile(destinationFilePath);
|
||||
}
|
||||
|
||||
getFilesStream() {
|
||||
@@ -119,14 +116,8 @@ jest.mock('@google-cloud/storage', () => {
|
||||
}
|
||||
|
||||
class Storage {
|
||||
storage = new StorageFilesMock();
|
||||
|
||||
constructor() {
|
||||
this.storage.emptyFiles();
|
||||
}
|
||||
|
||||
bucket(bucketName: string) {
|
||||
return new Bucket(bucketName, this.storage);
|
||||
return new Bucket(bucketName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +133,7 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
metadata: { namespace, name },
|
||||
} = entity;
|
||||
|
||||
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
return mockDir.resolve(namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
const logger = getVoidLogger();
|
||||
@@ -220,15 +211,11 @@ describe('GoogleGCSPublish', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[directory]: files,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
const publisher = createPublisherFromConfig();
|
||||
@@ -300,8 +287,7 @@ describe('GoogleGCSPublish', () => {
|
||||
});
|
||||
|
||||
it('should fail to publish a directory', async () => {
|
||||
const wrongPathToGeneratedDirectory = path.join(
|
||||
storageRootDir,
|
||||
const wrongPathToGeneratedDirectory = mockDir.resolve(
|
||||
'wrong',
|
||||
'path',
|
||||
'to',
|
||||
@@ -315,13 +301,9 @@ describe('GoogleGCSPublish', () => {
|
||||
directory: wrongPathToGeneratedDirectory,
|
||||
});
|
||||
|
||||
// Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error
|
||||
// Issue reported https://github.com/tschaub/mock-fs/issues/118
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
`Unable to upload file(s) to Google Cloud Storage. Error: Failed to read template directory: ENOENT, no such file or directory`,
|
||||
),
|
||||
});
|
||||
await expect(fails).rejects.toThrow(
|
||||
`Unable to upload file(s) to Google Cloud Storage. Error: Failed to read template directory: ENOENT: no such file or directory, scandir '${wrongPathToGeneratedDirectory}'`,
|
||||
);
|
||||
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(wrongPathToGeneratedDirectory),
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import mockFs from 'mock-fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
|
||||
import {
|
||||
getStaleFiles,
|
||||
@@ -27,6 +24,7 @@ import {
|
||||
lowerCaseEntityTripletInStoragePath,
|
||||
normalizeExternalStorageRootPath,
|
||||
} from './helpers';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
describe('getHeadersForFileExtension', () => {
|
||||
const correctMapOfExtensions = [
|
||||
@@ -57,30 +55,24 @@ describe('getHeadersForFileExtension', () => {
|
||||
});
|
||||
|
||||
describe('getFileTreeRecursively', () => {
|
||||
const root = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[root]: {
|
||||
file1: '',
|
||||
subDirA: {
|
||||
file2: '',
|
||||
emptyDir1: mockFs.directory(),
|
||||
},
|
||||
emptyDir2: mockFs.directory(),
|
||||
mockDir.setContent({
|
||||
file1: '',
|
||||
subDirA: {
|
||||
file2: '',
|
||||
emptyDir1: {},
|
||||
},
|
||||
emptyDir2: {},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('returns complete file tree of a path', async () => {
|
||||
const fileList = await getFileTreeRecursively(root);
|
||||
const fileList = await getFileTreeRecursively(mockDir.path);
|
||||
expect(fileList.length).toBe(2);
|
||||
expect(fileList).toContain(path.resolve(root, 'file1'));
|
||||
expect(fileList).toContain(path.resolve(root, 'subDirA/file2'));
|
||||
expect(fileList).toContain(mockDir.resolve('file1'));
|
||||
expect(fileList).toContain(mockDir.resolve('subDirA/file2'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
import {
|
||||
getVoidLogger,
|
||||
PluginEndpointDiscovery,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { overridePackagePathResolution } from '@backstage/backend-common/testUtils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import mockFs from 'mock-fs';
|
||||
import * as os from 'os';
|
||||
import { LocalPublish } from './local';
|
||||
import path from 'path';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const createMockEntity = (annotations = {}, lowerCase = false) => {
|
||||
return {
|
||||
@@ -44,30 +44,28 @@ const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
|
||||
getExternalBaseUrl: jest.fn(),
|
||||
};
|
||||
|
||||
const mockPublishDir = createMockDirectory();
|
||||
|
||||
overridePackagePathResolution({
|
||||
packageName: '@backstage/plugin-techdocs-backend',
|
||||
paths: {
|
||||
'static/docs': mockPublishDir.path,
|
||||
},
|
||||
});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
const tmpDir =
|
||||
os.platform() === 'win32' ? 'C:\\tmp\\generatedDir' : '/tmp/generatedDir';
|
||||
|
||||
const resolvedDir = resolvePackagePath(
|
||||
'@backstage/plugin-techdocs-backend',
|
||||
'static/docs',
|
||||
);
|
||||
|
||||
describe('local publisher', () => {
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
describe('publish', () => {
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
[tmpDir]: {
|
||||
'index.html': '',
|
||||
},
|
||||
mockPublishDir.clear();
|
||||
mockDir.setContent({
|
||||
'index.html': '',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should publish generated documentation dir', async () => {
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
@@ -79,7 +77,7 @@ describe('local publisher', () => {
|
||||
const mockEntity = createMockEntity();
|
||||
const lowerMockEntity = createMockEntity(undefined, true);
|
||||
|
||||
await publisher.publish({ entity: mockEntity, directory: tmpDir });
|
||||
await publisher.publish({ entity: mockEntity, directory: mockDir.path });
|
||||
|
||||
expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true);
|
||||
|
||||
@@ -102,12 +100,14 @@ describe('local publisher', () => {
|
||||
const mockEntity = createMockEntity();
|
||||
const lowerMockEntity = createMockEntity(undefined, true);
|
||||
|
||||
await publisher.publish({ entity: mockEntity, directory: tmpDir });
|
||||
await publisher.publish({ entity: mockEntity, directory: mockDir.path });
|
||||
|
||||
expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true);
|
||||
|
||||
// Lower/upper should be treated differently.
|
||||
expect(await publisher.hasDocsBeenGenerated(lowerMockEntity)).toBe(false);
|
||||
expect(await publisher.hasDocsBeenGenerated(lowerMockEntity)).toBe(
|
||||
os.platform() === 'darwin', // MacOS is case-insensitive
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw with unsafe triplet', async () => {
|
||||
@@ -126,7 +126,7 @@ describe('local publisher', () => {
|
||||
};
|
||||
|
||||
await expect(() =>
|
||||
publisher.publish({ entity: mockEntity, directory: tmpDir }),
|
||||
publisher.publish({ entity: mockEntity, directory: mockDir.path }),
|
||||
).rejects.toThrow('Unable to publish TechDocs site');
|
||||
});
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('local publisher', () => {
|
||||
};
|
||||
|
||||
await expect(() =>
|
||||
publisher.publish({ entity: mockEntity, directory: tmpDir }),
|
||||
publisher.publish({ entity: mockEntity, directory: mockDir.path }),
|
||||
).rejects.toThrow('Unable to publish TechDocs site');
|
||||
});
|
||||
});
|
||||
@@ -165,25 +165,19 @@ describe('local publisher', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
app = express().use(publisher.docsRouter());
|
||||
mockFs({
|
||||
[resolvedDir]: {
|
||||
'unsafe.html': '<html></html>',
|
||||
'unsafe.svg': '<svg></svg>',
|
||||
default: {
|
||||
testkind: {
|
||||
testname: {
|
||||
'index.html': 'found it',
|
||||
},
|
||||
mockPublishDir.setContent({
|
||||
'unsafe.html': '<html></html>',
|
||||
'unsafe.svg': '<svg></svg>',
|
||||
default: {
|
||||
testkind: {
|
||||
testname: {
|
||||
'index.html': 'found it',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should pass text/plain content-type for unsafe types', async () => {
|
||||
const htmlResponse = await request(app).get(`/unsafe.html`);
|
||||
expect(htmlResponse.text).toEqual('<html></html>');
|
||||
@@ -228,7 +222,7 @@ describe('local publisher', () => {
|
||||
const response = await request(app).get(
|
||||
'/default/TestKind/TestName/index.html',
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.status).toBe(os.platform() === 'darwin' ? 200 : 404);
|
||||
});
|
||||
|
||||
it('should work with a configured directory', async () => {
|
||||
@@ -236,15 +230,13 @@ describe('local publisher', () => {
|
||||
techdocs: {
|
||||
publisher: {
|
||||
local: {
|
||||
publishDirectory: tmpDir,
|
||||
publishDirectory: mockDir.path,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockFs({
|
||||
[tmpDir]: {
|
||||
'index.html': 'found it',
|
||||
},
|
||||
mockDir.setContent({
|
||||
'index.html': 'found it',
|
||||
});
|
||||
const legacyPublisher = LocalPublish.fromConfig(
|
||||
customConfig,
|
||||
|
||||
@@ -23,13 +23,14 @@ import {
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import mockFs from 'mock-fs';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { OpenStackSwiftPublish } from './openStackSwift';
|
||||
import { PublisherBase, TechDocsMetadata } from './types';
|
||||
import { storageRootDir } from '../../testUtils/StorageFilesMock';
|
||||
import { Stream, Readable } from 'stream';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
jest.mock('@trendyol-js/openstack-swift-sdk', () => {
|
||||
const {
|
||||
@@ -45,7 +46,7 @@ jest.mock('@trendyol-js/openstack-swift-sdk', () => {
|
||||
const checkFileExists = async (Key: string): Promise<boolean> => {
|
||||
// Key will always have / as file separator irrespective of OS since cloud providers expects /.
|
||||
// Normalize Key to OS specific path before checking if file exists.
|
||||
const filePath = path.join(storageRootDir, Key);
|
||||
const filePath = mockDir.resolve(Key);
|
||||
|
||||
try {
|
||||
await fs.access(filePath, fs.constants.F_OK);
|
||||
@@ -96,7 +97,7 @@ jest.mock('@trendyol-js/openstack-swift-sdk', () => {
|
||||
stream: Readable,
|
||||
) {
|
||||
try {
|
||||
const filePath = path.join(storageRootDir, destination);
|
||||
const filePath = mockDir.resolve(destination);
|
||||
const fileBuffer = await streamToBuffer(stream);
|
||||
|
||||
await fs.writeFile(filePath, fileBuffer);
|
||||
@@ -114,7 +115,7 @@ jest.mock('@trendyol-js/openstack-swift-sdk', () => {
|
||||
}
|
||||
|
||||
async download(_containerName: string, file: string) {
|
||||
const filePath = path.join(storageRootDir, file);
|
||||
const filePath = mockDir.resolve(file);
|
||||
const fileExists = await checkFileExists(file);
|
||||
if (!fileExists) {
|
||||
return new NotFound();
|
||||
@@ -151,7 +152,7 @@ const getEntityRootDir = (entity: Entity) => {
|
||||
metadata: { namespace, name },
|
||||
} = entity;
|
||||
|
||||
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
return mockDir.resolve(namespace || DEFAULT_NAMESPACE, kind, name);
|
||||
};
|
||||
|
||||
const getPosixEntityRootDir = (entity: Entity) => {
|
||||
@@ -193,11 +194,11 @@ beforeEach(() => {
|
||||
publisher = OpenStackSwiftPublish.fromConfig(mockConfig, logger);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
describe('OpenStackSwiftPublish', () => {
|
||||
afterEach(() => {
|
||||
mockDir.clear();
|
||||
});
|
||||
|
||||
describe('getReadiness', () => {
|
||||
it('should validate correct config', async () => {
|
||||
expect(await publisher.getReadiness()).toEqual({
|
||||
@@ -239,7 +240,7 @@ describe('OpenStackSwiftPublish', () => {
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[entityRootDir]: {
|
||||
'index.html': '',
|
||||
'404.html': '',
|
||||
@@ -254,12 +255,9 @@ describe('OpenStackSwiftPublish', () => {
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
expect(
|
||||
await publisher.publish({
|
||||
entity,
|
||||
directory: entityRootDir,
|
||||
}),
|
||||
).toMatchObject({
|
||||
await expect(
|
||||
publisher.publish({ entity, directory: entityRootDir }),
|
||||
).resolves.toMatchObject({
|
||||
objects: expect.arrayContaining([
|
||||
'test-namespace/TestKind/test-component-name/404.html',
|
||||
`test-namespace/TestKind/test-component-name/index.html`,
|
||||
@@ -269,8 +267,7 @@ describe('OpenStackSwiftPublish', () => {
|
||||
});
|
||||
|
||||
it('should fail to publish a directory', async () => {
|
||||
const wrongPathToGeneratedDirectory = path.join(
|
||||
storageRootDir,
|
||||
const wrongPathToGeneratedDirectory = mockDir.resolve(
|
||||
'wrong',
|
||||
'path',
|
||||
'to',
|
||||
@@ -290,13 +287,9 @@ describe('OpenStackSwiftPublish', () => {
|
||||
directory: wrongPathToGeneratedDirectory,
|
||||
});
|
||||
|
||||
// Can not do exact error message match due to mockFs adding unexpected characters in the path when throwing the error
|
||||
// Issue reported https://github.com/tschaub/mock-fs/issues/118
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(
|
||||
`Unable to upload file(s) to OpenStack Swift. Error: Failed to read template directory: ENOENT, no such file or directory`,
|
||||
),
|
||||
});
|
||||
await expect(fails).rejects.toThrow(
|
||||
`Unable to upload file(s) to OpenStack Swift. Error: Failed to read template directory: ENOENT: no such file or directory, scandir '${wrongPathToGeneratedDirectory}'`,
|
||||
);
|
||||
await expect(fails).rejects.toMatchObject({
|
||||
message: expect.stringContaining(wrongPathToGeneratedDirectory),
|
||||
});
|
||||
@@ -308,7 +301,7 @@ describe('OpenStackSwiftPublish', () => {
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[entityRootDir]: {
|
||||
'index.html': 'file-content',
|
||||
},
|
||||
@@ -330,7 +323,7 @@ describe('OpenStackSwiftPublish', () => {
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[entityRootDir]: {
|
||||
'techdocs_metadata.json':
|
||||
'{"site_name": "backstage", "site_description": "site_content", "etag": "etag", "build_timestamp": 612741599}',
|
||||
@@ -353,7 +346,7 @@ describe('OpenStackSwiftPublish', () => {
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[entityRootDir]: {
|
||||
'techdocs_metadata.json': `{'site_name': 'backstage', 'site_description': 'site_content', 'etag': 'etag', 'build_timestamp': 612741599}`,
|
||||
},
|
||||
@@ -393,7 +386,7 @@ describe('OpenStackSwiftPublish', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
app = express().use(publisher.docsRouter());
|
||||
mockFs({
|
||||
mockDir.setContent({
|
||||
[entityRootDir]: {
|
||||
html: {
|
||||
'unsafe.html': '<html></html>',
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* 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 os from 'os';
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { IStorageFilesMock } from './types';
|
||||
|
||||
export const storageRootDir: string =
|
||||
os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
|
||||
|
||||
const encoding = 'utf8';
|
||||
|
||||
export class StorageFilesMock implements IStorageFilesMock {
|
||||
static rootDir = storageRootDir;
|
||||
|
||||
private files: Record<string, string>;
|
||||
|
||||
constructor() {
|
||||
this.files = {};
|
||||
}
|
||||
|
||||
public emptyFiles(): void {
|
||||
this.files = {};
|
||||
}
|
||||
|
||||
public fileExists(targetPath: string): boolean {
|
||||
const filePath = path.join(storageRootDir, targetPath);
|
||||
const posixPath = filePath.split(path.posix.sep).join(path.sep);
|
||||
return this.files[posixPath] !== undefined;
|
||||
}
|
||||
|
||||
public readFile(targetPath: string): Buffer {
|
||||
const filePath = path.join(storageRootDir, targetPath);
|
||||
return Buffer.from(this.files[filePath] ?? '', encoding);
|
||||
}
|
||||
|
||||
public writeFile(targetPath: string, sourcePath: string): void;
|
||||
public writeFile(targetPath: string, sourceBuffer: Buffer): void;
|
||||
public writeFile(targetPath: string, source: string | Buffer): void {
|
||||
const filePath = path.join(storageRootDir, targetPath);
|
||||
if (typeof source === 'string') {
|
||||
this.files[filePath] = fs.readFileSync(source).toString(encoding);
|
||||
} else {
|
||||
this.files[filePath] = source.toString(encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface IStorageFilesMock {
|
||||
emptyFiles(): void;
|
||||
fileExists(targetPath: string): boolean;
|
||||
readFile(targetPath: string): Buffer;
|
||||
writeFile(targetPath: string, sourcePath: string): void;
|
||||
writeFile(targetPath: string, sourceBuffer: Buffer): void;
|
||||
writeFile(targetPath: string, source: string | Buffer): void;
|
||||
}
|
||||
Reference in New Issue
Block a user