2017-02-06 13:40:28 -05:00
|
|
|
// Imports
|
|
|
|
import * as cp from 'child_process';
|
|
|
|
import {EventEmitter} from 'events';
|
|
|
|
import * as fs from 'fs';
|
2017-06-17 14:22:44 -04:00
|
|
|
import * as path from 'path';
|
2017-02-06 13:40:28 -05:00
|
|
|
import * as shell from 'shelljs';
|
2017-06-25 15:13:03 -04:00
|
|
|
import {SHORT_SHA_LEN} from '../../lib/common/constants';
|
2018-08-27 11:07:25 -04:00
|
|
|
import {Logger} from '../../lib/common/utils';
|
2018-08-15 08:47:45 -04:00
|
|
|
import {BuildCreator} from '../../lib/preview-server/build-creator';
|
|
|
|
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
|
|
|
|
import {PreviewServerError} from '../../lib/preview-server/preview-error';
|
2020-05-02 09:14:14 -04:00
|
|
|
import {customAsyncMatchers} from './jasmine-custom-async-matchers';
|
|
|
|
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
// Tests
|
|
|
|
describe('BuildCreator', () => {
|
2018-05-10 08:56:07 -04:00
|
|
|
const pr = 9;
|
2017-02-06 13:40:28 -05:00
|
|
|
const sha = '9'.repeat(40);
|
2017-06-25 15:13:03 -04:00
|
|
|
const shortSha = sha.substr(0, SHORT_SHA_LEN);
|
2017-02-06 13:40:28 -05:00
|
|
|
const archive = 'snapshot.tar.gz';
|
2017-03-01 08:16:38 -05:00
|
|
|
const buildsDir = 'builds/dir';
|
2017-06-18 18:15:07 -04:00
|
|
|
const hiddenPrDir = path.join(buildsDir, `hidden--${pr}`);
|
2018-05-10 08:56:07 -04:00
|
|
|
const publicPrDir = path.join(buildsDir, `${pr}`);
|
2017-06-25 15:13:03 -04:00
|
|
|
const hiddenShaDir = path.join(hiddenPrDir, shortSha);
|
|
|
|
const publicShaDir = path.join(publicPrDir, shortSha);
|
2017-02-06 13:40:28 -05:00
|
|
|
let bc: BuildCreator;
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
beforeEach(() => jasmine.addAsyncMatchers(customAsyncMatchers));
|
2017-02-06 13:40:28 -05:00
|
|
|
beforeEach(() => bc = new BuildCreator(buildsDir));
|
|
|
|
|
|
|
|
|
|
|
|
describe('constructor()', () => {
|
|
|
|
|
|
|
|
it('should throw if \'buildsDir\' is missing or empty', () => {
|
|
|
|
expect(() => new BuildCreator('')).toThrowError('Missing or empty required parameter \'buildsDir\'!');
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should extend EventEmitter', () => {
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(bc).toBeInstanceOf(BuildCreator);
|
|
|
|
expect(bc).toBeInstanceOf(EventEmitter);
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
expect(Object.getPrototypeOf(bc)).toBe(BuildCreator.prototype);
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
describe('create()', () => {
|
|
|
|
let bcEmitSpy: jasmine.Spy;
|
|
|
|
let bcExistsSpy: jasmine.Spy;
|
|
|
|
let bcExtractArchiveSpy: jasmine.Spy;
|
2017-06-26 06:31:15 -04:00
|
|
|
let bcUpdatePrVisibilitySpy: jasmine.Spy;
|
2017-06-18 18:15:07 -04:00
|
|
|
let shellMkdirSpy: jasmine.Spy;
|
|
|
|
let shellRmSpy: jasmine.Spy;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
bcEmitSpy = spyOn(bc, 'emit');
|
|
|
|
bcExistsSpy = spyOn(bc as any, 'exists');
|
|
|
|
bcExtractArchiveSpy = spyOn(bc as any, 'extractArchive');
|
2017-06-26 06:31:15 -04:00
|
|
|
bcUpdatePrVisibilitySpy = spyOn(bc, 'updatePrVisibility');
|
2017-06-18 18:15:07 -04:00
|
|
|
shellMkdirSpy = spyOn(shell, 'mkdir');
|
|
|
|
shellRmSpy = spyOn(shell, 'rm');
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
[true, false].forEach(isPublic => {
|
|
|
|
const prDir = isPublic ? publicPrDir : hiddenPrDir;
|
|
|
|
const shaDir = isPublic ? publicShaDir : hiddenShaDir;
|
|
|
|
|
2017-02-06 13:40:28 -05:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should return a promise', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
const promise = bc.create(pr, sha, archive, isPublic);
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(promise).toBeInstanceOf(Promise);
|
2017-02-06 13:40:28 -05:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
// Do not complete the test (and release the spies) synchronously to avoid running the actual
|
|
|
|
// `extractArchive()`.
|
|
|
|
await promise;
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should update the PR\'s visibility first if necessary', async () => {
|
|
|
|
await bc.create(pr, sha, archive, isPublic);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledBefore(shellMkdirSpy);
|
|
|
|
expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(pr, isPublic);
|
|
|
|
expect(shellMkdirSpy).toHaveBeenCalled();
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should create the build directory (and any parent directories)', async () => {
|
|
|
|
await bc.create(pr, sha, archive, isPublic);
|
|
|
|
expect(shellMkdirSpy).toHaveBeenCalledWith('-p', shaDir);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should extract the archive contents into the build directory', async () => {
|
|
|
|
await bc.create(pr, sha, archive, isPublic);
|
|
|
|
expect(bcExtractArchiveSpy).toHaveBeenCalledWith(archive, shaDir);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should emit a CreatedBuildEvent on success', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
let emitted = false;
|
2017-02-06 13:40:28 -05:00
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
bcEmitSpy.and.callFake((type: string, evt: CreatedBuildEvent) => {
|
|
|
|
expect(type).toBe(CreatedBuildEvent.type);
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(evt).toBeInstanceOf(CreatedBuildEvent);
|
2017-06-18 18:15:07 -04:00
|
|
|
expect(evt.pr).toBe(+pr);
|
2017-06-25 15:13:03 -04:00
|
|
|
expect(evt.sha).toBe(shortSha);
|
2017-06-18 18:15:07 -04:00
|
|
|
expect(evt.isPublic).toBe(isPublic);
|
|
|
|
|
|
|
|
emitted = true;
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await bc.create(pr, sha, archive, isPublic);
|
|
|
|
expect(emitted).toBe(true);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
describe('on error', () => {
|
|
|
|
beforeEach(() => {
|
2020-05-02 09:14:14 -04:00
|
|
|
bcExistsSpy.and.returnValue(false);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if changing the PR\'s visibility fails', async () => {
|
2018-08-15 08:47:45 -04:00
|
|
|
const mockError = new PreviewServerError(543, 'Test');
|
2020-05-02 09:14:14 -04:00
|
|
|
bcUpdatePrVisibilitySpy.and.rejectWith(mockError);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWith(mockError);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(bcExistsSpy).not.toHaveBeenCalled();
|
|
|
|
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if the build does already exist', async () => {
|
|
|
|
bcExistsSpy.withArgs(shaDir).and.returnValue(true);
|
|
|
|
|
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
|
|
|
|
409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`);
|
|
|
|
|
|
|
|
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should detect existing build directory after visibility change', async () => {
|
|
|
|
bcUpdatePrVisibilitySpy.and.callFake(() => bcExistsSpy.and.returnValue(true));
|
2017-06-26 06:31:15 -04:00
|
|
|
|
|
|
|
expect(bcExistsSpy(prDir)).toBe(false);
|
|
|
|
expect(bcExistsSpy(shaDir)).toBe(false);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
|
|
|
|
409, `Request to overwrite existing ${isPublic ? '' : 'non-'}public directory: ${shaDir}`);
|
|
|
|
|
|
|
|
expect(shellMkdirSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if it fails to create the directories', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
shellMkdirSpy.and.throwError('');
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
|
|
|
|
|
|
|
|
expect(shellMkdirSpy).toHaveBeenCalled();
|
|
|
|
expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if it fails to extract the archive', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
bcExtractArchiveSpy.and.throwError('');
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
|
|
|
|
|
|
|
|
expect(shellMkdirSpy).toHaveBeenCalled();
|
|
|
|
expect(bcExtractArchiveSpy).toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should delete the PR directory (for new PR)', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
bcExtractArchiveSpy.and.throwError('');
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
|
|
|
|
expect(shellRmSpy).toHaveBeenCalledWith('-rf', prDir);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should delete the SHA directory (for existing PR)', async () => {
|
|
|
|
bcExistsSpy.withArgs(prDir).and.returnValue(true);
|
2017-06-18 18:15:07 -04:00
|
|
|
bcExtractArchiveSpy.and.throwError('');
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejected();
|
|
|
|
expect(shellRmSpy).toHaveBeenCalledWith('-rf', shaDir);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should reject with an PreviewServerError', async () => {
|
2017-09-23 08:24:04 -04:00
|
|
|
// tslint:disable-next-line: no-string-throw
|
2017-06-18 18:15:07 -04:00
|
|
|
shellMkdirSpy.and.callFake(() => { throw 'Test'; });
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(
|
|
|
|
500, `Error while creating preview at: ${shaDir}\nTest`);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should pass PreviewServerError instances unmodified', async () => {
|
2018-08-15 08:47:45 -04:00
|
|
|
shellMkdirSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(bc.create(pr, sha, archive, isPublic)).toBeRejectedWithPreviewServerError(543, 'Test');
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('updatePrVisibility()', () => {
|
|
|
|
let bcEmitSpy: jasmine.Spy;
|
|
|
|
let bcExistsSpy: jasmine.Spy;
|
|
|
|
let bcListShasByDate: jasmine.Spy;
|
|
|
|
let shellMvSpy: jasmine.Spy;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
bcEmitSpy = spyOn(bc, 'emit');
|
|
|
|
bcExistsSpy = spyOn(bc as any, 'exists');
|
|
|
|
bcListShasByDate = spyOn(bc as any, 'listShasByDate');
|
|
|
|
shellMvSpy = spyOn(shell, 'mv');
|
|
|
|
|
|
|
|
bcExistsSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
|
|
|
|
bcListShasByDate.and.returnValue([]);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should return a promise', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
const promise = bc.updatePrVisibility(pr, true);
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(promise).toBeInstanceOf(Promise);
|
2017-06-26 06:31:15 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
// Do not complete the test (and release the spies) synchronously to avoid running the actual `extractArchive()`.
|
|
|
|
await promise;
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
[true, false].forEach(makePublic => {
|
|
|
|
const oldPrDir = makePublic ? hiddenPrDir : publicPrDir;
|
|
|
|
const newPrDir = makePublic ? publicPrDir : hiddenPrDir;
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should rename the directory', async () => {
|
|
|
|
await bc.updatePrVisibility(pr, makePublic);
|
|
|
|
expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir);
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('when the visibility is updated', () => {
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should resolve to true', async () => {
|
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(true);
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should rename the directory', async () => {
|
|
|
|
await bc.updatePrVisibility(pr, makePublic);
|
|
|
|
expect(shellMvSpy).toHaveBeenCalledWith(oldPrDir, newPrDir);
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should emit a ChangedPrVisibilityEvent on success', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
let emitted = false;
|
|
|
|
|
|
|
|
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
|
|
|
|
expect(type).toBe(ChangedPrVisibilityEvent.type);
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent);
|
2017-06-26 06:31:15 -04:00
|
|
|
expect(evt.pr).toBe(+pr);
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(evt.shas).toBeInstanceOf(Array);
|
2017-06-26 06:31:15 -04:00
|
|
|
expect(evt.isPublic).toBe(makePublic);
|
|
|
|
|
|
|
|
emitted = true;
|
|
|
|
});
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await bc.updatePrVisibility(pr, makePublic);
|
|
|
|
expect(emitted).toBe(true);
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should include all shas in the emitted event', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
const shas = ['foo', 'bar', 'baz'];
|
|
|
|
let emitted = false;
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
bcListShasByDate.and.resolveTo(shas);
|
2017-06-26 06:31:15 -04:00
|
|
|
bcEmitSpy.and.callFake((type: string, evt: ChangedPrVisibilityEvent) => {
|
|
|
|
expect(bcListShasByDate).toHaveBeenCalledWith(newPrDir);
|
|
|
|
|
|
|
|
expect(type).toBe(ChangedPrVisibilityEvent.type);
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(evt).toBeInstanceOf(ChangedPrVisibilityEvent);
|
2017-06-26 06:31:15 -04:00
|
|
|
expect(evt.pr).toBe(+pr);
|
|
|
|
expect(evt.shas).toBe(shas);
|
|
|
|
expect(evt.isPublic).toBe(makePublic);
|
|
|
|
|
|
|
|
emitted = true;
|
|
|
|
});
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await bc.updatePrVisibility(pr, makePublic);
|
|
|
|
expect(emitted).toBe(true);
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should do nothing if the visibility is already up-to-date', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
bcExistsSpy.and.callFake((dir: string) => dir === newPrDir);
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false);
|
|
|
|
|
|
|
|
expect(shellMvSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should do nothing if the PR directory does not exist', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
bcExistsSpy.and.returnValue(false);
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeResolvedTo(false);
|
|
|
|
|
|
|
|
expect(shellMvSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('on error', () => {
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if both directories exist', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
bcExistsSpy.and.returnValue(true);
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(
|
|
|
|
409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
|
|
|
|
|
|
|
|
expect(shellMvSpy).not.toHaveBeenCalled();
|
|
|
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if it fails to rename the directory', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
shellMvSpy.and.throwError('');
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected();
|
|
|
|
|
|
|
|
expect(shellMvSpy).toHaveBeenCalled();
|
|
|
|
expect(bcListShasByDate).not.toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if it fails to list the SHAs', async () => {
|
2017-06-26 06:31:15 -04:00
|
|
|
bcListShasByDate.and.throwError('');
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejected();
|
|
|
|
|
|
|
|
expect(shellMvSpy).toHaveBeenCalled();
|
|
|
|
expect(bcListShasByDate).toHaveBeenCalled();
|
|
|
|
expect(bcEmitSpy).not.toHaveBeenCalled();
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should reject with an PreviewServerError', async () => {
|
2017-09-23 08:24:04 -04:00
|
|
|
// tslint:disable-next-line: no-string-throw
|
2017-06-26 06:31:15 -04:00
|
|
|
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(
|
|
|
|
500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
|
2017-06-26 06:31:15 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should pass PreviewServerError instances unmodified', async () => {
|
2018-08-15 08:47:45 -04:00
|
|
|
shellMvSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(bc.updatePrVisibility(pr, makePublic)).toBeRejectedWithPreviewServerError(543, 'Test');
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Protected methods
|
|
|
|
|
|
|
|
describe('exists()', () => {
|
|
|
|
let fsAccessSpy: jasmine.Spy;
|
2017-09-23 08:24:04 -04:00
|
|
|
let fsAccessCbs: ((v?: any) => void)[];
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
fsAccessCbs = [];
|
2020-05-02 09:14:05 -04:00
|
|
|
fsAccessSpy = spyOn(fs, 'access').and.callFake(
|
|
|
|
((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb)) as unknown as typeof fs.access,
|
|
|
|
);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should return a promise', () => {
|
2020-05-02 09:14:14 -04:00
|
|
|
expect((bc as any).exists('foo')).toBeInstanceOf(Promise);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should call \'fs.access()\' with the specified argument', () => {
|
|
|
|
(bc as any).exists('foo');
|
2018-05-10 08:56:07 -04:00
|
|
|
expect(fsAccessSpy).toHaveBeenCalledWith('foo', jasmine.any(Function));
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should resolve with \'true\' if \'fs.access()\' succeeds', async () => {
|
|
|
|
const existsPromises = [
|
|
|
|
(bc as any).exists('foo'),
|
|
|
|
(bc as any).exists('bar'),
|
|
|
|
];
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
fsAccessCbs[0]();
|
|
|
|
fsAccessCbs[1](null);
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([true, true]);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should resolve with \'false\' if \'fs.access()\' errors', async () => {
|
|
|
|
const existsPromises = [
|
|
|
|
(bc as any).exists('foo'),
|
|
|
|
(bc as any).exists('bar'),
|
|
|
|
];
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
fsAccessCbs[0]('Error');
|
|
|
|
fsAccessCbs[1](new Error());
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(Promise.all(existsPromises)).toBeResolvedTo([false, false]);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('extractArchive()', () => {
|
|
|
|
let consoleWarnSpy: jasmine.Spy;
|
|
|
|
let shellChmodSpy: jasmine.Spy;
|
|
|
|
let shellRmSpy: jasmine.Spy;
|
|
|
|
let cpExecSpy: jasmine.Spy;
|
2017-09-23 08:24:04 -04:00
|
|
|
let cpExecCbs: ((...args: any[]) => void)[];
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
cpExecCbs = [];
|
|
|
|
|
2018-08-27 11:07:25 -04:00
|
|
|
consoleWarnSpy = spyOn(Logger.prototype, 'warn');
|
2017-02-06 13:40:28 -05:00
|
|
|
shellChmodSpy = spyOn(shell, 'chmod');
|
|
|
|
shellRmSpy = spyOn(shell, 'rm');
|
2020-05-02 09:14:05 -04:00
|
|
|
cpExecSpy = spyOn(cp, 'exec').and.callFake(
|
|
|
|
((_: string, cb: (...args: any[]) => void) =>
|
|
|
|
cpExecCbs.push(cb)) as unknown as typeof cp.exec,
|
|
|
|
);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should return a promise', () => {
|
2020-05-02 09:14:14 -04:00
|
|
|
expect((bc as any).extractArchive('foo', 'bar')).toBeInstanceOf(Promise);
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should "gunzip" and "untar" the input file into the output directory', () => {
|
|
|
|
const cmd = 'tar --extract --gzip --directory "output/dir" --file "input/file"';
|
|
|
|
|
|
|
|
(bc as any).extractArchive('input/file', 'output/dir');
|
|
|
|
expect(cpExecSpy).toHaveBeenCalledWith(cmd, jasmine.any(Function));
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should log (as a warning) any stderr output if extracting succeeded', async () => {
|
|
|
|
const extractPromise = (bc as any).extractArchive('foo', 'bar');
|
2017-02-06 13:40:28 -05:00
|
|
|
cpExecCbs[0](null, 'This is stdout', 'This is stderr');
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(extractPromise).toBeResolved();
|
|
|
|
expect(consoleWarnSpy).toHaveBeenCalledWith('This is stderr');
|
|
|
|
});
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should make the build directory non-writable', async () => {
|
|
|
|
const extractPromise = (bc as any).extractArchive('foo', 'bar');
|
2017-02-06 13:40:28 -05:00
|
|
|
cpExecCbs[0]();
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(extractPromise).toBeResolved();
|
|
|
|
expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a-w', 'bar');
|
|
|
|
});
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should delete the build artifact file on success', async () => {
|
|
|
|
const extractPromise = (bc as any).extractArchive('input/file', 'output/dir');
|
2017-02-06 13:40:28 -05:00
|
|
|
cpExecCbs[0]();
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(extractPromise).toBeResolved();
|
|
|
|
expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file');
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe('on error', () => {
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if it fails to extract the archive', async () => {
|
|
|
|
const extractPromise = (bc as any).extractArchive('foo', 'bar');
|
2017-02-06 13:40:28 -05:00
|
|
|
cpExecCbs[0]('Test');
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(extractPromise).toBeRejectedWith('Test');
|
|
|
|
expect(shellChmodSpy).not.toHaveBeenCalled();
|
|
|
|
expect(shellRmSpy).not.toHaveBeenCalled();
|
|
|
|
});
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and skip further operations if it fails to make non-writable', async () => {
|
|
|
|
// tslint:disable-next-line: no-string-throw
|
|
|
|
shellChmodSpy.and.callFake(() => { throw 'Test'; });
|
2017-09-23 08:24:04 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
const extractPromise = (bc as any).extractArchive('foo', 'bar');
|
2017-02-06 13:40:28 -05:00
|
|
|
cpExecCbs[0]();
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync(extractPromise).toBeRejectedWith('Test');
|
|
|
|
expect(shellChmodSpy).toHaveBeenCalled();
|
|
|
|
expect(shellRmSpy).not.toHaveBeenCalled();
|
|
|
|
});
|
2017-02-06 13:40:28 -05:00
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should abort and reject if it fails to remove the build artifact file', async () => {
|
|
|
|
// tslint:disable-next-line: no-string-throw
|
|
|
|
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
2017-09-23 08:24:04 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
const extractPromise = (bc as any).extractArchive('foo', 'bar');
|
2017-02-06 13:40:28 -05:00
|
|
|
cpExecCbs[0]();
|
2020-05-02 09:14:14 -04:00
|
|
|
|
|
|
|
await expectAsync(extractPromise).toBeRejectedWith('Test');
|
|
|
|
expect(shellChmodSpy).toHaveBeenCalled();
|
|
|
|
expect(shellRmSpy).toHaveBeenCalled();
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2017-06-18 18:15:07 -04:00
|
|
|
|
|
|
|
describe('listShasByDate()', () => {
|
|
|
|
let shellLsSpy: jasmine.Spy;
|
|
|
|
const lsResult = (name: string, mtimeMs: number, isDirectory = true) => ({
|
|
|
|
isDirectory: () => isDirectory,
|
|
|
|
mtime: new Date(mtimeMs),
|
|
|
|
name,
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2020-05-02 09:14:05 -04:00
|
|
|
shellLsSpy = spyOn(shell, 'ls').and.returnValue([] as unknown as shell.ShellArray);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should return a promise', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
const promise = (bc as any).listShasByDate('input/dir');
|
2020-05-02 09:14:14 -04:00
|
|
|
expect(promise).toBeInstanceOf(Promise);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
// Do not complete the test (and release the spies) synchronously to avoid running the actual `ls()`.
|
|
|
|
await promise;
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should `ls()` files with their metadata', async () => {
|
|
|
|
await (bc as any).listShasByDate('input/dir');
|
|
|
|
expect(shellLsSpy).toHaveBeenCalledWith('-l', 'input/dir');
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should reject if listing files fails', async () => {
|
|
|
|
shellLsSpy.and.rejectWith('Test');
|
|
|
|
await expectAsync((bc as any).listShasByDate('input/dir')).toBeRejectedWith('Test');
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should return the filenames', async () => {
|
|
|
|
shellLsSpy.and.resolveTo([
|
2017-06-18 18:15:07 -04:00
|
|
|
lsResult('foo', 100),
|
|
|
|
lsResult('bar', 200),
|
|
|
|
lsResult('baz', 300),
|
2020-05-02 09:14:14 -04:00
|
|
|
]);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'bar', 'baz']);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should sort by date', async () => {
|
|
|
|
shellLsSpy.and.resolveTo([
|
2017-06-18 18:15:07 -04:00
|
|
|
lsResult('foo', 300),
|
|
|
|
lsResult('bar', 100),
|
|
|
|
lsResult('baz', 200),
|
2020-05-02 09:14:14 -04:00
|
|
|
]);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should not break with ShellJS\' custom `sort()` method', async () => {
|
2017-06-18 18:15:07 -04:00
|
|
|
const mockArray = [
|
|
|
|
lsResult('foo', 300),
|
|
|
|
lsResult('bar', 100),
|
|
|
|
lsResult('baz', 200),
|
|
|
|
];
|
|
|
|
mockArray.sort = jasmine.createSpy('sort');
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
shellLsSpy.and.resolveTo(mockArray);
|
|
|
|
|
|
|
|
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['bar', 'baz', 'foo']);
|
|
|
|
expect(mockArray.sort).not.toHaveBeenCalled();
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
it('should only include directories', async () => {
|
|
|
|
shellLsSpy.and.resolveTo([
|
2017-06-18 18:15:07 -04:00
|
|
|
lsResult('foo', 100),
|
|
|
|
lsResult('bar', 200, false),
|
|
|
|
lsResult('baz', 300),
|
2020-05-02 09:14:14 -04:00
|
|
|
]);
|
2017-06-18 18:15:07 -04:00
|
|
|
|
2020-05-02 09:14:14 -04:00
|
|
|
await expectAsync((bc as any).listShasByDate('input/dir')).toBeResolvedTo(['foo', 'baz']);
|
2017-06-18 18:15:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2017-02-06 13:40:28 -05:00
|
|
|
});
|