diff --git a/__tests__/cmds/docs.test.js b/__tests__/cmds/docs.test.js index c3334f548..ee2e0171e 100644 --- a/__tests__/cmds/docs.test.js +++ b/__tests__/cmds/docs.test.js @@ -45,9 +45,25 @@ describe('rdme docs', () => { ); }); - it.todo('should error if the argument isnt a folder'); + it('should error if the argument isnt a folder', async () => { + const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version }); + + await expect(docs.run({ key, version: '1.0.0', folder: 'not-a-folder' })).rejects.toThrow( + "ENOENT: no such file or directory, scandir 'not-a-folder'" + ); + + versionMock.done(); + }); + + it('should error if the folder contains no markdown files', async () => { + const versionMock = getApiNock().get(`/api/v1/version/${version}`).basicAuth({ user: key }).reply(200, { version }); + + await expect(docs.run({ key, version: '1.0.0', folder: '.github/workflows' })).rejects.toThrow( + 'We were unable to locate Markdown files in .github/workflows.' + ); - it.todo('should error if the folder contains no markdown files'); + versionMock.done(); + }); describe('existing docs', () => { let simpleDoc; @@ -112,14 +128,12 @@ describe('rdme docs', () => { return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, 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).toStrictEqual([ - { - category, - slug: simpleDoc.slug, - body: simpleDoc.doc.content, - }, - { category, slug: anotherDoc.slug, body: anotherDoc.doc.content }, - ]); + expect(updatedDocs).toBe( + [ + "✏️ successfully updated 'simple-doc' with contents from __tests__/__fixtures__/existing-docs/simple-doc.md", + "✏️ successfully updated 'another-doc' with contents from __tests__/__fixtures__/existing-docs/subdir/another-doc.md", + ].join('\n') + ); getMocks.done(); updateMocks.done(); @@ -127,6 +141,43 @@ describe('rdme docs', () => { }); }); + it('should return doc update info for dry run', () => { + expect.assertions(1); + + const getMocks = getNockWithVersionHeader(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 = getApiNock() + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + + return docs + .run({ dryRun: true, folder: './__tests__/__fixtures__/existing-docs', key, 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__/__fixtures__/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__/__fixtures__/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); @@ -144,15 +195,48 @@ describe('rdme docs', () => { .reply(200, { version }); return docs.run({ folder: './__tests__/__fixtures__/existing-docs', key, version }).then(skippedDocs => { - expect(skippedDocs).toStrictEqual([ - '`simple-doc` was not updated because there were no changes.', - '`another-doc` was not updated because there were no changes.', - ]); + 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 = getNockWithVersionHeader(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 = getApiNock() + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + + return docs + .run({ dryRun: true, folder: './__tests__/__fixtures__/existing-docs', key, 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', () => { @@ -181,21 +265,44 @@ describe('rdme docs', () => { .basicAuth({ user: key }) .reply(200, { version }); - await expect(docs.run({ folder: './__tests__/__fixtures__/new-docs', key, version })).resolves.toStrictEqual([ - { - slug: 'new-doc', - body: '\nBody\n', - category: '5ae122e10fdf4e39bb34db6f', - title: 'This is the document title', - lastUpdatedHash: 'a23046c1e9d8ab47f8875ae7c5e429cb95be1c48', - }, - ]); + await expect(docs.run({ folder: './__tests__/__fixtures__/new-docs', key, version })).resolves.toBe( + "🌱 successfully created 'new-doc' with contents from __tests__/__fixtures__/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(fixturesDir, `/new-docs/${slug}.md`))); + + const getMock = getNockWithVersionHeader(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 = getApiNock() + .get(`/api/v1/version/${version}`) + .basicAuth({ user: key }) + .reply(200, { version }); + + await expect(docs.run({ dryRun: true, folder: './__tests__/__fixtures__/new-docs', key, version })).resolves.toBe( + `🎭 dry run! This will create 'new-doc' with contents from __tests__/__fixtures__/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 = 'fail-doc'; @@ -301,15 +408,9 @@ describe('rdme docs', () => { .basicAuth({ user: key }) .reply(200, { version }); - await expect(docs.run({ folder: './__tests__/__fixtures__/slug-docs', key, version })).resolves.toStrictEqual([ - { - slug: 'marc-actually-wrote-a-test', - body: '\nBody\n', - category: 'CATEGORY_ID', - title: 'This is the document title', - lastUpdatedHash: 'c9cb7cc26e90775548e1d182ae7fcaa0eaba96bc', - }, - ]); + await expect(docs.run({ folder: './__tests__/__fixtures__/slug-docs', key, version })).resolves.toBe( + "🌱 successfully created 'marc-actually-wrote-a-test' with contents from __tests__/__fixtures__/slug-docs/new-doc-slug.md" + ); getMock.done(); postMock.done(); diff --git a/src/cmds/docs/index.js b/src/cmds/docs/index.js index 8c576addf..52c596dc5 100644 --- a/src/cmds/docs/index.js +++ b/src/cmds/docs/index.js @@ -37,11 +37,16 @@ module.exports = class DocsCommand { type: String, defaultOption: true, }, + { + name: 'dryRun', + type: Boolean, + description: 'Runs the command without creating/updating any docs in ReadMe. Useful for debugging.', + }, ]; } async run(opts) { - const { folder, key, version } = opts; + const { dryRun, folder, key, version } = opts; debug(`command: ${this.command}`); debug(`opts: ${JSON.stringify(opts)}`); @@ -83,9 +88,15 @@ module.exports = class DocsCommand { return Promise.reject(new Error(`We were unable to locate Markdown files in ${folder}.`)); } - function createDoc(slug, file, hash, err) { + function createDoc(slug, file, filename, hash, err) { if (err.error !== 'DOC_NOTFOUND') return Promise.reject(err); + if (dryRun) { + return `🎭 dry run! This will create '${slug}' with contents from ${filename} with the following metadata: ${JSON.stringify( + file.data + )}`; + } + return fetch(`${config.get('host')}/api/v1/docs`, { method: 'post', headers: cleanHeaders(key, { @@ -98,12 +109,22 @@ module.exports = class DocsCommand { ...file.data, lastUpdatedHash: hash, }), - }).then(res => handleRes(res)); + }) + .then(res => handleRes(res)) + .then(res => `🌱 successfully created '${res.slug}' with contents from ${filename}`); } - function updateDoc(slug, file, hash, existingDoc) { + function updateDoc(slug, file, filename, hash, existingDoc) { if (hash === existingDoc.lastUpdatedHash) { - return `\`${slug}\` was not updated because there were no changes.`; + return `${dryRun ? '🎭 dry run! ' : ''}\`${slug}\` ${ + dryRun ? 'will not be' : 'was not' + } updated because there were no changes.`; + } + + if (dryRun) { + return `🎭 dry run! This will update '${slug}' with contents from ${filename} with the following metadata: ${JSON.stringify( + file.data + )}`; } return fetch(`${config.get('host')}/api/v1/docs/${slug}`, { @@ -119,7 +140,9 @@ module.exports = class DocsCommand { lastUpdatedHash: hash, }) ), - }).then(res => handleRes(res)); + }) + .then(res => handleRes(res)) + .then(res => `✏️ successfully updated '${res.slug}' with contents from ${filename}`); } const updatedDocs = await Promise.all( @@ -147,10 +170,10 @@ module.exports = class DocsCommand { debug(`GET /docs/:slug API response for ${slug}: ${JSON.stringify(res)}`); if (res.error) { debug(`error retrieving data for ${slug}, creating doc`); - return createDoc(slug, matter, hash, res); + return createDoc(slug, matter, filename, hash, res); } debug(`data received for ${slug}, updating doc`); - return updateDoc(slug, matter, hash, res); + return updateDoc(slug, matter, filename, hash, res); }) .catch(err => { // eslint-disable-next-line no-param-reassign @@ -160,6 +183,6 @@ module.exports = class DocsCommand { }) ); - return updatedDocs; + return chalk.green(updatedDocs.join('\n')); } };