Skip to content

Commit b793afc

Browse files
kanadguptaerunion
andauthored
fix(docs): directory sync fix + test bed rewrites (#421)
* chore: rename test name, rename console.log to console.info I don't like the word 'only' in tests lol, makes searching for .only() tests kinda annoying! Also using console.info so our console mocking doesn't interfere with our local debugging. ESLint config on this to come! * fix: make prompt for creating new version more generic * test(edit): refactor tests to check for resolved/rejected promise values * docs(edit): add some comments on anti-patterns stuff we're doing * fix: reject promise if any files fail to upload * fix: don't allow new version creation in docs syncing * test: rewrite test for handling invalid docs syncing * docs: more notes re: categories * test: remove before/afterEach hooks We're only using this mock in a single place! * chore: PR feedback Co-Authored-By: Jon Ursenbach <jon@ursenba.ch> Co-authored-by: Jon Ursenbach <jon@ursenba.ch>
1 parent 0c579b7 commit b793afc

File tree

4 files changed

+90
-106
lines changed

4 files changed

+90
-106
lines changed

__tests__/cmds/docs.test.js

Lines changed: 74 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
const nock = require('nock');
2+
const chalk = require('chalk');
23
const config = require('config');
34
const fs = require('fs');
45
const path = require('path');
56
const crypto = require('crypto');
67
const frontMatter = require('gray-matter');
78

9+
const APIError = require('../../src/lib/apiError');
10+
811
const docs = require('../../src/cmds/docs');
912
const docsEdit = require('../../src/cmds/docs/edit');
1013

@@ -186,18 +189,21 @@ describe('rdme docs', () => {
186189
});
187190
});
188191

189-
it('should create only valid docs', () => {
190-
console.log = jest.fn();
191-
expect.assertions(2);
192-
192+
it('should fail if any docs are invalid', async () => {
193+
const folder = 'failure-docs';
193194
const slug = 'fail-doc';
194195
const slugTwo = 'new-doc';
195196

196-
const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slug}.md`)));
197-
const docTwo = frontMatter(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slugTwo}.md`)));
197+
const errorObject = {
198+
error: 'DOC_INVALID',
199+
message: "We couldn't save this doc (Path `category` is required.).",
200+
};
201+
202+
const doc = frontMatter(fs.readFileSync(path.join(fixturesDir, `/${folder}/${slug}.md`)));
203+
const docTwo = frontMatter(fs.readFileSync(path.join(fixturesDir, `/${folder}/${slugTwo}.md`)));
198204

