From f260a004d53a4ece9d33236b1d66665cab3ba68e Mon Sep 17 00:00:00 2001
From: Kanad Gupta
Date: Thu, 12 Dec 2024 17:55:37 -0600
Subject: [PATCH] feat!: v10 release (#1113)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## π§° Changes
this PR aggregates all of the PRs going out as part of the v10 release
(i.e, the second section of PRs below). all PRs should be reviewed prior
to being merged into this branch.
### outstanding tasks
#### needs to go out _before_ v10 is released (i.e., in the v9 release
channel)
- [x] https://github.com/readmeio/rdme/pull/1082
#### needs to go out as part of v10 release
- [x] `openapi upload`
- [x] https://github.com/readmeio/rdme/pull/1111
- [x] https://github.com/readmeio/rdme/pull/1116
- [x] https://github.com/readmeio/rdme/pull/1107
- [x] https://github.com/readmeio/rdme/pull/1104
- [x] https://github.com/readmeio/rdme/pull/1108
#### merge into `v9` branch once `v10` release is successfully released
- [ ] https://github.com/readmeio/rdme/pull/1121
#### double-check these things before merging
- [x] swap out any links to the `v9` docs (e.g., `/tree/v9`) with `v10`
as needed
(https://github.com/readmeio/rdme/pull/1113/commits/b19416d53cb7f4ab4fb8e62048786eec2fb14ce4)
- [x] make sure all API v1 requests in `v10` will work
- [x] make sure v10 migration guide reflects the final design decisions
around `openapi upload`
## β οΈ Breaking Changes
listing all of the breaking changes 1 by 1 below so they get picked
up by semantic release...
BREAKING CHANGE: `categories`, `custompages`, `docs` and `versions` have
now been removed. Please use a bidirectional syncing workflow instead.
Read more in [our migration
guide](https://github.com/readmeio/rdme/tree/v10/documentation/migration-guide.md).
BREAKING CHANGE: `rdme openapi` has been replaced by `rdme openapi
upload`. Read more in [our migration
guide](https://github.com/readmeio/rdme/tree/v10/documentation/migration-guide.md).
---
.github/workflows/ci.yml | 33 +-
.github/workflows/docs.yml | 33 +-
README.md | 13 +-
__tests__/commands/categories/create.test.ts | 161 --
__tests__/commands/categories/index.test.ts | 76 -
__tests__/commands/custompages/index.test.ts | 350 ----
__tests__/commands/custompages/single.test.ts | 291 ----
.../docs/__snapshots__/index.test.ts.snap | 124 --
.../docs/__snapshots__/multiple.test.ts.snap | 3 -
__tests__/commands/docs/index.test.ts | 729 ---------
__tests__/commands/docs/multiple.test.ts | 180 --
__tests__/commands/docs/prune.test.ts | 172 --
__tests__/commands/docs/single.test.ts | 443 -----
__tests__/commands/open.test.ts | 75 -
__tests__/commands/openapi/index.test.ts | 1441 -----------------
__tests__/commands/openapi/upload.test.ts | 409 +++++
__tests__/commands/versions/create.test.ts | 176 --
__tests__/commands/versions/delete.test.ts | 55 -
__tests__/commands/versions/index.test.ts | 65 -
__tests__/commands/versions/update.test.ts | 388 -----
__tests__/helpers/get-api-mock.ts | 29 +-
__tests__/helpers/oclif.ts | 8 +-
.../lib/__snapshots__/createGHA.test.ts.snap | 410 -----
__tests__/lib/createGHA.test.ts | 14 -
__tests__/lib/prompts.test.ts | 103 --
documentation/commands/categories.md | 84 -
documentation/commands/changelogs.md | 2 +-
documentation/commands/custompages.md | 47 -
documentation/commands/docs.md | 102 --
documentation/commands/openapi.md | 180 +-
documentation/commands/versions.md | 161 --
documentation/migration-guide.md | 69 +-
documentation/rdme.md | 4 -
package-lock.json | 165 +-
package.json | 15 +-
src/commands/categories/create.ts | 96 --
src/commands/categories/index.ts | 41 -
src/commands/changelogs.ts | 15 +-
src/commands/custompages.ts | 46 -
src/commands/docs/index.ts | 67 -
src/commands/docs/prune.ts | 104 --
src/commands/open.ts | 63 -
src/commands/openapi/index.ts | 353 ----
src/commands/openapi/upload.ts | 214 +++
src/commands/versions/create.ts | 104 --
src/commands/versions/delete.ts | 54 -
src/commands/versions/index.ts | 58 -
src/commands/versions/update.ts | 98 --
src/index.ts | 27 +-
src/lib/apiError.ts | 51 +-
src/lib/baseCommand.ts | 19 +
src/lib/castStringOptToBool.ts | 23 -
src/lib/config.ts | 5 +-
src/lib/deleteDoc.ts | 28 -
src/lib/flags.ts | 36 +-
src/lib/getCategories.ts | 41 -
src/lib/getDocs.ts | 56 -
src/lib/prompts.ts | 204 ---
src/lib/readmeAPIFetch.ts | 139 +-
src/lib/streamSpecToRegistry.ts | 57 -
src/lib/syncDocsPath.ts | 22 +-
src/lib/versionSelect.ts | 70 -
62 files changed, 1067 insertions(+), 7634 deletions(-)
delete mode 100644 __tests__/commands/categories/create.test.ts
delete mode 100644 __tests__/commands/categories/index.test.ts
delete mode 100644 __tests__/commands/custompages/index.test.ts
delete mode 100644 __tests__/commands/custompages/single.test.ts
delete mode 100644 __tests__/commands/docs/__snapshots__/index.test.ts.snap
delete mode 100644 __tests__/commands/docs/__snapshots__/multiple.test.ts.snap
delete mode 100644 __tests__/commands/docs/index.test.ts
delete mode 100644 __tests__/commands/docs/multiple.test.ts
delete mode 100644 __tests__/commands/docs/prune.test.ts
delete mode 100644 __tests__/commands/docs/single.test.ts
delete mode 100644 __tests__/commands/open.test.ts
delete mode 100644 __tests__/commands/openapi/index.test.ts
create mode 100644 __tests__/commands/openapi/upload.test.ts
delete mode 100644 __tests__/commands/versions/create.test.ts
delete mode 100644 __tests__/commands/versions/delete.test.ts
delete mode 100644 __tests__/commands/versions/index.test.ts
delete mode 100644 __tests__/commands/versions/update.test.ts
delete mode 100644 __tests__/lib/prompts.test.ts
delete mode 100644 documentation/commands/categories.md
delete mode 100644 documentation/commands/custompages.md
delete mode 100644 documentation/commands/docs.md
delete mode 100644 documentation/commands/versions.md
delete mode 100644 src/commands/categories/create.ts
delete mode 100644 src/commands/categories/index.ts
delete mode 100644 src/commands/custompages.ts
delete mode 100644 src/commands/docs/index.ts
delete mode 100644 src/commands/docs/prune.ts
delete mode 100644 src/commands/open.ts
delete mode 100644 src/commands/openapi/index.ts
create mode 100644 src/commands/openapi/upload.ts
delete mode 100644 src/commands/versions/create.ts
delete mode 100644 src/commands/versions/delete.ts
delete mode 100644 src/commands/versions/index.ts
delete mode 100644 src/commands/versions/update.ts
delete mode 100644 src/lib/castStringOptToBool.ts
delete mode 100644 src/lib/deleteDoc.ts
delete mode 100644 src/lib/getCategories.ts
delete mode 100644 src/lib/getDocs.ts
delete mode 100644 src/lib/prompts.ts
delete mode 100644 src/lib/streamSpecToRegistry.ts
delete mode 100644 src/lib/versionSelect.ts
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f3135c2f7..11326b9b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -103,17 +103,6 @@ jobs:
if: ${{ steps.openapi-validate-fail.outcome == 'success' }}
run: echo "::error::Expected validation in previous step to fail" && exit 1
- # Docs: https://rdme-test.readme.io
- - name: Run `openapi` command
- uses: ./rdme-repo/
- with:
- rdme: openapi oas-examples-repo/3.1/json/petstore.json --key=${{ secrets.RDME_TEST_PROJECT_API_KEY }} --id=${{ secrets.RDME_TEST_PROJECT_API_SETTING }}
-
- - name: Run `openapi` command with weird arg syntax
- uses: ./rdme-repo/
- with:
- rdme: openapi "oas-examples-repo/3.1/json/petstore.json" --key "${{ secrets.RDME_TEST_PROJECT_API_KEY }}" --id=${{ secrets.RDME_TEST_PROJECT_API_SETTING }}
-
# this is a test to ensure that the rdme github action can run properly
# the way that our users invoke it
- name: E2E run of `openapi validate` on `next` branch
@@ -121,3 +110,25 @@ jobs:
if: ${{ github.ref }} == 'refs/heads/next'
with:
rdme: openapi validate oas-examples-repo/3.1/json/petstore.json
+
+ # Docs: https://rdme-test.readme.io
+ - name: Run `openapi` command
+ uses: ./rdme-repo/
+ with:
+ rdme: openapi upload oas-examples-repo/3.1/json/petstore.json --key=${{ secrets.RDME_REFACTORED_TEST_PROJECT_API_KEY }}
+ - name: Run `openapi` command with env var
+ uses: ./rdme-repo/
+ with:
+ rdme: openapi upload oas-examples-repo/3.1/json/petstore.json
+ env:
+ RDME_API_KEY: ${{ secrets.RDME_REFACTORED_TEST_PROJECT_API_KEY }}
+ - name: Run `openapi` command with other env var
+ uses: ./rdme-repo/
+ with:
+ rdme: openapi upload oas-examples-repo/3.1/json/petstore.json
+ env:
+ README_API_KEY: ${{ secrets.RDME_REFACTORED_TEST_PROJECT_API_KEY }}
+ - name: Run `openapi` command with weird arg syntax
+ uses: ./rdme-repo/
+ with:
+ rdme: openapi upload "oas-examples-repo/3.1/json/petstore.json" --key "${{ secrets.RDME_REFACTORED_TEST_PROJECT_API_KEY }}"
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index c49ed6984..a8331530b 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,7 +1,10 @@
name: Sync `documentation` directory to ReadMe
# Run workflow for every push
-on: push
+on:
+ push:
+ branches:
+ - main
jobs:
sync:
@@ -40,33 +43,7 @@ jobs:
regex: false
include: documentation/*
- # Since this workflow file is in the `rdme` repository itself,
- # we need to test the GitHub Action using the current commit.
- # This step builds the `rdme` action code so we can do that.
- #
- # This step is not required for syncing your docs to ReadMe!
- - name: Rebuild GitHub Action for testing purposes
- run: npm run build:gha
-
- # And finally, with our updated documentation,
- # we run the `rdme` GitHub Action to sync the Markdown file
- # in the `documentation` directory.
- # Here's the page we're syncing: https://docs.readme.com/docs/rdme
-
- # First we're going to perform a dry run of syncing process.
- # We do this on every push to ensure that an actual sync will work properly
- - name: Sync docs to ReadMe (dry run)
- uses: ./
- with:
- rdme: docs ./documentation --key=${{ secrets.README_DEVELOPERS_API_KEY }} --version=${{ vars.README_DEVELOPERS_MAIN_VERSION }} --dryRun
-
- # And finally, we perform an actual sync to ReadMe if we're on the main branch
- name: Sync docs to ReadMe
- if: github.event_name == 'push' && github.event.ref == 'refs/heads/main'
- # We use the `main` branch as ref for GitHub Action
- # This is NOT recommended, as it can break your workflows without notice!
- # We recommend specifying a fixed version, i.e. @8.0.0
- # Docs: https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#example-using-versioned-actions
- uses: readmeio/rdme@main
+ uses: readmeio/rdme@v9
with:
rdme: docs ./documentation --key=${{ secrets.README_DEVELOPERS_API_KEY }} --version=${{ vars.README_DEVELOPERS_MAIN_VERSION }}
diff --git a/README.md b/README.md
index a3ed86942..fc2e0a0a9 100644
--- a/README.md
+++ b/README.md
@@ -15,12 +15,12 @@
-With `rdme`, you can manage your API definition (we support [OpenAPI](https://spec.openapis.org/oas/v3.1.0.html), [Swagger](https://swagger.io/specification/v2/), and [Postman](https://schema.postman.com/)) and sync it to your API reference docs on ReadMe. You can also access other parts of [ReadMe's RESTful API](https://docs.readme.com/reference), including syncing Markdown documentation with your ReadMe project and managing project versions.
+With `rdme`, you can manage your API definition (we support [OpenAPI](https://spec.openapis.org/oas/v3.1.0.html), [Swagger](https://swagger.io/specification/v2/), and [Postman](https://schema.postman.com/)) and sync it to your API reference docs on ReadMe.
Not using ReadMe for your docs? No worries. `rdme` has a variety of tools to help you identify issues with your API definition β no ReadMe account required.
-> [!WARNING]
-> Heads up: our [new ReadMe Refactored experience](https://docs.readme.com/main/docs/welcome-to-readme-refactored) doesnβt yet support `rdme`. If your project is using the new ReadMe Refactored experience, we recommend [enabling bi-directional syncing via Git](https://docs.readme.com/main/docs/bi-directional-sync) for an even better editing experience for the technical and non-technical users on your team!
+> [!NOTE]
+> If you're using [ReadMe Refactored](https://docs.readme.com/main/docs/welcome-to-readme-refactored), you'll want to use `rdme@10` or later. If you're **not** using ReadMe Refactored, you'll want to use `rdme@9`. More info can be found in our [migration guide](https://github.com/readmeio/rdme/blob/next/documentation/migration-guide.md).
# Table of Contents
@@ -170,16 +170,15 @@ npm run build && npm run build:docs
# Command Topics
* [`rdme autocomplete`](documentation/commands/autocomplete.md) - Display autocomplete installation instructions.
-* [`rdme categories`](documentation/commands/categories.md) - List or create categories in your ReadMe developer hub.
* [`rdme changelogs`](documentation/commands/changelogs.md) - Sync Markdown files to your ReadMe project as Changelog posts.
-* [`rdme custompages`](documentation/commands/custompages.md) - Sync Markdown/HTML files to your ReadMe project as Custom Pages.
-* [`rdme docs`](documentation/commands/docs.md) - Sync or prune Guides pages in your ReadMe developer hub.
* [`rdme help`](documentation/commands/help.md) - Display help for rdme.
* [`rdme login`](documentation/commands/login.md) - Login to a ReadMe project.
* [`rdme logout`](documentation/commands/logout.md) - Logs the currently authenticated user out of ReadMe.
* [`rdme openapi`](documentation/commands/openapi.md) - Manage your API definition (e.g., syncing, validation, analysis, conversion, etc.). Supports OpenAPI, Swagger, and Postman collections, in either JSON or YAML formats.
-* [`rdme versions`](documentation/commands/versions.md) - Manage your documentation versions.
* [`rdme whoami`](documentation/commands/whoami.md) - Displays the current user and project authenticated with ReadMe.
+
+> [!IMPORTANT]
+> You'll notice that several previous `rdme` commands are no longer present. That's because this version is for projects that use [ReadMe Refactored](https://docs.readme.com/main/docs/welcome-to-readme-refactored) and [bi-directional syncing](https://docs.readme.com/main/docs/bi-directional-sync) is the recommended approach for most workflows previously managed via `rdme`. See more in [our migration guide](./documentation/migration-guide.md).
diff --git a/__tests__/commands/categories/create.test.ts b/__tests__/commands/categories/create.test.ts
deleted file mode 100644
index 8b16d38a7..000000000
--- a/__tests__/commands/categories/create.test.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-import nock from 'nock';
-import { describe, beforeAll, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/categories/create.js';
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme categories create', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => nock.cleanAll());
-
- it('should error if no title provided', () => {
- return expect(run(['--key', key])).rejects.toThrow('Missing 1 required arg:\ntitle');
- });
-
- it('should error if categoryType is blank', () => {
- return expect(run(['--key', key, 'Test Title'])).rejects.toThrow('Missing required flag categoryType');
- });
-
- it('should error if categoryType is not `guide` or `reference`', () => {
- return expect(run(['--key', key, 'Test Title', '--categoryType', 'test'])).rejects.toThrow(
- 'Expected --categoryType=test to be one of: guide, reference',
- );
- });
-
- it('should create a new category if the title and type do not match and preventDuplicates=true', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .persist()
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'Existing Category', slug: 'existing-category', type: 'guide' }], {
- 'x-total-count': '1',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/categories')
- .basicAuth({ user: key })
- .reply(201, { title: 'New Category', slug: 'new-category', type: 'guide', id: '123' });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run(['New Category', '--categoryType', 'guide', '--key', key, '--version', '1.0.0', '--preventDuplicates']),
- ).resolves.toBe("π± successfully created 'New Category' with a type of 'guide' and an id of '123'");
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should create a new category if the title matches but the type does not match and preventDuplicates=true', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .persist()
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'Category', slug: 'category', type: 'guide' }], {
- 'x-total-count': '1',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/categories')
- .basicAuth({ user: key })
- .reply(201, { title: 'Category', slug: 'category', type: 'reference', id: '123' });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run(['--categoryType', 'reference', '--key', key, '--version', '1.0.0', '--preventDuplicates', 'Category']),
- ).resolves.toBe("π± successfully created 'Category' with a type of 'reference' and an id of '123'");
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should create a new category if the title and type match and preventDuplicates=false', async () => {
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/categories')
- .basicAuth({ user: key })
- .reply(201, { title: 'Category', slug: 'category', type: 'reference', id: '123' });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['Category', '--categoryType', 'guide', '--key', key, '--version', '1.0.0'])).resolves.toBe(
- "π± successfully created 'Category' with a type of 'reference' and an id of '123'",
- );
-
- postMock.done();
- versionMock.done();
- });
-
- it('should not create a new category if the title and type match and preventDuplicates=true', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .persist()
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'Category', slug: 'category', type: 'guide', id: '123' }], {
- 'x-total-count': '1',
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run(['Category', '--categoryType', 'guide', '--key', key, '--version', '1.0.0', '--preventDuplicates']),
- ).rejects.toStrictEqual(
- new Error(
- "The 'Category' category with a type of 'guide' already exists with an id of '123'. A new category was not created.",
- ),
- );
-
- getMock.done();
- versionMock.done();
- });
-
- it('should not create a new category if the non case sensitive title and type match and preventDuplicates=true', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .persist()
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'Category', slug: 'category', type: 'guide', id: '123' }], {
- 'x-total-count': '1',
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run(['Category', '--categoryType', 'guide', '--key', key, '--version', '1.0.0', '--preventDuplicates']),
- ).rejects.toStrictEqual(
- new Error(
- "The 'Category' category with a type of 'guide' already exists with an id of '123'. A new category was not created.",
- ),
- );
-
- getMock.done();
- versionMock.done();
- });
-});
diff --git a/__tests__/commands/categories/index.test.ts b/__tests__/commands/categories/index.test.ts
deleted file mode 100644
index f3ebb3b54..000000000
--- a/__tests__/commands/categories/index.test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import nock from 'nock';
-import { describe, beforeAll, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/categories/index.js';
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme categories', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => nock.cleanAll());
-
- it('should return all categories for a single page', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .persist()
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'One Category', slug: 'one-category', type: 'guide' }], {
- 'x-total-count': '1',
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, '--version', '1.0.0'])).resolves.toBe(
- JSON.stringify([{ title: 'One Category', slug: 'one-category', type: 'guide' }], null, 2),
- );
-
- getMock.done();
- versionMock.done();
- });
-
- it('should return all categories for multiple pages', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .persist()
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'One Category', slug: 'one-category', type: 'guide' }], {
- 'x-total-count': '21',
- })
- .get('/api/v1/categories?perPage=20&page=2')
- .basicAuth({ user: key })
- .reply(200, [{ title: 'Another Category', slug: 'another-category', type: 'guide' }], {
- 'x-total-count': '21',
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, '--version', '1.0.0'])).resolves.toBe(
- JSON.stringify(
- [
- { title: 'One Category', slug: 'one-category', type: 'guide' },
- { title: 'Another Category', slug: 'another-category', type: 'guide' },
- ],
- null,
- 2,
- ),
- );
-
- getMock.done();
- versionMock.done();
- });
-});
diff --git a/__tests__/commands/custompages/index.test.ts b/__tests__/commands/custompages/index.test.ts
deleted file mode 100644
index a0b5061d2..000000000
--- a/__tests__/commands/custompages/index.test.ts
+++ /dev/null
@@ -1,350 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-import chalk from 'chalk';
-import frontMatter from 'gray-matter';
-import nock from 'nock';
-import { describe, beforeAll, afterAll, beforeEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/custompages.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock } from '../../helpers/get-api-mock.js';
-import hashFileContents from '../../helpers/hash-file-contents.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const fixturesBaseDir = '__fixtures__/custompages';
-const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
-const key = 'API_KEY';
-
-describe('rdme custompages', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterAll(() => nock.cleanAll());
-
- it('should error if no path provided', () => {
- return expect(run(['--key', key])).rejects.toThrow('Missing 1 required arg:\npath');
- });
-
- it('should error if the argument is not a folder', () => {
- return expect(run(['--key', key, 'not-a-folder'])).rejects.toStrictEqual(
- new Error("Oops! We couldn't locate a file or directory at the path you provided."),
- );
- });
-
- it('should error if the folder contains no markdown nor HTML files', () => {
- return expect(run(['--key', key, '.github/workflows'])).rejects.toStrictEqual(
- new Error(
- "The directory you provided (.github/workflows) doesn't contain any of the following required files: .html, .markdown, .md.",
- ),
- );
- });
-
- describe('existing custompages', () => {
- let simpleDoc;
- let anotherDoc;
-
- beforeEach(() => {
- let fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/simple-doc.md'));
- simpleDoc = {
- slug: 'simple-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
-
- fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/subdir/another-doc.md'));
- anotherDoc = {
- slug: 'another-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
- });
-
- it('should fetch custom page and merge with what is returned', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, htmlmode: false, lastUpdatedHash: 'anOldHash' })
- .get('/api/v1/custompages/another-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: anotherDoc.slug, htmlmode: false, lastUpdatedHash: 'anOldHash' });
-
- const updateMocks = getAPIv1Mock()
- .put('/api/v1/custompages/simple-doc', {
- body: simpleDoc.doc.content,
- htmlmode: false,
- lastUpdatedHash: simpleDoc.hash,
- ...simpleDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, {
- slug: simpleDoc.slug,
- htmlmode: false,
- body: simpleDoc.doc.content,
- })
- .put('/api/v1/custompages/another-doc', {
- body: anotherDoc.doc.content,
- htmlmode: false,
- lastUpdatedHash: anotherDoc.hash,
- ...anotherDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, { slug: anotherDoc.slug, body: anotherDoc.doc.content, htmlmode: false });
-
- return run([`./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key]).then(updatedDocs => {
- // All custompages should have been updated because their hashes from the GET request were different from what they
- // are currently.
- expect(updatedDocs).toBe(
- [
- `βοΈ successfully updated 'simple-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- `βοΈ successfully updated 'another-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/subdir/another-doc.md`,
- ].join('\n'),
- );
-
- getMocks.done();
- updateMocks.done();
- });
- });
-
- it('should return custom page update info for dry run', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' })
- .get('/api/v1/custompages/another-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: anotherDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- return run(['--dryRun', `./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key]).then(updatedDocs => {
- // All custompages should have been updated because their hashes from the GET request were different from what they
- // are currently.
- expect(updatedDocs).toBe(
- [
- `π dry run! This will update 'simple-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/simple-doc.md with the following metadata: ${JSON.stringify(
- simpleDoc.doc.data,
- )}`,
- `π dry run! This will update 'another-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/subdir/another-doc.md with the following metadata: ${JSON.stringify(
- anotherDoc.doc.data,
- )}`,
- ].join('\n'),
- );
-
- getMocks.done();
- });
- });
-
- it('should not send requests for custompages that have not changed', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash })
- .get('/api/v1/custompages/another-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash });
-
- return run([`./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key]).then(skippedDocs => {
- expect(skippedDocs).toBe(
- [
- '`simple-doc` was not updated because there were no changes.',
- '`another-doc` was not updated because there were no changes.',
- ].join('\n'),
- );
-
- getMocks.done();
- });
- });
-
- it('should adjust "no changes" message if in dry run', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash })
- .get('/api/v1/custompages/another-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash });
-
- return run(['--dryRun', `./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key]).then(skippedDocs => {
- expect(skippedDocs).toBe(
- [
- 'π dry run! `simple-doc` will not be updated because there were no changes.',
- 'π dry run! `another-doc` will not be updated because there were no changes.',
- ].join('\n'),
- );
-
- getMocks.done();
- });
- });
- });
-
- describe('new custompages', () => {
- it('should create new custom page', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs`, '--key', key])).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/new-docs/new-doc.md`,
- );
-
- getMock.done();
- postMock.done();
- });
-
- it('should create new HTML custom page', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs-html/${slug}.html`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs-html/${slug}.html`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, html: doc.content, htmlmode: true, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, html: doc.content, htmlmode: true, ...doc.data, lastUpdatedHash: hash });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs-html`, '--key', key])).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/new-docs-html/new-doc.html`,
- );
-
- getMock.done();
- postMock.done();
- });
-
- it('should return creation info for dry run', async () => {
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- await expect(run(['--dryRun', `./__tests__/${fixturesBaseDir}/new-docs`, '--key', key])).resolves.toBe(
- `π dry run! This will create 'new-doc' with contents from __tests__/${fixturesBaseDir}/new-docs/new-doc.md with the following metadata: ${JSON.stringify(
- doc.data,
- )}`,
- );
-
- getMock.done();
- });
-
- it('should fail if any custompages are invalid', async () => {
- const folder = 'failure-docs';
- const slug = 'new-doc';
-
- const errorObject = {
- error: 'CUSTOMPAGE_INVALID',
- message: "We couldn't save this page (Custom page title cannot be blank).",
- suggestion: 'Make sure all the data is correct, and the body is valid Markdown or HTML.',
- docs: 'fake-metrics-uuid',
- help: "If you need help, email support@readme.io and include the following link to your API log: 'fake-metrics-uuid'.",
- };
-
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/${folder}/${slug}.md`)));
-
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/${folder}/${slug}.md`)));
-
- const getMocks = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMocks = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, body: doc.content, htmlmode: false, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(400, errorObject);
-
- const fullDirectory = `__tests__/${fixturesBaseDir}/${folder}`;
-
- const formattedErrorObject = {
- ...errorObject,
- message: `Error uploading ${chalk.underline(`${fullDirectory}/${slug}.md`)}:\n\n${errorObject.message}`,
- };
-
- await expect(run([`./${fullDirectory}`, '--key', key])).rejects.toStrictEqual(
- new APIv1Error(formattedErrorObject),
- );
-
- getMocks.done();
- postMocks.done();
- });
- });
-
- describe('slug metadata', () => {
- it('should use provided slug', async () => {
- const slug = 'new-doc-slug';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${doc.data.slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug: doc.data.slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/slug-docs`, '--key', key])).resolves.toBe(
- `π± successfully created 'marc-actually-wrote-a-test' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/slug-docs/new-doc-slug.md`,
- );
-
- getMock.done();
- postMock.done();
- });
- });
-});
diff --git a/__tests__/commands/custompages/single.test.ts b/__tests__/commands/custompages/single.test.ts
deleted file mode 100644
index e255b3d6c..000000000
--- a/__tests__/commands/custompages/single.test.ts
+++ /dev/null
@@ -1,291 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-import chalk from 'chalk';
-import frontMatter from 'gray-matter';
-import nock from 'nock';
-import { describe, beforeAll, afterAll, beforeEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/custompages.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock } from '../../helpers/get-api-mock.js';
-import hashFileContents from '../../helpers/hash-file-contents.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const fixturesBaseDir = '__fixtures__/custompages';
-const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
-const key = 'API_KEY';
-
-describe('rdme custompages (single)', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterAll(() => nock.cleanAll());
-
- it('should error if no file path provided', () => {
- return expect(run(['--key', key])).rejects.toThrow('Missing 1 required arg:\npath');
- });
-
- it('should error if the argument is not a Markdown/HTML file', () => {
- return expect(run(['--key', key, 'package.json'])).rejects.toStrictEqual(
- new Error('Invalid file extension (.json). Must be one of the following: .html, .markdown, .md'),
- );
- });
-
- it('should error if file path cannot be found', () => {
- return expect(run(['--key', key, 'non-existent-file.markdown'])).rejects.toStrictEqual(
- new Error("Oops! We couldn't locate a file or directory at the path you provided."),
- );
- });
-
- describe('new custompages', () => {
- it('should create new custom page', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, body: doc.content, ...doc.data });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`, '--key', key])).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from ./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`,
- );
-
- getMock.done();
- postMock.done();
- });
-
- it('should create new HTML custom page', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs-html/${slug}.html`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs-html/${slug}.html`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, html: doc.content, htmlmode: true, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, html: doc.content, htmlmode: true, ...doc.data });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs-html/new-doc.html`, '--key', key])).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from ./__tests__/${fixturesBaseDir}/new-docs-html/new-doc.html`,
- );
-
- getMock.done();
- postMock.done();
- });
-
- it('should return creation info for dry run', async () => {
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- await expect(run(['--dryRun', `./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`, '--key', key])).resolves.toBe(
- `π dry run! This will create 'new-doc' with contents from ./__tests__/${fixturesBaseDir}/new-docs/new-doc.md with the following metadata: ${JSON.stringify(
- doc.data,
- )}`,
- );
-
- getMock.done();
- });
-
- it('should skip if it does not contain any front matter attributes', async () => {
- const filePath = `./__tests__/${fixturesBaseDir}/failure-docs/doc-sans-attributes.md`;
-
- await expect(run([filePath, '--key', key])).resolves.toBe(
- `βοΈ no front matter attributes found for ${filePath}, skipping`,
- );
- });
-
- it('should fail if some other error when retrieving page slug', async () => {
- const slug = 'new-doc';
-
- const errorObject = {
- error: 'INTERNAL_ERROR',
- message: 'Unknown error (yikes)',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${slug}`)
- .basicAuth({ user: key })
- .reply(500, errorObject);
-
- const filePath = `./__tests__/${fixturesBaseDir}/failure-docs/${slug}.md`;
-
- const formattedErrorObject = {
- ...errorObject,
- message: `Error uploading ${chalk.underline(`${filePath}`)}:\n\n${errorObject.message}`,
- };
-
- await expect(run([filePath, '--key', key])).rejects.toStrictEqual(new APIv1Error(formattedErrorObject));
-
- getMock.done();
- });
- });
-
- describe('slug metadata', () => {
- it('should use provided slug', async () => {
- const slug = 'new-doc-slug';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/custompages/${doc.data.slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'CUSTOMPAGE_NOTFOUND',
- message: `The custom page with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/custompages', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug: doc.data.slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/slug-docs/new-doc-slug.md`, '--key', key])).resolves.toBe(
- `π± successfully created 'marc-actually-wrote-a-test' (ID: 1234) with contents from ./__tests__/${fixturesBaseDir}/slug-docs/new-doc-slug.md`,
- );
-
- getMock.done();
- postMock.done();
- });
- });
-
- describe('existing custompages', () => {
- let simpleDoc;
-
- beforeEach(() => {
- const fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/simple-doc.md'));
- simpleDoc = {
- slug: 'simple-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
- });
-
- it('should fetch custom page and merge with what is returned', () => {
- const getMock = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const updateMock = getAPIv1Mock()
- .put('/api/v1/custompages/simple-doc', {
- body: simpleDoc.doc.content,
- htmlmode: false,
- lastUpdatedHash: simpleDoc.hash,
- ...simpleDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, {
- slug: simpleDoc.slug,
- body: simpleDoc.doc.content,
- htmlmode: false,
- });
-
- return run([`./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key]).then(updatedDocs => {
- expect(updatedDocs).toBe(
- `βοΈ successfully updated 'simple-doc' with contents from ./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- );
-
- getMock.done();
- updateMock.done();
- });
- });
-
- it('should return custom page update info for dry run', () => {
- expect.assertions(1);
-
- const getMock = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- return run(['--dryRun', `./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key]).then(
- updatedDocs => {
- // All custompages should have been updated because their hashes from the GET request were different from what they
- // are currently.
- expect(updatedDocs).toBe(
- [
- `π dry run! This will update 'simple-doc' with contents from ./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md with the following metadata: ${JSON.stringify(
- simpleDoc.doc.data,
- )}`,
- ].join('\n'),
- );
-
- getMock.done();
- },
- );
- });
-
- it('should not send requests for custompages that have not changed', () => {
- expect.assertions(1);
-
- const getMock = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash });
-
- return run([`./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key]).then(skippedDocs => {
- expect(skippedDocs).toBe('`simple-doc` was not updated because there were no changes.');
-
- getMock.done();
- });
- });
-
- it('should adjust "no changes" message if in dry run', () => {
- const getMock = getAPIv1Mock()
- .get('/api/v1/custompages/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash });
-
- return run(['--dryRun', `./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key]).then(
- skippedDocs => {
- expect(skippedDocs).toBe('π dry run! `simple-doc` will not be updated because there were no changes.');
-
- getMock.done();
- },
- );
- });
- });
-});
diff --git a/__tests__/commands/docs/__snapshots__/index.test.ts.snap b/__tests__/commands/docs/__snapshots__/index.test.ts.snap
deleted file mode 100644
index 3f7082f4c..000000000
--- a/__tests__/commands/docs/__snapshots__/index.test.ts.snap
+++ /dev/null
@@ -1,124 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`rdme docs > GHA onboarding E2E tests > should create GHA workflow with version passed as opt (github flag enabled) 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/docs-test-file-github-flag.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`rdme docs > GHA onboarding E2E tests > should create GHA workflow with version passed as opt (github flag enabled) 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`docs-test-branch-github-flag\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - docs-test-branch-github-flag
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./__tests__/__fixtures__/docs/new-docs --key=\${{ secrets.README_API_KEY }} --version=1.0.0
-"
-`;
-
-exports[`rdme docs > GHA onboarding E2E tests > should create GHA workflow with version passed in via opt 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/docs-test-file.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`rdme docs > GHA onboarding E2E tests > should create GHA workflow with version passed in via opt 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`docs-test-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - docs-test-branch
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./__tests__/__fixtures__/docs/new-docs --key=\${{ secrets.README_API_KEY }} --version=1.0.0
-"
-`;
-
-exports[`rdme docs > GHA onboarding E2E tests > should create GHA workflow with version passed in via prompt 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/docs-test-file.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`rdme docs > GHA onboarding E2E tests > should create GHA workflow with version passed in via prompt 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`docs-test-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - docs-test-branch
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./__tests__/__fixtures__/docs/new-docs --key=\${{ secrets.README_API_KEY }} --version=1.0.1
-"
-`;
diff --git a/__tests__/commands/docs/__snapshots__/multiple.test.ts.snap b/__tests__/commands/docs/__snapshots__/multiple.test.ts.snap
deleted file mode 100644
index e6de4e4bc..000000000
--- a/__tests__/commands/docs/__snapshots__/multiple.test.ts.snap
+++ /dev/null
@@ -1,3 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`rdme docs (multiple) > should return an error message when it encounters a cycle 1`] = `[Error: Cyclic dependency, node was:{"content":"\\n# Parent Body\\n","data":{"title":"Parent","parentDocSlug":"grandparent"},"filePath":"__tests__/__fixtures__/docs/multiple-docs-cycle/parent.md","hash":"0fc832371f8e240047bfc14bc8be9e37d50c8bb8","slug":"parent"}]`;
diff --git a/__tests__/commands/docs/index.test.ts b/__tests__/commands/docs/index.test.ts
deleted file mode 100644
index f71d9d12c..000000000
--- a/__tests__/commands/docs/index.test.ts
+++ /dev/null
@@ -1,729 +0,0 @@
-/* eslint-disable no-console */
-
-import fs from 'node:fs';
-import path from 'node:path';
-
-import chalk from 'chalk';
-import frontMatter from 'gray-matter';
-import nock from 'nock';
-import prompts from 'prompts';
-import { describe, beforeAll, afterAll, beforeEach, afterEach, it, expect, vi, type MockInstance } from 'vitest';
-
-import Command from '../../../src/commands/docs/index.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import { after, before } from '../../helpers/get-gha-setup.js';
-import hashFileContents from '../../helpers/hash-file-contents.js';
-import { runCommandAndReturnResult, runCommandWithHooks } from '../../helpers/oclif.js';
-import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
-
-const fixturesBaseDir = '__fixtures__/docs';
-const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
-
-const key = 'API_KEY';
-const version = '1.0.0';
-const category = 'CATEGORY_ID';
-
-describe('rdme docs', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterAll(() => nock.cleanAll());
-
- it('should error if no path provided', () => {
- return expect(run(['--key', key, '--version', '1.0.0'])).rejects.toThrow('Missing 1 required arg:\npath');
- });
-
- it('should error if the argument is not a folder', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, '--version', '1.0.0', 'not-a-folder'])).rejects.toStrictEqual(
- new Error("Oops! We couldn't locate a file or directory at the path you provided."),
- );
-
- versionMock.done();
- });
-
- it('should error if the folder contains no markdown files', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, '--version', '1.0.0', '.github/workflows'])).rejects.toStrictEqual(
- new Error(
- "The directory you provided (.github/workflows) doesn't contain any of the following required files: .markdown, .md.",
- ),
- );
-
- versionMock.done();
- });
-
- describe('existing docs', () => {
- let simpleDoc;
- let anotherDoc;
-
- beforeEach(() => {
- let fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/simple-doc.md'));
- simpleDoc = {
- slug: 'simple-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
-
- fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/subdir/another-doc.md'));
- anotherDoc = {
- slug: 'another-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
- });
-
- it('should fetch doc and merge with what is returned', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' })
- .get('/api/v1/docs/another-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const updateMocks = getAPIv1MockWithVersionHeader(version)
- .put('/api/v1/docs/simple-doc', {
- body: simpleDoc.doc.content,
- lastUpdatedHash: simpleDoc.hash,
- ...simpleDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, {
- category,
- slug: simpleDoc.slug,
- body: simpleDoc.doc.content,
- })
- .put('/api/v1/docs/another-doc', {
- body: anotherDoc.doc.content,
- lastUpdatedHash: anotherDoc.hash,
- ...anotherDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, body: anotherDoc.doc.content });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- return run([`./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key, '--version', version]).then(
- updatedDocs => {
- // All docs should have been updated because their hashes from the GET request were different from what they
- // are currently.
- expect(updatedDocs).toBe(
- [
- `βοΈ successfully updated 'simple-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- `βοΈ successfully updated 'another-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/subdir/another-doc.md`,
- ].join('\n'),
- );
-
- getMocks.done();
- updateMocks.done();
- versionMock.done();
- },
- );
- });
-
- it('should return doc update info for dry run', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' })
- .get('/api/v1/docs/another-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- return run(['--dryRun', `./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key, '--version', version]).then(
- updatedDocs => {
- // All docs should have been updated because their hashes from the GET request were different from what they
- // are currently.
- expect(updatedDocs).toBe(
- [
- `π dry run! This will update 'simple-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/simple-doc.md with the following metadata: ${JSON.stringify(
- simpleDoc.doc.data,
- )}`,
- `π dry run! This will update 'another-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/subdir/another-doc.md with the following metadata: ${JSON.stringify(
- anotherDoc.doc.data,
- )}`,
- ].join('\n'),
- );
-
- getMocks.done();
- versionMock.done();
- },
- );
- });
-
- it('should not send requests for docs that have not changed', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash })
- .get('/api/v1/docs/another-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- return run([`./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key, '--version', version]).then(
- skippedDocs => {
- expect(skippedDocs).toBe(
- [
- '`simple-doc` was not updated because there were no changes.',
- '`another-doc` was not updated because there were no changes.',
- ].join('\n'),
- );
-
- getMocks.done();
- versionMock.done();
- },
- );
- });
-
- it('should adjust "no changes" message if in dry run', () => {
- expect.assertions(1);
-
- const getMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash })
- .get('/api/v1/docs/another-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: anotherDoc.hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- return run(['--dryRun', `./__tests__/${fixturesBaseDir}/existing-docs`, '--key', key, '--version', version]).then(
- skippedDocs => {
- expect(skippedDocs).toBe(
- [
- 'π dry run! `simple-doc` will not be updated because there were no changes.',
- 'π dry run! `another-doc` will not be updated because there were no changes.',
- ].join('\n'),
- );
-
- getMocks.done();
- versionMock.done();
- },
- );
- });
- });
-
- describe('new docs', () => {
- it('should create new doc', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs`, '--key', key, '--version', version])).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/new-docs/new-doc.md`,
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should return creation info for dry run', async () => {
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run(['--dryRun', `./__tests__/${fixturesBaseDir}/new-docs`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `π dry run! This will create 'new-doc' with contents from __tests__/${fixturesBaseDir}/new-docs/new-doc.md with the following metadata: ${JSON.stringify(
- doc.data,
- )}`,
- );
-
- getMock.done();
- versionMock.done();
- });
-
- it('should fail if any docs are invalid', async () => {
- const folder = 'failure-docs';
- const slug = 'new-doc';
-
- const errorObject = {
- error: 'DOC_INVALID',
- message: "We couldn't save this doc (Path `category` is required.).",
- };
-
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/${folder}/${slug}.md`)));
-
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/${folder}/${slug}.md`)));
-
- const getMocks = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMocks = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(400, errorObject);
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const fullDirectory = `__tests__/${fixturesBaseDir}/${folder}`;
-
- const formattedErrorObject = {
- ...errorObject,
- message: `Error uploading ${chalk.underline(`${fullDirectory}/${slug}.md`)}:\n\n${errorObject.message}`,
- };
-
- await expect(run([`./${fullDirectory}`, '--key', key, '--version', version])).rejects.toStrictEqual(
- new APIv1Error(formattedErrorObject),
- );
-
- getMocks.done();
- postMocks.done();
- versionMock.done();
- });
- });
-
- describe('slug metadata', () => {
- it('should use provided slug', async () => {
- const slug = 'new-doc-slug';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/docs/${doc.data.slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug: doc.data.slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/slug-docs`, '--key', key, '--version', version])).resolves.toBe(
- `π± successfully created 'marc-actually-wrote-a-test' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/slug-docs/new-doc-slug.md`,
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
- });
-
- describe('GHA onboarding E2E tests', () => {
- let consoleInfoSpy: MockInstance;
- let yamlOutput;
-
- const getCommandOutput = () => {
- return [consoleInfoSpy.mock.calls.join('\n\n')].filter(Boolean).join('\n\n');
- };
-
- beforeEach(() => {
- consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
-
- before((fileName, data) => {
- yamlOutput = data;
- });
- });
-
- afterEach(() => {
- after();
-
- consoleInfoSpy.mockRestore();
- });
-
- it('should create GHA workflow with version passed in via prompt', async () => {
- expect.assertions(6);
-
- const altVersion = '1.0.1';
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const versionsMock = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }, { version: altVersion }]);
-
- const getMock = getAPIv1MockWithVersionHeader(altVersion)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(altVersion)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { _id: id, slug, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const fileName = 'docs-test-file';
- prompts.inject([altVersion, true, 'docs-test-branch', fileName]);
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs`, '--key', key])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${fileName}.yml`, expect.any(String));
- expect(console.info).toHaveBeenCalledTimes(2);
- const output = getCommandOutput();
- expect(output).toMatch("Looks like you're running this command in a GitHub Repository!");
- expect(output).toMatch(`successfully created '${slug}' (ID: ${id}) with contents from`);
-
- versionsMock.done();
- getMock.done();
- postMock.done();
- });
-
- it('should create GHA workflow with version passed in via opt', async () => {
- expect.assertions(3);
-
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const fileName = 'docs-test-file';
- prompts.inject([true, 'docs-test-branch', fileName]);
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/new-docs`, '--key', key, '--version', version]),
- ).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${fileName}.yml`, expect.any(String));
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should create GHA workflow with version passed as opt (github flag enabled)', async () => {
- expect.assertions(3);
-
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const fileName = 'docs-test-file-github-flag';
- prompts.inject(['docs-test-branch-github-flag', fileName]);
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/new-docs`, '--github', '--key', key, '--version', version]),
- ).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${fileName}.yml`, expect.any(String));
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should reject if user says no to creating GHA workflow', async () => {
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- prompts.inject([false]);
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/new-docs`, '--key', key, '--version', version]),
- ).rejects.toStrictEqual(
- new Error(
- 'GitHub Actions workflow creation cancelled. If you ever change your mind, you can run this command again with the `--github` flag.',
- ),
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
- });
-
- describe('command execution in GitHub Actions runner', () => {
- beforeEach(() => {
- beforeGHAEnv();
- });
-
- afterEach(afterGHAEnv);
-
- it('should sync new docs directory with correct headers', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/docs/new-docs/new-doc.md',
- 'x-readme-version': version,
- })
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run([`./__tests__/${fixturesBaseDir}/new-docs`, '--key', key, '--version', version])).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/new-docs/new-doc.md`,
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should sync existing docs directory with correct headers', () => {
- let fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/simple-doc.md'));
- const simpleDoc = {
- slug: 'simple-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
-
- fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/subdir/another-doc.md'));
- const anotherDoc = {
- slug: 'another-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
-
- expect.assertions(1);
-
- const getMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' })
- .get('/api/v1/docs/another-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const firstUpdateMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/docs/existing-docs/simple-doc.md',
- 'x-readme-version': version,
- })
- .put('/api/v1/docs/simple-doc', {
- body: simpleDoc.doc.content,
- lastUpdatedHash: simpleDoc.hash,
- ...simpleDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, {
- category,
- slug: simpleDoc.slug,
- body: simpleDoc.doc.content,
- });
-
- const secondUpdateMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/docs/existing-docs/subdir/another-doc.md',
- 'x-readme-version': version,
- })
- .put('/api/v1/docs/another-doc', {
- body: anotherDoc.doc.content,
- lastUpdatedHash: anotherDoc.hash,
- ...anotherDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, { category, slug: anotherDoc.slug, body: anotherDoc.doc.content });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- return run([`__tests__/${fixturesBaseDir}/existing-docs`, '--key', key, '--version', version]).then(
- updatedDocs => {
- // All docs should have been updated because their hashes from the GET request were different from what they
- // are currently.
- expect(updatedDocs).toBe(
- [
- `βοΈ successfully updated 'simple-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- `βοΈ successfully updated 'another-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/subdir/another-doc.md`,
- ].join('\n'),
- );
-
- getMocks.done();
- firstUpdateMock.done();
- secondUpdateMock.done();
- versionMock.done();
- },
- );
- });
- });
-
- describe('rdme guides', () => {
- it('should error if no path provided', async () => {
- return expect(
- (await runCommandWithHooks(['guides', '--key', key, '--version', '1.0.0'])).error.message,
- ).toContain('Missing 1 required arg:\npath');
- });
- });
-});
diff --git a/__tests__/commands/docs/multiple.test.ts b/__tests__/commands/docs/multiple.test.ts
deleted file mode 100644
index 40c0a29de..000000000
--- a/__tests__/commands/docs/multiple.test.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-import frontMatter from 'gray-matter';
-import nock from 'nock';
-import { describe, beforeAll, afterAll, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/docs/index.js';
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import hashFileContents from '../../helpers/hash-file-contents.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const fixturesBaseDir = '__fixtures__/docs';
-const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme docs (multiple)', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterAll(() => nock.cleanAll());
-
- it('should upload parent docs first', async () => {
- const dir = 'multiple-docs';
- const slugs = ['grandparent', 'parent', 'child', 'friend'];
- let id = 1234;
-
- const mocks = slugs.flatMap(slug => {
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/${dir}/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/${dir}/${slug}.md`)));
-
- return [
- getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- }),
- getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- // eslint-disable-next-line no-plusplus
- .reply(201, { slug, _id: id++, body: doc.content, ...doc.data, lastUpdatedHash: hash }),
- ];
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const promise = run([`./__tests__/${fixturesBaseDir}/${dir}`, '--key', key, '--version', version]);
-
- await expect(promise).resolves.toStrictEqual(
- [
- `π± successfully created 'friend' (ID: 1237) with contents from __tests__/${fixturesBaseDir}/${dir}/friend.md`,
- `π± successfully created 'grandparent' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/${dir}/grandparent.md`,
- `π± successfully created 'parent' (ID: 1235) with contents from __tests__/${fixturesBaseDir}/${dir}/parent.md`,
- `π± successfully created 'child' (ID: 1236) with contents from __tests__/${fixturesBaseDir}/${dir}/child.md`,
- ].join('\n'),
- );
-
- mocks.forEach(mock => mock.done());
- versionMock.done();
- });
-
- it('should upload docs with parent doc ids first', async () => {
- const dir = 'docs-with-parent-ids';
- const slugs = ['child', 'friend', 'with-parent-doc', 'parent'];
- let id = 1234;
-
- const mocks = slugs.flatMap(slug => {
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/${dir}/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/${dir}/${slug}.md`)));
-
- return [
- getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- }),
- getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- // eslint-disable-next-line no-plusplus
- .reply(201, { slug, _id: id++, body: doc.content, ...doc.data, lastUpdatedHash: hash }),
- ];
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const promise = run([`./__tests__/${fixturesBaseDir}/${dir}`, '--key', key, '--version', version]);
-
- await expect(promise).resolves.toStrictEqual(
- [
- `π± successfully created 'with-parent-doc' (ID: 1236) with contents from __tests__/${fixturesBaseDir}/${dir}/with-parent-doc.md`,
- `π± successfully created 'friend' (ID: 1235) with contents from __tests__/${fixturesBaseDir}/${dir}/friend.md`,
- `π± successfully created 'parent' (ID: 1237) with contents from __tests__/${fixturesBaseDir}/${dir}/parent.md`,
- `π± successfully created 'child' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/${dir}/child.md`,
- ].join('\n'),
- );
-
- mocks.forEach(mock => mock.done());
- versionMock.done();
- });
-
- it('should upload child docs without the parent', async () => {
- const dir = 'multiple-docs-no-parents';
- const slugs = ['child', 'friend'];
- let id = 1234;
-
- const mocks = slugs.flatMap(slug => {
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/${dir}/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/${dir}/${slug}.md`)));
-
- return [
- getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- }),
- getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- // eslint-disable-next-line no-plusplus
- .reply(201, { slug, _id: id++, body: doc.content, ...doc.data, lastUpdatedHash: hash }),
- ];
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const promise = run([`./__tests__/${fixturesBaseDir}/${dir}`, '--key', key, '--version', version]);
-
- await expect(promise).resolves.toStrictEqual(
- [
- `π± successfully created 'child' (ID: 1234) with contents from __tests__/${fixturesBaseDir}/${dir}/child.md`,
- `π± successfully created 'friend' (ID: 1235) with contents from __tests__/${fixturesBaseDir}/${dir}/friend.md`,
- ].join('\n'),
- );
-
- mocks.forEach(mock => mock.done());
- versionMock.done();
- });
-
- it('should return an error message when it encounters a cycle', async () => {
- const dir = 'multiple-docs-cycle';
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const promise = run([`./__tests__/${fixturesBaseDir}/${dir}`, '--key', key, '--version', version]);
-
- await expect(promise).rejects.toMatchSnapshot();
- versionMock.done();
- });
-});
diff --git a/__tests__/commands/docs/prune.test.ts b/__tests__/commands/docs/prune.test.ts
deleted file mode 100644
index 162e5cb7e..000000000
--- a/__tests__/commands/docs/prune.test.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import nock from 'nock';
-import prompts from 'prompts';
-import { describe, beforeAll, afterAll, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/docs/prune.js';
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult, runCommandWithHooks } from '../../helpers/oclif.js';
-
-const fixturesBaseDir = '__fixtures__/docs';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme docs prune', () => {
- const folder = `./__tests__/${fixturesBaseDir}/delete-docs`;
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterAll(() => nock.cleanAll());
-
- it('should error if no folder provided', () => {
- return expect(run(['--key', key, '--version', version])).rejects.rejects.toThrow('Missing 1 required arg:\nfolder');
- });
-
- it('should error if the argument is not a folder', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, '--version', version, 'not-a-folder'])).rejects.toStrictEqual(
- new Error("ENOENT: no such file or directory, scandir 'not-a-folder'"),
- );
-
- versionMock.done();
- });
-
- it('should do nothing if the user aborted', async () => {
- prompts.inject([false]);
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run([folder, '--key', key, '--version', version])).rejects.toStrictEqual(
- new Error('Aborting, no changes were made.'),
- );
-
- versionMock.done();
- });
-
- it('should not ask for user confirmation if `confirm` is set to true', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const apiMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'category1', type: 'guide' }], { 'x-total-count': '1' })
- .get('/api/v1/categories/category1/docs')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'this-doc-should-be-missing-in-folder' }, { slug: 'some-doc' }])
- .delete('/api/v1/docs/this-doc-should-be-missing-in-folder')
- .basicAuth({ user: key })
- .reply(204, '');
-
- await expect(run([folder, '--key', key, '--version', version, '--confirm'])).resolves.toBe(
- 'ποΈ successfully deleted `this-doc-should-be-missing-in-folder`.',
- );
-
- apiMocks.done();
- versionMock.done();
- });
-
- it('should delete doc if file is missing', async () => {
- prompts.inject([true]);
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const apiMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'category1', type: 'guide' }], { 'x-total-count': '1' })
- .get('/api/v1/categories/category1/docs')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'this-doc-should-be-missing-in-folder' }, { slug: 'some-doc' }])
- .delete('/api/v1/docs/this-doc-should-be-missing-in-folder')
- .basicAuth({ user: key })
- .reply(204, '');
-
- await expect(run([folder, '--key', key, '--version', version])).resolves.toBe(
- 'ποΈ successfully deleted `this-doc-should-be-missing-in-folder`.',
- );
-
- apiMocks.done();
- versionMock.done();
- });
-
- it('should delete doc and its child if they are missing', async () => {
- prompts.inject([true]);
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const apiMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'category1', type: 'guide' }], { 'x-total-count': '1' })
- .get('/api/v1/categories/category1/docs')
- .basicAuth({ user: key })
- .reply(200, [
- { slug: 'this-doc-should-be-missing-in-folder', children: [{ slug: 'this-child-is-also-missing' }] },
- { slug: 'some-doc' },
- ])
- .delete('/api/v1/docs/this-doc-should-be-missing-in-folder')
- .basicAuth({ user: key })
- .reply(204, '')
- .delete('/api/v1/docs/this-child-is-also-missing')
- .basicAuth({ user: key })
- .reply(204, '');
-
- await expect(run([folder, '--key', key, '--version', version])).resolves.toBe(
- 'ποΈ successfully deleted `this-child-is-also-missing`.\nποΈ successfully deleted `this-doc-should-be-missing-in-folder`.',
- );
-
- apiMocks.done();
- versionMock.done();
- });
-
- it('should return doc delete info for dry run', async () => {
- prompts.inject([true]);
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
- const apiMocks = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/categories?perPage=20&page=1')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'category1', type: 'guide' }], { 'x-total-count': '1' })
- .get('/api/v1/categories/category1/docs')
- .basicAuth({ user: key })
- .reply(200, [{ slug: 'this-doc-should-be-missing-in-folder' }]);
-
- await expect(run([folder, '--key', key, '--version', version, '--dryRun'])).resolves.toBe(
- 'π dry run! This will delete `this-doc-should-be-missing-in-folder`.',
- );
-
- apiMocks.done();
- versionMock.done();
- });
-
- describe('rdme guides prune', () => {
- it('should error if no folder provided', async () => {
- return expect(
- (await runCommandWithHooks(['guides', 'prune', '--key', key, '--version', version])).error.message,
- ).toContain('Missing 1 required arg:\nfolder');
- });
- });
-});
diff --git a/__tests__/commands/docs/single.test.ts b/__tests__/commands/docs/single.test.ts
deleted file mode 100644
index 007bd28d2..000000000
--- a/__tests__/commands/docs/single.test.ts
+++ /dev/null
@@ -1,443 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-import chalk from 'chalk';
-import frontMatter from 'gray-matter';
-import nock from 'nock';
-import { describe, beforeAll, afterAll, beforeEach, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/docs/index.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import hashFileContents from '../../helpers/hash-file-contents.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
-
-const fixturesBaseDir = '__fixtures__/docs';
-const fullFixturesDir = `${__dirname}./../../${fixturesBaseDir}`;
-
-const key = 'API_KEY';
-const version = '1.0.0';
-const category = 'CATEGORY_ID';
-
-describe('rdme docs (single)', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterAll(() => nock.cleanAll());
-
- it('should error if no file path provided', () => {
- return expect(run(['--key', key, '--version', version])).rejects.toThrow('Missing 1 required arg:\npath');
- });
-
- it('should error if the argument is not a Markdown file', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, '--version', version, 'not-a-markdown-file'])).rejects.toStrictEqual(
- new Error("Oops! We couldn't locate a file or directory at the path you provided."),
- );
-
- versionMock.done();
- });
-
- it('should support .markdown files but error if file path cannot be found', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
- await expect(run(['--key', key, '--version', version, 'non-existent-file.markdown'])).rejects.toStrictEqual(
- new Error("Oops! We couldn't locate a file or directory at the path you provided."),
- );
- versionMock.done();
- });
-
- describe('new docs', () => {
- it('should create new doc', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from ./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`,
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should return creation info for dry run', async () => {
- const slug = 'new-doc';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run(['--dryRun', `./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `π dry run! This will create 'new-doc' with contents from ./__tests__/${fixturesBaseDir}/new-docs/new-doc.md with the following metadata: ${JSON.stringify(
- doc.data,
- )}`,
- );
-
- getMock.done();
- versionMock.done();
- });
-
- it('should skip doc if it does not contain any front matter attributes', async () => {
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const filePath = `./__tests__/${fixturesBaseDir}/failure-docs/doc-sans-attributes.md`;
-
- await expect(run(['--key', key, '--version', version, filePath])).resolves.toBe(
- `βοΈ no front matter attributes found for ${filePath}, skipping`,
- );
-
- versionMock.done();
- });
-
- it('should fail if some other error when retrieving page slug', async () => {
- const slug = 'new-doc';
-
- const errorObject = {
- error: 'INTERNAL_ERROR',
- message: 'Unknown error (yikes)',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(500, errorObject);
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- const filePath = `./__tests__/${fixturesBaseDir}/failure-docs/${slug}.md`;
-
- const formattedErrorObject = {
- ...errorObject,
- message: `Error uploading ${chalk.underline(`${filePath}`)}:\n\n${errorObject.message}`,
- };
-
- await expect(run([filePath, '--key', key, '--version', version])).rejects.toStrictEqual(
- new APIv1Error(formattedErrorObject),
- );
-
- getMock.done();
- versionMock.done();
- });
- });
-
- describe('slug metadata', () => {
- it('should use provided slug', async () => {
- const slug = 'new-doc-slug';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/slug-docs/${slug}.md`)));
-
- const getMock = getAPIv1Mock()
- .get(`/api/v1/docs/${doc.data.slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock()
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug: doc.data.slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/slug-docs/new-doc-slug.md`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `π± successfully created 'marc-actually-wrote-a-test' (ID: 1234) with contents from ./__tests__/${fixturesBaseDir}/slug-docs/new-doc-slug.md`,
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
- });
-
- describe('existing docs', () => {
- let simpleDoc;
-
- beforeEach(() => {
- const fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/simple-doc.md'));
- simpleDoc = {
- slug: 'simple-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
- });
-
- it('should fetch doc and merge with what is returned', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const updateMock = getAPIv1MockWithVersionHeader(version)
- .put('/api/v1/docs/simple-doc', {
- body: simpleDoc.doc.content,
- lastUpdatedHash: simpleDoc.hash,
- ...simpleDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, {
- category,
- slug: simpleDoc.slug,
- body: simpleDoc.doc.content,
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `βοΈ successfully updated 'simple-doc' with contents from ./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- );
-
- getMock.done();
- updateMock.done();
- versionMock.done();
- });
-
- it('should return doc update info for dry run', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([
- '--dryRun',
- `./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- '--key',
- key,
- '--version',
- version,
- ]),
- ).resolves.toBe(
- [
- `π dry run! This will update 'simple-doc' with contents from ./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md with the following metadata: ${JSON.stringify(
- simpleDoc.doc.data,
- )}`,
- ].join('\n'),
- );
-
- getMock.done();
- versionMock.done();
- });
-
- it('should not send requests for docs that have not changed', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key, '--version', version]),
- ).resolves.toBe('`simple-doc` was not updated because there were no changes.');
-
- getMock.done();
- versionMock.done();
- });
-
- it('should adjust "no changes" message if in dry run', async () => {
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: simpleDoc.hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([
- '--dryRun',
- `./__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- '--key',
- key,
- '--version',
- version,
- ]),
- ).resolves.toBe('π dry run! `simple-doc` will not be updated because there were no changes.');
-
- getMock.done();
- versionMock.done();
- });
- });
-
- describe('command execution in GitHub Actions runner', () => {
- beforeEach(() => {
- beforeGHAEnv();
- });
-
- afterEach(afterGHAEnv);
-
- it('should sync new doc with correct headers', async () => {
- const slug = 'new-doc';
- const id = '1234';
- const doc = frontMatter(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
- const hash = hashFileContents(fs.readFileSync(path.join(fullFixturesDir, `/new-docs/${slug}.md`)));
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get(`/api/v1/docs/${slug}`)
- .basicAuth({ user: key })
- .reply(404, {
- error: 'DOC_NOTFOUND',
- message: `The doc with the slug '${slug}' couldn't be found`,
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- });
-
- const postMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/docs/new-docs/new-doc.md',
- 'x-readme-version': version,
- })
- .post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
- .basicAuth({ user: key })
- .reply(201, { slug, _id: id, body: doc.content, ...doc.data, lastUpdatedHash: hash });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([`./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `π± successfully created 'new-doc' (ID: 1234) with contents from ./__tests__/${fixturesBaseDir}/new-docs/new-doc.md`,
- );
-
- getMock.done();
- postMock.done();
- versionMock.done();
- });
-
- it('should sync existing doc with correct headers', async () => {
- const fileContents = fs.readFileSync(path.join(fullFixturesDir, '/existing-docs/simple-doc.md'));
- const simpleDoc = {
- slug: 'simple-doc',
- doc: frontMatter(fileContents),
- hash: hashFileContents(fileContents),
- };
-
- const getMock = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/docs/simple-doc')
- .basicAuth({ user: key })
- .reply(200, { category, slug: simpleDoc.slug, lastUpdatedHash: 'anOldHash' });
-
- const updateMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/docs/existing-docs/simple-doc.md',
- 'x-readme-version': version,
- })
- .put('/api/v1/docs/simple-doc', {
- body: simpleDoc.doc.content,
- lastUpdatedHash: simpleDoc.hash,
- ...simpleDoc.doc.data,
- })
- .basicAuth({ user: key })
- .reply(200, {
- category,
- slug: simpleDoc.slug,
- body: simpleDoc.doc.content,
- });
-
- const versionMock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(
- run([`__tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`, '--key', key, '--version', version]),
- ).resolves.toBe(
- `βοΈ successfully updated 'simple-doc' with contents from __tests__/${fixturesBaseDir}/existing-docs/simple-doc.md`,
- );
-
- getMock.done();
- updateMock.done();
- versionMock.done();
- });
- });
-});
diff --git a/__tests__/commands/open.test.ts b/__tests__/commands/open.test.ts
deleted file mode 100644
index 3ab9829f0..000000000
--- a/__tests__/commands/open.test.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import type { Version } from '../../src/commands/versions/index.js';
-
-import chalk from 'chalk';
-import { describe, afterEach, beforeAll, it, expect } from 'vitest';
-
-import pkg from '../../package.json' with { type: 'json' };
-import Command from '../../src/commands/open.js';
-import configStore from '../../src/lib/configstore.js';
-import { getAPIv1Mock } from '../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../helpers/oclif.js';
-
-const mockArg = ['--mock'];
-
-describe('rdme open', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => {
- configStore.clear();
- });
-
- it('should error if no project provided', () => {
- configStore.delete('project');
-
- return expect(run(mockArg)).rejects.toStrictEqual(new Error(`Please login using \`${pkg.name} login\`.`));
- });
-
- it('should open the project', () => {
- configStore.set('project', 'subdomain');
-
- const projectUrl = 'https://subdomain.readme.io';
-
- return expect(run(mockArg)).resolves.toBe(`Opening ${chalk.green(projectUrl)} in your browser...`);
- });
-
- describe('open --dash', () => {
- it('should open the dash', async () => {
- configStore.set('project', 'subdomain');
- configStore.set('apiKey', '12345');
-
- const version = '1.0';
- const key = '12345';
- const versionPayload: Version = {
- createdAt: '2019-06-17T22:39:56.462Z',
- is_deprecated: false,
- is_hidden: false,
- is_beta: false,
- is_stable: true,
- codename: '',
- version,
- };
-
- const mockRequest = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [versionPayload, { version: '1.0.1' }]);
-
- const dashUrl = 'https://dash.readme.com/project/subdomain/v1.0/overview';
-
- await expect(run(mockArg.concat('--dash'))).resolves.toBe(`Opening ${chalk.green(dashUrl)} in your browser...`);
- mockRequest.done();
- });
-
- it('should require user to be logged in', () => {
- configStore.set('project', 'subdomain');
-
- return expect(run(mockArg.concat('--dash'))).rejects.toStrictEqual(
- new Error(`Please login using \`${pkg.name} login\`.`),
- );
- });
- });
-});
diff --git a/__tests__/commands/openapi/index.test.ts b/__tests__/commands/openapi/index.test.ts
deleted file mode 100644
index d013e4aa7..000000000
--- a/__tests__/commands/openapi/index.test.ts
+++ /dev/null
@@ -1,1441 +0,0 @@
-/* eslint-disable no-console */
-
-import fs from 'node:fs';
-
-import chalk from 'chalk';
-import nock from 'nock';
-import prompts from 'prompts';
-import { describe, beforeAll, beforeEach, afterEach, it, expect, vi, type MockInstance } from 'vitest';
-
-import Command from '../../../src/commands/openapi/index.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import config from '../../../src/lib/config.js';
-import petstoreWeird from '../../__fixtures__/petstore-simple-weird-version.json' with { type: 'json' };
-import { getAPIv1Mock, getAPIv1MockWithVersionHeader } from '../../helpers/get-api-mock.js';
-import { after, before } from '../../helpers/get-gha-setup.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-import { after as afterGHAEnv, before as beforeGHAEnv } from '../../helpers/setup-gha-env.js';
-
-let consoleInfoSpy: MockInstance;
-let consoleWarnSpy: MockInstance;
-
-const key = 'API_KEY';
-const id = '5aa0409b7cf527a93bfb44df';
-const version = '1.0.0';
-const exampleRefLocation = `${config.host}/project/example-project/1.0.1/refs/ex`;
-const successfulMessageBase = (specPath, specType) => [
- '',
- `\t${chalk.green(exampleRefLocation)}`,
- '',
- `To update your ${specType} definition, run the following:`,
- '',
- `\t${chalk.green(`rdme openapi ${specPath} --key= --id=1`)}`,
-];
-const successfulUpload = (specPath, specType = 'OpenAPI') =>
- [
- `You've successfully uploaded a new ${specType} file to your ReadMe project!`,
- ...successfulMessageBase(specPath, specType),
- ].join('\n');
-
-const successfulUpdate = (specPath, specType = 'OpenAPI') =>
- [
- `You've successfully updated an existing ${specType} file on your ReadMe project!`,
- ...successfulMessageBase(specPath, specType),
- ].join('\n');
-
-const getCommandOutput = () => {
- return [consoleWarnSpy.mock.calls.join('\n\n'), consoleInfoSpy.mock.calls.join('\n\n')].filter(Boolean).join('\n\n');
-};
-
-const getRandomRegistryId = () => Math.random().toString(36).substring(2);
-
-describe('rdme openapi', () => {
- let run: (args?: string[]) => Promise;
- let testWorkingDir: string;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- beforeEach(() => {
- consoleInfoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
- consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
- testWorkingDir = process.cwd();
- });
-
- afterEach(() => {
- consoleInfoSpy.mockRestore();
- consoleWarnSpy.mockRestore();
-
- process.chdir(testWorkingDir);
-
- nock.cleanAll();
- });
-
- describe('upload', () => {
- it.each([
- ['Swagger 2.0', 'json', '2.0', 'Swagger'],
- ['Swagger 2.0', 'yaml', '2.0', 'Swagger'],
- ['OpenAPI 3.0', 'json', '3.0', 'OpenAPI'],
- ['OpenAPI 3.0', 'yaml', '3.0', 'OpenAPI'],
- ['OpenAPI 3.1', 'json', '3.1', 'OpenAPI'],
- ['OpenAPI 3.1', 'yaml', '3.1', 'OpenAPI'],
-
- // Postman collections get automatically converted to OpenAPI 3.0 by `oas-normalize`.
- ['Postman', 'json', '3.0', 'Postman'],
- ['Postman', 'yaml', '3.0', 'Postman'],
- ])('should support uploading a %s definition (format: %s)', async (_, format, specVersion, type) => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: specVersion } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- let spec;
- if (type === 'Postman') {
- spec = require.resolve(`../../__fixtures__/postman/petstore.collection.${format}`);
- } else {
- spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore.${format}`);
- }
-
- await expect(run(['--key', key, '--version', version, spec])).resolves.toBe(successfulUpload(spec, type));
-
- expect(console.info).toHaveBeenCalledTimes(0);
-
- postMock.done();
- return mock.done();
- });
-
- it('should create a new spec via prompts', async () => {
- prompts.inject(['create']);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [{ _id: 'spec1', title: 'spec1_title' }])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, '--version', version, spec])).resolves.toBe(successfulUpload(spec));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should create a new spec via `--create` flag', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, '--version', version, spec, '--create'])).resolves.toBe(successfulUpload(spec));
-
- postMock.done();
- return mock.done();
- });
-
- it('should create a new spec via `--create` flag and ignore `--id`', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }])
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, '--id', 'some-id', spec, '--create'])).resolves.toBe(successfulUpload(spec));
-
- expect(console.warn).toHaveBeenCalledTimes(1);
- expect(console.info).toHaveBeenCalledTimes(0);
-
- const output = getCommandOutput();
-
- expect(output).toMatch(/the `--id` parameter will be ignored/i);
-
- postMock.done();
- return mock.done();
- });
-
- it('should bundle and upload the expected content', async () => {
- let requestBody;
- const registryUUID = getRandomRegistryId();
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, '--version', version, spec])).resolves.toBe(successfulUpload(spec));
-
- expect(console.info).toHaveBeenCalledTimes(0);
-
- expect(requestBody).toMatchSnapshot();
-
- postMock.done();
- return mock.done();
- });
-
- it('should update title, bundle and upload the expected content', async () => {
- let requestBody;
- const registryUUID = getRandomRegistryId();
- const title = 'some alternative title';
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, '--version', version, spec, '--title', title])).resolves.toBe(
- successfulUpload(spec),
- );
-
- expect(console.info).toHaveBeenCalledTimes(0);
-
- expect(requestBody).toMatchSnapshot();
-
- postMock.done();
- return mock.done();
- });
-
- it('should upload the expected content and return raw output', async () => {
- let requestBody;
- const registryUUID = getRandomRegistryId();
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, '--version', version, spec, '--raw'])).resolves.toMatchSnapshot();
-
- postMock.done();
- return mock.done();
- });
- });
-
- describe('updates / resyncs', () => {
- it.each([
- ['Swagger 2.0', 'json', '2.0', 'Swagger'],
- ['Swagger 2.0', 'yaml', '2.0', 'Swagger'],
- ['OpenAPI 3.0', 'json', '3.0', 'OpenAPI'],
- ['OpenAPI 3.0', 'yaml', '3.0', 'OpenAPI'],
- ['OpenAPI 3.1', 'json', '3.1', 'OpenAPI'],
- ['OpenAPI 3.1', 'yaml', '3.1', 'OpenAPI'],
- ])('should support updating a %s definition (format: %s)', async (_, format, specVersion, type) => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: specVersion } });
-
- const putMock = getAPIv1MockWithVersionHeader(version)
- .put(`/api/v1/api-specification/${id}`, { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore.${format}`);
-
- await expect(run(['--key', key, '--id', id, spec, '--version', version])).resolves.toBe(
- successfulUpdate(spec, type),
- );
-
- putMock.done();
- return mock.done();
- });
-
- it('should return warning if providing `id` and `version`', async () => {
- expect.assertions(4);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const putMock = getAPIv1MockWithVersionHeader(version)
- .put(`/api/v1/api-specification/${id}`, { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = require.resolve('@readme/oas-examples/3.1/json/petstore.json');
-
- await expect(run(['--key', key, '--id', id, spec, '--version', version])).resolves.toBe(successfulUpdate(spec));
-
- expect(console.warn).toHaveBeenCalledTimes(1);
- expect(console.info).toHaveBeenCalledTimes(0);
-
- const output = getCommandOutput();
-
- expect(output).toMatch(/the `--version` option will be ignored/i);
-
- putMock.done();
- return mock.done();
- });
-
- it('should update a spec via prompts', async () => {
- prompts.inject(['update', 'spec2']);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [
- { _id: 'spec1', title: 'spec1_title' },
- { _id: 'spec2', title: 'spec2_title' },
- ])
- .put('/api/v1/api-specification/spec2', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, spec, '--version', version])).resolves.toBe(successfulUpdate(spec));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should discover and upload an API definition if none is provided', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => {
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = 'petstore.json';
-
- await expect(
- run(['--key', key, '--version', version, '--workingDirectory', './__tests__/__fixtures__/relative-ref-oas']),
- ).resolves.toBe(successfulUpload(spec));
-
- expect(console.info).toHaveBeenCalledTimes(1);
-
- const output = getCommandOutput();
- expect(output).toBe(chalk.yellow(`βΉοΈ We found ${spec} and are attempting to upload it.`));
-
- postMock.done();
- return mock.done();
- });
-
- it('should use specified working directory and upload the expected content', async () => {
- let requestBody;
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = 'petstore.json';
-
- await expect(
- run([
- spec,
- '--key',
- key,
- '--version',
- version,
- '--workingDirectory',
- './__tests__/__fixtures__/relative-ref-oas',
- ]),
- ).resolves.toBe(successfulUpload(spec));
-
- expect(console.info).toHaveBeenCalledTimes(0);
-
- expect(requestBody).toMatchSnapshot();
-
- postMock.done();
- return mock.done();
- });
-
- it('should return spec update info for dry run', async () => {
- prompts.inject(['update', 'spec2']);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [
- { _id: 'spec1', title: 'spec1_title' },
- { _id: 'spec2', title: 'spec2_title' },
- ]);
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, spec, '--version', version, '--dryRun'])).resolves.toMatch(
- `dry run! The API Definition located at ${spec} will update this API Definition ID: spec2`,
- );
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should return spec create info for dry run (with working directory)', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => {
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- await expect(
- run([
- '--key',
- key,
- '--version',
- version,
- '--workingDirectory',
- './__tests__/__fixtures__/relative-ref-oas',
- '--dryRun',
- ]),
- ).resolves.toMatch(
- 'π dry run! The API Definition located at petstore.json will be created for this project version: 1.0.0',
- );
-
- const output = getCommandOutput();
- expect(output).toMatch(
- chalk.yellow('π dry run option detected! No API definitions will be created or updated in ReadMe.'),
- );
-
- return mock.done();
- });
-
- describe('--update', () => {
- it("should update a spec file without prompts if providing `update` and it's the one spec available", async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [{ _id: 'spec1', title: 'spec1_title' }])
- .put('/api/v1/api-specification/spec1', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, spec, '--version', version, '--update'])).resolves.toBe(successfulUpdate(spec));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should error if providing `update` and there are multiple specs available', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [
- { _id: 'spec1', title: 'spec1_title' },
- { _id: 'spec2', title: 'spec2_title' },
- ]);
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, spec, '--version', version, '--update'])).rejects.toStrictEqual(
- new Error(
- "The `--update` option cannot be used when there's more than one API definition available (found 2).",
- ),
- );
- return mock.done();
- });
-
- it('should warn if providing both `update` and `id`', async () => {
- expect.assertions(5);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .put('/api/v1/api-specification/spec1', { registryUUID })
- .basicAuth({ user: key })
- .reply(function (uri, rBody, cb) {
- expect(this.req.headers['x-readme-version']).toBeUndefined();
- return cb(null, [201, { _id: 1 }, { location: exampleRefLocation }]);
- });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, spec, '--id', 'spec1', '--update'])).resolves.toBe(successfulUpdate(spec));
-
- expect(console.warn).toHaveBeenCalledTimes(1);
- expect(console.info).toHaveBeenCalledTimes(0);
-
- const output = getCommandOutput();
- expect(output).toMatch(/the `--update` parameter will be ignored./);
- return mock.done();
- });
- });
-
- it.todo('should paginate to next and previous pages of specs');
- });
-
- describe('versioning', () => {
- it('should use version from version param properly', async () => {
- expect.assertions(2);
- let requestBody = '';
- const registryUUID = getRandomRegistryId();
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(function (uri, rBody, cb) {
- expect(this.req.headers['x-readme-version']).toBe(version);
- return cb(null, [201, { _id: 1 }, { location: exampleRefLocation }]);
- });
-
- const spec = './__tests__/__fixtures__/petstore-simple-weird-version.json';
-
- await expect(run(['--key', key, '--version', version, spec])).resolves.toBe(successfulUpload(spec));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should use version from spec file properly', async () => {
- expect.assertions(2);
- const specVersion = '1.2.3';
- let requestBody = '';
- const registryUUID = getRandomRegistryId();
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${specVersion}`)
- .basicAuth({ user: key })
- .reply(200, { version: specVersion })
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(specVersion)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(function (uri, rBody, cb) {
- expect(this.req.headers['x-readme-version']).toBe(specVersion);
- return cb(null, [201, { _id: 1 }, { location: exampleRefLocation }]);
- });
-
- const spec = './__tests__/__fixtures__/petstore-simple-weird-version.json';
-
- await expect(run(['--key', key, spec, '--version', version, '--useSpecVersion'])).resolves.toBe(
- successfulUpload(spec),
- );
-
- mockWithHeader.done();
- return mock.done();
- });
-
- describe('CI version handling', () => {
- beforeEach(() => {
- process.env.TEST_RDME_CI = 'true';
- });
-
- afterEach(() => {
- delete process.env.TEST_RDME_CI;
- });
-
- it('should omit version header in CI environment', async () => {
- expect.assertions(2);
- let requestBody = '';
- const registryUUID = getRandomRegistryId();
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => {
- requestBody = body.substring(body.indexOf('{'), body.lastIndexOf('}') + 1);
- requestBody = JSON.parse(requestBody);
-
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(function (uri, rBody, cb) {
- expect(this.req.headers['x-readme-version']).toBeUndefined();
- return cb(null, [201, { _id: 1 }, { location: exampleRefLocation }]);
- });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run(['--key', key, spec])).resolves.toBe(successfulUpload(spec));
-
- return mock.done();
- });
- });
-
- it('should error if version flag sent to API returns a 404', async () => {
- const invalidVersion = 'v1000';
-
- const errorObject = {
- error: 'VERSION_NOTFOUND',
- message: `The version you specified (${invalidVersion}) doesn't match any of the existing versions (1.0) in ReadMe.`,
- suggestion:
- 'You can pass the version in via the `x-readme-version` header. If you want to create a new version, do so in the Versions section inside ReadMe. Note that the version in the URL is our API version, not the version of your docs.',
- docs: 'https://docs.readme.com/logs/xx-xx-xx',
- help: "If you need help, email support@readme.io and include the following link to your API log: 'https://docs.readme.com/logs/xx-xx-xx'.",
- poem: [
- 'We looked high and low,',
- 'Searched up, down and around.',
- "You'll have to give it another go,",
- `Because version ${invalidVersion}'s not found!`,
- ],
- };
-
- const mock = getAPIv1Mock().get(`/api/v1/version/${invalidVersion}`).reply(404, errorObject);
-
- await expect(
- run([
- '--key',
- key,
- require.resolve('@readme/oas-examples/3.1/json/petstore.json'),
- '--version',
- invalidVersion,
- ]),
- ).rejects.toStrictEqual(new APIv1Error(errorObject));
-
- return mock.done();
- });
-
- it('should request a version list if version is not found', async () => {
- const selectedVersion = '1.0.1';
- prompts.inject([selectedVersion]);
-
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version: '1.0.0' }, { version: '1.0.1' }])
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(selectedVersion)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = require.resolve('@readme/oas-examples/2.0/json/petstore.json');
-
- await expect(run(['--key', key, spec])).resolves.toBe(successfulUpload(spec, 'Swagger'));
-
- mockWithHeader.done();
- return mock.done();
- });
- });
-
- describe('error handling', () => {
- it('should error if `--create` and `--update` flags are passed simultaneously', () => {
- return expect(run(['--key', key, '--create', '--update'])).rejects.toThrow(
- '--update=true cannot also be provided when using --create',
- );
- });
-
- it('should error if invalid API key is sent and version list does not load', async () => {
- const errorObject = {
- error: 'APIKEY_NOTFOUND',
- message: "We couldn't find your API key.",
- suggestion:
- "The API key you passed in (API_KEY) doesn't match any keys we have in our system. API keys must be passed in as the username part of basic auth. You can get your API key in Configuration > API Key, or in the docs.",
- docs: 'https://docs.readme.com/logs/xx-xx-xx',
- help: "If you need help, email support@readme.io and include the following link to your API log: 'https://docs.readme.com/logs/xx-xx-xx'.",
- poem: [
- 'The ancient gatekeeper declares:',
- "'To pass, reveal your API key.'",
- "'API_KEY', you start to ramble",
- 'Oops, you remembered it poorly!',
- ],
- };
-
- const mock = getAPIv1Mock().get('/api/v1/version').reply(401, errorObject);
-
- await expect(
- run([require.resolve('@readme/oas-examples/3.1/json/petstore.json'), '--key', 'key']),
- ).rejects.toStrictEqual(new APIv1Error(errorObject));
-
- return mock.done();
- });
-
- it('should throw an error if an invalid OpenAPI 3.0 definition is supplied', () => {
- return expect(
- run(['./__tests__/__fixtures__/invalid-oas.json', '--key', key, '--id', id, '--version', version]),
- ).rejects.toMatchSnapshot();
- });
-
- it('should throw an error if an invalid OpenAPI 3.1 definition is supplied', () => {
- return expect(
- run(['./__tests__/__fixtures__/invalid-oas-3.1.json', '--key', key, '--id', id, '--version', version]),
- ).rejects.toMatchSnapshot();
- });
-
- it('should throw an error if an invalid ref is supplied', () => {
- return expect(
- run(['./__tests__/__fixtures__/invalid-ref-oas/petstore.json', '--key', key, '--id', id, '--version', version]),
- ).rejects.toMatchSnapshot();
- });
-
- it('should throw an error if an invalid Swagger definition is supplied (create)', async () => {
- const errorObject = {
- error: 'INTERNAL_ERROR',
- message: 'Unknown error (README VALIDATION ERROR "x-samples-languages" must be of type "Array")',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(400, errorObject);
-
- await expect(
- run(['./__tests__/__fixtures__/swagger-with-invalid-extensions.json', '--key', key, '--version', version]),
- ).rejects.toStrictEqual(new APIv1Error(errorObject));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should throw an error if an invalid Swagger definition is supplied (update)', async () => {
- const errorObject = {
- error: 'INTERNAL_ERROR',
- message: 'Unknown error (README VALIDATION ERROR "x-samples-languages" must be of type "Array")',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const putMock = getAPIv1MockWithVersionHeader(version)
- .put(`/api/v1/api-specification/${id}`, { registryUUID })
- .basicAuth({ user: key })
- .reply(400, errorObject);
-
- await expect(
- run([
- './__tests__/__fixtures__/swagger-with-invalid-extensions.json',
- '--key',
- key,
- '--id',
- id,
- '--version',
- version,
- ]),
- ).rejects.toStrictEqual(new APIv1Error(errorObject));
-
- putMock.done();
- return mock.done();
- });
-
- it('should throw an error if registry upload fails', async () => {
- const errorObject = {
- error: 'INTERNAL_ERROR',
- message: 'Unknown error (Registry is offline? lol idk)',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(400, errorObject);
-
- await expect(
- run(['./__tests__/__fixtures__/swagger-with-invalid-extensions.json', '--key', key, '--version', version]),
- ).rejects.toStrictEqual(new APIv1Error(errorObject));
-
- return mock.done();
- });
-
- it('should error if API errors', async () => {
- const errorObject = {
- error: 'SPEC_VERSION_NOTFOUND',
- message:
- "The version you specified ({version}) doesn't match any of the existing versions ({versions_list}) in ReadMe.",
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(400, errorObject);
-
- await expect(
- run([require.resolve('@readme/oas-examples/2.0/json/petstore.json'), '--key', key, '--version', version]),
- ).rejects.toStrictEqual(new APIv1Error(errorObject));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should error if API errors (generic upload error)', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(400, 'some non-JSON upload error');
-
- await expect(
- run([require.resolve('@readme/oas-examples/2.0/json/petstore.json'), '--key', key, '--version', version]),
- ).rejects.toStrictEqual(
- new Error(
- 'Yikes, something went wrong! Please try uploading your spec again and if the problem persists, get in touch with our support team at support@readme.io.',
- ),
- );
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should error if API errors (request timeout)', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version: '1.0.0' })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(500, 'Application Error');
-
- await expect(
- run([require.resolve('@readme/oas-examples/2.0/json/petstore.json'), '--key', key, '--version', version]),
- ).rejects.toStrictEqual(
- new Error(
- "We're sorry, your upload request timed out. Please try again or split your file up into smaller chunks.",
- ),
- );
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should error if no file was provided or able to be discovered', () => {
- return expect(run(['--key', key, '--version', version, '--workingDirectory', 'bin'])).rejects.toStrictEqual(
- new Error(
- "We couldn't find an OpenAPI or Swagger definition.\n\nPlease specify the path to your definition with `rdme openapi ./path/to/api/definition`.",
- ),
- );
- });
- });
-
- describe('GHA onboarding E2E tests', () => {
- let yamlOutput;
-
- beforeEach(() => {
- before((fileName, data) => {
- yamlOutput = data;
- });
- });
-
- afterEach(() => {
- after();
- });
-
- it('should create GHA workflow (create spec)', async () => {
- expect.assertions(6);
- const yamlFileName = 'openapi-file';
- prompts.inject(['create', true, 'openapi-branch', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [{ _id: 'spec1', title: 'spec1_title' }])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', version])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${yamlFileName}.yml`, expect.any(String));
- expect(console.info).toHaveBeenCalledTimes(2);
- const output = getCommandOutput();
- expect(output).toMatch("Looks like you're running this command in a GitHub Repository!");
- expect(output).toMatch('successfully uploaded a new OpenAPI file to your ReadMe project');
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should create GHA workflow (--github flag enabled)', async () => {
- expect.assertions(6);
- const yamlFileName = 'openapi-file-github-flag';
- prompts.inject(['create', 'openapi-branch-github-flag', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [{ _id: 'spec1', title: 'spec1_title' }])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', version, '--github'])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${yamlFileName}.yml`, expect.any(String));
- expect(console.info).toHaveBeenCalledTimes(2);
- const output = getCommandOutput();
- expect(output).toMatch("Let's get you set up with GitHub Actions!");
- expect(output).toMatch('successfully uploaded a new OpenAPI file to your ReadMe project');
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should create GHA workflow (update spec via prompt)', async () => {
- expect.assertions(3);
- const yamlFileName = 'openapi-file-update-prompt';
- prompts.inject(['update', 'spec2', true, 'openapi-branch-update-prompt', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [
- { _id: 'spec1', title: 'spec1_title' },
- { _id: 'spec2', title: 'spec2_title' },
- ])
- .put('/api/v1/api-specification/spec2', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 'spec2' }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', version])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${yamlFileName}.yml`, expect.any(String));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should create GHA workflow (--create flag enabled)', async () => {
- expect.assertions(3);
- const yamlFileName = 'openapi-file-create-flag';
- const altVersion = '1.0.1';
- prompts.inject([true, 'openapi-branch-create-flag', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${altVersion}`)
- .basicAuth({ user: key })
- .reply(200, { version: altVersion })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(altVersion)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', altVersion, '--create'])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${yamlFileName}.yml`, expect.any(String));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should create GHA workflow (--create flag enabled with ignored id opt)', async () => {
- expect.assertions(3);
- const yamlFileName = 'openapi-file-create-flag-id-opt';
- prompts.inject([version, true, 'openapi-branch-create-flag-id-opt', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }, { version: '1.1.0' }])
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--id', 'some-id', '--create'])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${yamlFileName}.yml`, expect.any(String));
-
- postMock.done();
- return mock.done();
- });
-
- it('should create GHA workflow (--update flag enabled)', async () => {
- expect.assertions(3);
- const yamlFileName = 'openapi-file-update-flag';
- prompts.inject([true, 'openapi-branch-update-flag', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [{ _id: 'spec1', title: 'spec1_title' }])
- .put('/api/v1/api-specification/spec1', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', version, '--update'])).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledWith(`.github/workflows/${yamlFileName}.yml`, expect.any(String));
-
- mockWithHeader.done();
- return mock.done();
- });
-
- it('should create GHA workflow (including workingDirectory)', async () => {
- const yamlFileName = 'openapi-file-workingdirectory';
- prompts.inject([true, 'openapi-branch-workingdirectory', yamlFileName]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => {
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1MockWithVersionHeader(version)
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = 'petstore.json';
-
- await expect(
- run([
- spec,
- '--key',
- key,
- '--version',
- version,
- '--workingDirectory',
- './__tests__/__fixtures__/relative-ref-oas',
- ]),
- ).resolves.toMatchSnapshot();
-
- expect(yamlOutput).toMatchSnapshot();
- expect(fs.writeFileSync).toHaveBeenCalledTimes(2);
- expect(fs.writeFileSync).toHaveBeenNthCalledWith(2, `.github/workflows/${yamlFileName}.yml`, expect.any(String));
-
- postMock.done();
- return mock.done();
- });
-
- it('should reject if user says no to creating GHA workflow', async () => {
- prompts.inject(['create', false]);
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } });
-
- const mockWithHeader = getAPIv1MockWithVersionHeader(version)
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, [{ _id: 'spec1', title: 'spec1_title' }])
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', version])).rejects.toStrictEqual(
- new Error(
- 'GitHub Actions workflow creation cancelled. If you ever change your mind, you can run this command again with the `--github` flag.',
- ),
- );
-
- mockWithHeader.done();
- return mock.done();
- });
- });
-
- describe('command execution in GitHub Actions runner', () => {
- beforeEach(() => {
- beforeGHAEnv();
- });
-
- afterEach(afterGHAEnv);
-
- it('should error out if multiple possible spec matches were found', () => {
- return expect(run(['--key', key, '--version', version])).rejects.toStrictEqual(
- new Error('Multiple API definitions found in current directory. Please specify file.'),
- );
- });
-
- it('should send proper headers in GitHub Actions CI for local spec file', async () => {
- const registryUUID = getRandomRegistryId();
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID });
-
- const putMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/ref-oas/petstore.json',
- 'x-readme-version': version,
- })
- .put(`/api/v1/api-specification/${id}`, { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = './__tests__/__fixtures__/ref-oas/petstore.json';
-
- await expect(run([spec, '--key', key, '--version', version, '--id', id])).resolves.toBe(successfulUpdate(spec));
-
- putMock.done();
- return mock.done();
- });
-
- it('should send proper headers in GitHub Actions CI for spec hosted at URL', async () => {
- const registryUUID = getRandomRegistryId();
- const spec = 'https://example.com/openapi.json';
-
- const mock = getAPIv1Mock()
- .post('/api/v1/api-registry', body => body.match('form-data; name="spec"'))
- .reply(201, { registryUUID });
-
- const exampleMock = nock('https://example.com').get('/openapi.json').reply(200, petstoreWeird);
-
- const putMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url': spec,
- 'x-readme-version': version,
- })
- .put(`/api/v1/api-specification/${id}`, { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- await expect(run([spec, '--key', key, '--version', version, '--id', id])).resolves.toBe(successfulUpdate(spec));
-
- putMock.done();
- exampleMock.done();
- return mock.done();
- });
-
- it('should contain request header with correct URL with working directory', async () => {
- const registryUUID = getRandomRegistryId();
- const mock = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .post('/api/v1/api-registry', body => {
- return body.match('form-data; name="spec"');
- })
- .reply(201, { registryUUID, spec: { openapi: '3.0.0' } })
- .get('/api/v1/api-specification')
- .basicAuth({ user: key })
- .reply(200, []);
-
- const postMock = getAPIv1Mock({
- 'x-rdme-ci': 'GitHub Actions (test)',
- 'x-readme-source': 'cli-gh',
- 'x-readme-source-url':
- 'https://github.com/octocat/Hello-World/blob/ffac537e6cbbf934b08745a378932722df287a53/__tests__/__fixtures__/relative-ref-oas/petstore.json',
- 'x-readme-version': version,
- })
- .post('/api/v1/api-specification', { registryUUID })
- .basicAuth({ user: key })
- .reply(201, { _id: 1 }, { location: exampleRefLocation });
-
- const spec = 'petstore.json';
-
- await expect(
- run([
- spec,
- '--key',
- key,
- '--version',
- version,
- '--workingDirectory',
- './__tests__/__fixtures__/relative-ref-oas',
- ]),
- ).resolves.toBe(successfulUpload(spec));
-
- after();
-
- postMock.done();
- return mock.done();
- });
- });
-});
diff --git a/__tests__/commands/openapi/upload.test.ts b/__tests__/commands/openapi/upload.test.ts
new file mode 100644
index 000000000..7e405999e
--- /dev/null
+++ b/__tests__/commands/openapi/upload.test.ts
@@ -0,0 +1,409 @@
+import nock from 'nock';
+import prompts from 'prompts';
+import slugify from 'slugify';
+import { describe, beforeAll, beforeEach, afterEach, it, expect } from 'vitest';
+
+import Command from '../../../src/commands/openapi/upload.js';
+import petstore from '../../__fixtures__/petstore-simple-weird-version.json' with { type: 'json' };
+import { getAPIv2Mock, getAPIv2MockForGHA } from '../../helpers/get-api-mock.js';
+import { runCommand, type OclifOutput } from '../../helpers/oclif.js';
+import { after, before } from '../../helpers/setup-gha-env.js';
+
+const key = 'rdme_123';
+const version = '1.0.0';
+const filename = '__tests__/__fixtures__/petstore-simple-weird-version.json';
+const fileUrl = 'https://example.com/openapi.json';
+const slugifiedFilename = slugify.default(filename);
+
+describe('rdme openapi upload', () => {
+ let run: (args?: string[]) => OclifOutput;
+
+ beforeAll(() => {
+ nock.disableNetConnect();
+ run = runCommand(Command);
+ });
+
+ afterEach(() => {
+ nock.cleanAll();
+ });
+
+ describe('flag error handling', () => {
+ it('should throw if an error if both `--version` and `--useSpecVersion` flags are passed', async () => {
+ const result = await run(['--useSpecVersion', '--version', version, filename, '--key', key]);
+ expect(result.error.message).toContain('--version=1.0.0 cannot also be provided when using --useSpecVersion');
+ });
+ });
+
+ describe('given that the API definition is a local file', () => {
+ it('should create a new API definition in ReadMe', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.stdout).toContain('was successfully created in ReadMe!');
+
+ mock.done();
+ });
+
+ it('should update an existing API definition in ReadMe', async () => {
+ prompts.inject([true]);
+
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [{ filename: slugifiedFilename }] })
+ .put(`/versions/1.0.0/apis/${slugifiedFilename}`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.stdout).toContain('was successfully updated in ReadMe!');
+
+ mock.done();
+ });
+
+ it('should handle upload failures', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'fail' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.error.message).toBe(
+ 'Your API definition upload failed with an unexpected error. Please get in touch with us at support@readme.io.',
+ );
+
+ mock.done();
+ });
+
+ describe('and the `--slug` flag is passed', () => {
+ it('should use the provided slug (no file extension) as the filename', async () => {
+ const customSlug = 'custom-slug';
+ const customSlugWithExtension = `${customSlug}.json`;
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${customSlugWithExtension}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/${customSlugWithExtension}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key, '--slug', customSlug]);
+ expect(result.stdout).toContain(
+ `Your API definition (${customSlugWithExtension}) was successfully created in ReadMe!`,
+ );
+
+ mock.done();
+ });
+
+ it('should use the provided slug (includes file extension) as the filename', async () => {
+ const customSlug = 'custom-slug.json';
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body => body.match(`form-data; name="schema"; filename="${customSlug}"`))
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/${customSlug}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key, '--slug', customSlug]);
+ expect(result.stdout).toContain(`Your API definition (${customSlug}) was successfully created in ReadMe!`);
+
+ mock.done();
+ });
+
+ it('should handle a slug with an invalid file extension', async () => {
+ const customSlug = 'custom-slug.yikes';
+
+ const result = await run(['--version', version, filename, '--key', key, '--slug', customSlug]);
+ expect(result.error.message).toBe(
+ 'Please provide a valid file extension that matches the extension on the file you provided. Must be `.json`, `.yaml`, or `.yml`.',
+ );
+ });
+
+ it('should handle a slug with a valid but mismatching file extension', async () => {
+ const customSlug = 'custom-slug.yml';
+
+ const result = await run(['--version', version, filename, '--key', key, '--slug', customSlug]);
+ expect(result.error.message).toBe(
+ 'Please provide a valid file extension that matches the extension on the file you provided. Must be `.json`, `.yaml`, or `.yml`.',
+ );
+ });
+ });
+
+ describe('and the upload status initially is a pending state', () => {
+ it('should poll the API until the upload is complete', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'pending' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ })
+ .get(`/versions/${version}/apis/${slugifiedFilename}`)
+ .times(9)
+ .reply(200, {
+ data: {
+ upload: { status: 'pending' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ })
+ .get(`/versions/${version}/apis/${slugifiedFilename}`)
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.stdout).toContain('was successfully created in ReadMe!');
+
+ mock.done();
+ });
+
+ it('should poll the API and handle timeouts', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'pending' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ })
+ .get(`/versions/${version}/apis/${slugifiedFilename}`)
+ .times(10)
+ .reply(200, {
+ data: {
+ upload: { status: 'pending' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.error.message).toBe('Sorry, this upload timed out. Please try again later.');
+
+ mock.done();
+ });
+
+ it('should poll the API once and handle a failure state with a 4xx', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'pending' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ })
+ .get(`/versions/${version}/apis/${slugifiedFilename}`)
+ .reply(400);
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.error.message).toBe(
+ 'The ReadMe API responded with an unexpected error. Please try again and if this issue persists, get in touch with us at support@readme.io.',
+ );
+
+ mock.done();
+ });
+
+ it('should poll the API once and handle an unexpected state with a 2xx', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${version}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'pending' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ })
+ .get(`/versions/${version}/apis/${slugifiedFilename}`)
+ .reply(200, {
+ data: {
+ upload: { status: 'something-unexpected' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.error).toStrictEqual(
+ new Error(
+ 'Your API definition upload failed with an unexpected error. Please get in touch with us at support@readme.io.',
+ ),
+ );
+
+ mock.done();
+ });
+ });
+
+ describe('and the command is being run in a CI environment', () => {
+ beforeEach(before);
+
+ afterEach(after);
+
+ it('should overwrite an existing API definition without asking for confirmation', async () => {
+ const mock = getAPIv2MockForGHA({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [{ filename: slugifiedFilename }] })
+ .put(`/versions/1.0.0/apis/${slugifiedFilename}`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--version', version, filename, '--key', key]);
+ expect(result.stdout).toContain('was successfully updated in ReadMe!');
+
+ mock.done();
+ });
+ });
+
+ describe('given that the `--version` flag is not set', () => {
+ it('should default to the `stable` version', async () => {
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get('/versions/stable/apis')
+ .reply(200, { data: [] })
+ .post('/versions/stable/apis', body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/stable/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run([filename, '--key', key]);
+ expect(result.stdout).toContain('was successfully created in ReadMe!');
+
+ mock.done();
+ });
+
+ it('should use the version from the spec file if --`useSpecVersion` is passed', async () => {
+ const altVersion = '1.2.3';
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${altVersion}/apis`)
+ .reply(200, { data: [] })
+ .post(`/versions/${altVersion}/apis`, body =>
+ body.match(`form-data; name="schema"; filename="${slugifiedFilename}"`),
+ )
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${altVersion}/apis/${slugifiedFilename}`,
+ },
+ });
+
+ const result = await run(['--useSpecVersion', filename, '--key', key]);
+ expect(result.stdout).toContain('was successfully created in ReadMe!');
+
+ mock.done();
+ });
+ });
+ });
+
+ describe('given that the API definition is a URL', () => {
+ it('should create a new API definition in ReadMe', async () => {
+ const fileMock = nock('https://example.com').get('/openapi.json').reply(200, petstore);
+
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, {})
+ .post(`/versions/${version}/apis`, body => body.match(`form-data; name="url"\r\n\r\n${fileUrl}`))
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/openapi.json`,
+ },
+ });
+
+ const result = await run(['--version', version, fileUrl, '--key', key]);
+ expect(result.stdout).toContain('was successfully created in ReadMe!');
+
+ fileMock.done();
+ mock.done();
+ });
+
+ it('should update an existing API definition in ReadMe', async () => {
+ prompts.inject([true]);
+
+ const fileMock = nock('https://example.com').get('/openapi.json').reply(200, petstore);
+
+ const mock = getAPIv2Mock({ authorization: `Bearer ${key}` })
+ .get(`/versions/${version}/apis`)
+ .reply(200, { data: [{ filename: 'openapi.json' }] })
+ .put('/versions/1.0.0/apis/openapi.json', body => body.match(`form-data; name="url"\r\n\r\n${fileUrl}`))
+ .reply(200, {
+ data: {
+ upload: { status: 'done' },
+ uri: `/versions/${version}/apis/openapi.json`,
+ },
+ });
+
+ const result = await run(['--version', version, fileUrl, '--key', key]);
+ expect(result.stdout).toContain('was successfully updated in ReadMe!');
+
+ fileMock.done();
+ mock.done();
+ });
+
+ it('should handle issues fetching from the URL', async () => {
+ const fileMock = nock('https://example.com').get('/openapi.json').reply(400, {});
+
+ const result = await run(['--version', version, fileUrl, '--key', key]);
+ expect(result.error.message).toBe('Unknown file detected.');
+
+ fileMock.done();
+ });
+ });
+});
diff --git a/__tests__/commands/versions/create.test.ts b/__tests__/commands/versions/create.test.ts
deleted file mode 100644
index 014bad9ff..000000000
--- a/__tests__/commands/versions/create.test.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import nock from 'nock';
-import prompts from 'prompts';
-import { describe, beforeAll, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/versions/create.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme versions create', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => nock.cleanAll());
-
- it('should error if no version provided', () => {
- return expect(run(['--key', key])).rejects.toThrow('Missing 1 required arg:\nversion');
- });
-
- it('should error if invalid version provided', () => {
- return expect(run(['--key', key, 'test'])).rejects.toStrictEqual(
- new Error('Please specify a semantic version. See `rdme help versions create` for help.'),
- );
- });
-
- it('should create a specific version', async () => {
- prompts.inject([version, false, true, true, false]);
- const newVersion = '1.0.1';
-
- const mockRequest = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }, { version: '1.1.0' }])
- .post('/api/v1/version', {
- version: newVersion,
- is_stable: false,
- is_beta: true,
- from: '1.0.0',
- is_hidden: true,
- is_deprecated: false,
- })
- .basicAuth({ user: key })
- .reply(201, { version: newVersion });
-
- await expect(run(['--key', key, newVersion])).resolves.toBe(`Version ${newVersion} created successfully.`);
- mockRequest.done();
- });
-
- it('should create a specific version with options', async () => {
- const newVersion = '1.0.1';
-
- const mockRequest = getAPIv1Mock()
- .post('/api/v1/version', {
- version: newVersion,
- codename: 'test',
- from: '1.0.0',
- is_beta: false,
- is_deprecated: false,
- is_hidden: false,
- is_stable: false,
- })
- .basicAuth({ user: key })
- .reply(201, { version: newVersion });
-
- await expect(
- run([
- '--key',
- key,
- newVersion,
- '--fork',
- version,
- '--beta',
- 'false',
- '--deprecated',
- 'false',
- '--main',
- 'false',
- '--codename',
- 'test',
- '--hidden',
- 'false',
- ]),
- ).resolves.toBe(`Version ${newVersion} created successfully.`);
-
- mockRequest.done();
- });
-
- it('should create successfully a main version', async () => {
- const newVersion = '1.0.1';
-
- const mockRequest = getAPIv1Mock()
- .post('/api/v1/version', {
- version: newVersion,
- from: '1.0.0',
- is_beta: false,
- is_stable: true,
- })
- .basicAuth({ user: key })
- .reply(201, { version: newVersion });
-
- await expect(
- run([
- '--key',
- key,
- newVersion,
- '--fork',
- version,
- '--beta',
- 'false',
- '--main',
- 'true',
- '--hidden',
- 'true',
- '--deprecated',
- 'true',
- ]),
- ).resolves.toBe(`Version ${newVersion} created successfully.`);
-
- mockRequest.done();
- });
-
- it('should catch any post request errors', async () => {
- const errorResponse = {
- error: 'VERSION_EMPTY',
- message: 'You need to include an x-readme-version header',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const mockRequest = getAPIv1Mock().post('/api/v1/version').basicAuth({ user: key }).reply(400, errorResponse);
-
- await expect(run(['--key', key, version, '--fork', '0.0.5'])).rejects.toStrictEqual(new APIv1Error(errorResponse));
- mockRequest.done();
- });
-
- describe('bad flag values', () => {
- it('should throw if non-boolean `beta` flag is passed', () => {
- const newVersion = '1.0.1';
-
- return expect(run(['--key', key, newVersion, '--fork', version, '--beta', 'test'])).rejects.toThrow(
- 'Expected --beta=test to be one of: true, false',
- );
- });
-
- it('should throw if non-boolean `deprecated` flag is passed', () => {
- const newVersion = '1.0.1';
-
- return expect(run(['--key', key, newVersion, '--fork', version, '--deprecated', 'test'])).rejects.toThrow(
- 'Expected --deprecated=test to be one of: true, false',
- );
- });
-
- it('should throw if non-boolean `hidden` flag is passed', () => {
- const newVersion = '1.0.1';
-
- return expect(run(['--key', key, newVersion, '--fork', version, '--hidden', 'test'])).rejects.toThrow(
- 'Expected --hidden=test to be one of: true, false',
- );
- });
-
- it('should throw if non-boolean `main` flag is passed', () => {
- const newVersion = '1.0.1';
-
- return expect(run(['--key', key, newVersion, '--fork', version, '--main', 'test'])).rejects.toThrow(
- 'Expected --main=test to be one of: true, false',
- );
- });
- });
-});
diff --git a/__tests__/commands/versions/delete.test.ts b/__tests__/commands/versions/delete.test.ts
deleted file mode 100644
index a82b11efe..000000000
--- a/__tests__/commands/versions/delete.test.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import nock from 'nock';
-import { describe, beforeAll, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/versions/delete.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme versions delete', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => nock.cleanAll());
-
- it('should delete a specific version', async () => {
- const mockRequest = getAPIv1Mock()
- .delete(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { removed: true })
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, version])).resolves.toBe('Version 1.0.0 deleted successfully.');
- mockRequest.done();
- });
-
- it('should catch any request errors', async () => {
- const errorResponse = {
- error: 'VERSION_NOTFOUND',
- message:
- "The version you specified ({version}) doesn't match any of the existing versions ({versions_list}) in ReadMe.",
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const mockRequest = getAPIv1Mock()
- .delete(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(404, errorResponse)
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version });
-
- await expect(run(['--key', key, version])).rejects.toStrictEqual(new APIv1Error(errorResponse));
- mockRequest.done();
- });
-});
diff --git a/__tests__/commands/versions/index.test.ts b/__tests__/commands/versions/index.test.ts
deleted file mode 100644
index 7ba6650d7..000000000
--- a/__tests__/commands/versions/index.test.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { Version } from '../../../src/commands/versions/index.js';
-
-import nock from 'nock';
-import { describe, beforeAll, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/versions/index.js';
-import { getAPIv1Mock } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-const version2 = '2.0.0';
-
-const versionPayload: Version = {
- createdAt: '2019-06-17T22:39:56.462Z',
- is_deprecated: false,
- is_hidden: false,
- is_beta: false,
- is_stable: true,
- codename: '',
- version,
-};
-
-const version2Payload: Version = {
- createdAt: '2019-06-17T22:39:56.462Z',
- is_deprecated: false,
- is_hidden: false,
- is_beta: false,
- is_stable: true,
- codename: '',
- version: version2,
-};
-
-describe('rdme versions', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => nock.cleanAll());
-
- it('should make a request to get a list of existing versions', async () => {
- const mockRequest = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [versionPayload, version2Payload]);
-
- const output = await run(['--key', key]);
- expect(output).toStrictEqual(JSON.stringify([versionPayload, version2Payload], null, 2));
- mockRequest.done();
- });
-
- it('should get a specific version object if version flag provided', async () => {
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, versionPayload);
-
- const output = await run(['--key', key, '--version', version]);
- expect(output).toStrictEqual(JSON.stringify(versionPayload, null, 2));
- mockRequest.done();
- });
-});
diff --git a/__tests__/commands/versions/update.test.ts b/__tests__/commands/versions/update.test.ts
deleted file mode 100644
index 66734d9ab..000000000
--- a/__tests__/commands/versions/update.test.ts
+++ /dev/null
@@ -1,388 +0,0 @@
-import nock from 'nock';
-import prompts from 'prompts';
-import { describe, beforeAll, afterEach, it, expect } from 'vitest';
-
-import Command from '../../../src/commands/versions/update.js';
-import { APIv1Error } from '../../../src/lib/apiError.js';
-import { getAPIv1Mock } from '../../helpers/get-api-mock.js';
-import { runCommandAndReturnResult } from '../../helpers/oclif.js';
-
-const key = 'API_KEY';
-const version = '1.0.0';
-
-describe('rdme versions update', () => {
- let run: (args?: string[]) => Promise;
-
- beforeAll(() => {
- nock.disableNetConnect();
- run = runCommandAndReturnResult(Command);
- });
-
- afterEach(() => nock.cleanAll());
-
- it('should update a specific version object using prompts', async () => {
- const versionToChange = '1.1.0';
- prompts.inject([versionToChange, undefined, false, true, false, false]);
-
- const updatedVersionObject = {
- version: versionToChange,
- is_stable: false,
- is_beta: true,
- is_deprecated: false,
- is_hidden: false,
- };
-
- const mockRequest = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }, { version: versionToChange }])
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(run(['--key', key])).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it('should rename a specific version object using prompts', async () => {
- const versionToChange = '1.1.0';
- const renamedVersion = '1.1.0-update';
- prompts.inject([versionToChange, renamedVersion, false, true, false, false]);
-
- const updatedVersionObject = {
- version: renamedVersion,
- is_stable: false,
- is_beta: true,
- is_deprecated: false,
- is_hidden: false,
- };
-
- const mockRequest = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }, { version: versionToChange }])
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(run(['--key', key])).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it('should use subset of prompts when updating stable version', async () => {
- const versionToChange = '1.1.0';
- prompts.inject([versionToChange, undefined, true]);
-
- const updatedVersionObject = {
- version: versionToChange,
- is_beta: true,
- };
-
- const mockRequest = getAPIv1Mock()
- .get('/api/v1/version')
- .basicAuth({ user: key })
- .reply(200, [{ version }, { version: versionToChange, is_stable: true }])
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange, is_stable: true })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(run(['--key', key])).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it('should update a specific version object using flags', async () => {
- const versionToChange = '1.1.0';
- const renamedVersion = '1.1.0-update';
-
- const updatedVersionObject = {
- codename: 'updated-test',
- version: renamedVersion,
- is_beta: true,
- is_deprecated: true,
- is_hidden: false,
- is_stable: false,
- };
-
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(
- run([
- '--key',
- key,
- versionToChange,
- '--newVersion',
- renamedVersion,
- '--deprecated',
- 'true',
- '--beta',
- 'true',
- '--main',
- 'false',
- '--codename',
- 'updated-test',
- '--hidden',
- 'false',
- ]),
- ).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it("should update a specific version object using flags that contain the string 'false'", async () => {
- const versionToChange = '1.1.0';
- const renamedVersion = '1.1.0-update';
-
- const updatedVersionObject = {
- codename: 'updated-test',
- version: renamedVersion,
- is_beta: false,
- is_deprecated: false,
- is_hidden: true,
- is_stable: false,
- };
-
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(
- run([
- '--key',
- key,
- versionToChange,
- '--newVersion',
- renamedVersion,
- '--beta',
- 'false',
- '--deprecated',
- 'false',
- '--main',
- 'false',
- '--codename',
- 'updated-test',
- '--hidden',
- 'true',
- ]),
- ).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it("should update a specific version object using flags that contain the string 'false' and a prompt", async () => {
- const versionToChange = '1.1.0';
- const renamedVersion = '1.1.0-update';
- // prompt for beta flag
- prompts.inject([false]);
-
- const updatedVersionObject = {
- codename: 'updated-test',
- version: renamedVersion,
- is_beta: false,
- is_hidden: false,
- is_stable: false,
- };
-
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(
- run([
- '--key',
- key,
- versionToChange,
- '--newVersion',
- renamedVersion,
- '--main',
- 'false',
- '--codename',
- 'updated-test',
- '--hidden',
- 'false',
- ]),
- ).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it('should update a specific version object even if user bypasses prompt for new version name', async () => {
- const versionToChange = '1.1.0';
- // simulating user entering nothing for the prompt to enter a new version name
- prompts.inject(['']);
-
- const updatedVersionObject = {
- codename: 'updated-test',
- is_beta: false,
- is_hidden: false,
- is_stable: false,
- version: versionToChange,
- };
-
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(
- run([
- '--key',
- key,
- versionToChange,
- '--beta',
- 'false',
- '--main',
- 'false',
- '--codename',
- 'updated-test',
- '--hidden',
- 'false',
- ]),
- ).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it('should update a version to be the main one', async () => {
- const versionToChange = '1.1.0';
- const renamedVersion = '1.1.0-update';
-
- const updatedVersionObject = {
- version: renamedVersion,
- is_beta: false,
- is_stable: true,
- };
-
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .get(`/api/v1/version/${versionToChange}`)
- .basicAuth({ user: key })
- .reply(200, { version: versionToChange })
- .put(`/api/v1/version/${versionToChange}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(201, updatedVersionObject);
-
- await expect(
- run([
- '--key',
- key,
- versionToChange,
- '--newVersion',
- renamedVersion,
- '--deprecated',
- 'true',
- '--beta',
- 'false',
- '--main',
- 'true',
- '--hidden',
- 'true',
- ]),
- ).resolves.toBe(`Version ${versionToChange} updated successfully.`);
- mockRequest.done();
- });
-
- it('should catch any put request errors', async () => {
- const renamedVersion = '1.0.0-update';
-
- const updatedVersionObject = {
- version: renamedVersion,
- is_beta: true,
- is_deprecated: true,
- is_hidden: false,
- is_stable: false,
- };
-
- prompts.inject([renamedVersion, false, true, false, true]);
-
- const errorResponse = {
- error: 'VERSION_DUPLICATE',
- message: 'The version already exists.',
- suggestion: '...a suggestion to resolve the issue...',
- help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
- };
-
- const mockRequest = getAPIv1Mock()
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .get(`/api/v1/version/${version}`)
- .basicAuth({ user: key })
- .reply(200, { version })
- .put(`/api/v1/version/${version}`, updatedVersionObject)
- .basicAuth({ user: key })
- .reply(400, errorResponse);
-
- await expect(run(['--key', key, version])).rejects.toStrictEqual(new APIv1Error(errorResponse));
- mockRequest.done();
- });
-
- describe('bad flag values', () => {
- it('should throw if non-boolean `beta` flag is passed', () => {
- const versionToChange = '1.1.0';
-
- return expect(run(['--key', key, versionToChange, '--beta', 'hi'])).rejects.toThrow(
- 'Expected --beta=hi to be one of: true, false',
- );
- });
-
- it('should throw if non-boolean `deprecated` flag is passed', () => {
- const versionToChange = '1.1.0';
-
- return expect(run(['--key', key, versionToChange, '--deprecated', 'hi'])).rejects.toThrow(
- 'Expected --deprecated=hi to be one of: true, false',
- );
- });
-
- it('should throw if non-boolean `hidden` flag is passed', () => {
- const versionToChange = '1.1.0';
-
- return expect(run(['--key', key, versionToChange, '--hidden', 'hi'])).rejects.toThrow(
- 'Expected --hidden=hi to be one of: true, false',
- );
- });
-
- it('should throw if non-boolean `main` flag is passed', () => {
- const versionToChange = '1.1.0';
-
- return expect(run(['--key', key, versionToChange, '--main', 'hi'])).rejects.toThrow(
- 'Expected --main=hi to be one of: true, false',
- );
- });
- });
-});
diff --git a/__tests__/helpers/get-api-mock.ts b/__tests__/helpers/get-api-mock.ts
index c8fc83ff9..687ddabe5 100644
--- a/__tests__/helpers/get-api-mock.ts
+++ b/__tests__/helpers/get-api-mock.ts
@@ -3,12 +3,14 @@ import nock from 'nock';
import config from '../../src/lib/config.js';
import { getUserAgent } from '../../src/lib/readmeAPIFetch.js';
+import { mockVersion } from './oclif.js';
+
/**
* Nock wrapper for ReadMe API v1 that adds required
* `user-agent` request header so it gets properly picked up by nock.
*/
export function getAPIv1Mock(reqHeaders = {}) {
- return nock(config.host, {
+ return nock(config.host.v1, {
reqheaders: {
'User-Agent': getUserAgent(),
...reqHeaders,
@@ -16,8 +18,27 @@ export function getAPIv1Mock(reqHeaders = {}) {
});
}
-export function getAPIv1MockWithVersionHeader(v: string) {
- return getAPIv1Mock({
- 'x-readme-version': v,
+/**
+ * Nock wrapper for ReadMe API v2 that adds required
+ * `user-agent` request header so it gets properly picked up by nock.
+ */
+export function getAPIv2Mock(reqHeaders: nock.Options['reqheaders'] = {}) {
+ return nock(config.host.v2, {
+ reqheaders: {
+ 'User-Agent': ua => ua.startsWith(`rdme/${mockVersion}`),
+ 'x-readme-source': 'cli',
+ ...reqHeaders,
+ },
+ });
+}
+
+/**
+ * Variant of `getAPIv2Mock` for mocking a GitHub Actions environment.
+ */
+export function getAPIv2MockForGHA(reqHeaders: nock.Options['reqheaders'] = {}) {
+ return getAPIv2Mock({
+ 'User-Agent': ua => ua.startsWith(`rdme-github/${mockVersion}`),
+ 'x-readme-source': 'cli-gh',
+ ...reqHeaders,
});
}
diff --git a/__tests__/helpers/oclif.ts b/__tests__/helpers/oclif.ts
index a6ec356c4..9462dbed5 100644
--- a/__tests__/helpers/oclif.ts
+++ b/__tests__/helpers/oclif.ts
@@ -5,8 +5,12 @@ import path from 'node:path';
import { Config } from '@oclif/core';
import { captureOutput, runCommand as oclifRunCommand } from '@oclif/test';
+export type OclifOutput = ReturnType>;
+
const testNodeEnv = process.env.NODE_ENV;
+export const mockVersion = '7.0.0';
+
/**
* Used for setting up the oclif configuration for simulating commands in tests.
* This is a really barebones approach so we can continue using vitest + nock
@@ -21,7 +25,7 @@ export function setupOclifConfig() {
return Config.load({
root,
- version: '7.0.0',
+ version: mockVersion,
});
}
@@ -32,7 +36,7 @@ export function setupOclifConfig() {
*
* @example runCommand(LoginCommand)(['--email', 'owlbert@example.com', '--password', 'password'])
*/
-function runCommand(Command: T) {
+export function runCommand(Command: T) {
return async function runCommandAgainstArgs(args?: string[]) {
const oclifConfig = await setupOclifConfig();
// @ts-expect-error this is the pattern recommended by the @oclif/test docs.
diff --git a/__tests__/lib/__snapshots__/createGHA.test.ts.snap b/__tests__/lib/__snapshots__/createGHA.test.ts.snap
index fce4a5120..1937da436 100644
--- a/__tests__/lib/__snapshots__/createGHA.test.ts.snap
+++ b/__tests__/lib/__snapshots__/createGHA.test.ts.snap
@@ -164,416 +164,6 @@ jobs:
"
`;
-exports[`#createGHA > command inputs > 'custompages' ' (single)' > should run GHA creation workflow and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-custompages.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' ' (single)' > should run GHA creation workflow and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`some-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - some-branch
-
-jobs:
- rdme-custompages:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`custompages\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: custompages ./custompages/rdme.md --key=\${{ secrets.README_API_KEY }}
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' ' (single)' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-custompages-with-github-flag.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' ' (single)' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`another-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - another-branch
-
-jobs:
- rdme-custompages:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`custompages\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: custompages ./custompages/rdme.md --key=\${{ secrets.README_API_KEY }}
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' '' > should run GHA creation workflow and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-custompages.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' '' > should run GHA creation workflow and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`some-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - some-branch
-
-jobs:
- rdme-custompages:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`custompages\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: custompages ./custompages --key=\${{ secrets.README_API_KEY }}
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' '' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-custompages-with-github-flag.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'custompages' '' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`another-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - another-branch
-
-jobs:
- rdme-custompages:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`custompages\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: custompages ./custompages --key=\${{ secrets.README_API_KEY }}
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' ' (single)' > should run GHA creation workflow and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-docs.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' ' (single)' > should run GHA creation workflow and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`some-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - some-branch
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./docs/rdme.md --key=\${{ secrets.README_API_KEY }} --version=1.0.0
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' ' (single)' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-docs-with-github-flag.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' ' (single)' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`another-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - another-branch
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./docs/rdme.md --key=\${{ secrets.README_API_KEY }} --version=1.0.0
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' '' > should run GHA creation workflow and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-docs.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' '' > should run GHA creation workflow and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`some-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - some-branch
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./docs --key=\${{ secrets.README_API_KEY }} --version=1.0.0
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' '' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-docs-with-github-flag.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'docs' '' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`another-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - another-branch
-
-jobs:
- rdme-docs:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`docs\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: docs ./docs --key=\${{ secrets.README_API_KEY }} --version=1.0.0
-"
-`;
-
-exports[`#createGHA > command inputs > 'openapi' '' > should run GHA creation workflow and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-openapi.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'openapi' '' > should run GHA creation workflow and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`some-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - some-branch
-
-jobs:
- rdme-openapi:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`openapi\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: openapi petstore.json --key=\${{ secrets.README_API_KEY }} --id=spec_id
-"
-`;
-
-exports[`#createGHA > command inputs > 'openapi' '' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 1`] = `
-"
-Your GitHub Actions workflow file has been created! β¨
-
-Almost done! Just a couple more steps:
-1. Push your newly created file (.github/workflows/rdme-openapi-with-github-flag.yml) to GitHub π
-2. Create a GitHub secret called README_API_KEY and populate the value with your ReadMe API key (β’β’β’β’β’β’β’β’β’β’β’β’I_KEY) π
-
-π Check out GitHub's docs for more info on creating encrypted secrets (https://docs.github.com/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository)
-
-π¦ If you have any more questions, feel free to drop us a line! support@readme.io
-"
-`;
-
-exports[`#createGHA > command inputs > 'openapi' '' > should run GHA creation workflow with \`--github\` flag and messy file name and generate valid workflow file 2`] = `
-"# This GitHub Actions workflow was auto-generated by the \`rdme\` cli on 2022-01-01T00:00:00.000Z
-# You can view our full documentation here: https://docs.readme.com/docs/rdme
-name: ReadMe GitHub Action π¦
-
-on:
- push:
- branches:
- # This workflow will run every time you push code to the following branch: \`another-branch\`
- # Check out GitHub's docs for more info on configuring this:
- # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- - another-branch
-
-jobs:
- rdme-openapi:
- runs-on: ubuntu-latest
- steps:
- - name: Check out repo π
- uses: actions/checkout@v4
-
- - name: Run \`openapi\` command π
- uses: readmeio/rdme@v7
- with:
- rdme: openapi petstore.json --key=\${{ secrets.README_API_KEY }} --id=spec_id
-"
-`;
-
exports[`#createGHA > command inputs > 'openapi:validate' '' > should run GHA creation workflow and generate valid workflow file 1`] = `
"
Your GitHub Actions workflow file has been created! β¨
diff --git a/__tests__/lib/createGHA.test.ts b/__tests__/lib/createGHA.test.ts
index 943bb90f9..2f78f0954 100644
--- a/__tests__/lib/createGHA.test.ts
+++ b/__tests__/lib/createGHA.test.ts
@@ -58,14 +58,6 @@ describe('#createGHA', () => {
// `openapi:validate` is the ID we define in src/index.ts for backwards compatibility,
// hence we're using this command ID here
{ cmd: 'openapi:validate', opts: { spec: 'petstore.json' }, label: '' },
- { cmd: 'openapi', opts: { key, spec: 'petstore.json', id: 'spec_id' }, label: '' },
- { cmd: 'docs', opts: { key, path: './docs', version: '1.0.0' }, label: '' },
- {
- cmd: 'docs',
-
- label: ' (single)',
- opts: { key, path: './docs/rdme.md', version: '1.0.0' },
- },
{ cmd: 'changelogs', opts: { key, path: './changelogs' }, label: '' },
{
cmd: 'changelogs',
@@ -73,12 +65,6 @@ describe('#createGHA', () => {
label: ' (single)',
opts: { key, path: './changelogs/rdme.md' },
},
- { cmd: 'custompages', opts: { key, path: './custompages' }, label: '' },
- {
- cmd: 'custompages',
- label: ' (single)',
- opts: { key, path: './custompages/rdme.md' },
- },
])('$cmd $label', ({ cmd, opts }) => {
let CurrentCommand: Command.Class;
diff --git a/__tests__/lib/prompts.test.ts b/__tests__/lib/prompts.test.ts
deleted file mode 100644
index d1dba0a90..000000000
--- a/__tests__/lib/prompts.test.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import prompts from 'prompts';
-import { describe, it, expect } from 'vitest';
-
-import * as promptHandler from '../../src/lib/prompts.js';
-import promptTerminal from '../../src/lib/promptWrapper.js';
-
-const versionlist = [
- {
- version: '1',
- is_stable: true,
- },
- {
- version: '2',
- is_stable: false,
- },
-];
-
-const specList = [
- {
- _id: 'spec1',
- title: 'spec1_title',
- },
- {
- _id: 'spec2',
- title: 'spec2_title',
- },
-];
-
-const getSpecs = () => {
- return {
- body: [
- {
- _id: 'spec3',
- title: 'spec3_title',
- },
- ],
- } as unknown as Promise;
-};
-
-describe('prompt test bed', () => {
- describe('createOasPrompt()', () => {
- it('should return a create option if selected', async () => {
- prompts.inject(['create']);
-
- const answer = await promptTerminal(
- promptHandler.createOasPrompt(
- [
- {
- _id: '1234',
- title: 'buster',
- },
- ],
- {},
- 1,
- null,
- ),
- );
-
- expect(answer).toStrictEqual({ option: 'create' });
- });
-
- it('should return specId if user chooses to update file', async () => {
- prompts.inject(['update', 'spec1']);
-
- const parsedDocs = {
- next: {
- page: 2,
- url: '',
- },
- prev: {
- page: 1,
- url: '',
- },
- };
-
- const answer = await promptTerminal(promptHandler.createOasPrompt(specList, parsedDocs, 1, getSpecs));
-
- expect(answer).toStrictEqual({ option: 'spec1' });
- });
- });
-
- describe('versionPrompt()', () => {
- it('should allow user to choose a fork if flag is not passed (creating version)', async () => {
- prompts.inject(['1', true, true]);
-
- const answer = await promptTerminal(promptHandler.versionPrompt(versionlist));
- expect(answer).toStrictEqual({ from: '1', is_stable: true, is_beta: true });
- });
-
- it('should skip fork prompt if value passed (updating version)', async () => {
- prompts.inject(['1.2.1', false, true, true, false]);
-
- const answer = await promptTerminal(promptHandler.versionPrompt(versionlist, { is_stable: false }));
- expect(answer).toStrictEqual({
- newVersion: '1.2.1',
- is_stable: false,
- is_beta: true,
- is_hidden: true,
- is_deprecated: false,
- });
- });
- });
-});
diff --git a/documentation/commands/categories.md b/documentation/commands/categories.md
deleted file mode 100644
index 8779ff5ab..000000000
--- a/documentation/commands/categories.md
+++ /dev/null
@@ -1,84 +0,0 @@
-`rdme categories`
-=================
-
-List or create categories in your ReadMe developer hub.
-
-* [`rdme categories`](#rdme-categories)
-* [`rdme categories create TITLE`](#rdme-categories-create-title)
-
-## `rdme categories`
-
-Get all categories in your ReadMe project.
-
-```
-USAGE
- $ rdme categories --key [--version ]
-
-FLAGS
- --key= (required) ReadMe project API key
- --version= ReadMe project version
-
-DESCRIPTION
- Get all categories in your ReadMe project.
-
-EXAMPLES
- Get all categories associated to your project version:
-
- $ rdme categories --version={project-version}
-
-FLAG DESCRIPTIONS
- --key= ReadMe project API key
-
- An API key for your ReadMe project. Note that API authentication is required despite being omitted from the example
- usage. See our docs for more information: https://github.com/readmeio/rdme/tree/v9#authentication
-
- --version= ReadMe project version
-
- If running command in a CI environment and this option is not passed, the main project version will be used. See our
- docs for more information: https://docs.readme.com/main/docs/versions
-```
-
-## `rdme categories create TITLE`
-
-Create a category with the specified title and guide in your ReadMe project.
-
-```
-USAGE
- $ rdme categories create TITLE --categoryType guide|reference --key [--preventDuplicates] [--version ]
-
-ARGUMENTS
- TITLE Title of the category
-
-FLAGS
- --categoryType=