diff --git a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts index 60a705f6a7..29f3178efb 100644 --- a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts +++ b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts @@ -14,6 +14,10 @@ * limitations under the License. */ import fs from 'fs'; +import type { + BlobUploadCommonResponse, + ContainerGetPropertiesResponse, +} from '@azure/storage-blob'; export class BlockBlobClient { private readonly blobName; @@ -22,23 +26,29 @@ export class BlockBlobClient { this.blobName = blobName; } - uploadFile(source: string) { - return new Promise((resolve, reject) => { - if (!fs.existsSync(source)) { - reject(''); - } else { - resolve(''); - } + uploadFile(source: string): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 200, + headers: {} as any, + }, }); } exists() { - return new Promise((resolve, reject) => { - if (fs.existsSync(this.blobName)) { - resolve(true); - } else { - reject({ message: 'The object doest not exist !' }); - } + return Promise.resolve(fs.existsSync(this.blobName)); + } +} + +class BlockBlobClientFailUpload extends BlockBlobClient { + uploadFile(source: string): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 500, + headers: {} as any, + }, }); } } @@ -50,9 +60,14 @@ export class ContainerClient { this.containerName = containerName; } - getProperties() { - return new Promise(resolve => { - resolve(''); + getProperties(): Promise { + return Promise.resolve({ + _response: { + request: {} as any, + status: 200, + headers: {} as any, + parsedHeaders: {}, + }, }); } @@ -61,6 +76,25 @@ export class ContainerClient { } } +class ContainerClientFailGetProperties extends ContainerClient { + getProperties(): Promise { + return Promise.resolve({ + _response: { + request: {} 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; @@ -71,6 +105,12 @@ export class BlobServiceClient { } getContainerClient(containerName: string) { + if (containerName === 'bad_container') { + return new ContainerClientFailGetProperties(containerName); + } + if (this.credential.accountName === 'failupload') { + return new ContainerClientFailUpload(containerName); + } return new ContainerClient(containerName); } } diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 18724a84f9..b37d6c9379 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -154,51 +154,71 @@ describe('publishing with valid credentials', () => { }); }); -describe('attempting to publish with invalid credentials', () => { - let logger: Logger; - - describe('accountKey is not specified', () => { - beforeEach(async () => { - const mockConfig = new ConfigReader({ - techdocs: { - requestUrl: 'http://localhost:7000', - publisher: { - type: 'azureBlobStorage', - azureBlobStorage: { - credentials: { - accountName: 'accountName', - }, - containerName: 'containerName', +describe('error reporting', () => { + it('reports an error when unable to read container properties', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'accountName', }, + containerName: 'bad_container', }, }, - }); - - logger = createLogger(); - - publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + }, }); - describe('without Azure environment variables', () => { - it('should log an error', async () => { - const entity = createMockEntity(); - const entityRootDir = getEntityRootDir(entity); + const logger = createLogger(); - mockFs({ - [entityRootDir]: { - 'index.html': '', + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + + expect(logger.error).toHaveBeenCalledWith( + `Could not read Azure Blob Storage container properties`, + ); + }); + + it('reports an error when bad account credentials', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + requestUrl: 'http://localhost:7000', + publisher: { + type: 'azureBlobStorage', + azureBlobStorage: { + credentials: { + accountName: 'failupload', + accountKey: 'accountKey', + }, + containerName: 'containerName', }, - }); - - await publisher.publish({ - entity, - directory: entityRootDir, - }); - - expect(logger.error).toHaveBeenCalled(); - - mockFs.restore(); - }); + }, + }, }); + + const logger = createLogger(); + + publisher = await AzureBlobStoragePublish.fromConfig(mockConfig, logger); + + const entity = createMockEntity(); + const entityRootDir = getEntityRootDir(entity); + + mockFs({ + [entityRootDir]: { + 'index.html': '', + }, + }); + + await publisher.publish({ + entity, + directory: entityRootDir, + }); + + expect(logger.error).toHaveBeenCalledWith( + `Unable to upload 1 file(s) to Azure Blob Storage.`, + ); + + mockFs.restore(); }); }); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index e36254b164..b0b2bfd855 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -82,10 +82,16 @@ export class AzureBlobStoragePublish implements PublisherBase { await storageClient .getContainerClient(containerName) .getProperties() - .then(() => { - logger.info( - `Successfully connected to the Azure Blob Storage container ${containerName}.`, - ); + .then(result => { + if (result?._response?.status >= 400) { + logger.error( + 'Could not read Azure Blob Storage container properties', + ); + } else { + logger.info( + `Successfully connected to the Azure Blob Storage container ${containerName}.`, + ); + } }) .catch(reason => { logger.error( @@ -145,16 +151,23 @@ export class AzureBlobStoragePublish implements PublisherBase { }); }); - await Promise.all(promises).then(() => { + let responses: BlobUploadCommonResponse[] = []; + + responses = await Promise.all(promises); + + const failed = responses.filter(r => r?._response?.status >= 400); + if (failed.length === 0) { this.logger.info( `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${allFilesToUpload.length}`, ); - }); - return; + } else { + const errorMessage = `Unable to upload ${failed.length} file(s) to Azure Blob Storage.`; + this.logger.error(errorMessage); + } } catch (e) { const errorMessage = `Unable to upload file(s) to Azure Blob Storage. Error ${e.message}`; this.logger.error(errorMessage); - throw new Error(errorMessage); + return; } } @@ -237,19 +250,11 @@ export class AzureBlobStoragePublish implements PublisherBase { * A helper function which checks if index.html of an Entity's docs site is available. This * can be used to verify if there are any pre-generated docs available to serve. */ - async hasDocsBeenGenerated(entity: Entity): Promise { - return new Promise(resolve => { - const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; - this.storageClient - .getContainerClient(this.containerName) - .getBlockBlobClient(`${entityRootDir}/index.html`) - .exists() - .then((response: boolean) => { - resolve(response); - }) - .catch(() => { - resolve(false); - }); - }); + hasDocsBeenGenerated(entity: Entity): Promise { + const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; + return this.storageClient + .getContainerClient(this.containerName) + .getBlockBlobClient(`${entityRootDir}/index.html`) + .exists(); } }