199-
const hash = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slug}.md`)));
200-
const hashTwo = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/failure-docs/${slugTwo}.md`)));
205+
const hash = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/${folder}/${slug}.md`)));
206+
const hashTwo = hashFileContents(fs.readFileSync(path.join(fixturesDir, `/${folder}/${slugTwo}.md`)));
201207

202208
const getMocks = getNockWithVersionHeader(version)
203209
.get(`/api/v1/docs/${slug}`)
@@ -238,43 +244,27 @@ describe('rdme docs', () => {
238244
})
239245
.post('/api/v1/docs', { slug, body: doc.content, ...doc.data, lastUpdatedHash: hash })
240246
.basicAuth({ user: key })
241-
.reply(400, {
242-
error: 'DOC_INVALID',
243-
message: "We couldn't save this doc (Path `category` is required.).",
244-
});
247+
.reply(400, errorObject);
245248

246249
const versionMock = nock(config.host)
247250
.get(`/api/v1/version/${version}`)
248251
.basicAuth({ user: key })
249252
.reply(200, { version });
250253

251-
return docs.run({ folder: './__tests__/__fixtures__/failure-docs', key, version }).then(message => {
252-
expect(console.log).toHaveBeenCalledTimes(1);
253-
expect(message).toStrictEqual([
254-
{
255-
metadata: { image: [], title: '', description: '' },
256-
api: {
257-
method: 'post',
258-
url: '',
259-
auth: 'required',
260-
params: [],
261-
apiSetting,
262-
},
263-
title: 'This is the document title',
264-
updates: [],
265-
type: 'endpoint',
266-
slug: slugTwo,
267-
body: 'Body',
268-
category,
269-
},
270-
]);
254+
const fullDirectory = `__tests__/__fixtures__/${folder}`;
271255

272-
getMocks.done();
273-
postMocks.done();
274-
versionMock.done();
256+
const formattedErrorObject = {
257+
...errorObject,
258+
message: `Error uploading ${chalk.underline(`${fullDirectory}/${slug}.md`)}:\n\n${errorObject.message}`,
259+
};
275260

276-
console.log.mockRestore();
277-
});
261+
await expect(docs.run({ folder: `./${fullDirectory}`, key, version })).rejects.toStrictEqual(
262+
new APIError(formattedErrorObject)
263+
);
264+
265+
getMocks.done();
266+
postMocks.done();
267+
versionMock.done();
278268
});
279269
});
280270

@@ -314,14 +304,6 @@ describe('rdme docs', () => {
314304
});
315305

316306
describe('rdme docs:edit', () => {
317-
beforeEach(() => {
318-
console.log = jest.fn();
319-
});
320-
321-
afterEach(() => {
322-
console.log.mockRestore();
323-
});
324-
325307
it('should error if no api key provided', () => {
326308
return expect(docsEdit.run({})).rejects.toThrow('No project API key provided. Please use `--key`.');
327309
});
@@ -332,8 +314,9 @@ describe('rdme docs:edit', () => {
332314
);
333315
});
334316

335-
it('should fetch the doc from the api', () => {
336-
expect.assertions(4);
317+
it('should fetch the doc from the api', async () => {
318+
expect.assertions(5);
319+
console.info = jest.fn();
337320
const slug = 'getting-started';
338321
const body = 'abcdef';
339322
const edits = 'ghijkl';
@@ -363,55 +346,55 @@ describe('rdme docs:edit', () => {
363346
fs.appendFile(filename, edits, cb.bind(null, 0));
364347
}
365348

366-
return docsEdit.run({ slug, key, version: '1.0.0', mockEditor }).then(() => {
367-
getMock.done();
368-
putMock.done();
369-
versionMock.done();
370-
expect(fs.existsSync(`${slug}.md`)).toBe(false);
349+
await expect(docsEdit.run({ slug, key, version: '1.0.0', mockEditor })).resolves.toBeUndefined();
371350

372-
expect(console.log).toHaveBeenCalledWith('Doc successfully updated. Cleaning up local file.');
373-
});
351+
getMock.done();
352+
putMock.done();
353+
versionMock.done();
354+
355+
expect(fs.existsSync(`${slug}.md`)).toBe(false);
356+
expect(console.info).toHaveBeenCalledWith('Doc successfully updated. Cleaning up local file.');
357+
return console.info.mockRestore();
374358
});
375359

376-
it('should error if remote doc does not exist', () => {
377-
expect.assertions(2);
360+
it('should error if remote doc does not exist', async () => {
378361
const slug = 'no-such-doc';
379362

380-
const getMock = nock(config.host)
381-
.get(`/api/v1/docs/${slug}`)
382-
.reply(404, {
383-
error: 'DOC_NOTFOUND',
384-
message: `The doc with the slug '${slug}' couldn't be found`,
385-
suggestion: '...a suggestion to resolve the issue...',
386-
help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
387-
});
363+
const errorObject = {
364+
error: 'DOC_NOTFOUND',
365+
message: `The doc with the slug '${slug}' couldn't be found`,
366+
suggestion: '...a suggestion to resolve the issue...',
367+
help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
368+
};
369+
370+
const getMock = nock(config.host).get(`/api/v1/docs/${slug}`).reply(404, errorObject);
388371

389372
const versionMock = nock(config.host)
390373
.get(`/api/v1/version/${version}`)
391374
.basicAuth({ user: key })
392375
.reply(200, { version });
393376

394-
return docsEdit.run({ slug, key, version: '1.0.0' }).catch(err => {
395-
getMock.done();
396-
versionMock.done();
397-
expect(err.code).toBe('DOC_NOTFOUND');
398-
expect(err.message).toContain("The doc with the slug 'no-such-doc' couldn't be found");
399-
});
377+
await expect(docsEdit.run({ slug, key, version: '1.0.0' })).rejects.toThrow(new APIError(errorObject));
378+
379+
getMock.done();
380+
return versionMock.done();
400381
});
401382

