diff --git a/src/__mocks__/fetch.ts b/src/__mocks__/fetch.ts index 2e693866..493f72f2 100644 --- a/src/__mocks__/fetch.ts +++ b/src/__mocks__/fetch.ts @@ -1,16 +1,31 @@ "use strict"; -let mockResponses: { [url: string]: string } = {}; +type Json = { [key: string]: unknown }; +type MockResponse = { status: number; statusText: string; ok: boolean; body: Json }; + +let mockResponses: { [url: string]: Partial } = {}; + +const defaultMockResponseParams = { + status: 200, + statusText: "OK", + ok: true, +}; export default async function fetch(url: string) { const mockResponse = mockResponses[url]; if (mockResponse) { - return { json: () => mockResponse }; + const fullMockResponse = { ...defaultMockResponseParams, ...mockResponse }; + return { + status: fullMockResponse.status, + statusText: fullMockResponse.statusText, + ok: fullMockResponse.ok, + json: () => Promise.resolve(fullMockResponse.body), + }; } throw new Error(`Unknown URL: ${url}`); } -export function __setMockResponses(newMockResponses: { [url: string]: string }) { +export function __setMockResponses(newMockResponses: { [url: string]: Partial }) { mockResponses = newMockResponses; } diff --git a/src/changelog.spec.ts b/src/changelog.spec.ts index 7d312813..460d7dd4 100644 --- a/src/changelog.spec.ts +++ b/src/changelog.spec.ts @@ -69,17 +69,21 @@ describe("Changelog", () => { const usersCache = { "https://api.github.com/users/test-user": { - login: "test-user", - html_url: "https://github.com/test-user", - name: "Test User", + body: { + login: "test-user", + html_url: "https://github.com/test-user", + name: "Test User", + }, }, }; const issuesCache = { "https://api.github.com/repos/lerna/lerna-changelog/issues/2": { - number: 2, - title: "This is the commit title for the issue (#2)", - labels: [{ name: "Type: New Feature" }, { name: "Status: In Progress" }], - user: usersCache["https://api.github.com/users/test-user"], + body: { + number: 2, + title: "This is the commit title for the issue (#2)", + labels: [{ name: "Type: New Feature" }, { name: "Status: In Progress" }], + user: usersCache["https://api.github.com/users/test-user"].body, + }, }, }; require("./fetch").__setMockResponses({ @@ -107,24 +111,32 @@ describe("Changelog", () => { const usersCache = { "https://api.github.com/users/test-user": { - login: "test-user", - html_url: "https://github.com/test-user", - name: "Test User", + body: { + login: "test-user", + html_url: "https://github.com/test-user", + name: "Test User", + }, }, "https://api.github.com/users/test-user-1": { - login: "test-user-1", - html_url: "https://github.com/test-user-1", - name: "Test User 1", + body: { + login: "test-user-1", + html_url: "https://github.com/test-user-1", + name: "Test User 1", + }, }, "https://api.github.com/users/test-user-2": { - login: "test-user-2", - html_url: "https://github.com/test-user-2", - name: "Test User 2", + body: { + login: "test-user-2", + html_url: "https://github.com/test-user-2", + name: "Test User 2", + }, }, "https://api.github.com/users/user-bot": { - login: "user-bot", - html_url: "https://github.com/user-bot", - name: "User Bot", + body: { + login: "user-bot", + html_url: "https://github.com/user-bot", + name: "User Bot", + }, }, }; require("./fetch").__setMockResponses(usersCache); diff --git a/src/changelog.ts b/src/changelog.ts index f52e4bee..75f2aa49 100644 --- a/src/changelog.ts +++ b/src/changelog.ts @@ -215,17 +215,19 @@ export default class Changelog { private async fillInPackages(commits: CommitInfo[]) { progressBar.init("Mapping commits to packages…", commits.length); - await pMap( - commits, - async (commit: CommitInfo) => { - commit.packages = await this.getListOfUniquePackages(commit.commitSHA); - - progressBar.tick(); - }, - { concurrency: 5 } - ); - - progressBar.terminate(); + try { + await pMap( + commits, + async (commit: CommitInfo) => { + commit.packages = await this.getListOfUniquePackages(commit.commitSHA); + + progressBar.tick(); + }, + { concurrency: 5 } + ); + } finally { + progressBar.terminate(); + } } private async fillInContributors(releases: Release[]) { diff --git a/src/cli.ts b/src/cli.ts index 8a0ea4ce..fb6ff387 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -88,5 +88,7 @@ export async function run() { } else { console.log(chalk.red(e.stack)); } + + process.exitCode = 1; } } diff --git a/src/functional/markdown-empty.spec.ts b/src/functional/markdown-empty.spec.ts index 76f16758..c14923ec 100644 --- a/src/functional/markdown-empty.spec.ts +++ b/src/functional/markdown-empty.spec.ts @@ -50,48 +50,60 @@ const listOfFileForEachCommit: { [id: string]: string[] } = { const usersCache = { "https://api.github.com/users/luke": { - login: "luke", - html_url: "https://github.com/luke", - name: "Luke Skywalker", + body: { + login: "luke", + html_url: "https://github.com/luke", + name: "Luke Skywalker", + }, }, "https://api.github.com/users/vader": { - login: "vader", - html_url: "https://github.com/vader", - name: "Darth Vader", + body: { + login: "vader", + html_url: "https://github.com/vader", + name: "Darth Vader", + }, }, "https://api.github.com/users/gtarkin": { - login: "gtarkin", - html_url: "https://github.com/gtarkin", - name: "Governor Tarkin", + body: { + login: "gtarkin", + html_url: "https://github.com/gtarkin", + name: "Governor Tarkin", + }, }, }; const issuesCache = { "https://api.github.com/repos/lerna/lerna-changelog/issues/1": { - number: 1, - title: "feat: May the force be with you", - labels: [{ name: "Type: New Feature" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/1", + body: { + number: 1, + title: "feat: May the force be with you", + labels: [{ name: "Type: New Feature" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/1", + }, + user: usersCache["https://api.github.com/users/luke"], }, - user: usersCache["https://api.github.com/users/luke"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/2": { - number: 2, - title: "chore: Terminate her... immediately!", - labels: [{ name: "Type: Breaking Change" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/2", + body: { + number: 2, + title: "chore: Terminate her... immediately!", + labels: [{ name: "Type: Breaking Change" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/2", + }, + user: usersCache["https://api.github.com/users/gtarkin"], }, - user: usersCache["https://api.github.com/users/gtarkin"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/3": { - number: 3, - title: "fix: Get me the rebels base!", - labels: [{ name: "Type: Bug" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/3", + body: { + number: 3, + title: "fix: Get me the rebels base!", + labels: [{ name: "Type: Bug" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/3", + }, + user: usersCache["https://api.github.com/users/vader"], }, - user: usersCache["https://api.github.com/users/vader"], }, }; diff --git a/src/functional/markdown-full.spec.ts b/src/functional/markdown-full.spec.ts index 95a70bb3..4c9cb799 100644 --- a/src/functional/markdown-full.spec.ts +++ b/src/functional/markdown-full.spec.ts @@ -141,109 +141,139 @@ const listOfFileForEachCommit: { [id: string]: string[] } = { const usersCache = { "https://api.github.com/users/luke": { - login: "luke", - html_url: "https://github.com/luke", - name: "Luke Skywalker", + body: { + login: "luke", + html_url: "https://github.com/luke", + name: "Luke Skywalker", + }, }, "https://api.github.com/users/princess-leia": { - login: "princess-leia", - html_url: "https://github.com/princess-leia", - name: "Princess Leia Organa", + body: { + login: "princess-leia", + html_url: "https://github.com/princess-leia", + name: "Princess Leia Organa", + }, }, "https://api.github.com/users/vader": { - login: "vader", - html_url: "https://github.com/vader", - name: "Darth Vader", + body: { + login: "vader", + html_url: "https://github.com/vader", + name: "Darth Vader", + }, }, "https://api.github.com/users/gtarkin": { - login: "gtarkin", - html_url: "https://github.com/gtarkin", - name: "Governor Tarkin", + body: { + login: "gtarkin", + html_url: "https://github.com/gtarkin", + name: "Governor Tarkin", + }, }, "https://api.github.com/users/han-solo": { - login: "han-solo", - html_url: "https://github.com/han-solo", - name: "Han Solo", + body: { + login: "han-solo", + html_url: "https://github.com/han-solo", + name: "Han Solo", + }, }, "https://api.github.com/users/chewbacca": { - login: "chewbacca", - html_url: "https://github.com/chewbacca", - name: "Chwebacca", + body: { + login: "chewbacca", + html_url: "https://github.com/chewbacca", + name: "Chwebacca", + }, }, "https://api.github.com/users/rd-d2": { - login: "rd-d2", - html_url: "https://github.com/rd-d2", - name: "R2-D2", + body: { + login: "rd-d2", + html_url: "https://github.com/rd-d2", + name: "R2-D2", + }, }, "https://api.github.com/users/c-3po": { - login: "c-3po", - html_url: "https://github.com/c-3po", - name: "C-3PO", + body: { + login: "c-3po", + html_url: "https://github.com/c-3po", + name: "C-3PO", + }, }, }; const issuesCache = { "https://api.github.com/repos/lerna/lerna-changelog/issues/1": { - number: 1, - title: "feat: May the force be with you", - labels: [{ name: "Type: New Feature" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/1", + body: { + number: 1, + title: "feat: May the force be with you", + labels: [{ name: "Type: New Feature" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/1", + }, + user: usersCache["https://api.github.com/users/luke"].body, }, - user: usersCache["https://api.github.com/users/luke"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/2": { - number: 2, - title: "chore: Terminate her... immediately!", - labels: [{ name: "Type: Breaking Change" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/2", + body: { + number: 2, + title: "chore: Terminate her... immediately!", + labels: [{ name: "Type: Breaking Change" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/2", + }, + user: usersCache["https://api.github.com/users/gtarkin"].body, }, - user: usersCache["https://api.github.com/users/gtarkin"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/3": { - number: 3, - title: "fix: Get me the rebels base!", - labels: [{ name: "Type: Bug" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/3", + body: { + number: 3, + title: "fix: Get me the rebels base!", + labels: [{ name: "Type: Bug" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/3", + }, + user: usersCache["https://api.github.com/users/vader"].body, }, - user: usersCache["https://api.github.com/users/vader"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/4": { - number: 4, - title: "fix: RRRAARRWHHGWWR", - labels: [{ name: "Type: Bug" }, { name: "Type: Maintenance" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/4", + body: { + number: 4, + title: "fix: RRRAARRWHHGWWR", + labels: [{ name: "Type: Bug" }, { name: "Type: Maintenance" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/4", + }, + user: usersCache["https://api.github.com/users/chewbacca"].body, }, - user: usersCache["https://api.github.com/users/chewbacca"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/5": { - number: 5, - title: "feat: I am your father", - labels: [{ name: "Type: New Feature" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/5", + body: { + number: 5, + title: "feat: I am your father", + labels: [{ name: "Type: New Feature" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/5", + }, + user: usersCache["https://api.github.com/users/vader"].body, }, - user: usersCache["https://api.github.com/users/vader"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/6": { - number: 6, - title: "refactor: he is my brother", - labels: [{ name: "Type: Enhancement" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/6", + body: { + number: 6, + title: "refactor: he is my brother", + labels: [{ name: "Type: Enhancement" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/6", + }, + user: usersCache["https://api.github.com/users/princess-leia"].body, }, - user: usersCache["https://api.github.com/users/princess-leia"], }, "https://api.github.com/repos/lerna/lerna-changelog/issues/7": { - number: 7, - title: "feat: that is not how the Force works!", - labels: [{ name: "Type: New Feature" }, { name: "Type: Enhancement" }], - pull_request: { - html_url: "https://github.com/lerna/lerna-changelog/pull/7", + body: { + number: 7, + title: "feat: that is not how the Force works!", + labels: [{ name: "Type: New Feature" }, { name: "Type: Enhancement" }], + pull_request: { + html_url: "https://github.com/lerna/lerna-changelog/pull/7", + }, + user: usersCache["https://api.github.com/users/han-solo"].body, }, - user: usersCache["https://api.github.com/users/han-solo"], }, }; @@ -351,4 +381,54 @@ describe("createMarkdown", () => { expect(markdown).toMatchSnapshot(); }); }); + + describe("authentication", () => { + describe("when github token is not valid", () => { + const badCredentials = { + message: "Bad credentials", + documentation_url: "https://developer.github.com/v3", + }; + beforeEach(() => { + require("../git").changedPaths.mockImplementation((sha: string) => listOfFileForEachCommit[sha]); + require("../git").lastTag.mockImplementation(() => "v8.0.0"); + require("../git").listCommits.mockImplementation(() => listOfCommits); + require("../git").listTagNames.mockImplementation(() => listOfTags); + + const unauthorized = { + status: 401, + statusText: "Unauthorized", + ok: false, + body: badCredentials, + }; + require("../fetch").__setMockResponses({ + ...usersCache, + ...Object.keys(issuesCache).reduce( + (unauthorizedIssues, issue) => ({ + ...unauthorizedIssues, + [issue]: unauthorized, + }), + {} + ), + }); + }); + afterEach(() => { + jest.resetAllMocks(); + }); + it("should abort with a proper error message", async () => { + const MockedChangelog = require("../changelog").default; + const changelog = new MockedChangelog(); + expect.assertions(2); + try { + await changelog.createMarkdown(); + } catch (error) { + expect(error).toEqual( + expect.objectContaining({ message: expect.stringContaining("Fetch error: Unauthorized") }) + ); + expect(error).toEqual( + expect.objectContaining({ message: expect.stringContaining(JSON.stringify(badCredentials)) }) + ); + } + }); + }); + }); }); diff --git a/src/github-api.ts b/src/github-api.ts index d0450597..b89ab717 100644 --- a/src/github-api.ts +++ b/src/github-api.ts @@ -61,7 +61,11 @@ export default class GithubAPI { Authorization: `token ${this.auth}`, }, }); - return res.json(); + const parsedResponse = await res.json(); + if (res.ok) { + return parsedResponse; + } + throw new ConfigurationError(`Fetch error: ${res.statusText}.\n${JSON.stringify(parsedResponse)}`); } private getAuthToken(): string {