// Imports import * as fs from 'fs'; import * as shell from 'shelljs'; import {BuildCleaner} from '../../lib/clean-up/build-cleaner'; import {GithubPullRequests} from '../../lib/common/github-pull-requests'; // Tests describe('BuildCleaner', () => { let cleaner: BuildCleaner; beforeEach(() => cleaner = new BuildCleaner('/foo/bar', 'baz/qux', '12345')); describe('constructor()', () => { it('should throw if \'buildsDir\' is empty', () => { expect(() => new BuildCleaner('', '/baz/qux')).toThrowError('Missing or empty required parameter \'buildsDir\'!'); }); it('should throw if \'repoSlug\' is empty', () => { expect(() => new BuildCleaner('/foo/bar', '')).toThrowError('Missing or empty required parameter \'repoSlug\'!'); }); }); describe('cleanUp()', () => { let cleanerGetExistingBuildNumbersSpy: jasmine.Spy; let cleanerGetOpenPrNumbersSpy: jasmine.Spy; let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy; let existingBuildsDeferred: {resolve: Function, reject: Function}; let openPrsDeferred: {resolve: Function, reject: Function}; let promise: Promise; beforeEach(() => { cleanerGetExistingBuildNumbersSpy = spyOn(cleaner as any, 'getExistingBuildNumbers').and.callFake(() => { return new Promise((resolve, reject) => existingBuildsDeferred = {resolve, reject}); }); cleanerGetOpenPrNumbersSpy = spyOn(cleaner as any, 'getOpenPrNumbers').and.callFake(() => { return new Promise((resolve, reject) => openPrsDeferred = {resolve, reject}); }); cleanerRemoveUnnecessaryBuildsSpy = spyOn(cleaner as any, 'removeUnnecessaryBuilds'); promise = cleaner.cleanUp(); }); it('should return a promise', () => { expect(promise).toEqual(jasmine.any(Promise)); }); it('should get the existing builds', () => { expect(cleanerGetExistingBuildNumbersSpy).toHaveBeenCalled(); }); it('should get the open PRs', () => { expect(cleanerGetOpenPrNumbersSpy).toHaveBeenCalled(); }); it('should reject if \'getExistingBuildNumbers()\' rejects', done => { promise.catch(err => { expect(err).toBe('Test'); done(); }); existingBuildsDeferred.reject('Test'); }); it('should reject if \'getOpenPrNumbers()\' rejects', done => { promise.catch(err => { expect(err).toBe('Test'); done(); }); openPrsDeferred.reject('Test'); }); it('should reject if \'removeUnnecessaryBuilds()\' rejects', done => { promise.catch(err => { expect(err).toBe('Test'); done(); }); cleanerRemoveUnnecessaryBuildsSpy.and.returnValue(Promise.reject('Test')); existingBuildsDeferred.resolve(); openPrsDeferred.resolve(); }); it('should pass existing builds and open PRs to \'removeUnnecessaryBuilds()\'', done => { promise.then(() => { expect(cleanerRemoveUnnecessaryBuildsSpy).toHaveBeenCalledWith('foo', 'bar'); done(); }); existingBuildsDeferred.resolve('foo'); openPrsDeferred.resolve('bar'); }); it('should resolve with the value returned by \'removeUnnecessaryBuilds()\'', done => { promise.then(result => { expect(result).toBe('Test'); done(); }); cleanerRemoveUnnecessaryBuildsSpy.and.returnValue(Promise.resolve('Test')); existingBuildsDeferred.resolve(); openPrsDeferred.resolve(); }); }); // Protected methods describe('getExistingBuildNumbers()', () => { let fsReaddirSpy: jasmine.Spy; let readdirCb: (err: any, files?: string[]) => void; let promise: Promise; beforeEach(() => { fsReaddirSpy = spyOn(fs, 'readdir').and.callFake((_: string, cb: typeof readdirCb) => readdirCb = cb); promise = (cleaner as any).getExistingBuildNumbers(); }); it('should return a promise', () => { expect(promise).toEqual(jasmine.any(Promise)); }); it('should get the contents of the builds directory', () => { expect(fsReaddirSpy).toHaveBeenCalled(); expect(fsReaddirSpy.calls.argsFor(0)[0]).toBe('/foo/bar'); }); it('should reject if an error occurs while getting the files', done => { promise.catch(err => { expect(err).toBe('Test'); done(); }); readdirCb('Test'); }); it('should resolve with the returned files (as numbers)', done => { promise.then(result => { expect(result).toEqual([12, 34, 56]); done(); }); readdirCb(null, ['12', '34', '56']); }); it('should ignore files with non-numeric (or zero) names', done => { promise.then(result => { expect(result).toEqual([12, 34, 56]); done(); }); readdirCb(null, ['12', 'foo', '34', 'bar', '56', '000']); }); }); describe('getOpenPrNumbers()', () => { let prDeferred: {resolve: Function, reject: Function}; let promise: Promise; beforeEach(() => { spyOn(GithubPullRequests.prototype, 'fetchAll').and.callFake(() => { return new Promise((resolve, reject) => prDeferred = {resolve, reject}); }); promise = (cleaner as any).getOpenPrNumbers(); }); it('should return a promise', () => { expect(promise).toEqual(jasmine.any(Promise)); }); it('should fetch open PRs via \'GithubPullRequests\'', () => { expect(GithubPullRequests.prototype.fetchAll).toHaveBeenCalledWith('open'); }); it('should reject if an error occurs while fetching PRs', done => { promise.catch(err => { expect(err).toBe('Test'); done(); }); prDeferred.reject('Test'); }); it('should resolve with the numbers of the fetched PRs', done => { promise.then(prNumbers => { expect(prNumbers).toEqual([1, 2, 3]); done(); }); prDeferred.resolve([{id: 0, number: 1}, {id: 1, number: 2}, {id: 2, number: 3}]); }); }); describe('removeDir()', () => { let shellChmodSpy: jasmine.Spy; let shellRmSpy: jasmine.Spy; beforeEach(() => { shellChmodSpy = spyOn(shell, 'chmod'); shellRmSpy = spyOn(shell, 'rm'); }); it('should remove the specified directory and its content', () => { (cleaner as any).removeDir('/foo/bar'); expect(shellRmSpy).toHaveBeenCalledWith('-rf', '/foo/bar'); }); it('should make the directory and its content writable before removing', () => { shellRmSpy.and.callFake(() => expect(shellChmodSpy).toHaveBeenCalledWith('-R', 'a+w', '/foo/bar')); (cleaner as any).removeDir('/foo/bar'); expect(shellRmSpy).toHaveBeenCalled(); }); it('should catch errors and log them', () => { const consoleErrorSpy = spyOn(console, 'error'); shellRmSpy.and.callFake(() => { throw 'Test'; }); (cleaner as any).removeDir('/foo/bar'); expect(consoleErrorSpy).toHaveBeenCalled(); expect(consoleErrorSpy.calls.argsFor(0)[0]).toContain('Unable to remove \'/foo/bar\''); expect(consoleErrorSpy.calls.argsFor(0)[1]).toBe('Test'); }); }); describe('removeUnnecessaryBuilds()', () => { let consoleLogSpy: jasmine.Spy; let cleanerRemoveDirSpy: jasmine.Spy; beforeEach(() => { consoleLogSpy = spyOn(console, 'log'); cleanerRemoveDirSpy = spyOn(cleaner as any, 'removeDir'); }); it('should log the number of existing builds, open PRs and builds to be removed', () => { (cleaner as any).removeUnnecessaryBuilds([1, 2, 3], [3, 4, 5, 6]); expect(console.log).toHaveBeenCalledWith('Existing builds: 3'); expect(console.log).toHaveBeenCalledWith('Open pull requests: 4'); expect(console.log).toHaveBeenCalledWith('Removing 2 build(s): 1, 2'); }); it('should construct full paths to directories (by prepending \'buildsDir\')', () => { (cleaner as any).removeUnnecessaryBuilds([1, 2, 3], []); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/1'); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/2'); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/3'); }); it('should remove the builds that do not correspond to open PRs', () => { (cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], [2, 4]); expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(2); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/1'); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/3'); cleanerRemoveDirSpy.calls.reset(); (cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], [1, 2, 3, 4]); expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(0); cleanerRemoveDirSpy.calls.reset(); (cleaner as any).removeUnnecessaryBuilds([1, 2, 3, 4], []); expect(cleanerRemoveDirSpy).toHaveBeenCalledTimes(4); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/1'); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/2'); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/3'); expect(cleanerRemoveDirSpy).toHaveBeenCalledWith('/foo/bar/4'); cleanerRemoveDirSpy.calls.reset(); }); }); });