402-
it('should error if doc fails validation', () => {
383+
it('should error if doc fails validation', async () => {
403384
expect.assertions(2);
404385
const slug = 'getting-started';
405386
const body = 'abcdef';
406387

407-
const getMock = nock(config.host).get(`/api/v1/docs/${slug}`).reply(200, { body });
408-
409-
const putMock = nock(config.host).put(`/api/v1/docs/${slug}`).reply(400, {
388+
const errorObject = {
410389
error: 'DOC_INVALID',
411-
message: "We couldn't save this doc ({error})",
390+
message: `We couldn't save this doc (${slug})`,
412391
suggestion: '...a suggestion to resolve the issue...',
413392
help: 'If you need help, email support@readme.io and mention log "fake-metrics-uuid".',
414-
});
393+
};
394+
395+
const getMock = nock(config.host).get(`/api/v1/docs/${slug}`).reply(200, { body });
396+
397+
const putMock = nock(config.host).put(`/api/v1/docs/${slug}`).reply(400, errorObject);
415398

416399
const versionMock = nock(config.host)
417400
.get(`/api/v1/version/${version}`)
@@ -422,17 +405,17 @@ describe('rdme docs:edit', () => {
422405
return cb(0);
423406
}
424407

425-
return docsEdit.run({ slug, key, version: '1.0.0', mockEditor }).catch(err => {
426-
expect(err.code).toBe('DOC_INVALID');
427-
getMock.done();
428-
putMock.done();
429-
versionMock.done();
430-
expect(fs.existsSync(`${slug}.md`)).toBe(true);
431-
fs.unlinkSync(`${slug}.md`);
432-
});
408+
await expect(docsEdit.run({ slug, key, version: '1.0.0', mockEditor })).rejects.toThrow(new APIError(errorObject));
409+
410+
getMock.done();
411+
putMock.done();
412+
versionMock.done();
413+
414+
expect(fs.existsSync(`${slug}.md`)).toBe(true);
415+
fs.unlinkSync(`${slug}.md`);
433416
});
434417

435-
it('should handle error if $EDITOR fails', () => {
418+
it('should handle error if $EDITOR fails', async () => {
436419
expect.assertions(1);
437420
const slug = 'getting-started';
438421
const body = 'abcdef';
@@ -448,10 +431,11 @@ describe('rdme docs:edit', () => {
448431
return cb(1);
449432
}
450433

451-
return docsEdit.run({ slug, key, version: '1.0.0', mockEditor }).catch(err => {
452-
getMock.done();
453-
expect(err.message).toBe('Non zero exit code from $EDITOR');
454-
fs.unlinkSync(`${slug}.md`);
455-
});
434+
await expect(docsEdit.run({ slug, key, version: '1.0.0', mockEditor })).rejects.toThrow(
435+
new Error('Non zero exit code from $EDITOR')
436+
);
437+
438+
getMock.done();
439+
fs.unlinkSync(`${slug}.md`);
456440
});
457441
});

src/cmds/docs/edit.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,17 @@ exports.run = async function (opts) {
8383
})
8484
.then(res => res.json())
8585
.then(async res => {
86+
// The reason we aren't using our handleRes() function here is
87+
// because we need to use the `reject` function from
88+
// the Promise that's wrapping this function.
8689
if (res.error) {
8790
return reject(new APIError(res));
8891
}
89-
console.log(`Doc successfully updated. Cleaning up local file.`);
92+
console.info(`Doc successfully updated. Cleaning up local file.`);
9093
await unlink(filename);
94+
// Normally we should resolve with a value that is logged to the console,
95+
// but since we need to wait for the temporary file to be removed,
96+
// it's okay to resolve the promise with no value.
9197
return resolve();
9298
});
9399
});

src/cmds/docs/index.js

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ exports.run = async function (opts) {
4848
return Promise.reject(new Error(`No folder provided. Usage \`${config.cli} ${exports.usage}\`.`));
4949
}
5050

51-
const selectedVersion = await getProjectVersion(version, key, true).catch(e => {
52-
return Promise.reject(e);
53-
});
51+
// TODO: should we allow version selection at all here?
52+
// Let's revisit this once we re-evaluate our category logic in the API.
53+
// Ideally we should ignore this parameter entirely if the category is included.
54+
const selectedVersion = await getProjectVersion(version, key, false);
5455

5556
// Find the files to sync
5657
const readdirRecursive = folderToSearch => {
@@ -110,7 +111,7 @@ exports.run = async function (opts) {
110111
}).then(res => handleRes(res));
111112
}
112113

113-
const updatedDocs = await Promise.allSettled(
114+
const updatedDocs = await Promise.all(
114115
files.map(async filename => {
115116
const file = await readFile(filename, 'utf8');
116117
const matter = frontMatter(file);
@@ -134,19 +135,12 @@ exports.run = async function (opts) {
134135
return updateDoc(slug, matter, hash, res);
135136
})
136137
.catch(err => {
137-
console.log(chalk.red(`\n\`${slug}\` failed to upload. ${err.message}\n`));
138+
// eslint-disable-next-line no-param-reassign
139+
err.message = `Error uploading ${chalk.underline(filename)}:\n\n${err.message}`;
140+
throw err;
138141
});
139142
})
140143
);
141144

142-
for (let i = 0; i < updatedDocs.length; ) {
143-
if (updatedDocs[i].value !== undefined) {
144-
updatedDocs[i] = updatedDocs[i].value; // returns only the value of the response
145-
i += 1;
146-
} else {
147-
updatedDocs.splice(i, 1); // we already displayed the error messages so we can filter those out
148-
}
149-
}
150-
151145
return updatedDocs;
152146
};

src/lib/prompts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ exports.generatePrompts = (versionList, selectOnly = false) => [
66
{
77
type: 'select',
88
name: 'option',
9-
message: 'Would you like to use an existing version or create a new one to associate with your OAS file?',
9+
message: 'Would you like to use an existing project version or create a new one?',
1010
skip() {
1111
return selectOnly;
1212
},

0 commit comments

Comments
 (0)