refactor(docs-infra): take advantage of latest Jasmine features in preview server tests (#36837)

This commit updates the preview server tests to take advantage of
features supported in the latest version of Jasmine that were not
supported when the rests were first written.

Changes include:

- Use [async/await] in tests.
- Use the new [toBeInstanceOf()] and [toHaveBeenCalledBefore()] matchers.
- Use the new [toBeResolved()] and [toBeRejected()] async matchers (and
  their variants).
- Use the new [withArgs()] method of `Spy` to simplify "trained"
  responses.
- Use the new [resolveTo()]/[rejectWith()] methods of `SpyStrategy` (and
  their variants) to simplify promise-based spies.
- Implement custom async matchers (via [addAsyncMatchers()]) to simplify
  certain tests.

[addAsyncMatchers()]: https://jasmine.github.io/api/3.5/jasmine.html#.addAsyncMatchers
[async/await]: https://jasmine.github.io/tutorials/async
[rejectWith()]: https://jasmine.github.io/api/3.5/SpyStrategy.html#rejectWith
[resolveTo()]: https://jasmine.github.io/api/3.5/SpyStrategy.html#resolveTo
[toBeInstanceOf()]: https://jasmine.github.io/api/3.5/matchers.html#toBeInstanceOf
[toBeRejected()]: https://jasmine.github.io/api/3.5/async-matchers.html#toBeRejected
[toBeResolved()]: https://jasmine.github.io/api/3.5/async-matchers.html#toBeResolved
[toHaveBeenCalledBefore()]: https://jasmine.github.io/api/3.5/matchers.html#toHaveBeenCalledBefore
[withArgs()]: https://jasmine.github.io/api/3.5/Spy.html#withArgs

PR Close #36837
This commit is contained in:
George Kalpakas 2020-05-02 16:14:14 +03:00 committed by Alex Rickabaugh
parent b3cf5b246f
commit 1b8752e595
17 changed files with 623 additions and 808 deletions

View File

@ -101,7 +101,7 @@ class Helper {
} }
public runForAllSupportedSchemes(suiteFactory: TestSuiteFactory): void { public runForAllSupportedSchemes(suiteFactory: TestSuiteFactory): void {
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme])); Object.entries(this.portPerScheme).forEach(([scheme, port]) => suiteFactory(scheme, port));
} }
public verifyResponse(status: number, regex: string | RegExp = /^/): VerifyCmdResultFn { public verifyResponse(status: number, regex: string | RegExp = /^/): VerifyCmdResultFn {

View File

@ -15,7 +15,7 @@ describe(`nginx`, () => {
afterEach(() => h.cleanUp()); afterEach(() => h.cleanUp());
it('should redirect HTTP to HTTPS', done => { it('should redirect HTTP to HTTPS', async () => {
const httpHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTP}`; const httpHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTP}`;
const httpsHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTPS}`; const httpsHost = `${AIO_NGINX_HOSTNAME}:${AIO_NGINX_PORT_HTTPS}`;
const urlMap = { const urlMap = {
@ -24,16 +24,15 @@ describe(`nginx`, () => {
[`http://foo.${httpHost}/`]: `https://foo.${httpsHost}/`, [`http://foo.${httpHost}/`]: `https://foo.${httpsHost}/`,
}; };
const verifyRedirection = (httpUrl: string) => h.runCmd(`curl -i ${httpUrl}`).then(result => { const verifyRedirection = async (fromUrl: string, toUrl: string) => {
const result = await h.runCmd(`curl -i ${fromUrl}`);
h.verifyResponse(307)(result); h.verifyResponse(307)(result);
const headers = result.stdout.split(/(?:\r?\n){2,}/)[0]; const headers = result.stdout.split(/(?:\r?\n){2,}/)[0];
expect(headers).toContain(`Location: ${urlMap[httpUrl]}`); expect(headers).toContain(`Location: ${toUrl}`);
}); };
Promise. await Promise.all(Object.entries(urlMap).map(urls => verifyRedirection(...urls)));
all(Object.keys(urlMap).map(verifyRedirection)).
then(done);
}); });
@ -62,15 +61,15 @@ describe(`nginx`, () => {
}); });
it('should return /index.html', done => { it('should return /index.html', async () => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`; const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)), h.runCmd(`curl -iL ${origin}/index.html`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)), h.runCmd(`curl -iL ${origin}/`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)), h.runCmd(`curl -iL ${origin}`).then(h.verifyResponse(200, bodyRegex)),
]).then(done); ]);
}); });
@ -90,12 +89,11 @@ describe(`nginx`, () => {
}); });
it('should return /foo/bar.js', done => { it('should return /foo/bar.js', async () => {
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`); const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /foo/bar\\.js$`);
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/bar.js`). await h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/bar.js`).
then(h.verifyResponse(200, bodyRegex)). then(h.verifyResponse(200, bodyRegex));
then(done);
}); });
@ -111,47 +109,46 @@ describe(`nginx`, () => {
}); });
it('should respond with 403 for directories', done => { it('should respond with 403 for directories', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/`).then(h.verifyResponse(403)), h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/`).then(h.verifyResponse(403)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo`).then(h.verifyResponse(403)), h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo`).then(h.verifyResponse(403)),
]).then(done); ]);
}); });
it('should respond with 404 for unknown paths to files', done => { it('should respond with 404 for unknown paths to files', async () => {
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/baz.css`). await h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}/foo/baz.css`).
then(h.verifyResponse(404)). then(h.verifyResponse(404));
then(done);
}); });
it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', done => { it('should rewrite to \'index.html\' for unknown paths that don\'t look like files', async () => {
const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`; const origin = `${scheme}://pr${pr}-${shortSha9}.${host}`;
const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); const bodyRegex = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${origin}/foo/baz`).then(h.verifyResponse(200, bodyRegex)), h.runCmd(`curl -iL ${origin}/foo/baz`).then(h.verifyResponse(200, bodyRegex)),
h.runCmd(`curl -iL ${origin}/foo/baz/`).then(h.verifyResponse(200, bodyRegex)), h.runCmd(`curl -iL ${origin}/foo/baz/`).then(h.verifyResponse(200, bodyRegex)),
]).then(done); ]);
}); });
it('should respond with 404 for unknown PRs/SHAs', done => { it('should respond with 404 for unknown PRs/SHAs', async () => {
const otherPr = 54321; const otherPr = 54321;
const otherShortSha = computeShortSha('8'.repeat(40)); const otherShortSha = computeShortSha('8'.repeat(40));
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}9-${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${pr}9-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${otherPr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${otherPr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}9.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}9.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${otherShortSha}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${pr}-${otherShortSha}.${host}`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
it('should respond with 404 if the subdomain format is wrong', done => { it('should respond with 404 if the subdomain format is wrong', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${scheme}://xpr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://xpr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://prx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://prx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://xx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://xx${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
@ -160,26 +157,25 @@ describe(`nginx`, () => {
h.runCmd(`curl -iL ${scheme}://${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${pr}-${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${pr}${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}_${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${pr}_${shortSha9}.${host}`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
it('should reject PRs with leading zeros', done => { it('should reject PRs with leading zeros', async () => {
h.runCmd(`curl -iL ${scheme}://pr0${pr}-${shortSha9}.${host}`). await h.runCmd(`curl -iL ${scheme}://pr0${pr}-${shortSha9}.${host}`).
then(h.verifyResponse(404)). then(h.verifyResponse(404));
then(done);
}); });
it('should accept SHAs with leading zeros (but not trim the zeros)', done => { it('should accept SHAs with leading zeros (but not trim the zeros)', async () => {
const bodyRegex9 = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`); const bodyRegex9 = new RegExp(`^PR: ${pr} | SHA: ${sha9} | File: /index\\.html$`);
const bodyRegex0 = new RegExp(`^PR: ${pr} | SHA: ${sha0} | File: /index\\.html$`); const bodyRegex0 = new RegExp(`^PR: ${pr} | SHA: ${sha0} | File: /index\\.html$`);
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${scheme}://pr${pr}-0${shortSha9}.${host}`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://pr${pr}-0${shortSha9}.${host}`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(200, bodyRegex9)), h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha9}.${host}`).then(h.verifyResponse(200, bodyRegex9)),
h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha0}.${host}`).then(h.verifyResponse(200, bodyRegex0)), h.runCmd(`curl -iL ${scheme}://pr${pr}-${shortSha0}.${host}`).then(h.verifyResponse(200, bodyRegex0)),
]).then(done); ]);
}); });
}); });
@ -231,23 +227,23 @@ describe(`nginx`, () => {
describe(`${host}/health-check`, () => { describe(`${host}/health-check`, () => {
it('should respond with 200', done => { it('should respond with 200', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${scheme}://${host}/health-check`).then(h.verifyResponse(200)), h.runCmd(`curl -iL ${scheme}://${host}/health-check`).then(h.verifyResponse(200)),
h.runCmd(`curl -iL ${scheme}://${host}/health-check/`).then(h.verifyResponse(200)), h.runCmd(`curl -iL ${scheme}://${host}/health-check/`).then(h.verifyResponse(200)),
]).then(done); ]);
}); });
it('should respond with 404 if the path does not match exactly', done => { it('should respond with 404 if the path does not match exactly', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${scheme}://${host}/health-check/foo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${host}/health-check/foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/health-check-foo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${host}/health-check-foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/health-checknfoo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${host}/health-checknfoo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/foo/health-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${host}/foo/health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/foo-health-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${host}/foo-health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${scheme}://${host}/foonhealth-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${scheme}://${host}/foonhealth-check`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
}); });
@ -291,29 +287,28 @@ describe(`nginx`, () => {
describe(`${host}/circle-build`, () => { describe(`${host}/circle-build`, () => {
it('should disallow non-POST requests', done => { it('should disallow non-POST requests', async () => {
const url = `${scheme}://${host}/circle-build`; const url = `${scheme}://${host}/circle-build`;
Promise.all([ await Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)),
]).then(done); ]);
}); });
it('should pass requests through to the preview server', done => { it('should pass requests through to the preview server', async () => {
h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`). await h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`).
then(h.verifyResponse(400, /Incorrect body content. Expected JSON/)). then(h.verifyResponse(400, /Incorrect body content. Expected JSON/));
then(done);
}); });
it('should respond with 404 for unknown paths', done => { it('should respond with 404 for unknown paths', async () => {
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`; const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
Promise.all([ await Promise.all([
h.runCmd(`${cmdPrefix}/foo/circle-build/`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo/circle-build/`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-circle-build/`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo-circle-build/`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/fooncircle-build/`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/fooncircle-build/`).then(h.verifyResponse(404)),
@ -322,7 +317,7 @@ describe(`nginx`, () => {
h.runCmd(`${cmdPrefix}/circle-buildnfoo/`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/circle-buildnfoo/`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/circle-build/pr`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/circle-build/pr`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/circle-build/42`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
}); });
@ -332,38 +327,33 @@ describe(`nginx`, () => {
const url = `${scheme}://${host}/pr-updated`; const url = `${scheme}://${host}/pr-updated`;
it('should disallow non-POST requests', done => { it('should disallow non-POST requests', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)), h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)),
]).then(done); ]);
}); });
it('should pass requests through to the preview server', done => { it('should pass requests through to the preview server', async () => {
const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`; await h.runCmd(`curl -iLX POST --header "Content-Type: application/json" ${url}`).
then(h.verifyResponse(400, /Missing or empty 'number' field/));
const cmd1 = `${cmdPrefix} ${url}`;
Promise.all([
h.runCmd(cmd1).then(h.verifyResponse(400, /Missing or empty 'number' field/)),
]).then(done);
}); });
it('should respond with 404 for unknown paths', done => { it('should respond with 404 for unknown paths', async () => {
const cmdPrefix = `curl -iLX POST ${scheme}://${host}`; const cmdPrefix = `curl -iLX POST ${scheme}://${host}`;
Promise.all([ await Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
}); });
@ -374,7 +364,7 @@ describe(`nginx`, () => {
beforeEach(() => { beforeEach(() => {
['index.html', 'foo.js', 'foo/index.html'].forEach(relFilePath => { ['index.html', 'foo.js', 'foo/index.html'].forEach(relFilePath => {
const absFilePath = path.join(AIO_BUILDS_DIR, relFilePath); const absFilePath = path.join(AIO_BUILDS_DIR, relFilePath);
return h.writeFile(absFilePath, {content: `File: /${relFilePath}`}); h.writeFile(absFilePath, {content: `File: /${relFilePath}`});
}); });
}); });

View File

@ -105,9 +105,9 @@ describe('preview-server', () => {
describe(`${host}/circle-build`, () => { describe(`${host}/circle-build`, () => {
const curl = makeCurl(`${host}/circle-build`); const curl = makeCurl(`${host}/circle-build`);
it('should disallow non-POST requests', async () => { it('should disallow non-POST requests', async () => {
const bodyRegex = /^Unknown resource/; const bodyRegex = /^Unknown resource/;
@ -189,8 +189,7 @@ describe('preview-server', () => {
}); });
it('should respond with 201 if a new public build is created', async () => { it('should respond with 201 if a new public build is created', async () => {
await curl(payload(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)) await curl(payload(BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER)).then(h.verifyResponse(201));
.then(h.verifyResponse(201));
expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER }).toExistAsABuild(); expect({ prNum: PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER }).toExistAsABuild();
}); });
@ -364,23 +363,23 @@ describe('preview-server', () => {
describe(`${host}/health-check`, () => { describe(`${host}/health-check`, () => {
it('should respond with 200', done => { it('should respond with 200', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${host}/health-check`).then(h.verifyResponse(200)), h.runCmd(`curl -iL ${host}/health-check`).then(h.verifyResponse(200)),
h.runCmd(`curl -iL ${host}/health-check/`).then(h.verifyResponse(200)), h.runCmd(`curl -iL ${host}/health-check/`).then(h.verifyResponse(200)),
]).then(done); ]);
}); });
it('should respond with 404 if the path does not match exactly', done => { it('should respond with 404 if the path does not match exactly', async () => {
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${host}/health-check/foo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${host}/health-check/foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/health-check-foo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${host}/health-check-foo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/health-checknfoo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${host}/health-checknfoo`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/foo/health-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${host}/foo/health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/foo-health-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${host}/foo-health-check`).then(h.verifyResponse(404)),
h.runCmd(`curl -iL ${host}/foonhealth-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL ${host}/foonhealth-check`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
}); });
@ -426,18 +425,18 @@ describe('preview-server', () => {
}); });
it('should respond with 404 for unknown paths', done => { it('should respond with 404 for unknown paths', async () => {
const mockPayload = JSON.stringify({number: 1}); // MockExternalApiFlags.TRUST_CHECK_ACTIVE_TRUSTED_USER }); const mockPayload = JSON.stringify({number: 1}); // MockExternalApiFlags.TRUST_CHECK_ACTIVE_TRUSTED_USER });
const cmdPrefix = `curl -iLX POST --data "${mockPayload}" ${host}`; const cmdPrefix = `curl -iLX POST --data "${mockPayload}" ${host}`;
Promise.all([ await Promise.all([
h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo/pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo-pr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foonpr-updated`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/pr-updated/foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/pr-updated-foo`).then(h.verifyResponse(404)),
h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/pr-updatednfoo`).then(h.verifyResponse(404)),
]).then(done); ]);
}); });
@ -551,10 +550,10 @@ describe('preview-server', () => {
describe(`${host}/*`, () => { describe(`${host}/*`, () => {
it('should respond with 404 for requests to unknown URLs', done => { it('should respond with 404 for requests to unknown URLs', async () => {
const bodyRegex = /^Unknown resource/; const bodyRegex = /^Unknown resource/;
Promise.all([ await Promise.all([
h.runCmd(`curl -iL ${host}/index.html`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iL ${host}/index.html`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL ${host}/`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iL ${host}/`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iL ${host}`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iL ${host}`).then(h.verifyResponse(404, bodyRegex)),
@ -562,7 +561,7 @@ describe('preview-server', () => {
h.runCmd(`curl -iLX POST ${host}`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iLX POST ${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX PATCH ${host}`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iLX PATCH ${host}`).then(h.verifyResponse(404, bodyRegex)),
h.runCmd(`curl -iLX DELETE ${host}`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iLX DELETE ${host}`).then(h.verifyResponse(404, bodyRegex)),
]).then(done); ]);
}); });
}); });

View File

@ -76,22 +76,18 @@ describe('BuildCleaner', () => {
let cleanerRemoveUnnecessaryDownloadsSpy: jasmine.Spy; let cleanerRemoveUnnecessaryDownloadsSpy: jasmine.Spy;
beforeEach(() => { beforeEach(() => {
cleanerGetExistingBuildNumbersSpy = spyOn(cleaner, 'getExistingBuildNumbers') cleanerGetExistingBuildNumbersSpy = spyOn(cleaner, 'getExistingBuildNumbers').and.resolveTo(EXISTING_BUILDS);
.and.callFake(() => Promise.resolve(EXISTING_BUILDS)); cleanerGetOpenPrNumbersSpy = spyOn(cleaner, 'getOpenPrNumbers').and.resolveTo(OPEN_PRS);
cleanerGetOpenPrNumbersSpy = spyOn(cleaner, 'getOpenPrNumbers') cleanerGetExistingDownloadsSpy = spyOn(cleaner, 'getExistingDownloads').and.resolveTo(EXISTING_DOWNLOADS);
.and.callFake(() => Promise.resolve(OPEN_PRS));
cleanerGetExistingDownloadsSpy = spyOn(cleaner, 'getExistingDownloads')
.and.callFake(() => Promise.resolve(EXISTING_DOWNLOADS));
cleanerRemoveUnnecessaryBuildsSpy = spyOn(cleaner, 'removeUnnecessaryBuilds'); cleanerRemoveUnnecessaryBuildsSpy = spyOn(cleaner, 'removeUnnecessaryBuilds');
cleanerRemoveUnnecessaryDownloadsSpy = spyOn(cleaner, 'removeUnnecessaryDownloads'); cleanerRemoveUnnecessaryDownloadsSpy = spyOn(cleaner, 'removeUnnecessaryDownloads');
}); });
it('should return a promise', async () => { it('should return a promise', async () => {
const promise = cleaner.cleanUp(); const promise = cleaner.cleanUp();
expect(promise).toEqual(jasmine.any(Promise)); expect(promise).toBeInstanceOf(Promise);
// Do not complete the test and release the spies synchronously, to avoid running the actual implementations. // Do not complete the test and release the spies synchronously, to avoid running the actual implementations.
await promise; await promise;
@ -174,7 +170,7 @@ describe('BuildCleaner', () => {
it('should return a promise', () => { it('should return a promise', () => {
expect(promise).toEqual(jasmine.any(Promise)); expect(promise).toBeInstanceOf(Promise);
}); });
@ -184,43 +180,27 @@ describe('BuildCleaner', () => {
}); });
it('should reject if an error occurs while getting the files', done => { it('should reject if an error occurs while getting the files', async () => {
promise.catch(err => {
expect(err).toBe('Test');
done();
});
readdirCb('Test'); readdirCb('Test');
await expectAsync(promise).toBeRejectedWith('Test');
}); });
it('should resolve with the returned files (as numbers)', done => { it('should resolve with the returned files (as numbers)', async () => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, ['12', '34', '56']); readdirCb(null, ['12', '34', '56']);
await expectAsync(promise).toBeResolvedTo([12, 34, 56]);
}); });
it('should remove `HIDDEN_DIR_PREFIX` from the filenames', done => { it('should remove `HIDDEN_DIR_PREFIX` from the filenames', async () => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, [`${HIDDEN_DIR_PREFIX}12`, '34', `${HIDDEN_DIR_PREFIX}56`]); readdirCb(null, [`${HIDDEN_DIR_PREFIX}12`, '34', `${HIDDEN_DIR_PREFIX}56`]);
await expectAsync(promise).toBeResolvedTo([12, 34, 56]);
}); });
it('should ignore files with non-numeric (or zero) names', done => { it('should ignore files with non-numeric (or zero) names', async () => {
promise.then(result => {
expect(result).toEqual([12, 34, 56]);
done();
});
readdirCb(null, ['12', 'foo', '34', 'bar', '56', '000']); readdirCb(null, ['12', 'foo', '34', 'bar', '56', '000']);
await expectAsync(promise).toBeResolvedTo([12, 34, 56]);
}); });
}); });
@ -240,7 +220,7 @@ describe('BuildCleaner', () => {
it('should return a promise', () => { it('should return a promise', () => {
expect(promise).toEqual(jasmine.any(Promise)); expect(promise).toBeInstanceOf(Promise);
}); });
@ -249,23 +229,15 @@ describe('BuildCleaner', () => {
}); });
it('should reject if an error occurs while fetching PRs', done => { it('should reject if an error occurs while fetching PRs', async () => {
promise.catch(err => {
expect(err).toBe('Test');
done();
});
prDeferred.reject('Test'); prDeferred.reject('Test');
await expectAsync(promise).toBeRejectedWith('Test');
}); });
it('should resolve with the numbers of the fetched PRs', done => { it('should resolve with the numbers of the fetched PRs', async () => {
promise.then(prNumbers => {
expect(prNumbers).toEqual([1, 2, 3]);
done();
});
prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]); prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]);
await expectAsync(promise).toBeResolvedTo([1, 2, 3]);
}); });
}); });
@ -285,7 +257,7 @@ describe('BuildCleaner', () => {
it('should return a promise', () => { it('should return a promise', () => {
expect(promise).toEqual(jasmine.any(Promise)); expect(promise).toBeInstanceOf(Promise);
}); });
@ -295,33 +267,21 @@ describe('BuildCleaner', () => {
}); });
it('should reject if an error occurs while getting the files', done => { it('should reject if an error occurs while getting the files', async () => {
promise.catch(err => {
expect(err).toBe('Test');
done();
});
readdirCb('Test'); readdirCb('Test');
await expectAsync(promise).toBeRejectedWith('Test');
}); });
it('should resolve with the returned file names', done => { it('should resolve with the returned file names', async () => {
promise.then(result => {
expect(result).toEqual(EXISTING_DOWNLOADS);
done();
});
readdirCb(null, EXISTING_DOWNLOADS); readdirCb(null, EXISTING_DOWNLOADS);
await expectAsync(promise).toBeResolvedTo(EXISTING_DOWNLOADS);
}); });
it('should ignore files that do not match the artifactPath', done => { it('should ignore files that do not match the artifactPath', async () => {
promise.then(result => {
expect(result).toEqual(['10-ABCDEF-build.zip', '30-FFFFFFF-build.zip']);
done();
});
readdirCb(null, ['10-ABCDEF-build.zip', '20-AAAAAAA-otherfile.zip', '30-FFFFFFF-build.zip']); readdirCb(null, ['10-ABCDEF-build.zip', '20-AAAAAAA-otherfile.zip', '30-FFFFFFF-build.zip']);
await expectAsync(promise).toBeResolvedTo(['10-ABCDEF-build.zip', '30-FFFFFFF-build.zip']);
}); });
}); });
@ -339,7 +299,7 @@ describe('BuildCleaner', () => {
}); });
it('should test if the directory exists (and return if is does not)', () => { it('should test if the directory exists (and return if it does not)', () => {
shellTestSpy.and.returnValue(false); shellTestSpy.and.returnValue(false);
cleaner.removeDir('/foo/bar'); cleaner.removeDir('/foo/bar');
@ -356,22 +316,19 @@ describe('BuildCleaner', () => {
it('should make the directory and its content writable before removing', () => { it('should make the directory and its content writable before removing', () => {
shellRmSpy.and.callFake(() => expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a+w', '/foo/bar'));
cleaner.removeDir('/foo/bar'); cleaner.removeDir('/foo/bar');
expect(shellChmodSpy).toHaveBeenCalledBefore(shellRmSpy);
expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a+w', '/foo/bar');
expect(shellRmSpy).toHaveBeenCalled(); expect(shellRmSpy).toHaveBeenCalled();
}); });
it('should catch errors and log them', () => { it('should catch errors and log them', () => {
shellRmSpy.and.callFake(() => { shellRmSpy.and.throwError('Test');
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cleaner.removeDir('/foo/bar'); cleaner.removeDir('/foo/bar');
expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', 'Test'); expect(loggerErrorSpy).toHaveBeenCalledWith('ERROR: Unable to remove \'/foo/bar\' due to:', new Error('Test'));
}); });
}); });
@ -424,7 +381,7 @@ describe('BuildCleaner', () => {
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(0); expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(0);
cleanerRemoveDirSpy.calls.reset(); cleanerRemoveDirSpy.calls.reset();
(cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], []); cleaner.removeUnnecessaryBuilds([1, 2, 3, 4], []);
expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(8); expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(8);
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/1')); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/1'));
expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/2')); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith(normalize('/foo/bar/2'));

View File

@ -45,25 +45,15 @@ describe('CircleCIApi', () => {
const errorMessage = 'Invalid request'; const errorMessage = 'Invalid request';
const request = nock(BASE_URL).get(`/${buildNum}?circle-token=${TOKEN}`); const request = nock(BASE_URL).get(`/${buildNum}?circle-token=${TOKEN}`);
try {
request.replyWithError(errorMessage); request.replyWithError(errorMessage);
await api.getBuildInfo(buildNum); await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError(
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI build info request failed ` + `CircleCI build info request failed ` +
`(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`); `(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
try {
request.reply(404, errorMessage); request.reply(404, errorMessage);
await api.getBuildInfo(buildNum); await expectAsync(api.getBuildInfo(buildNum)).toBeRejectedWithError(
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI build info request failed ` + `CircleCI build info request failed ` +
`(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`); `(request to ${BASE_URL}/${buildNum}?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
}); });
}); });
@ -78,8 +68,7 @@ describe('CircleCIApi', () => {
.get(`/${buildNum}/artifacts?circle-token=${TOKEN}`) .get(`/${buildNum}/artifacts?circle-token=${TOKEN}`)
.reply(200, [artifact0, artifact1, artifact2]); .reply(200, [artifact0, artifact1, artifact2]);
const artifactUrl = await api.getBuildArtifactUrl(buildNum, 'some/path/1'); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeResolvedTo('https://url/1');
expect(artifactUrl).toEqual('https://url/1');
request.done(); request.done();
}); });
@ -90,25 +79,15 @@ describe('CircleCIApi', () => {
const errorMessage = 'Invalid request'; const errorMessage = 'Invalid request';
const request = nock(BASE_URL).get(`/${buildNum}/artifacts?circle-token=${TOKEN}`); const request = nock(BASE_URL).get(`/${buildNum}/artifacts?circle-token=${TOKEN}`);
try {
request.replyWithError(errorMessage); request.replyWithError(errorMessage);
await api.getBuildArtifactUrl(buildNum, 'some/path/1'); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError(
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI artifact URL request failed ` + `CircleCI artifact URL request failed ` +
`(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`); `(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
try {
request.reply(404, errorMessage); request.reply(404, errorMessage);
await api.getBuildArtifactUrl(buildNum, 'some/path/1'); await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/1')).toBeRejectedWithError(
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI artifact URL request failed ` + `CircleCI artifact URL request failed ` +
`(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`); `(request to ${BASE_URL}/${buildNum}/artifacts?circle-token=${TOKEN} failed, reason: ${errorMessage})`);
}
}); });
it('should throw an error if the response does not contain the specified artifact', async () => { it('should throw an error if the response does not contain the specified artifact', async () => {
@ -121,14 +100,9 @@ describe('CircleCIApi', () => {
.get(`/${buildNum}/artifacts?circle-token=${TOKEN}`) .get(`/${buildNum}/artifacts?circle-token=${TOKEN}`)
.reply(200, [artifact0, artifact1, artifact2]); .reply(200, [artifact0, artifact1, artifact2]);
try { await expectAsync(api.getBuildArtifactUrl(buildNum, 'some/path/3')).toBeRejectedWithError(
await api.getBuildArtifactUrl(buildNum, 'some/path/3');
throw new Error('Exception Expected');
} catch (err) {
expect(err.message).toEqual(
`CircleCI artifact URL request failed ` + `CircleCI artifact URL request failed ` +
`(Missing artifact (some/path/3) for CircleCI build: ${buildNum})`); `(Missing artifact (some/path/3) for CircleCI build: ${buildNum})`);
}
}); });
}); });
}); });

View File

@ -118,7 +118,7 @@ describe('GithubApi', () => {
it('should return a promise', () => { it('should return a promise', () => {
expect((api as any).getPaginated()).toEqual(jasmine.any(Promise)); expect((api as any).getPaginated()).toBeInstanceOf(Promise);
}); });
@ -131,45 +131,30 @@ describe('GithubApi', () => {
}); });
it('should reject if the request fails', done => { it('should reject if the request fails', async () => {
(api as any).getPaginated('/foo/bar').catch((err: any) => { const responsePromise = (api as any).getPaginated('/foo/bar');
expect(err).toBe('Test');
done();
});
deferreds[0].reject('Test'); deferreds[0].reject('Test');
await expectAsync(responsePromise).toBeRejectedWith('Test');
}); });
it('should resolve with the returned items', done => { it('should resolve with the returned items', async () => {
const items = [{id: 1}, {id: 2}]; const items = [{id: 1}, {id: 2}];
const responsePromise = (api as any).getPaginated('/foo/bar');
(api as any).getPaginated('/foo/bar').then((data: any) => {
expect(data).toEqual(items);
done();
});
deferreds[0].resolve(items); deferreds[0].resolve(items);
await expectAsync(responsePromise).toBeResolvedTo(items);
}); });
it('should iteratively call \'get()\' to fetch all items', done => { it('should iteratively call \'get()\' to fetch all items', async () => {
// Create an array or 250 objects. // Create an array or 250 objects.
const allItems = '.'.repeat(250).split('').map((_, i) => ({id: i})); const allItems = new Array(250).fill(null).map((_, i) => ({id: i}));
const apiGetSpy = api.get as jasmine.Spy; const apiGetSpy = api.get as jasmine.Spy;
(api as any).getPaginated('/foo/bar', {baz: 'qux'}).then((data: any) => {
const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100}); const paramsForPage = (page: number) => ({baz: 'qux', page, per_page: 100});
expect(apiGetSpy).toHaveBeenCalledTimes(3); const responsePromise = (api as any).getPaginated('/foo/bar', {baz: 'qux'});
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
expect(data).toEqual(allItems);
done();
});
deferreds[0].resolve(allItems.slice(0, 100)); deferreds[0].resolve(allItems.slice(0, 100));
setTimeout(() => { setTimeout(() => {
@ -178,6 +163,13 @@ describe('GithubApi', () => {
deferreds[2].resolve(allItems.slice(200)); deferreds[2].resolve(allItems.slice(200));
}, 0); }, 0);
}, 0); }, 0);
await expectAsync(responsePromise).toBeResolvedTo(allItems);
expect(apiGetSpy).toHaveBeenCalledTimes(3);
expect(apiGetSpy.calls.argsFor(0)).toEqual(['/foo/bar', paramsForPage(1)]);
expect(apiGetSpy.calls.argsFor(1)).toEqual(['/foo/bar', paramsForPage(2)]);
expect(apiGetSpy.calls.argsFor(2)).toEqual(['/foo/bar', paramsForPage(3)]);
}); });
}); });
@ -218,7 +210,7 @@ describe('GithubApi', () => {
describe('request()', () => { describe('request()', () => {
it('should return a promise', () => { it('should return a promise', () => {
nock('https://api.github.com').get('/').reply(200); nock('https://api.github.com').get('/').reply(200);
expect((api as any).request()).toEqual(jasmine.any(Promise)); expect((api as any).request()).toBeInstanceOf(Promise);
}); });
@ -247,9 +239,8 @@ describe('GithubApi', () => {
nock('https://api.github.com') nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.replyWithError('Test'); .replyWithError('Test');
let message = 'Failed to reject error';
await (api as any).request('method', '/path').catch((err: any) => message = err.message); await expectAsync((api as any).request('method', '/path')).toBeRejectedWithError('Test');
expect(message).toEqual('Test');
}); });
@ -263,81 +254,69 @@ describe('GithubApi', () => {
}); });
it('should reject if response statusCode is <200', done => { it('should reject if response statusCode is <200', async () => {
const requestHandler = nock('https://api.github.com') const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.reply(199); .reply(199);
const responsePromise = (api as any).request('method', '/path');
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('failed'));
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('status: 199'));
(api as any).request('method', '/path')
.catch((err: string) => {
expect(err).toContain('failed');
expect(err).toContain('status: 199');
done();
});
requestHandler.done(); requestHandler.done();
}); });
it('should reject if response statusCode is >=400', done => { it('should reject if response statusCode is >=400', async () => {
const requestHandler = nock('https://api.github.com') const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.reply(400); .reply(400);
const responsePromise = (api as any).request('method', '/path');
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('failed'));
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('status: 400'));
(api as any).request('method', '/path')
.catch((err: string) => {
expect(err).toContain('failed');
expect(err).toContain('status: 400');
done();
});
requestHandler.done(); requestHandler.done();
}); });
it('should include the response text in the rejection message', done => { it('should include the response text in the rejection message', async () => {
const requestHandler = nock('https://api.github.com') const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.reply(500, 'Test'); .reply(500, 'Test');
const responsePromise = (api as any).request('method', '/path');
await expectAsync(responsePromise).toBeRejectedWith(jasmine.stringMatching('Test'));
(api as any).request('method', '/path')
.catch((err: string) => {
expect(err).toContain('Test');
done();
});
requestHandler.done(); requestHandler.done();
}); });
it('should resolve if returned statusCode is >=200 and <400', done => { it('should resolve if returned statusCode is >=200 and <400', async () => {
const requestHandler = nock('https://api.github.com') const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.reply(200); .reply(200);
(api as any).request('method', '/path').then(done); await expectAsync((api as any).request('method', '/path')).toBeResolved();
requestHandler.done(); requestHandler.done();
}); });
it('should parse the response body into an object using \'JSON.parse\'', done => { it('should parse the response body into an object using \'JSON.parse\'', async () => {
const requestHandler = nock('https://api.github.com') const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.reply(300, '{"foo": "bar"}'); .reply(300, '{"foo": "bar"}');
(api as any).request('method', '/path').then((data: any) => { await expectAsync((api as any).request('method', '/path')).toBeResolvedTo({foo: 'bar'});
expect(data).toEqual({foo: 'bar'});
done();
});
requestHandler.done(); requestHandler.done();
}); });
it('should reject if the response text is malformed JSON', done => { it('should reject if the response text is malformed JSON', async () => {
const requestHandler = nock('https://api.github.com') const requestHandler = nock('https://api.github.com')
.intercept('/path', 'method') .intercept('/path', 'method')
.reply(300, '}'); .reply(300, '}');
(api as any).request('method', '/path').catch((err: any) => { await expectAsync((api as any).request('method', '/path')).toBeRejectedWithError(SyntaxError);
expect(err).toEqual(jasmine.any(SyntaxError));
done();
});
requestHandler.done(); requestHandler.done();
}); });

View File

@ -53,21 +53,15 @@ describe('GithubPullRequests', () => {
}); });
it('should reject if the request fails', done => { it('should reject if the request fails', async () => {
githubApi.post.and.callFake(() => Promise.reject('Test')); githubApi.post.and.rejectWith('Test');
prs.addComment(42, 'body').catch(err => { await expectAsync(prs.addComment(42, 'body')).toBeRejectedWith('Test');
expect(err).toBe('Test');
done();
});
}); });
it('should resolve with the data from the Github POST', done => { it('should resolve with the data from the Github POST', async () => {
githubApi.post.and.resolveTo('Test'); githubApi.post.and.resolveTo('Test');
prs.addComment(42, 'body').then(data => { await expectAsync(prs.addComment(42, 'body')).toBeResolvedTo('Test');
expect(data).toBe('Test');
done();
});
}); });
}); });
@ -87,13 +81,11 @@ describe('GithubPullRequests', () => {
}); });
it('should resolve with the data returned from GitHub', done => { it('should resolve with the data returned from GitHub', async () => {
const expected: any = {number: 42}; const expected: any = {number: 42};
githubApi.get.and.callFake(() => Promise.resolve(expected)); githubApi.get.and.resolveTo(expected);
prs.fetch(42).then(data => {
expect(data).toEqual(expected); await expectAsync(prs.fetch(42)).toBeResolvedTo(expected);
done();
});
}); });
}); });
@ -152,13 +144,11 @@ describe('GithubPullRequests', () => {
}); });
it('should resolve with the data returned from GitHub', done => { it('should resolve with the data returned from GitHub', async () => {
const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}]; const expected: any = [{sha: 'ABCDE', filename: 'a/b/c'}, {sha: '12345', filename: 'x/y/z'}];
githubApi.getPaginated.and.callFake(() => Promise.resolve(expected)); githubApi.getPaginated.and.resolveTo(expected);
prs.fetchFiles(42).then(data => {
expect(data).toEqual(expected); await expectAsync(prs.fetchFiles(42)).toBeResolvedTo(expected);
done();
});
}); });
}); });

View File

@ -16,6 +16,7 @@ describe('GithubTeams', () => {
expect(() => new GithubTeams(githubApi, '')). expect(() => new GithubTeams(githubApi, '')).
toThrowError('Missing or empty required parameter \'githubOrg\'!'); toThrowError('Missing or empty required parameter \'githubOrg\'!');
}); });
}); });
@ -57,98 +58,75 @@ describe('GithubTeams', () => {
it('should return a promise', () => { it('should return a promise', () => {
githubApi.get.and.resolveTo(); githubApi.get.and.resolveTo();
const promise = teams.isMemberById('user', [1]); const promise = teams.isMemberById('user', [1]);
expect(promise).toEqual(jasmine.any(Promise)); expect(promise).toBeInstanceOf(Promise);
}); });
it('should resolve with false if called with an empty array', done => { it('should resolve with false if called with an empty array', async () => {
teams.isMemberById('user', []).then(isMember => { await expectAsync(teams.isMemberById('user', [])).toBeResolvedTo(false);
expect(isMember).toBe(false);
expect(githubApi.get).not.toHaveBeenCalled(); expect(githubApi.get).not.toHaveBeenCalled();
done();
});
}); });
it('should call \'get()\' with the correct pathname', done => { it('should call \'get()\' with the correct pathname', async () => {
githubApi.get.and.resolveTo(); githubApi.get.and.resolveTo();
teams.isMemberById('user', [1]).then(() => { await teams.isMemberById('user', [1]);
expect(githubApi.get).toHaveBeenCalledWith('/teams/1/memberships/user'); expect(githubApi.get).toHaveBeenCalledWith('/teams/1/memberships/user');
done();
});
}); });
it('should resolve with false if \'get()\' rejects', done => { it('should resolve with false if \'get()\' rejects', async () => {
githubApi.get.and.callFake(() => Promise.reject(null)); githubApi.get.and.rejectWith(null);
teams.isMemberById('user', [1]).then(isMember => {
expect(isMember).toBe(false); await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(false);
expect(githubApi.get).toHaveBeenCalled(); expect(githubApi.get).toHaveBeenCalled();
done();
});
}); });
it('should resolve with false if the membership is not active', done => { it('should resolve with false if the membership is not active', async () => {
githubApi.get.and.resolveTo({state: 'pending'}); githubApi.get.and.resolveTo({state: 'pending'});
teams.isMemberById('user', [1]).then(isMember => {
expect(isMember).toBe(false); await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(false);
expect(githubApi.get).toHaveBeenCalled(); expect(githubApi.get).toHaveBeenCalled();
done();
});
}); });
it('should resolve with true if the membership is active', done => { it('should resolve with true if the membership is active', async () => {
githubApi.get.and.resolveTo({state: 'active'}); githubApi.get.and.resolveTo({state: 'active'});
teams.isMemberById('user', [1]).then(isMember => { await expectAsync(teams.isMemberById('user', [1])).toBeResolvedTo(true);
expect(isMember).toBe(true);
done();
});
}); });
it('should sequentially call \'get()\' until an active membership is found', done => { it('should sequentially call \'get()\' until an active membership is found', async () => {
const trainedResponses: {[pathname: string]: Promise<any>} = { githubApi.get.
'/teams/1/memberships/user': Promise.resolve({state: 'pending'}), withArgs('/teams/1/memberships/user').and.resolveTo({state: 'pending'}).
'/teams/2/memberships/user': Promise.reject(null), withArgs('/teams/2/memberships/user').and.rejectWith(null).
'/teams/3/memberships/user': Promise.resolve({state: 'active'}), withArgs('/teams/3/memberships/user').and.resolveTo({state: 'active'});
};
githubApi.get.and.callFake((pathname: string) => trainedResponses[pathname]);
teams.isMemberById('user', [1, 2, 3, 4]).then(isMember => { await expectAsync(teams.isMemberById('user', [1, 2, 3, 4])).toBeResolvedTo(true);
expect(isMember).toBe(true);
expect(githubApi.get).toHaveBeenCalledTimes(3); expect(githubApi.get).toHaveBeenCalledTimes(3);
expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user'); expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user');
expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user'); expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user');
expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user'); expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user');
done();
});
}); });
it('should resolve with false if no active membership is found', done => { it('should resolve with false if no active membership is found', async () => {
const trainedResponses: {[pathname: string]: Promise<any>} = { githubApi.get.
'/teams/1/memberships/user': Promise.resolve({state: 'pending'}), withArgs('/teams/1/memberships/user').and.resolveTo({state: 'pending'}).
'/teams/2/memberships/user': Promise.reject(null), withArgs('/teams/2/memberships/user').and.rejectWith(null).
'/teams/3/memberships/user': Promise.resolve({state: 'not active'}), withArgs('/teams/3/memberships/user').and.resolveTo({state: 'not active'}).
'/teams/4/memberships/user': Promise.reject(null), withArgs('/teams/4/memberships/user').and.rejectWith(null);
};
githubApi.get.and.callFake((pathname: string) => trainedResponses[pathname]);
teams.isMemberById('user', [1, 2, 3, 4]).then(isMember => { await expectAsync(teams.isMemberById('user', [1, 2, 3, 4])).toBeResolvedTo(false);
expect(isMember).toBe(false);
expect(githubApi.get).toHaveBeenCalledTimes(4); expect(githubApi.get).toHaveBeenCalledTimes(4);
expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user'); expect(githubApi.get.calls.argsFor(0)[0]).toBe('/teams/1/memberships/user');
expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user'); expect(githubApi.get.calls.argsFor(1)[0]).toBe('/teams/2/memberships/user');
expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user'); expect(githubApi.get.calls.argsFor(2)[0]).toBe('/teams/3/memberships/user');
expect(githubApi.get.calls.argsFor(3)[0]).toBe('/teams/4/memberships/user'); expect(githubApi.get.calls.argsFor(3)[0]).toBe('/teams/4/memberships/user');
done();
});
}); });
}); });
@ -162,14 +140,13 @@ describe('GithubTeams', () => {
beforeEach(() => { beforeEach(() => {
teams = new GithubTeams(githubApi, 'foo'); teams = new GithubTeams(githubApi, 'foo');
const mockResponse = Promise.resolve([{id: 1, slug: 'team1'}, {id: 2, slug: 'team2'}]); teamsFetchAllSpy = spyOn(teams, 'fetchAll').and.resolveTo([{id: 1, slug: 'team1'}, {id: 2, slug: 'team2'}]);
teamsFetchAllSpy = spyOn(teams, 'fetchAll').and.returnValue(mockResponse);
teamsIsMemberByIdSpy = spyOn(teams, 'isMemberById'); teamsIsMemberByIdSpy = spyOn(teams, 'isMemberById');
}); });
it('should return a promise', () => { it('should return a promise', () => {
expect(teams.isMemberBySlug('user', ['team-slug'])).toEqual(jasmine.any(Promise)); expect(teams.isMemberBySlug('user', ['team-slug'])).toBeInstanceOf(Promise);
}); });
@ -179,55 +156,46 @@ describe('GithubTeams', () => {
}); });
it('should resolve with false if \'fetchAll()\' rejects', done => { it('should resolve with false if \'fetchAll()\' rejects', async () => {
teamsFetchAllSpy.and.callFake(() => Promise.reject(null)); teamsFetchAllSpy.and.rejectWith(null);
teams.isMemberBySlug('user', ['team-slug']).then(isMember => { await expectAsync(teams.isMemberBySlug('user', ['team-slug'])).toBeResolvedTo(false);
expect(isMember).toBe(false);
done();
});
}); });
it('should call \'isMemberById()\' with the correct params if no team is found', done => { it('should call \'isMemberById()\' with the correct params if no team is found', async () => {
teams.isMemberBySlug('user', ['no-match']).then(() => { await teams.isMemberBySlug('user', ['no-match']);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', []); expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', []);
done();
});
}); });
it('should call \'isMemberById()\' with the correct params if teams are found', done => { it('should call \'isMemberById()\' with the correct params if teams are found', async () => {
const spy = teamsIsMemberByIdSpy; await teams.isMemberBySlug('userA', ['team1']);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userA', [1]);
Promise.all([ await teams.isMemberBySlug('userB', ['team2']);
teams.isMemberBySlug('user', ['team1']).then(() => expect(spy).toHaveBeenCalledWith('user', [1])), expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userB', [2]);
teams.isMemberBySlug('user', ['team2']).then(() => expect(spy).toHaveBeenCalledWith('user', [2])),
teams.isMemberBySlug('user', ['team1', 'team2']).then(() => expect(spy).toHaveBeenCalledWith('user', [1, 2])), await teams.isMemberBySlug('userC', ['team1', 'team2']);
]).then(done); expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userC', [1, 2]);
}); });
it('should resolve with false if \'isMemberById()\' rejects', done => { it('should resolve with false if \'isMemberById()\' rejects', async () => {
teamsIsMemberByIdSpy.and.callFake(() => Promise.reject(null)); teamsIsMemberByIdSpy.and.rejectWith(null);
teams.isMemberBySlug('user', ['team1']).then(isMember => {
expect(isMember).toBe(false); await expectAsync(teams.isMemberBySlug('user', ['team1'])).toBeResolvedTo(false);
expect(teamsIsMemberByIdSpy).toHaveBeenCalled(); expect(teamsIsMemberByIdSpy).toHaveBeenCalled();
done();
});
}); });
it('should resolve with the value \'isMemberById()\' resolves with', async () => { it('should resolve with the value \'isMemberById()\' resolves with', async () => {
teamsIsMemberByIdSpy.and.resolveTo(true);
await expectAsync(teams.isMemberBySlug('userA', ['team1'])).toBeResolvedTo(true);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userA', [1]);
teamsIsMemberByIdSpy.and.callFake(() => Promise.resolve(true)); teamsIsMemberByIdSpy.and.resolveTo(false);
const isMember1 = await teams.isMemberBySlug('user', ['team1']); await expectAsync(teams.isMemberBySlug('userB', ['team1'])).toBeResolvedTo(false);
expect(isMember1).toBe(true); expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('userB', [1]);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', [1]);
teamsIsMemberByIdSpy.and.callFake(() => Promise.resolve(false));
const isMember2 = await teams.isMemberBySlug('user', ['team1']);
expect(isMember2).toBe(false);
expect(teamsIsMemberByIdSpy).toHaveBeenCalledWith('user', [1]);
}); });
}); });

View File

@ -9,7 +9,8 @@ import {Logger} from '../../lib/common/utils';
import {BuildCreator} from '../../lib/preview-server/build-creator'; import {BuildCreator} from '../../lib/preview-server/build-creator';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events'; import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
import {PreviewServerError} from '../../lib/preview-server/preview-error'; import {PreviewServerError} from '../../lib/preview-server/preview-error';
import {expectToBePreviewServerError} from './helpers'; import {customAsyncMatchers} from './jasmine-custom-async-matchers';
// Tests // Tests
describe('BuildCreator', () => { describe('BuildCreator', () => {
@ -24,6 +25,7 @@ describe('BuildCreator', () => {
const publicShaDir = path.join(publicPrDir, shortSha); const publicShaDir = path.join(publicPrDir, shortSha);
let bc: BuildCreator; let bc: BuildCreator;
beforeEach(() => jasmine.addAsyncMatchers(customAsyncMatchers));
beforeEach(() => bc = new BuildCreator(buildsDir)); beforeEach(() => bc = new BuildCreator(buildsDir));
@ -35,8 +37,8 @@ describe('BuildCreator', () => {
it('should extend EventEmitter', () => { it('should extend EventEmitter', () => {
expect(bc).toEqual(jasmine.any(BuildCreator)); expect(bc).toBeInstanceOf(BuildCreator);
expect(bc).toEqual(jasmine.any(EventEmitter)); expect(bc).toBeInstanceOf(EventEmitter);
expect(Object.getPrototypeOf(bc)).toBe(BuildCreator.prototype); expect(Object.getPrototypeOf(bc)).toBe(BuildCreator.prototype);
}); });
@ -67,47 +69,43 @@ describe('BuildCreator', () => {
const shaDir = isPublic ? publicShaDir : hiddenShaDir; const shaDir = isPublic ? publicShaDir : hiddenShaDir;
it('should return a promise', done => { it('should return a promise', async () => {
const promise = bc.create(pr, sha, archive, isPublic); const promise = bc.create(pr, sha, archive, isPublic);
promise.then(done); // Do not complete the test (and release the spies) synchronously expect(promise).toBeInstanceOf(Promise);
// to avoid running the actual `extractArchive()`.
expect(promise).toEqual(jasmine.any(Promise)); // Do not complete the test (and release the spies) synchronously to avoid running the actual
// `extractArchive()`.
await promise;
}); });
it('should update the PR\'s visibility first if necessary', done => { it('should update the PR\'s visibility first if necessary', async () => {
bcUpdatePrVisibilitySpy.and.callFake(() => expect(shellMkdirSpy).not.toHaveBeenCalled()); await bc.create(pr, sha, archive, isPublic);
bc.create(pr, sha, archive, isPublic). expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledBefore(shellMkdirSpy);
then(() => {
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic); expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
expect(shellMkdirSpy).toHaveBeenCalled(); expect(shellMkdirSpy).toHaveBeenCalled();
}).
then(done);
}); });
it('should create the build directory (and any parent directories)', done => { it('should create the build directory (and any parent directories)', async () => {
bc.create(pr, sha, archive, isPublic). await bc.create(pr, sha, archive, isPublic);
then(() => expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir)). expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir);
then(done);
}); });
it('should extract the archive contents into the build directory', done => { it('should extract the archive contents into the build directory', async () => {
bc.create(pr, sha, archive, isPublic). await bc.create(pr, sha, archive, isPublic);
then(() => expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir)). expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir);
then(done);
}); });
it('should emit a CreatedBuildEvent on success', done => { it('should emit a CreatedBuildEvent on success', async () => {
let emitted = false; let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => { bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => {
expect(type).toBe(CreatedBuildEvent.type); expect(type).toBe(CreatedBuildEvent.type);
expect(evt).toEqual(jasmine.any(CreatedBuildEvent)); expect(evt).toBeInstanceOf(CreatedBuildEvent);
expect(evt.pr).toBe(+pr); expect(evt.pr).toBe(+pr);
expect(evt.sha).toBe(shortSha); expect(evt.sha).toBe(shortSha);
expect(evt.isPublic).toBe(isPublic); expect(evt.isPublic).toBe(isPublic);
@ -115,130 +113,108 @@ describe('BuildCreator', () => {
emitted = true; emitted = true;
}); });
bc.create(pr, sha, archive, isPublic). await bc.create(pr, sha, archive, isPublic);
then(() => expect(emitted).toBe(true)). expect(emitted).toBe(true);
then(done);
}); });
describe('on error', () => { describe('on error', () => {
let existsValues: {[dir: string]: boolean};
beforeEach(() => { beforeEach(() => {
existsValues = { bcExistsSpy.and.returnValue(false);
[prDir]: false,
[shaDir]: false,
};
bcExistsSpy.and.callFake((dir: string) => existsValues[dir]);
}); });
it('should abort and skip further operations if changing the PR\'s visibility fails', done => { it('should abort and skip further operations if changing the PR\'s visibility fails', async () => {
const mockError = new PreviewServerError(543, 'Test'); const mockError = new PreviewServerError(543, 'Test');
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject(mockError)); bcUpdatePrVisibilitySpy.and.rejectWith(mockError);
bc.create(pr, sha, archive, isPublic).catch(err => { await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWith(mockError);
expect(err).toBe(mockError);
expect(bcExistsSpy).not.toHaveBeenCalled(); expect(bcExistsSpy).not.toHaveBeenCalled();
expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should abort and skip further operations if the build does already exist', done => { it('should abort and skip further operations if the build does already exist', async () => {
existsValues[shaDir] = true; bcExistsSpy.withArgs(shaDir).and.returnValue(true);
bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public'; await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
expectToBePreviewServerError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); 409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should detect existing build directory after visibility change', done => { it('should detect existing build directory after visibility change', async () => {
bcUpdatePrVisibilitySpy.and.callFake(() => existsValues[prDir] = existsValues[shaDir] = true); bcUpdatePrVisibilitySpy.and.callFake(() => bcExistsSpy.and.returnValue(true));
expect(bcExistsSpy(prDir)).toBe(false); expect(bcExistsSpy(prDir)).toBe(false);
expect(bcExistsSpy(shaDir)).toBe(false); expect(bcExistsSpy(shaDir)).toBe(false);
bc.create(pr, sha, archive, isPublic).catch(err => { await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
const publicOrNot = isPublic ? 'public' : 'non-public'; 409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`);
expectToBePreviewServerError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should abort and skip further operations if it fails to create the directories', done => { it('should abort and skip further operations if it fails to create the directories', async () => {
shellMkdirSpy.and.throwError(''); shellMkdirSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
expect(shellMkdirSpy).toHaveBeenCalled(); expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should abort and skip further operations if it fails to extract the archive', done => { it('should abort and skip further operations if it fails to extract the archive', async () => {
bcExtractArchiveSpy.and.throwError(''); bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
expect(shellMkdirSpy).toHaveBeenCalled(); expect(shellMkdirSpy).toHaveBeenCalled();
expect(bcExtractArchiveSpy).toHaveBeenCalled(); expect(bcExtractArchiveSpy).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should delete the PR directory (for new PR)', done => { it('should delete the PR directory (for new PR)', async () => {
bcExtractArchiveSpy.and.throwError(''); bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => {
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir); expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir);
done();
});
}); });
it('should delete the SHA directory (for existing PR)', done => { it('should delete the SHA directory (for existing PR)', async () => {
existsValues[prDir] = true; bcExistsSpy.withArgs(prDir).and.returnValue(true);
bcExtractArchiveSpy.and.throwError(''); bcExtractArchiveSpy.and.throwError('');
bc.create(pr, sha, archive, isPublic).catch(() => { await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir); expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir);
done();
});
}); });
it('should reject with an PreviewServerError', done => { it('should reject with an PreviewServerError', async () => {
// tslint:disable-next-line: no-string-throw // tslint:disable-next-line: no-string-throw
shellMkdirSpy.and.callFake(() => { throw 'Test'; }); shellMkdirSpy.and.callFake(() => { throw 'Test'; });
bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBePreviewServerError(err, 500, `Error while creating preview at: ${shaDir}\nTest`); await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
done(); 500, `Error while creating preview at: ${shaDir}\nTest`);
});
}); });
it('should pass PreviewServerError instances unmodified', done => { it('should pass PreviewServerError instances unmodified', async () => {
shellMkdirSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); }); shellMkdirSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
bc.create(pr, sha, archive, isPublic).catch(err => { await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(543, 'Test');
expectToBePreviewServerError(err, 543, 'Test');
done();
});
}); });
}); });
@ -265,12 +241,12 @@ describe('BuildCreator', () => {
}); });
it('should return a promise', done => { it('should return a promise', async () => {
const promise = bc.updatePrVisibility(pr, true); const promise = bc.updatePrVisibility(pr, true);
promise.then(done); // Do not complete the test (and release the spies) synchronously expect(promise).toBeInstanceOf(Promise);
// to avoid running the actual `extractArchive()`.
expect(promise).toEqual(jasmine.any(Promise)); // Do not complete the test (and release the spies) synchronously to avoid running the actual `extractArchive()`.
await promise;
}); });
@ -279,58 +255,53 @@ describe('BuildCreator', () => {
const newPrDir = makePublic ? publicPrDir : hiddenPrDir; const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
it('should rename the directory', done => { it('should rename the directory', async () => {
bc.updatePrVisibility(pr, makePublic). await bc.updatePrVisibility(pr, makePublic);
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)). expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir);
then(done);
}); });
describe('when the visibility is updated', () => { describe('when the visibility is updated', () => {
it('should resolve to true', done => { it('should resolve to true', async () => {
bc.updatePrVisibility(pr, makePublic). await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(true);
then(result => expect(result).toBe(true)).
then(done);
}); });
it('should rename the directory', done => { it('should rename the directory', async () => {
bc.updatePrVisibility(pr, makePublic). await bc.updatePrVisibility(pr, makePublic);
then(() => expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir)). expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir);
then(done);
}); });
it('should emit a ChangedPrVisibilityEvent on success', done => { it('should emit a ChangedPrVisibilityEvent on success', async () => {
let emitted = false; let emitted = false;
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => { bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(type).toBe(ChangedPrVisibilityEvent.type); expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent)); expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent);
expect(evt.pr).toBe(+pr); expect(evt.pr).toBe(+pr);
expect(evt.shas).toEqual(jasmine.any(Array)); expect(evt.shas).toBeInstanceOf(Array);
expect(evt.isPublic).toBe(makePublic); expect(evt.isPublic).toBe(makePublic);
emitted = true; emitted = true;
}); });
bc.updatePrVisibility(pr, makePublic). await bc.updatePrVisibility(pr, makePublic);
then(() => expect(emitted).toBe(true)). expect(emitted).toBe(true);
then(done);
}); });
it('should include all shas in the emitted event', done => { it('should include all shas in the emitted event', async () => {
const shas = ['foo', 'bar', 'baz']; const shas = ['foo', 'bar', 'baz'];
let emitted = false; let emitted = false;
bcListShasByDate.and.callFake(() => Promise.resolve(shas)); bcListShasByDate.and.resolveTo(shas);
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => { bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir); expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
expect(type).toBe(ChangedPrVisibilityEvent.type); expect(type).toBe(ChangedPrVisibilityEvent.type);
expect(evt).toEqual(jasmine.any(ChangedPrVisibilityEvent)); expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent);
expect(evt.pr).toBe(+pr); expect(evt.pr).toBe(+pr);
expect(evt.shas).toBe(shas); expect(evt.shas).toBe(shas);
expect(evt.isPublic).toBe(makePublic); expect(evt.isPublic).toBe(makePublic);
@ -338,94 +309,82 @@ describe('BuildCreator', () => {
emitted = true; emitted = true;
}); });
bc.updatePrVisibility(pr, makePublic). await bc.updatePrVisibility(pr, makePublic);
then(() => expect(emitted).toBe(true)). expect(emitted).toBe(true);
then(done);
}); });
}); });
it('should do nothing if the visibility is already up-to-date', done => { it('should do nothing if the visibility is already up-to-date', async () => {
bcExistsSpy.and.callFake((dir: string) => dir === newPrDir); bcExistsSpy.and.callFake((dir: string) => dir === newPrDir);
bc.updatePrVisibility(pr, makePublic).
then(result => { await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false);
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled(); expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled(); expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
}); });
it('should do nothing if the PR directory does not exist', done => { it('should do nothing if the PR directory does not exist', async () => {
bcExistsSpy.and.returnValue(false); bcExistsSpy.and.returnValue(false);
bc.updatePrVisibility(pr, makePublic).
then(result => { await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false);
expect(result).toBe(false);
expect(shellMvSpy).not.toHaveBeenCalled(); expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled(); expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
}).
then(done);
}); });
describe('on error', () => { describe('on error', () => {
it('should abort and skip further operations if both directories exist', done => { it('should abort and skip further operations if both directories exist', async () => {
bcExistsSpy.and.returnValue(true); bcExistsSpy.and.returnValue(true);
bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBePreviewServerError(err, 409, await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(
`Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`); 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled(); expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled(); expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should abort and skip further operations if it fails to rename the directory', done => { it('should abort and skip further operations if it fails to rename the directory', async () => {
shellMvSpy.and.throwError(''); shellMvSpy.and.throwError('');
bc.updatePrVisibility(pr, makePublic).catch(() => {
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected();
expect(shellMvSpy).toHaveBeenCalled(); expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled(); expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should abort and skip further operations if it fails to list the SHAs', done => { it('should abort and skip further operations if it fails to list the SHAs', async () => {
bcListShasByDate.and.throwError(''); bcListShasByDate.and.throwError('');
bc.updatePrVisibility(pr, makePublic).catch(() => {
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected();
expect(shellMvSpy).toHaveBeenCalled(); expect(shellMvSpy).toHaveBeenCalled();
expect(bcListShasByDate).toHaveBeenCalled(); expect(bcListShasByDate).toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
done();
});
}); });
it('should reject with an PreviewServerError', done => { it('should reject with an PreviewServerError', async () => {
// tslint:disable-next-line: no-string-throw // tslint:disable-next-line: no-string-throw
shellMvSpy.and.callFake(() => { throw 'Test'; }); shellMvSpy.and.callFake(() => { throw 'Test'; });
bc.updatePrVisibility(pr, makePublic).catch(err => { await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(
expectToBePreviewServerError(err, 500, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
`Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
done();
});
}); });
it('should pass PreviewServerError instances unmodified', done => { it('should pass PreviewServerError instances unmodified', async () => {
shellMvSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); }); shellMvSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
bc.updatePrVisibility(pr, makePublic).catch(err => { await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(543, 'Test');
expectToBePreviewServerError(err, 543, 'Test');
done();
});
}); });
}); });
@ -450,7 +409,7 @@ describe('BuildCreator', () => {
it('should return a promise', () => { it('should return a promise', () => {
expect((bc as any).exists('foo')).toEqual(jasmine.any(Promise)); expect((bc as any).exists('foo')).toBeInstanceOf(Promise);
}); });
@ -460,25 +419,29 @@ describe('BuildCreator', () => {
}); });
it('should resolve with \'true\' if \'fs.access()\' succeeds', done => { it('should resolve with \'true\' if \'fs.access()\' succeeds', async () => {
Promise. const existsPromises = [
all([(bc as any).exists('foo'), (bc as any).exists('bar')]). (bc as any).exists('foo'),
then(results => expect(results).toEqual([true, true])). (bc as any).exists('bar'),
then(done); ];
fsAccessCbs[0](); fsAccessCbs[0]();
fsAccessCbs[1](null); fsAccessCbs[1](null);
await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([true, true]);
}); });
it('should resolve with \'false\' if \'fs.access()\' errors', done => { it('should resolve with \'false\' if \'fs.access()\' errors', async () => {
Promise. const existsPromises = [
all([(bc as any).exists('foo'), (bc as any).exists('bar')]). (bc as any).exists('foo'),
then(results => expect(results).toEqual([false, false])). (bc as any).exists('bar'),
then(done); ];
fsAccessCbs[0]('Error'); fsAccessCbs[0]('Error');
fsAccessCbs[1](new Error()); fsAccessCbs[1](new Error());
await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([false, false]);
}); });
}); });
@ -505,7 +468,7 @@ describe('BuildCreator', () => {
it('should return a promise', () => { it('should return a promise', () => {
expect((bc as any).extractArchive('foo', 'bar')).toEqual(jasmine.any(Promise)); expect((bc as any).extractArchive('foo', 'bar')).toBeInstanceOf(Promise);
}); });
@ -517,78 +480,68 @@ describe('BuildCreator', () => {
}); });
it('should log (as a warning) any stderr output if extracting succeeded', done => { it('should log (as a warning) any stderr output if extracting succeeded', async () => {
(bc as any).extractArchive('foo', 'bar'). const extractPromise = (bc as any).extractArchive('foo', 'bar');
then(() => expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr')).
then(done);
cpExecCbs[0](null, 'This is stdout', 'This is stderr'); cpExecCbs[0](null, 'This is stdout', 'This is stderr');
await expectAsync(extractPromise).toBeResolved();
expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr');
}); });
it('should make the build directory non-writable', done => { it('should make the build directory non-writable', async () => {
(bc as any).extractArchive('foo', 'bar'). const extractPromise = (bc as any).extractArchive('foo', 'bar');
then(() => expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a-w', 'bar')).
then(done);
cpExecCbs[0](); cpExecCbs[0]();
await expectAsync(extractPromise).toBeResolved();
expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a-w', 'bar');
}); });
it('should delete the build artifact file on success', done => { it('should delete the build artifact file on success', async () => {
(bc as any).extractArchive('input/file', 'output/dir'). const extractPromise = (bc as any).extractArchive('input/file', 'output/dir');
then(() => expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file')).
then(done);
cpExecCbs[0](); cpExecCbs[0]();
await expectAsync(extractPromise).toBeResolved();
expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file');
}); });
describe('on error', () => { describe('on error', () => {
it('should abort and skip further operations if it fails to extract the archive', done => { it('should abort and skip further operations if it fails to extract the archive', async () => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => { const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0]('Test');
await expectAsync(extractPromise).toBeRejectedWith('Test');
expect(shellChmodSpy).not.toHaveBeenCalled(); expect(shellChmodSpy).not.toHaveBeenCalled();
expect(shellRmSpy).not.toHaveBeenCalled(); expect(shellRmSpy).not.toHaveBeenCalled();
expect(err).toBe('Test');
done();
});
cpExecCbs[0]('Test');
}); });
it('should abort and skip further operations if it fails to make non-writable', done => { it('should abort and skip further operations if it fails to make non-writable', async () => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => { // tslint:disable-next-line: no-string-throw
shellChmodSpy.and.callFake(() => { throw 'Test'; });
const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0]();
await expectAsync(extractPromise).toBeRejectedWith('Test');
expect(shellChmodSpy).toHaveBeenCalled(); expect(shellChmodSpy).toHaveBeenCalled();
expect(shellRmSpy).not.toHaveBeenCalled(); expect(shellRmSpy).not.toHaveBeenCalled();
expect(err).toBe('Test');
done();
}); });
shellChmodSpy.and.callFake(() => {
it('should abort and reject if it fails to remove the build artifact file', async () => {
// tslint:disable-next-line: no-string-throw // tslint:disable-next-line: no-string-throw
throw 'Test'; shellRmSpy.and.callFake(() => { throw 'Test'; });
});
const extractPromise = (bc as any).extractArchive('foo', 'bar');
cpExecCbs[0](); cpExecCbs[0]();
});
await expectAsync(extractPromise).toBeRejectedWith('Test');
it('should abort and reject if it fails to remove the build artifact file', done => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => {
expect(shellChmodSpy).toHaveBeenCalled(); expect(shellChmodSpy).toHaveBeenCalled();
expect(shellRmSpy).toHaveBeenCalled(); expect(shellRmSpy).toHaveBeenCalled();
expect(err).toBe('Test');
done();
});
shellRmSpy.and.callFake(() => {
// tslint:disable-next-line: no-string-throw
throw 'Test';
});
cpExecCbs[0]();
}); });
}); });
@ -609,58 +562,50 @@ describe('BuildCreator', () => {
}); });
it('should return a promise', done => { it('should return a promise', async () => {
const promise = (bc as any).listShasByDate('input/dir'); const promise = (bc as any).listShasByDate('input/dir');
promise.then(done); // Do not complete the test (and release the spies) synchronously expect(promise).toBeInstanceOf(Promise);
// to avoid running the actual `ls()`.
expect(promise).toEqual(jasmine.any(Promise)); // Do not complete the test (and release the spies) synchronously to avoid running the actual `ls()`.
await promise;
}); });
it('should `ls()` files with their metadata', done => { it('should `ls()` files with their metadata', async () => {
(bc as any).listShasByDate('input/dir'). await (bc as any).listShasByDate('input/dir');
then(() => expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir')). expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir');
then(done);
}); });
it('should reject if listing files fails', done => { it('should reject if listing files fails', async () => {
shellLsSpy.and.callFake(() => Promise.reject('Test')); shellLsSpy.and.rejectWith('Test');
(bc as any).listShasByDate('input/dir').catch((err: string) => { await expectAsync((bc as any).listShasByDate('input/dir')).toBeRejectedWith('Test');
expect(err).toBe('Test');
done();
});
}); });
it('should return the filenames', done => { it('should return the filenames', async () => {
shellLsSpy.and.callFake(() => Promise.resolve([ shellLsSpy.and.resolveTo([
lsResult('foo', 100), lsResult('foo', 100),
lsResult('bar', 200), lsResult('bar', 200),
lsResult('baz', 300), lsResult('baz', 300),
])); ]);
(bc as any).listShasByDate('input/dir'). await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'bar', 'baz']);
then((shas: string[]) => expect(shas).toEqual(['foo', 'bar', 'baz'])).
then(done);
}); });
it('should sort by date', done => { it('should sort by date', async () => {
shellLsSpy.and.callFake(() => Promise.resolve([ shellLsSpy.and.resolveTo([
lsResult('foo', 300), lsResult('foo', 300),
lsResult('bar', 100), lsResult('bar', 100),
lsResult('baz', 200), lsResult('baz', 200),
])); ]);
(bc as any).listShasByDate('input/dir'). await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']);
then((shas: string[]) => expect(shas).toEqual(['bar', 'baz', 'foo'])).
then(done);
}); });
it('should not break with ShellJS\' custom `sort()` method', done => { it('should not break with ShellJS\' custom `sort()` method', async () => {
const mockArray = [ const mockArray = [
lsResult('foo', 300), lsResult('foo', 300),
lsResult('bar', 100), lsResult('bar', 100),
@ -668,26 +613,21 @@ describe('BuildCreator', () => {
]; ];
mockArray.sort = jasmine.createSpy('sort'); mockArray.sort = jasmine.createSpy('sort');
shellLsSpy.and.callFake(() => Promise.resolve(mockArray)); shellLsSpy.and.resolveTo(mockArray);
(bc as any).listShasByDate('input/dir').
then((shas: string[]) => { await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']);
expect(shas).toEqual(['bar', 'baz', 'foo']);
expect(mockArray.sort).not.toHaveBeenCalled(); expect(mockArray.sort).not.toHaveBeenCalled();
}).
then(done);
}); });
it('should only include directories', done => { it('should only include directories', async () => {
shellLsSpy.and.callFake(() => Promise.resolve([ shellLsSpy.and.resolveTo([
lsResult('foo', 100), lsResult('foo', 100),
lsResult('bar', 200, false), lsResult('bar', 200, false),
lsResult('baz', 300), lsResult('baz', 300),
])); ]);
(bc as any).listShasByDate('input/dir'). await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'baz']);
then((shas: string[]) => expect(shas).toEqual(['foo', 'baz'])).
then(done);
}); });
}); });

View File

@ -32,9 +32,8 @@ describe('BuildRetriever', () => {
}; };
api = new CircleCiApi('ORG', 'REPO', 'TOKEN'); api = new CircleCiApi('ORG', 'REPO', 'TOKEN');
spyOn(api, 'getBuildInfo').and.callFake(() => Promise.resolve(BUILD_INFO)); spyOn(api, 'getBuildInfo').and.resolveTo(BUILD_INFO);
getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl') getBuildArtifactUrlSpy = spyOn(api, 'getBuildArtifactUrl').and.resolveTo(BASE_URL + ARTIFACT_PATH);
.and.callFake(() => Promise.resolve(BASE_URL + ARTIFACT_PATH));
WRITEFILE_RESULT = undefined; WRITEFILE_RESULT = undefined;
writeFileSpy = spyOn(fs, 'writeFile').and.callFake( writeFileSpy = spyOn(fs, 'writeFile').and.callFake(
@ -57,6 +56,7 @@ describe('BuildRetriever', () => {
expect(() => new BuildRetriever(api, -1, DOWNLOAD_DIR)) expect(() => new BuildRetriever(api, -1, DOWNLOAD_DIR))
.toThrowError(`Invalid parameter "downloadSizeLimit" should be a number greater than 0.`); .toThrowError(`Invalid parameter "downloadSizeLimit" should be a number greater than 0.`);
}); });
it('should fail if the "downloadDir" is missing', () => { it('should fail if the "downloadDir" is missing', () => {
expect(() => new BuildRetriever(api, MAX_DOWNLOAD_SIZE, '')) expect(() => new BuildRetriever(api, MAX_DOWNLOAD_SIZE, ''))
.toThrowError(`Missing or empty required parameter 'downloadDir'!`); .toThrowError(`Missing or empty required parameter 'downloadDir'!`);
@ -73,14 +73,10 @@ describe('BuildRetriever', () => {
}); });
it('should error if it is not possible to extract the PR number from the branch', async () => { it('should error if it is not possible to extract the PR number from the branch', async () => {
const retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
try {
BUILD_INFO.branch = 'master'; BUILD_INFO.branch = 'master';
await retriever.getGithubInfo(12345); const retriever = new BuildRetriever(api, MAX_DOWNLOAD_SIZE, DOWNLOAD_DIR);
throw new Error('Exception Expected');
} catch (error) { await expectAsync(retriever.getGithubInfo(12345)).toBeRejectedWithError('No PR found in branch field: master');
expect(error.message).toEqual('No PR found in branch field: master');
}
}); });
}); });
@ -111,12 +107,10 @@ describe('BuildRetriever', () => {
it('should fail if the artifact is too large', async () => { it('should fail if the artifact is too large', async () => {
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS); const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
retriever = new BuildRetriever(api, 10, DOWNLOAD_DIR); retriever = new BuildRetriever(api, 10, DOWNLOAD_DIR);
try {
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
throw new Error('Exception Expected'); toBeRejectedWith(jasmine.objectContaining({status: 413}));
} catch (error) {
expect(error.status).toEqual(413);
}
artifactRequest.done(); artifactRequest.done();
}); });
@ -145,49 +139,39 @@ describe('BuildRetriever', () => {
}); });
it('should fail if the CircleCI API fails', async () => { it('should fail if the CircleCI API fails', async () => {
try { getBuildArtifactUrlSpy.and.rejectWith('getBuildArtifactUrl failed');
getBuildArtifactUrlSpy.and.callFake(() => Promise.reject('getBuildArtifactUrl failed')); await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); toBeRejectedWithError('CircleCI artifact download failed (getBuildArtifactUrl failed)');
throw new Error('Exception Expected');
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed (getBuildArtifactUrl failed)');
}
}); });
it('should fail if the URL fetch errors', async () => { it('should fail if the URL fetch errors', async () => {
// create a new handler that errors // create a new handler that errors
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).replyWithError('Artifact Request Failed'); const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).replyWithError('Artifact Request Failed');
try {
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).toBeRejectedWithError(
throw new Error('Exception Expected'); 'CircleCI artifact download failed ' +
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed ' +
'(request to http://test.com/some/path/build.zip failed, reason: Artifact Request Failed)'); '(request to http://test.com/some/path/build.zip failed, reason: Artifact Request Failed)');
}
artifactRequest.done(); artifactRequest.done();
}); });
it('should fail if the URL fetch 404s', async () => { it('should fail if the URL fetch 404s', async () => {
// create a new handler that errors // create a new handler that errors
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(404, 'No such artifact'); const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(404, 'No such artifact');
try {
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH); await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
throw new Error('Exception Expected'); toBeRejectedWithError('CircleCI artifact download failed (Error 404 - Not Found)');
} catch (error) {
expect(error.message).toEqual('CircleCI artifact download failed (Error 404 - Not Found)');
}
artifactRequest.done(); artifactRequest.done();
}); });
it('should fail if file write fails', async () => { it('should fail if file write fails', async () => {
const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS); const artifactRequest = nock(BASE_URL).get(ARTIFACT_PATH).reply(200, ARTIFACT_CONTENTS);
try {
WRITEFILE_RESULT = 'Test Error'; WRITEFILE_RESULT = 'Test Error';
await retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH);
throw new Error('Exception Expected'); await expectAsync(retriever.downloadBuildArtifact(12345, 777, 'COMMIT', ARTIFACT_PATH)).
} catch (error) { toBeRejectedWithError('CircleCI artifact download failed (Test Error)');
expect(error.message).toEqual('CircleCI artifact download failed (Test Error)');
}
artifactRequest.done(); artifactRequest.done();
}); });
}); });

View File

@ -51,10 +51,10 @@ describe('BuildVerifier', () => {
describe('getSignificantFilesChanged', () => { describe('getSignificantFilesChanged', () => {
it('should return false if none of the fetched files match the given pattern', async () => { it('should return false if none of the fetched files match the given pattern', async () => {
const fetchFilesSpy = spyOn(prs, 'fetchFiles'); const fetchFilesSpy = spyOn(prs, 'fetchFiles');
fetchFilesSpy.and.callFake(() => Promise.resolve([ fetchFilesSpy.and.resolveTo([
{filename: 'a/b/c', sha: 'a1'}, {filename: 'a/b/c', sha: 'a1'},
{filename: 'd/e/f', sha: 'b2'}, {filename: 'd/e/f', sha: 'b2'},
])); ]);
expect(await bv.getSignificantFilesChanged(777, /^x/)).toEqual(false); expect(await bv.getSignificantFilesChanged(777, /^x/)).toEqual(false);
expect(fetchFilesSpy).toHaveBeenCalledWith(777); expect(fetchFilesSpy).toHaveBeenCalledWith(777);
@ -81,37 +81,30 @@ describe('BuildVerifier', () => {
user: {login: 'username'}, user: {login: 'username'},
}; };
prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch'). prsFetchSpy = spyOn(GithubPullRequests.prototype, 'fetch').and.resolveTo(mockPrInfo);
and.callFake(() => Promise.resolve(mockPrInfo)); teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').and.resolveTo(true);
teamsIsMemberBySlugSpy = spyOn(GithubTeams.prototype, 'isMemberBySlug').
and.callFake(() => Promise.resolve(true));
}); });
it('should return a promise', done => { it('should return a promise', async () => {
const promise = bv.getPrIsTrusted(pr); const promise = bv.getPrIsTrusted(pr);
promise.then(done); // Do not complete the test (and release the spies) synchronously expect(promise).toBeInstanceOf(Promise);
// to avoid running the actual `GithubTeams#isMemberBySlug()`.
expect(promise).toEqual(jasmine.any(Promise)); // Do not complete the test (and release the spies) synchronously to avoid running the actual
// `GithubTeams#isMemberBySlug()`.
await promise;
}); });
it('should fetch the corresponding PR', done => { it('should fetch the corresponding PR', async () => {
bv.getPrIsTrusted(pr).then(() => { await bv.getPrIsTrusted(pr);
expect(prsFetchSpy).toHaveBeenCalledWith(pr); expect(prsFetchSpy).toHaveBeenCalledWith(pr);
done();
});
}); });
it('should fail if fetching the PR errors', done => { it('should fail if fetching the PR errors', async () => {
prsFetchSpy.and.callFake(() => Promise.reject('Test')); prsFetchSpy.and.rejectWith('Test');
bv.getPrIsTrusted(pr).catch(err => { await expectAsync(bv.getPrIsTrusted(pr)).toBeRejectedWith('Test');
expect(err).toBe('Test');
done();
});
}); });
@ -120,19 +113,14 @@ describe('BuildVerifier', () => {
beforeEach(() => mockPrInfo.labels.push({name: 'trusted: pr-label'})); beforeEach(() => mockPrInfo.labels.push({name: 'trusted: pr-label'}));
it('should resolve to true', done => { it('should resolve to true', async () => {
bv.getPrIsTrusted(pr).then(isTrusted => { await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(true);
expect(isTrusted).toBe(true);
done();
});
}); });
it('should not try to verify the author\'s membership status', done => { it('should not try to verify the author\'s membership status', async () => {
bv.getPrIsTrusted(pr).then(() => { await expectAsync(bv.getPrIsTrusted(pr));
expect(teamsIsMemberBySlugSpy).not.toHaveBeenCalled(); expect(teamsIsMemberBySlugSpy).not.toHaveBeenCalled();
done();
});
}); });
}); });
@ -140,40 +128,27 @@ describe('BuildVerifier', () => {
describe('when the PR does not have the "trusted PR" label', () => { describe('when the PR does not have the "trusted PR" label', () => {
it('should verify the PR author\'s membership in the specified teams', done => { it('should verify the PR author\'s membership in the specified teams', async () => {
bv.getPrIsTrusted(pr).then(() => { await bv.getPrIsTrusted(pr);
expect(teamsIsMemberBySlugSpy).toHaveBeenCalledWith('username', ['team1', 'team2']); expect(teamsIsMemberBySlugSpy).toHaveBeenCalledWith('username', ['team1', 'team2']);
done();
});
}); });
it('should fail if verifying membership errors', done => { it('should fail if verifying membership errors', async () => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.reject('Test')); teamsIsMemberBySlugSpy.and.rejectWith('Test');
bv.getPrIsTrusted(pr).catch(err => { await expectAsync(bv.getPrIsTrusted(pr)).toBeRejectedWith('Test');
expect(err).toBe('Test');
done();
});
}); });
it('should resolve to true if the PR\'s author is a member', done => { it('should resolve to true if the PR\'s author is a member', async () => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.resolve(true)); teamsIsMemberBySlugSpy.and.resolveTo(true);
await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(true);
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(true);
done();
});
}); });
it('should resolve to false if the PR\'s author is not a member', done => { it('should resolve to false if the PR\'s author is not a member', async () => {
teamsIsMemberBySlugSpy.and.callFake(() => Promise.resolve(false)); teamsIsMemberBySlugSpy.and.resolveTo(false);
await expectAsync(bv.getPrIsTrusted(pr)).toBeResolvedTo(false);
bv.getPrIsTrusted(pr).then(isTrusted => {
expect(isTrusted).toBe(false);
done();
});
}); });
}); });

View File

@ -1,11 +0,0 @@
import {PreviewServerError} from '../../lib/preview-server/preview-error';
export const expectToBePreviewServerError = (actual: PreviewServerError, status?: number, message?: string) => {
expect(actual).toEqual(jasmine.any(PreviewServerError));
if (status != null) {
expect(actual.status).toBe(status);
}
if (message != null) {
expect(actual.message).toBe(message);
}
};

View File

@ -0,0 +1,5 @@
declare module jasmine {
interface AsyncMatchers {
toBeRejectedWithPreviewServerError(status: number, message?: string | RegExp): Promise<void>;
}
}

View File

@ -0,0 +1,59 @@
import {PreviewServerError} from '../../lib/preview-server/preview-error';
// Matchers
const toBeRejectedWithPreviewServerError: jasmine.CustomAsyncMatcherFactory = () => {
return {
async compare(actualPromise: Promise<never>, expectedStatus: number, expectedMessage?: string | RegExp) {
if (!(actualPromise instanceof Promise)) {
throw new Error(`Expected '${toBeRejectedWithPreviewServerError.name}()' to be called on a promise.`);
}
try {
await actualPromise;
return {
pass: false,
message: `Expected a promise to be rejected with a '${PreviewServerError.name}', but it was resolved.`,
};
} catch (actualError) {
const actualPrintValue = stringify(actualError);
const expectedPrintValue =
stringify(new PreviewServerError(expectedStatus, expectedMessage && `${expectedMessage}`));
const pass = errorMatches(actualError, expectedStatus, expectedMessage);
const message =
`Expected a promise ${pass ? 'not ' : ''}to be rejected with ${expectedPrintValue}, but is was` +
`${pass ? '' : ` rejected with ${actualPrintValue}`}.`;
return {pass, message};
}
},
};
// Helpers
function errorMatches(actualErr: unknown, expectedStatus: number, expectedMsg?: string | RegExp): boolean {
if (!(actualErr instanceof PreviewServerError)) return false;
if (actualErr.status !== expectedStatus) return false;
return messageMatches(actualErr.message, expectedMsg);
}
function messageMatches(actualMsg: string, expectedMsg?: string | RegExp): boolean {
if (typeof expectedMsg === 'undefined') return true;
if (typeof expectedMsg === 'string') return actualMsg === expectedMsg;
return expectedMsg.test(actualMsg);
}
function stringify(value: unknown): string {
if (value instanceof PreviewServerError) {
return `${PreviewServerError.name}(${value.status}${value.message ? `, ${value.message}` : ''})`;
}
return jasmine.pp(value);
}
};
// Exports
export const customAsyncMatchers: jasmine.CustomAsyncMatcherFactories = {
toBeRejectedWithPreviewServerError,
};

View File

@ -9,8 +9,8 @@ describe('PreviewServerError', () => {
it('should extend Error', () => { it('should extend Error', () => {
expect(err).toEqual(jasmine.any(PreviewServerError)); expect(err).toBeInstanceOf(PreviewServerError);
expect(err).toEqual(jasmine.any(Error)); expect(err).toBeInstanceOf(Error);
expect(Object.getPrototypeOf(err)).toBe(PreviewServerError.prototype); expect(Object.getPrototypeOf(err)).toBe(PreviewServerError.prototype);
}); });

View File

@ -229,7 +229,7 @@ describe('PreviewServerFactory', () => {
expect(prsAddCommentSpy).toHaveBeenCalledTimes(2); expect(prsAddCommentSpy).toHaveBeenCalledTimes(2);
expect(prs).toBe(allCalls[1].object); expect(prs).toBe(allCalls[1].object);
expect(prs).toEqual(jasmine.any(GithubPullRequests)); expect(prs).toBeInstanceOf(GithubPullRequests);
expect(prs.repoSlug).toBe('organisation/repo'); expect(prs.repoSlug).toBe('organisation/repo');
}); });
@ -301,9 +301,8 @@ describe('PreviewServerFactory', () => {
let bvGetSignificantFilesChangedSpy: jasmine.Spy; let bvGetSignificantFilesChangedSpy: jasmine.Spy;
beforeEach(() => { beforeEach(() => {
bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.returnValue(Promise.resolve(true)); bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.resolveTo(true);
bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged'). bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').and.resolveTo(true);
and.returnValue(Promise.resolve(true));
}); });
@ -330,7 +329,7 @@ describe('PreviewServerFactory', () => {
it('should respond appropriately if the PR did not touch any significant files', async () => { it('should respond appropriately if the PR did not touch any significant files', async () => {
bvGetSignificantFilesChangedSpy.and.returnValue(Promise.resolve(false)); bvGetSignificantFilesChangedSpy.and.resolveTo(false);
const expectedResponse = {canHavePublicPreview: false, reason: 'No significant files touched.'}; const expectedResponse = {canHavePublicPreview: false, reason: 'No significant files touched.'};
const expectedLog = `PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`; const expectedLog = `PR:${pr} - Cannot have a public preview, because it did not touch any significant files.`;
@ -344,7 +343,7 @@ describe('PreviewServerFactory', () => {
it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => { it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => {
bvGetPrIsTrustedSpy.and.returnValue(Promise.resolve(false)); bvGetPrIsTrustedSpy.and.resolveTo(false);
const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'}; const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'};
const expectedLog = const expectedLog =
@ -371,7 +370,7 @@ describe('PreviewServerFactory', () => {
it('should respond with error if `getSignificantFilesChanged()` fails', async () => { it('should respond with error if `getSignificantFilesChanged()` fails', async () => {
bvGetSignificantFilesChangedSpy.and.callFake(() => Promise.reject('getSignificantFilesChanged error')); bvGetSignificantFilesChangedSpy.and.rejectWith('getSignificantFilesChanged error');
await agent.get(url).expect(500, 'getSignificantFilesChanged error'); await agent.get(url).expect(500, 'getSignificantFilesChanged error');
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error'); expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error');
@ -379,11 +378,10 @@ describe('PreviewServerFactory', () => {
it('should respond with error if `getPrIsTrusted()` fails', async () => { it('should respond with error if `getPrIsTrusted()` fails', async () => {
const error = new Error('getPrIsTrusted error'); bvGetPrIsTrustedSpy.and.throwError('getPrIsTrusted error');
bvGetPrIsTrustedSpy.and.callFake(() => { throw error; });
await agent.get(url).expect(500, 'getPrIsTrusted error'); await agent.get(url).expect(500, 'getPrIsTrusted error');
expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', error); expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', new Error('getPrIsTrusted error'));
}); });
}); });
@ -496,7 +494,7 @@ describe('PreviewServerFactory', () => {
// Note it is important to put the `reject` into `and.callFake`; // Note it is important to put the `reject` into `and.callFake`;
// If you just `and.returnValue` the rejected promise // If you just `and.returnValue` the rejected promise
// then you get an "unhandled rejection" message in the console. // then you get an "unhandled rejection" message in the console.
getGithubInfoSpy.and.callFake(() => Promise.reject('Test Error')); getGithubInfoSpy.and.rejectWith('Test Error');
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled(); expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
@ -517,7 +515,7 @@ describe('PreviewServerFactory', () => {
}); });
it('should fail if the artifact fetch request fails', async () => { it('should fail if the artifact fetch request fails', async () => {
downloadBuildArtifactSpy.and.callFake(() => Promise.reject('Test Error')); downloadBuildArtifactSpy.and.rejectWith('Test Error');
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).toHaveBeenCalled(); expect(downloadBuildArtifactSpy).toHaveBeenCalled();
@ -526,7 +524,7 @@ describe('PreviewServerFactory', () => {
}); });
it('should fail if verifying the PR fails', async () => { it('should fail if verifying the PR fails', async () => {
getPrIsTrustedSpy.and.callFake(() => Promise.reject('Test Error')); getPrIsTrustedSpy.and.rejectWith('Test Error');
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).toHaveBeenCalled(); expect(downloadBuildArtifactSpy).toHaveBeenCalled();
@ -535,7 +533,7 @@ describe('PreviewServerFactory', () => {
}); });
it('should fail if creating the preview build fails', async () => { it('should fail if creating the preview build fails', async () => {
createBuildSpy.and.callFake(() => Promise.reject('Test Error')); createBuildSpy.and.rejectWith('Test Error');
await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error'); await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(downloadBuildArtifactSpy).toHaveBeenCalled(); expect(downloadBuildArtifactSpy).toHaveBeenCalled();
@ -604,7 +602,7 @@ describe('PreviewServerFactory', () => {
it('should propagate errors from BuildVerifier', async () => { it('should propagate errors from BuildVerifier', async () => {
bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test')); bvGetPrIsTrustedSpy.and.rejectWith('Test');
await createRequest(+pr).expect(500, 'Test'); await createRequest(+pr).expect(500, 'Test');
@ -614,7 +612,9 @@ describe('PreviewServerFactory', () => {
it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => { it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42)); bvGetPrIsTrustedSpy.
withArgs(24).and.resolveTo(false).
withArgs(42).and.resolveTo(true);
await createRequest(24); await createRequest(24);
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false); expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
@ -625,7 +625,7 @@ describe('PreviewServerFactory', () => {
it('should propagate errors from BuildCreator', async () => { it('should propagate errors from BuildCreator', async () => {
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test')); bcUpdatePrVisibilitySpy.and.rejectWith('Test');
await createRequest(+pr).expect(500, 'Test'); await createRequest(+pr).expect(500, 'Test');
}); });
@ -633,7 +633,9 @@ describe('PreviewServerFactory', () => {
describe('on success', () => { describe('on success', () => {
it('should respond with 200 (action: undefined)', async () => { it('should respond with 200 (action: undefined)', async () => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false)); bvGetPrIsTrustedSpy.
withArgs(2).and.resolveTo(false).
withArgs(4).and.resolveTo(true);
const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200])); const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
await Promise.all(reqs); await Promise.all(reqs);
@ -641,7 +643,9 @@ describe('PreviewServerFactory', () => {
it('should respond with 200 (action: labeled)', async () => { it('should respond with 200 (action: labeled)', async () => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false)); bvGetPrIsTrustedSpy.
withArgs(2).and.resolveTo(false).
withArgs(4).and.resolveTo(true);
const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200])); const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
await Promise.all(reqs); await Promise.all(reqs);
@ -649,7 +653,9 @@ describe('PreviewServerFactory', () => {
it('should respond with 200 (action: unlabeled)', async () => { it('should respond with 200 (action: unlabeled)', async () => {
bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false)); bvGetPrIsTrustedSpy.
withArgs(2).and.resolveTo(false).
withArgs(4).and.resolveTo(true);
const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200])); const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
await Promise.all(reqs); await Promise.all(reqs);

View File

@ -39,7 +39,7 @@ describe('preview-server/utils', () => {
throwRequestError(505, 'ERROR MESSAGE', request); throwRequestError(505, 'ERROR MESSAGE', request);
} catch (error) { } catch (error) {
caught = true; caught = true;
expect(error).toEqual(jasmine.any(PreviewServerError)); expect(error).toBeInstanceOf(PreviewServerError);
expect(error.status).toEqual(505); expect(error.status).toEqual(505);
expect(error.message).toEqual(`ERROR MESSAGE in request: POST some.domain.com/path "The request body"`); expect(error.message).toEqual(`ERROR MESSAGE in request: POST some.domain.com/path "The request body"`);
} }