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=