Repository: Redocly/openapi-cli
Pull request #267 (2021-03-15 18:19:05)

fix: add error handling for failed file upload on push command
fix https://github.com/Redocly/openapi-cli/issues/248


Patch:
diff --git a/__mocks__/@redocly/openapi-core.ts b/__mocks__/@redocly/openapi-core.ts
new file mode 100644
index 00000000..78aa0089
--- /dev/null
+++ b/__mocks__/@redocly/openapi-core.ts
@@ -0,0 +1,16 @@
+export const __redoclyClient = {
+  isAuthorizedWithRedocly: jest.fn(() => true),
+  login: jest.fn(),
+  getOrganizationId: jest.fn(() => ({ organizationById: 'organizationById' })),
+  getDefinitionVersion: jest.fn(),
+  getSignedUrl: jest.fn(() => ({
+    signFileUploadCLI: { signedFileUrl: null, uploadedFilePath: null },
+  })),
+  getDefinitionByName: jest.fn(() => ({ definition: 'definition' })),
+  createDefinitionVersion: jest.fn(),
+  updateDefinitionVersion: jest.fn(),
+};
+
+export const RedoclyClient = jest.fn(() => __redoclyClient);
+export const loadConfig = jest.fn(() => ({ configFile: null }));
+export const bundle = jest.fn(() => ({ bundle: { parsed: null }, problems: null }));
diff --git a/__mocks__/fs.ts b/__mocks__/fs.ts
new file mode 100644
index 00000000..e65a37f7
--- /dev/null
+++ b/__mocks__/fs.ts
@@ -0,0 +1,4 @@
+export const existsSync =  jest.fn();
+export const readFileSync =  jest.fn(() => '');
+export const statSync =  jest.fn(() => ({ size: 0 }));
+export const createReadStream =  jest.fn();
diff --git a/__mocks__/node-fetch.ts b/__mocks__/node-fetch.ts
new file mode 100644
index 00000000..24f4fca7
--- /dev/null
+++ b/__mocks__/node-fetch.ts
@@ -0,0 +1,1 @@
+export default jest.fn(() => ({ ok: true }));
diff --git a/packages/cli/src/__mocks__/utils.ts b/packages/cli/src/__mocks__/utils.ts
new file mode 100644
index 00000000..5027dcbc
--- /dev/null
+++ b/packages/cli/src/__mocks__/utils.ts
@@ -0,0 +1,6 @@
+export const getFallbackEntryPointsOrExit = jest.fn(() => ['']);
+export const getTotals = jest.fn(() => ({ errors: 0 }));
+export const dumpBundle = jest.fn();
+export const slash = jest.fn();
+export const pluralize = jest.fn();
+export const printExecutionTime = jest.fn();
diff --git a/packages/cli/src/commands/push.ts b/packages/cli/src/commands/push.ts
index 8ab7cc05..5eeb8205 100644
--- a/packages/cli/src/commands/push.ts
+++ b/packages/cli/src/commands/push.ts
@@ -79,7 +79,7 @@ export async function handlePush (argv: {
     await client.createDefinitionVersion(definitionId, apiVersion, PUSH_SOURCE_TYPE, updatePatch.source);
   }
 
-  process.stderr.write(`Definition: ${blue(entrypoint!)} is successfully pushed to Redocly API Registry \n`);
+  process.stdout.write(`Definition: ${blue(entrypoint!)} is successfully pushed to Redocly API Registry \n`);
   printExecutionTime('push', startedAt, entrypoint!);
 
   async function doesOrganizationExist(organizationId: string) {
@@ -100,8 +100,15 @@ export async function handlePush (argv: {
       if (file.filePath === filesToUpload.root) { source['root'] = uploadedFilePath; }
       source.files.push(uploadedFilePath);
       process.stdout.write(`Uploading ${file.contents ? 'bundle for ' : ''}${blue(file.filePath)}...`);
-      await uploadFileToS3(signedFileUrl, file.contents || file.filePath);
-      process.stdout.write(green(`✓ (${++uploaded}/${filesToUpload.files.length})\n`));
+      const uploadResponse = await uploadFileToS3(signedFileUrl, file.contents || file.filePath);
+
+      const fileCounter = `(${++uploaded}/${filesToUpload.files.length})`;
+
+      if (!uploadResponse.ok) {
+        exitWithError(`✗ ${fileCounter}\nFile upload failed\n`);
+      }
+
+      process.stdout.write(green(`✓ ${fileCounter}\n`));
     }
 
     process.stdout.write('\n');
@@ -227,5 +234,5 @@ function uploadFileToS3(url: string, filePathOrBuffer: string | Buffer) {
       'Content-Length': fileSizeInBytes.toString()
     },
     body: readStream
-  })
-}
\ No newline at end of file
+  });
+}


Test patch:
diff --git a/packages/cli/src/__tests__/commands/push.test.ts b/packages/cli/src/__tests__/commands/push.test.ts
new file mode 100644
index 00000000..aadf2680
--- /dev/null
+++ b/packages/cli/src/__tests__/commands/push.test.ts
@@ -0,0 +1,47 @@
+import { handlePush } from '../../commands/push'
+
+jest.mock('fs');
+jest.mock('node-fetch');
+jest.mock('@redocly/openapi-core');
+jest.mock('../../utils');
+
+describe('push', () => {
+  const redoclyClient = require('@redocly/openapi-core').__redoclyClient;
+
+  beforeAll(() => {
+    jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
+  });
+
+  afterEach(() => {
+    redoclyClient.createDefinitionVersion.mockClear();
+    redoclyClient.updateDefinitionVersion.mockClear();
+  });
+
+  it('creates new definition', async () => {
+    redoclyClient.getDefinitionVersion.mockImplementationOnce(() => ({ version: null }));
+
+    await handlePush({
+      upsert: true,
+      entrypoint: 'spec.yaml',
+      destination: '@org/my-api@1.0.1',
+      branchName: 'test',
+    });
+
+    expect(redoclyClient.createDefinitionVersion).toBeCalledTimes(1);
+    expect(redoclyClient.updateDefinitionVersion).toBeCalledTimes(0);
+  });
+
+  it('updates existing definition', async () => {
+    redoclyClient.getDefinitionVersion.mockImplementationOnce(() => ({ version: '1.0.0' }));
+
+    await handlePush({
+      upsert: true,
+      entrypoint: 'spec.json',
+      destination: '@org/my-api@1.0.0',
+      branchName: 'test',
+    });
+
+    expect(redoclyClient.createDefinitionVersion).toBeCalledTimes(0);
+    expect(redoclyClient.updateDefinitionVersion).toBeCalledTimes(1);
+  });
+});