Repository: Redocly/redocly-cli
Pull request #1007 (2023-01-31 14:31:38)

fix: do not fail the push command when organization is provided via redocly.yaml
## What/Why/How?

- Do not fail the push command when organization is provided via redocly.yaml
- Add more tests for push command

## Reference

## Testing

## Screenshots (optional)

## Check yourself

- [x] Code is linted
- [ ] Tested with redoc/reference-docs/workflows
- [x] All new/updated code is covered with tests

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


github-actions[bot]: <!-- jest coverage report action for options with hash 3339ae53a38c44382c0fb4491bdb282d -->

## Coverage report 



| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :--------- | :-------------: |
|                                                                           🟡                                                                           | Statements | 73.79%     |    3432/4651    |
|                                                                           🟡                                                                           | Branches   | 65.26%     |    1950/2988    |
|                                                                           🟡                                                                           | Functions  | 66.1%      |     579/876     |
|                                                                           🟡                                                                           | Lines      | 73.81%     |    3191/4323    |


<details><summary>Show files with reduced coverage 🔻</summary>
<br/>

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | File                                                        | Statements | Branches | Functions                                                      | Lines  |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------- | :--------- | :------- | :------------------------------------------------------------- | :----- |
|                                                                           🟡                                                                           | <div title="cli/src/commands/push.ts">`...` / push.ts</div> | 71.14%     | 57.8%    | <div title="Base coverage is: 57.14%">59.09% (+1.95% 🔼)</div> | 73.38% |

</details>

## Test suite run success
561 tests passing in 85 suites.

<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from 8f09de563817201bdb907d07d543559056d2ede4</p>


Patch:
diff --git a/packages/cli/src/commands/push.ts b/packages/cli/src/commands/push.ts
index 6c8973a1..85373e79 100644
--- a/packages/cli/src/commands/push.ts
+++ b/packages/cli/src/commands/push.ts
@@ -27,6 +27,9 @@ import { promptClientToken } from './login';
 
 const DEFAULT_VERSION = 'latest';
 
