Merge pull request #10146 from backstage/rugvip/noglobalmocks

scaffolder-backend,techdocs-node: remove global dependency mocks
This commit is contained in:
Patrik Oldsberg
2022-03-17 14:08:35 +01:00
committed by GitHub
21 changed files with 658 additions and 810 deletions
@@ -1,20 +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 class DefaultAzureCredential {
/**
* Creates an instance of the DefaultAzureCredential class.
*/
}
@@ -1,195 +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 {
BlobUploadCommonResponse,
ContainerGetPropertiesResponse,
} from '@azure/storage-blob';
import { EventEmitter } from 'events';
import { IStorageFilesMock } from '../../src/testUtils/types';
const storage = global.storageFilesMock as IStorageFilesMock;
export class BlockBlobClient {
private readonly blobName;
constructor(blobName: string) {
this.blobName = blobName;
}
uploadFile(source: string): Promise<BlobUploadCommonResponse> {
storage.writeFile(this.blobName, source);
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 200,
headers: {} as any,
},
});
}
exists() {
return storage.fileExists(this.blobName);
}
download() {
const emitter = new EventEmitter();
setTimeout(() => {
if (storage.fileExists(this.blobName)) {
emitter.emit('data', storage.readFile(this.blobName));
emitter.emit('end');
} else {
emitter.emit(
'error',
new Error(`The file ${this.blobName} does not exist!`),
);
}
}, 0);
return Promise.resolve({
readableStreamBody: emitter,
});
}
}
class BlockBlobClientFailUpload extends BlockBlobClient {
uploadFile(): Promise<BlobUploadCommonResponse> {
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 500,
headers: {} as any,
},
});
}
}
class ContainerClientIterator {
private containerName: string;
constructor(containerName) {
this.containerName = containerName;
}
async next() {
if (
this.containerName === 'delete_stale_files_success' ||
this.containerName === 'delete_stale_files_error'
) {
return {
value: {
segment: {
blobItems: [{ name: `stale_file.png` }],
},
},
};
}
return {
value: {
segment: {
blobItems: [],
},
},
};
}
}
export class ContainerClient {
getProperties(): Promise<ContainerGetPropertiesResponse> {
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 200,
headers: {} as any,
parsedHeaders: {},
},
});
}
getBlockBlobClient(blobName: string) {
return new BlockBlobClient(blobName);
}
listBlobsFlat() {
return {
byPage: () => {
return new ContainerClientIterator(this.containerName);
},
};
}
deleteBlob() {
if (this.containerName === 'delete_stale_files_error') {
throw new Error('Message');
}
}
}
class ContainerClientFailGetProperties extends ContainerClient {
getProperties(): Promise<ContainerGetPropertiesResponse> {
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 404,
headers: {} as any,
parsedHeaders: {},
},
});
}
}
class ContainerClientFailUpload extends ContainerClient {
getBlockBlobClient(blobName: string) {
return new BlockBlobClientFailUpload(blobName);
}
}
export class BlobServiceClient {
private readonly url;
private readonly credential;
constructor(url: string, credential?: StorageSharedKeyCredential) {
storage.emptyFiles();
this.url = url;
this.credential = credential;
}
getContainerClient(containerName: string) {
if (containerName === 'bad_container') {
return new ContainerClientFailGetProperties();
}
if (this.credential.accountName === 'bad_account_credentials') {
return new ContainerClientFailUpload();
}
return new ContainerClient();
}
}
export class StorageSharedKeyCredential {
private readonly accountName;
private readonly accountKey;
constructor(accountName: string, accountKey: string) {
this.accountName = accountName;
this.accountKey = accountKey;
}
}
@@ -1,119 +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 { Readable } from 'stream';
import { IStorageFilesMock } from '../../src/testUtils/types';
const storage = global.storageFilesMock as IStorageFilesMock;
class GCSFile {
private readonly path: string;
constructor(path: string) {
this.path = path;
}
exists() {
return new Promise(async (resolve, reject) => {
if (storage.fileExists(this.path)) {
resolve([true]);
} else {
reject();
}
});
}
createReadStream() {
const readable = new Readable();
readable._read = () => {};
process.nextTick(() => {
if (storage.fileExists(this.path)) {
if (readable.eventNames().includes('pipe')) {
readable.emit('pipe');
}
readable.emit('data', storage.readFile(this.path));
readable.emit('end');
} else {
readable.emit(
'error',
new Error(`The file ${this.path} does not exist!`),
);
}
});
return readable;
}
delete() {
return Promise.resolve();
}
}
class Bucket {
private readonly bucketName;
constructor(bucketName: string) {
this.bucketName = bucketName;
}
async getMetadata() {
if (this.bucketName === 'bad_bucket_name') {
throw Error('Bucket does not exist');
}
return '';
}
upload(source: string, { destination }) {
return new Promise(async resolve => {
storage.writeFile(destination, source);
resolve(null);
});
}
file(destinationFilePath: string) {
if (this.bucketName === 'delete_stale_files_error') {
throw Error('Message');
}
return new GCSFile(destinationFilePath);
}
getFilesStream() {
const readable = new Readable();
readable._read = () => {};
process.nextTick(() => {
if (
this.bucketName === 'delete_stale_files_success' ||
this.bucketName === 'delete_stale_files_error'
) {
readable.emit('data', { name: 'stale-file.png' });
}
readable.emit('end');
});
return readable;
}
}
export class Storage {
constructor() {
storage.emptyFiles();
}
bucket(bucketName) {
return new Bucket(bucketName);
}
}
@@ -1,103 +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 fs from 'fs-extra';
import os from 'os';
import path from 'path';
import {
ContainerMetaResponse,
DownloadResponse,
NotFound,
ObjectMetaResponse,
UploadResponse,
} from '@trendyol-js/openstack-swift-sdk';
import { Stream, Readable } from 'stream';
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
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(rootDir, Key);
try {
await fs.access(filePath, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
};
const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
return new Promise((resolve, reject) => {
try {
const chunks: any[] = [];
stream.on('data', chunk => chunks.push(chunk));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks)));
} catch (e) {
throw new Error(`Unable to parse the response data ${e.message}`);
}
});
};
export class SwiftClient {
async getMetadata(_containerName: string, file: string) {
const fileExists = await checkFileExists(file);
if (fileExists) {
return new ObjectMetaResponse({
fullPath: file,
});
}
return new NotFound();
}
async getContainerMetadata(containerName: string) {
if (containerName === 'mock') {
return new ContainerMetaResponse({
size: 10,
});
}
return new NotFound();
}
async upload(_containerName: string, destination: string, stream: Readable) {
try {
const filePath = path.join(rootDir, destination);
const fileBuffer = await streamToBuffer(stream);
await fs.writeFile(filePath, fileBuffer);
const fileExists = await checkFileExists(destination);
if (fileExists) {
return new UploadResponse(filePath);
}
const errorMessage = `Unable to upload file(s) to OpenStack Swift.`;
throw new Error(errorMessage);
} catch (error) {
const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${error}`;
throw new Error(errorMessage);
}
}
async download(_containerName: string, file: string) {
const filePath = path.join(rootDir, file);
const fileExists = await checkFileExists(file);
if (!fileExists) {
return new NotFound();
}
return new DownloadResponse([], fs.createReadStream(filePath));
}
}
-114
View File
@@ -1,114 +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 { EventEmitter } from 'events';
import { ReadStream } from 'fs';
import { IStorageFilesMock } from '../src/testUtils/types';
export { Credentials } from 'aws-sdk';
const storage = global.storageFilesMock as IStorageFilesMock;
export class S3 {
constructor() {
storage.emptyFiles();
}
headObject({ Key }: { Key: string }) {
return {
promise: async () => {
if (!storage.fileExists(Key)) {
throw new Error('File does not exist');
}
},
};
}
getObject({ Key }: { Key: string }) {
return {
promise: async () => storage.fileExists(Key),
createReadStream: () => {
const emitter = new EventEmitter();
process.nextTick(() => {
if (storage.fileExists(Key)) {
emitter.emit('data', Buffer.from(storage.readFile(Key)));
emitter.emit('end');
} else {
emitter.emit('error', new Error(`The file ${Key} does not exist!`));
}
});
return emitter;
},
};
}
headBucket({ Bucket }) {
return {
promise: async () => {
if (Bucket === 'errorBucket') {
throw new Error('Bucket does not exist');
}
return {};
},
};
}
upload({ Key, Body }: { Key: string; Body: ReadStream }) {
return {
promise: () =>
new Promise(async resolve => {
const chunks = [];
Body.on('data', chunk => {
chunks.push(chunk);
});
Body.once('end', () => {
storage.writeFile(Key, Buffer.concat(chunks));
resolve(null);
});
}),
};
}
listObjectsV2({ Bucket }) {
return {
promise: () => {
if (
Bucket === 'delete_stale_files_success' ||
Bucket === 'delete_stale_files_error'
) {
return Promise.resolve({
Contents: [{ Key: 'stale_file.png' }],
});
}
return Promise.resolve({});
},
};
}
deleteObject({ Bucket }) {
return {
promise: () => {
if (Bucket === 'delete_stale_files_error') {
throw new Error('Message');
}
return Promise.resolve();
},
};
}
}
export default {
S3,
};
-5
View File
@@ -72,10 +72,5 @@
"@types/recursive-readdir": "^2.2.0",
"@types/supertest": "^2.0.8",
"supertest": "^6.1.3"
},
"jest": {
"roots": [
".."
]
}
}
-20
View File
@@ -1,20 +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 { StorageFilesMock } from './testUtils/StorageFilesMock';
(global as any).rootDir = StorageFilesMock.rootDir;
(global as any).storageFilesMock = new StorageFilesMock();
@@ -21,12 +21,112 @@ import express from 'express';
import request from 'supertest';
import mockFs from 'mock-fs';
import path from 'path';
import fs from 'fs-extra';
import fs, { ReadStream } from 'fs-extra';
import { EventEmitter } from 'events';
import { AwsS3Publish } from './awsS3';
import { storageRootDir } from '../../testUtils/StorageFilesMock';
// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock aws-sdk client library
jest.mock('aws-sdk', () => {
const { StorageFilesMock } = require('../../testUtils/StorageFilesMock');
const storage = new StorageFilesMock();
const rootDir = (global as any).rootDir; // Set by setupTests.ts
return {
__esModule: true,
Credentials: jest.requireActual('aws-sdk').Credentials,
default: {
S3: class {
constructor() {
storage.emptyFiles();
}
headObject({ Key }: { Key: string }) {
return {
promise: async () => {
if (!storage.fileExists(Key)) {
throw new Error('File does not exist');
}
},
};
}
getObject({ Key }: { Key: string }) {
return {
promise: async () => storage.fileExists(Key),
createReadStream: () => {
const emitter = new EventEmitter();
process.nextTick(() => {
if (storage.fileExists(Key)) {
emitter.emit('data', Buffer.from(storage.readFile(Key)));
emitter.emit('end');
} else {
emitter.emit(
'error',
new Error(`The file ${Key} does not exist!`),
);
}
});
return emitter;
},
};
}
headBucket({ Bucket }: { Bucket: string }) {
return {
promise: async () => {
if (Bucket === 'errorBucket') {
throw new Error('Bucket does not exist');
}
return {};
},
};
}
upload({ Key, Body }: { Key: string; Body: ReadStream }) {
return {
promise: () =>
new Promise(async resolve => {
const chunks = new Array<Buffer>();
Body.on('data', chunk => {
chunks.push(chunk as Buffer);
});
Body.once('end', () => {
storage.writeFile(Key, Buffer.concat(chunks));
resolve(null);
});
}),
};
}
listObjectsV2({ Bucket }: { Bucket: string }) {
return {
promise: () => {
if (
Bucket === 'delete_stale_files_success' ||
Bucket === 'delete_stale_files_error'
) {
return Promise.resolve({
Contents: [{ Key: 'stale_file.png' }],
});
}
return Promise.resolve({});
},
};
}
deleteObject({ Bucket }: { Bucket: string }) {
return {
promise: () => {
if (Bucket === 'delete_stale_files_error') {
throw new Error('Message');
}
return Promise.resolve();
},
};
}
},
},
};
});
const getEntityRootDir = (entity: Entity) => {
const {
@@ -34,7 +134,7 @@ const getEntityRootDir = (entity: Entity) => {
metadata: { namespace, name },
} = entity;
return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name);
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
};
const logger = getVoidLogger();
@@ -213,7 +313,7 @@ describe('AwsS3Publish', () => {
it('should fail to publish a directory', async () => {
const wrongPathToGeneratedDirectory = path.join(
rootDir,
storageRootDir,
'wrong',
'path',
'to',
@@ -23,10 +23,207 @@ import mockFs from 'mock-fs';
import path from 'path';
import fs from 'fs-extra';
import { AzureBlobStoragePublish } from './azureBlobStorage';
import { EventEmitter } from 'events';
import {
BlobUploadCommonResponse,
ContainerGetPropertiesResponse,
} from '@azure/storage-blob';
import {
storageRootDir,
StorageFilesMock,
} from '../../testUtils/StorageFilesMock';
// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock Azure client library
jest.mock('@azure/identity', () => ({
__esModule: true,
DefaultAzureCredential: class {},
}));
const rootDir = (global as any).rootDir; // Set by setupTests.ts
jest.mock('@azure/storage-blob', () => {
class BlockBlobClient {
constructor(
private readonly blobName: string,
private readonly storage: StorageFilesMock,
) {}
uploadFile(source: string): Promise<BlobUploadCommonResponse> {
this.storage.writeFile(this.blobName, source);
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 200,
headers: {} as any,
},
});
}
exists() {
return this.storage.fileExists(this.blobName);
}
download() {
const emitter = new EventEmitter();
setTimeout(() => {
if (this.storage.fileExists(this.blobName)) {
emitter.emit('data', this.storage.readFile(this.blobName));
emitter.emit('end');
} else {
emitter.emit(
'error',
new Error(`The file ${this.blobName} does not exist!`),
);
}
}, 0);
return Promise.resolve({
readableStreamBody: emitter,
});
}
}
class BlockBlobClientFailUpload extends BlockBlobClient {
uploadFile(): Promise<BlobUploadCommonResponse> {
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 500,
headers: {} as any,
},
});
}
}
class ContainerClientIterator {
private containerName: string;
constructor(containerName: string) {
this.containerName = containerName;
}
async next() {
if (
this.containerName === 'delete_stale_files_success' ||
this.containerName === 'delete_stale_files_error'
) {
return {
value: {
segment: {
blobItems: [{ name: `stale_file.png` }],
},
},
};
}
return {
value: {
segment: {
blobItems: [],
},
},
};
}
}
class ContainerClient {
constructor(
private readonly containerName: string,
protected readonly storage: StorageFilesMock,
) {}
getProperties(): Promise<ContainerGetPropertiesResponse> {
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 200,
headers: {} as any,
parsedHeaders: {},
},
});
}
getBlockBlobClient(blobName: string) {
return new BlockBlobClient(blobName, this.storage);
}
listBlobsFlat() {
return {
byPage: () => {
return new ContainerClientIterator(this.containerName);
},
};
}
deleteBlob() {
if (this.containerName === 'delete_stale_files_error') {
throw new Error('Message');
}
}
}
class ContainerClientFailGetProperties extends ContainerClient {
getProperties(): Promise<ContainerGetPropertiesResponse> {
return Promise.resolve({
_response: {
request: {
url: `https://example.blob.core.windows.net`,
} as any,
status: 404,
headers: {} as any,
parsedHeaders: {},
},
});
}
}
class ContainerClientFailUpload extends ContainerClient {
getBlockBlobClient(blobName: string) {
return new BlockBlobClientFailUpload(blobName, this.storage);
}
}
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,
);
}
if (this.credential?.accountName === 'bad_account_credentials') {
return new ContainerClientFailUpload(containerName, this.storage);
}
return new ContainerClient(containerName, this.storage);
}
}
class StorageSharedKeyCredential {
readonly accountName;
readonly accountKey;
constructor(accountName: string, accountKey: string) {
this.accountName = accountName;
this.accountKey = accountKey;
}
}
return {
__esModule: true,
BlobServiceClient,
StorageSharedKeyCredential,
};
});
const getEntityRootDir = (entity: Entity) => {
const {
@@ -34,7 +231,7 @@ const getEntityRootDir = (entity: Entity) => {
metadata: { namespace, name },
} = entity;
return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name);
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
};
const logger = getVoidLogger();
@@ -178,7 +375,7 @@ describe('AzureBlobStoragePublish', () => {
it('should fail to publish a directory', async () => {
const wrongPathToGeneratedDirectory = path.join(
rootDir,
storageRootDir,
'wrong',
'path',
'to',
@@ -22,11 +22,119 @@ 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';
// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock Google Cloud Storage client library
jest.mock('@google-cloud/storage', () => {
class GCSFile {
constructor(
private readonly filePath: string,
private readonly storage: StorageFilesMock,
) {}
const rootDir = (global as any).rootDir; // Set by setupTests.ts
exists() {
return new Promise(async (resolve, reject) => {
if (this.storage.fileExists(this.filePath)) {
resolve([true]);
} else {
reject();
}
});
}
createReadStream() {
const readable = new Readable();
readable._read = () => {};
process.nextTick(() => {
if (this.storage.fileExists(this.filePath)) {
if (readable.eventNames().includes('pipe')) {
readable.emit('pipe');
}
readable.emit('data', this.storage.readFile(this.filePath));
readable.emit('end');
} else {
readable.emit(
'error',
new Error(`The file ${this.filePath} does not exist!`),
);
}
});
return readable;
}
delete() {
return Promise.resolve();
}
}
class Bucket {
constructor(
private readonly bucketName: string,
private readonly storage: StorageFilesMock,
) {}
async getMetadata() {
if (this.bucketName === 'bad_bucket_name') {
throw Error('Bucket does not exist');
}
return '';
}
upload(source: string, { destination }: { destination: string }) {
return new Promise(async resolve => {
this.storage.writeFile(destination, source);
resolve(null);
});
}
file(destinationFilePath: string) {
if (this.bucketName === 'delete_stale_files_error') {
throw Error('Message');
}
return new GCSFile(destinationFilePath, this.storage);
}
getFilesStream() {
const readable = new Readable();
readable._read = () => {};
process.nextTick(() => {
if (
this.bucketName === 'delete_stale_files_success' ||
this.bucketName === 'delete_stale_files_error'
) {
readable.emit('data', { name: 'stale-file.png' });
}
readable.emit('end');
});
return readable;
}
}
class Storage {
storage = new StorageFilesMock();
constructor() {
this.storage.emptyFiles();
}
bucket(bucketName: string) {
return new Bucket(bucketName, this.storage);
}
}
return {
__esModule: true,
Storage,
};
});
const getEntityRootDir = (entity: Entity) => {
const {
@@ -34,7 +142,7 @@ const getEntityRootDir = (entity: Entity) => {
metadata: { namespace, name },
} = entity;
return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name);
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
};
const logger = getVoidLogger();
@@ -193,7 +301,7 @@ describe('GoogleGCSPublish', () => {
it('should fail to publish a directory', async () => {
const wrongPathToGeneratedDirectory = path.join(
rootDir,
storageRootDir,
'wrong',
'path',
'to',
@@ -24,12 +24,106 @@ import { ConfigReader } from '@backstage/config';
import express from 'express';
import request from 'supertest';
import mockFs from 'mock-fs';
import os from 'os';
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';
// NOTE: /plugins/techdocs-node/__mocks__ is being used to mock @trendyol-js/openstack-swift-sdk client library
jest.mock('@trendyol-js/openstack-swift-sdk', () => {
const {
ContainerMetaResponse,
DownloadResponse,
NotFound,
ObjectMetaResponse,
UploadResponse,
}: typeof import('@trendyol-js/openstack-swift-sdk') = jest.requireActual(
'@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);
try {
await fs.access(filePath, fs.constants.F_OK);
return true;
} catch (err) {
return false;
}
};
const streamToBuffer = (stream: Stream | Readable): Promise<Buffer> => {
return new Promise((resolve, reject) => {
try {
const chunks: any[] = [];
stream.on('data', chunk => chunks.push(chunk));
stream.on('error', reject);
stream.on('end', () => resolve(Buffer.concat(chunks)));
} catch (e) {
throw new Error(`Unable to parse the response data ${e.message}`);
}
});
};
return {
__esModule: true,
SwiftClient: class {
async getMetadata(_containerName: string, file: string) {
const fileExists = await checkFileExists(file);
if (fileExists) {
return new ObjectMetaResponse({
fullPath: file,
});
}
return new NotFound();
}
async getContainerMetadata(containerName: string) {
if (containerName === 'mock') {
return new ContainerMetaResponse({
size: 10,
});
}
return new NotFound();
}
async upload(
_containerName: string,
destination: string,
stream: Readable,
) {
try {
const filePath = path.join(storageRootDir, destination);
const fileBuffer = await streamToBuffer(stream);
await fs.writeFile(filePath, fileBuffer);
const fileExists = await checkFileExists(destination);
if (fileExists) {
return new UploadResponse(filePath);
}
const errorMessage = `Unable to upload file(s) to OpenStack Swift.`;
throw new Error(errorMessage);
} catch (error) {
const errorMessage = `Unable to upload file(s) to OpenStack Swift. ${error}`;
throw new Error(errorMessage);
}
}
async download(_containerName: string, file: string) {
const filePath = path.join(storageRootDir, file);
const fileExists = await checkFileExists(file);
if (!fileExists) {
return new NotFound();
}
return new DownloadResponse([], fs.createReadStream(filePath));
}
},
};
});
const createMockEntity = (annotations = {}): Entity => {
return {
@@ -51,15 +145,13 @@ const createMockEntityName = (): CompoundEntityRef => ({
namespace: 'test-namespace',
});
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
const getEntityRootDir = (entity: Entity) => {
const {
kind,
metadata: { namespace, name },
} = entity;
return path.join(rootDir, namespace || DEFAULT_NAMESPACE, kind, name);
return path.join(storageRootDir, namespace || DEFAULT_NAMESPACE, kind, name);
};
const getPosixEntityRootDir = (entity: Entity) => {
@@ -179,7 +271,7 @@ describe('OpenStackSwiftPublish', () => {
it('should fail to publish a directory', async () => {
const wrongPathToGeneratedDirectory = path.join(
rootDir,
storageRootDir,
'wrong',
'path',
'to',
@@ -31,6 +31,11 @@ const discovery: jest.Mocked<PluginEndpointDiscovery> = {
getExternalBaseUrl: jest.fn(),
};
jest.mock('@azure/identity', () => ({
__esModule: true,
DefaultAzureCredential: class {},
}));
describe('Publisher', () => {
beforeEach(() => {
jest.resetModules(); // clear the cache
@@ -19,12 +19,13 @@ import path from 'path';
import fs from 'fs-extra';
import { IStorageFilesMock } from './types';
const rootDir: string = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
export const storageRootDir: string =
os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
const encoding = 'utf8';
export class StorageFilesMock implements IStorageFilesMock {
static rootDir = rootDir;
static rootDir = storageRootDir;
private files: Record<string, string>;
@@ -37,20 +38,20 @@ export class StorageFilesMock implements IStorageFilesMock {
}
public fileExists(targetPath: string): boolean {
const filePath = path.join(rootDir, targetPath);
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(rootDir, targetPath);
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(rootDir, targetPath);
const filePath = path.join(storageRootDir, targetPath);
if (typeof source === 'string') {
this.files[filePath] = fs.readFileSync(source).toString(encoding);
} else {