+const DESTINATION_REGEX =
+  /^(@(?<organizationId>[\w\-\s]+)\/)?(?<name>[^@]*)@(?<version>[\w\.\-]+)$/;
+
 type PushArgs = {
   api?: string;
   destination?: string;
@@ -55,10 +58,7 @@ export async function handlePush(argv: PushArgs): Promise<void> {
   const batchId = argv['batch-id'];
   const batchSize = argv['batch-size'];
 
-  const isValidDestination =
-    destination && Object.values(validateDestination(destination) || {}).every(Boolean);
-
-  if (!isValidDestination) {
+  if (destination && !DESTINATION_REGEX.test(destination)) {
     exitWithError(
       `Destination argument value is not valid, please use the right format: ${yellow(
         '<@organization-id/api-name@api-version>'
@@ -98,6 +98,11 @@ export async function handlePush(argv: PushArgs): Promise<void> {
   }
 
   const apis = api ? { [`${name}@${version}`]: { root: api } } : config.apis;
+  if (!Object.keys(apis).length) {
+    exitWithError(
+      `Api not found. Please make sure you have provided the correct data in the config file.`
+    );
+  }
 
   for (const [apiNameAndVersion, { root: api }] of Object.entries(apis)) {
     const resolvedConfig = getMergedConfig(config, apiNameAndVersion);
@@ -301,17 +306,17 @@ function hashFiles(filePaths: { filePath: string }[]) {
   return sum.digest('hex');
 }
 
-function validateDestination(destination: string) {
-  const regexp = /^(@(?<organizationId>[\w\-\s]+)\/)?(?<name>[^@]*)@(?<version>[\w\.\-]+)$/;
-
-  return destination?.match(regexp)?.groups;
+function parseDestination(
+  destination?: string
+): { organizationId?: string; name?: string; version?: string } | undefined {
+  return destination?.match(DESTINATION_REGEX)?.groups;
 }
 
 export function getDestinationProps(
   destination: string | undefined,
   organization: string | undefined
 ) {
-  const groups = destination && validateDestination(destination);
+  const groups = destination && parseDestination(destination);
   if (groups) {
     groups.name && (groups.name = encodeURIComponent(groups.name));
     return {


Test patch:
diff --git a/packages/cli/src/__tests__/commands/push.test.ts b/packages/cli/src/__tests__/commands/push.test.ts
index aceb3458..9dd759af 100644
--- a/packages/cli/src/__tests__/commands/push.test.ts
+++ b/packages/cli/src/__tests__/commands/push.test.ts
@@ -3,6 +3,7 @@ import { Config, getMergedConfig } from '@redocly/openapi-core';
 import { exitWithError, loadConfigAndHandleErrors } from '../../utils';
 import { getApiRoot, getDestinationProps, handlePush, transformPush } from '../../commands/push';
 import { ConfigFixture } from '../fixtures/config';
+import { yellow } from 'colorette';
 
 jest.mock('fs');
 jest.mock('node-fetch', () => ({
@@ -107,6 +108,92 @@ describe('push', () => {
     });
     expect(redoclyClient.registryApi.prepareFileUpload).toBeCalledTimes(3);
   });
+
+  it('push should fail if organization not provided', async () => {
+    await handlePush({
+      upsert: true,
+      api: 'spec.json',
+      destination: 'test@v1',
+      branchName: 'test',
+      public: true,
+      'batch-id': '123',
+      'batch-size': 2,
+    });
+
+    expect(exitWithError).toBeCalledTimes(1);
+    expect(exitWithError).toBeCalledWith(
+      `No organization provided, please use the right format: ${yellow(
+        '<@organization-id/api-name@api-version>'
+      )} or specify the 'organization' field in the config file.`
+    );
+  });
+
+  it('push should work with organization in config', async () => {
+    (loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
+      return { ...ConfigFixture, organization: 'test_org' };
+    });
+    await handlePush({
+      upsert: true,
+      api: 'spec.json',
+      destination: 'my-api@1.0.0',
+      branchName: 'test',
+      public: true,
+      'batch-id': '123',
+      'batch-size': 2,
+    });
+
+    expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
+    expect(redoclyClient.registryApi.pushApi).toHaveBeenLastCalledWith({
+      branch: 'test',
+      filePaths: ['filePath'],
+      isUpsert: true,
+      isPublic: true,
+      name: 'my-api',
+      organizationId: 'test_org',
+      rootFilePath: 'filePath',
+      version: '1.0.0',
+      batchId: '123',
+      batchSize: 2,
+    });
+  });
+
+  it('push should work if destination not provided and api in config is provided', async () => {
+    (loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
+      return {
+        ...ConfigFixture,
+        organization: 'test_org',
+        apis: { 'my-api@1.0.0': { root: 'path' } },
+      };
+    });
+    await handlePush({
+      upsert: true,
+      api: 'spec.json',
+      branchName: 'test',
+      public: true,
+      'batch-id': '123',
+      'batch-size': 2,
+    });
+
+    expect(redoclyClient.registryApi.pushApi).toBeCalledTimes(1);
+  });
+
+  it('push should fail if destination and apis not provided', async () => {
+    (loadConfigAndHandleErrors as jest.Mock).mockImplementation(() => {
+      return { organization: 'test_org', apis: {} };
+    });
+    await handlePush({
+      upsert: true,
+      branchName: 'test',
+      public: true,
+      'batch-id': '123',
+      'batch-size': 2,
+    });
+
+    expect(exitWithError).toBeCalledTimes(1);
+    expect(exitWithError).toHaveBeenLastCalledWith(
+      'Api not found. Please make sure you have provided the correct data in the config file.'
+    );
+  });
 });
 
 describe('transformPush', () => {