`supertest.Request` extends `Promise` and can be used directly without "promisifying". PR Close #25671
		
			
				
	
	
		
			693 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			693 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| // Imports
 | |
| import * as express from 'express';
 | |
| import * as http from 'http';
 | |
| import * as supertest from 'supertest';
 | |
| import {CircleCiApi} from '../../lib/common/circle-ci-api';
 | |
| import {GithubApi} from '../../lib/common/github-api';
 | |
| import {GithubPullRequests} from '../../lib/common/github-pull-requests';
 | |
| import {GithubTeams} from '../../lib/common/github-teams';
 | |
| import {Logger} from '../../lib/common/utils';
 | |
| import {BuildCreator} from '../../lib/preview-server/build-creator';
 | |
| import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
 | |
| import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
 | |
| import {BuildVerifier} from '../../lib/preview-server/build-verifier';
 | |
| import {PreviewServerConfig, PreviewServerFactory} from '../../lib/preview-server/preview-server-factory';
 | |
| 
 | |
| interface CircleCiWebHookPayload {
 | |
|   payload: {
 | |
|     build_num: number;
 | |
|     build_parameters: {
 | |
|       CIRCLE_JOB: string;
 | |
|     }
 | |
|   };
 | |
| }
 | |
| 
 | |
| // Tests
 | |
| describe('PreviewServerFactory', () => {
 | |
|   const defaultConfig: PreviewServerConfig = {
 | |
|     buildArtifactPath: 'artifact/path.zip',
 | |
|     buildsDir: 'builds/dir',
 | |
|     circleCiToken: 'CIRCLE_CI_TOKEN',
 | |
|     domainName: 'domain.name',
 | |
|     downloadSizeLimit: 999,
 | |
|     downloadsDir: '/tmp/aio-create-builds',
 | |
|     githubOrg: 'organisation',
 | |
|     githubRepo: 'repo',
 | |
|     githubTeamSlugs: ['team1', 'team2'],
 | |
|     githubToken: '12345',
 | |
|     significantFilesPattern: '^(?:aio|packages)\\/(?!.*[._]spec\\.[jt]s$)',
 | |
|     trustedPrLabel: 'trusted: pr-label',
 | |
|   };
 | |
|   let loggerErrorSpy: jasmine.Spy;
 | |
|   let loggerInfoSpy: jasmine.Spy;
 | |
|   let loggerLogSpy: jasmine.Spy;
 | |
| 
 | |
|   // Helpers
 | |
|   const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
 | |
|     PreviewServerFactory.create({...defaultConfig, ...partialConfig});
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     loggerErrorSpy = spyOn(Logger.prototype, 'error');
 | |
|     loggerInfoSpy = spyOn(Logger.prototype, 'info');
 | |
|     loggerLogSpy = spyOn(Logger.prototype, 'log');
 | |
|   });
 | |
| 
 | |
|   describe('create()', () => {
 | |
|     let usfCreateMiddlewareSpy: jasmine.Spy;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       usfCreateMiddlewareSpy = spyOn(PreviewServerFactory, 'createMiddleware').and.callThrough();
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'buildsDir\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({buildsDir: ''})).
 | |
|         toThrowError('Missing or empty required parameter \'buildsDir\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'domainName\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({domainName: ''})).
 | |
|         toThrowError('Missing or empty required parameter \'domainName\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'githubToken\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({githubToken: ''})).
 | |
|         toThrowError('Missing or empty required parameter \'githubToken\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'githubOrg\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({githubOrg: ''})).
 | |
|         toThrowError('Missing or empty required parameter \'githubOrg\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'githubTeamSlugs\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({githubTeamSlugs: []})).
 | |
|         toThrowError('Missing or empty required parameter \'allowedTeamSlugs\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'githubRepo\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({githubRepo: ''})).
 | |
|         toThrowError('Missing or empty required parameter \'githubRepo\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should throw if \'trustedPrLabel\' is missing or empty', () => {
 | |
|       expect(() => createPreviewServer({trustedPrLabel: ''})).
 | |
|         toThrowError('Missing or empty required parameter \'trustedPrLabel\'!');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should return an http.Server', () => {
 | |
|       const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
 | |
|       const server = createPreviewServer();
 | |
| 
 | |
|       expect(server).toBe(httpCreateServerSpy.calls.mostRecent().returnValue);
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should create and use an appropriate BuildCreator', () => {
 | |
|       const usfCreateBuildCreatorSpy = spyOn(PreviewServerFactory, 'createBuildCreator').and.callThrough();
 | |
| 
 | |
|       createPreviewServer();
 | |
|       const buildRetriever = jasmine.any(BuildRetriever);
 | |
|       const buildVerifier = jasmine.any(BuildVerifier);
 | |
|       const prs = jasmine.any(GithubPullRequests);
 | |
|       const buildCreator: BuildCreator = usfCreateBuildCreatorSpy.calls.mostRecent().returnValue;
 | |
| 
 | |
|       expect(usfCreateMiddlewareSpy).toHaveBeenCalledWith(buildRetriever, buildVerifier, buildCreator, defaultConfig);
 | |
|       expect(usfCreateBuildCreatorSpy).toHaveBeenCalledWith(prs, 'builds/dir', 'domain.name');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should create and use an appropriate middleware', () => {
 | |
|       const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
 | |
| 
 | |
|       createPreviewServer();
 | |
| 
 | |
|       const buildRetriever = jasmine.any(BuildRetriever);
 | |
|       const buildVerifier = jasmine.any(BuildVerifier);
 | |
|       const buildCreator = jasmine.any(BuildCreator);
 | |
|       expect(usfCreateMiddlewareSpy).toHaveBeenCalledWith(buildRetriever, buildVerifier, buildCreator, defaultConfig);
 | |
| 
 | |
|       const middleware: express.Express = usfCreateMiddlewareSpy.calls.mostRecent().returnValue;
 | |
|       expect(httpCreateServerSpy).toHaveBeenCalledWith(middleware);
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should log the server address info on \'listening\'', () => {
 | |
|       const server = createPreviewServer();
 | |
|       server.address = () => ({address: 'foo', family: '', port: 1337});
 | |
| 
 | |
|       expect(loggerInfoSpy).not.toHaveBeenCalled();
 | |
| 
 | |
|       server.emit('listening');
 | |
|       expect(loggerInfoSpy).toHaveBeenCalledWith('Up and running (and listening on foo:1337)...');
 | |
|     });
 | |
| 
 | |
|   });
 | |
| 
 | |
| 
 | |
|   // Protected methods
 | |
| 
 | |
|   describe('createBuildCreator()', () => {
 | |
|     let buildCreator: BuildCreator;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       const api = new GithubApi(defaultConfig.githubToken);
 | |
|       const prs = new GithubPullRequests(api, defaultConfig.githubOrg, defaultConfig.githubRepo);
 | |
|       buildCreator = PreviewServerFactory.createBuildCreator(prs, defaultConfig.buildsDir, defaultConfig.domainName);
 | |
|     });
 | |
| 
 | |
|     it('should pass the \'buildsDir\' to the BuildCreator', () => {
 | |
|       expect((buildCreator as any).buildsDir).toBe('builds/dir');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('on \'build.created\'', () => {
 | |
|       let prsAddCommentSpy: jasmine.Spy;
 | |
| 
 | |
|       beforeEach(() => prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment'));
 | |
| 
 | |
| 
 | |
|       it('should post a comment on GitHub for public previews', () => {
 | |
|         const commentBody = 'You can preview 1234567890 at https://pr42-1234567890.domain.name/.';
 | |
| 
 | |
|         buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: true});
 | |
|         expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should not post a comment on GitHub for non-public previews', () => {
 | |
|         buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: false});
 | |
|         expect(prsAddCommentSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('on \'pr.changedVisibility\'', () => {
 | |
|       let prsAddCommentSpy: jasmine.Spy;
 | |
| 
 | |
|       beforeEach(() => prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment'));
 | |
| 
 | |
| 
 | |
|       it('should post a comment on GitHub (for all SHAs) for PRs made public', () => {
 | |
|         const commentBody = 'You can preview 12345 at https://pr42-12345.domain.name/.\n' +
 | |
|                             'You can preview 67890 at https://pr42-67890.domain.name/.';
 | |
| 
 | |
|         buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: true});
 | |
|         expect(prsAddCommentSpy).toHaveBeenCalledWith(42, commentBody);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should not post a comment on GitHub if no SHAs were affected', () => {
 | |
|         buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: [], isPublic: true});
 | |
|         expect(prsAddCommentSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should not post a comment on GitHub for PRs made non-public', () => {
 | |
|         buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: false});
 | |
|         expect(prsAddCommentSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should pass the correct parameters to GithubPullRequests', () => {
 | |
|       const prsAddCommentSpy = spyOn(GithubPullRequests.prototype, 'addComment');
 | |
| 
 | |
|       buildCreator.emit(CreatedBuildEvent.type, {pr: 42, sha: '1234567890', isPublic: true});
 | |
|       buildCreator.emit(ChangedPrVisibilityEvent.type, {pr: 42, shas: ['12345', '67890'], isPublic: true});
 | |
| 
 | |
|       const allCalls = prsAddCommentSpy.calls.all();
 | |
|       const prs: GithubPullRequests = allCalls[0].object;
 | |
| 
 | |
|       expect(prsAddCommentSpy).toHaveBeenCalledTimes(2);
 | |
|       expect(prs).toBe(allCalls[1].object);
 | |
|       expect(prs).toEqual(jasmine.any(GithubPullRequests));
 | |
|       expect(prs.repoSlug).toBe('organisation/repo');
 | |
|     });
 | |
| 
 | |
|   });
 | |
| 
 | |
| 
 | |
|   describe('createMiddleware()', () => {
 | |
|     let buildRetriever: BuildRetriever;
 | |
|     let buildVerifier: BuildVerifier;
 | |
|     let buildCreator: BuildCreator;
 | |
|     let agent: supertest.SuperTest<supertest.Test>;
 | |
| 
 | |
|     beforeEach(() => {
 | |
|       const circleCiApi = new CircleCiApi(defaultConfig.githubOrg, defaultConfig.githubRepo,
 | |
|                                           defaultConfig.circleCiToken);
 | |
|       const githubApi = new GithubApi(defaultConfig.githubToken);
 | |
|       const prs = new GithubPullRequests(githubApi, defaultConfig.githubOrg, defaultConfig.githubRepo);
 | |
|       const teams = new GithubTeams(githubApi, defaultConfig.githubOrg);
 | |
| 
 | |
|       buildRetriever = new BuildRetriever(circleCiApi, defaultConfig.downloadSizeLimit, defaultConfig.downloadsDir);
 | |
|       buildVerifier = new BuildVerifier(prs, teams, defaultConfig.githubTeamSlugs, defaultConfig.trustedPrLabel);
 | |
|       buildCreator = new BuildCreator(defaultConfig.buildsDir);
 | |
| 
 | |
|       const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator,
 | |
|                                                                defaultConfig);
 | |
|       agent = supertest.agent(middleware);
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('GET /health-check', () => {
 | |
| 
 | |
|       it('should respond with 200', async () => {
 | |
|         await Promise.all([
 | |
|           agent.get('/health-check').expect(200),
 | |
|           agent.get('/health-check/').expect(200),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 404 for non-GET requests', async () => {
 | |
|         await Promise.all([
 | |
|           agent.put('/health-check').expect(404),
 | |
|           agent.post('/health-check').expect(404),
 | |
|           agent.patch('/health-check').expect(404),
 | |
|           agent.delete('/health-check').expect(404),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 404 if the path does not match exactly', async () => {
 | |
|         await Promise.all([
 | |
|           agent.get('/health-check/foo').expect(404),
 | |
|           agent.get('/health-check-foo').expect(404),
 | |
|           agent.get('/health-checknfoo').expect(404),
 | |
|           agent.get('/foo/health-check').expect(404),
 | |
|           agent.get('/foo-health-check').expect(404),
 | |
|           agent.get('/foonhealth-check').expect(404),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('GET /can-have-public-preview/<pr>', () => {
 | |
|       const baseUrl = '/can-have-public-preview';
 | |
|       const pr = 777;
 | |
|       const url = `${baseUrl}/${pr}`;
 | |
|       let bvGetPrIsTrustedSpy: jasmine.Spy;
 | |
|       let bvGetSignificantFilesChangedSpy: jasmine.Spy;
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted').and.returnValue(Promise.resolve(true));
 | |
|         bvGetSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged').
 | |
|           and.returnValue(Promise.resolve(true));
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 404 for non-GET requests', async () => {
 | |
|         await Promise.all([
 | |
|           agent.put(url).expect(404),
 | |
|           agent.post(url).expect(404),
 | |
|           agent.patch(url).expect(404),
 | |
|           agent.delete(url).expect(404),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 404 if the path does not match exactly', async () => {
 | |
|         await Promise.all([
 | |
|           agent.get('/can-have-public-preview/42/foo').expect(404),
 | |
|           agent.get('/can-have-public-preview-foo/42').expect(404),
 | |
|           agent.get('/can-have-public-previewnfoo/42').expect(404),
 | |
|           agent.get('/foo/can-have-public-preview/42').expect(404),
 | |
|           agent.get('/foo-can-have-public-preview/42').expect(404),
 | |
|           agent.get('/fooncan-have-public-preview/42').expect(404),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond appropriately if the PR did not touch any significant files', async () => {
 | |
|         bvGetSignificantFilesChangedSpy.and.returnValue(Promise.resolve(false));
 | |
| 
 | |
|         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.`;
 | |
| 
 | |
|         await agent.get(url).expect(200, expectedResponse);
 | |
| 
 | |
|         expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
 | |
|         expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|         expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond appropriately if the PR is not automatically verifiable as "trusted"', async () => {
 | |
|         bvGetPrIsTrustedSpy.and.returnValue(Promise.resolve(false));
 | |
| 
 | |
|         const expectedResponse = {canHavePublicPreview: false, reason: 'Not automatically verifiable as "trusted".'};
 | |
|         const expectedLog =
 | |
|           `PR:${pr} - Cannot have a public preview, because not automatically verifiable as "trusted".`;
 | |
| 
 | |
|         await agent.get(url).expect(200, expectedResponse);
 | |
| 
 | |
|         expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
 | |
|         expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
 | |
|         expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond appropriately if the PR can have a preview', async () => {
 | |
|         const expectedResponse = {canHavePublicPreview: true, reason: null};
 | |
|         const expectedLog = `PR:${pr} - Can have a public preview.`;
 | |
| 
 | |
|         await agent.get(url).expect(200, expectedResponse);
 | |
| 
 | |
|         expect(bvGetSignificantFilesChangedSpy).toHaveBeenCalledWith(pr, jasmine.any(RegExp));
 | |
|         expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(pr);
 | |
|         expect(loggerLogSpy).toHaveBeenCalledWith(expectedLog);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with error if `getSignificantFilesChanged()` fails', async () => {
 | |
|         bvGetSignificantFilesChangedSpy.and.callFake(() => Promise.reject('getSignificantFilesChanged error'));
 | |
| 
 | |
|         await agent.get(url).expect(500, 'getSignificantFilesChanged error');
 | |
|         expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', 'getSignificantFilesChanged error');
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with error if `getPrIsTrusted()` fails', async () => {
 | |
|         const error = new Error('getPrIsTrusted error');
 | |
|         bvGetPrIsTrustedSpy.and.callFake(() => { throw error; });
 | |
| 
 | |
|         await agent.get(url).expect(500, 'getPrIsTrusted error');
 | |
|         expect(loggerErrorSpy).toHaveBeenCalledWith('Previewability check error', error);
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('POST /circle-build', () => {
 | |
|       let getGithubInfoSpy: jasmine.Spy;
 | |
|       let getSignificantFilesChangedSpy: jasmine.Spy;
 | |
|       let downloadBuildArtifactSpy: jasmine.Spy;
 | |
|       let getPrIsTrustedSpy: jasmine.Spy;
 | |
|       let createBuildSpy: jasmine.Spy;
 | |
|       let IS_PUBLIC: boolean;
 | |
|       let BUILD_INFO: GithubInfo;
 | |
|       let AFFECTS_SIGNIFICANT_FILES: boolean;
 | |
|       let BASIC_PAYLOAD: CircleCiWebHookPayload;
 | |
|       const URL = '/circle-build';
 | |
|       const BUILD_NUM = 12345;
 | |
|       const PR = 777;
 | |
|       const SHA = 'COMMIT';
 | |
|       const DOWNLOADED_ARTIFACT_PATH = 'downloads/777-COMMIT-build.zip';
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         IS_PUBLIC = true;
 | |
|         BUILD_INFO  = {
 | |
|           org: defaultConfig.githubOrg,
 | |
|           pr: PR,
 | |
|           repo: defaultConfig.githubRepo,
 | |
|           sha: SHA,
 | |
|           success: true,
 | |
|         };
 | |
|         BASIC_PAYLOAD = { payload: { build_num: BUILD_NUM, build_parameters: { CIRCLE_JOB: 'aio_preview' } } };
 | |
|         AFFECTS_SIGNIFICANT_FILES = true;
 | |
|         getGithubInfoSpy = spyOn(buildRetriever, 'getGithubInfo')
 | |
|           .and.callFake(() => Promise.resolve(BUILD_INFO));
 | |
|         getSignificantFilesChangedSpy = spyOn(buildVerifier, 'getSignificantFilesChanged')
 | |
|           .and.callFake(() => Promise.resolve(AFFECTS_SIGNIFICANT_FILES));
 | |
|         downloadBuildArtifactSpy = spyOn(buildRetriever, 'downloadBuildArtifact')
 | |
|           .and.callFake(() => Promise.resolve(DOWNLOADED_ARTIFACT_PATH));
 | |
|         getPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted')
 | |
|           .and.callFake(() => Promise.resolve(IS_PUBLIC));
 | |
|         createBuildSpy = spyOn(buildCreator, 'create');
 | |
|       });
 | |
| 
 | |
|       it('should respond with 400 if the request body is not in the correct format', async () => {
 | |
|         await Promise.all([
 | |
|           agent.post(URL).expect(400),
 | |
|           agent.post(URL).send().expect(400),
 | |
|           agent.post(URL).send({}).expect(400),
 | |
|           agent.post(URL).send({ payload: {} }).expect(400),
 | |
|           agent.post(URL).send({ payload: { build_num: -1 } }).expect(400),
 | |
|           agent.post(URL).send({ payload: { build_num: 4000 } }).expect(400),
 | |
|           agent.post(URL).send({ payload: { build_num: 4000, build_parameters: { } } }).expect(400),
 | |
|           agent.post(URL).send({ payload: { build_num: 4000, build_parameters: { CIRCLE_JOB: '' } } }).expect(400),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
|       it('should create a preview if everything is good and the build succeeded', async () => {
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(201);
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
 | |
|         expect(downloadBuildArtifactSpy).toHaveBeenCalledWith(BUILD_NUM, PR, SHA, defaultConfig.buildArtifactPath);
 | |
|         expect(getPrIsTrustedSpy).toHaveBeenCalledWith(PR);
 | |
|         expect(createBuildSpy).toHaveBeenCalledWith(PR, SHA, DOWNLOADED_ARTIFACT_PATH, IS_PUBLIC);
 | |
|       });
 | |
| 
 | |
|       it('should respond with 204 if the reported build is not the "AIO preview" job', async () => {
 | |
|         BASIC_PAYLOAD.payload.build_parameters.CIRCLE_JOB = 'lint';
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
 | |
|         expect(getGithubInfoSpy).not.toHaveBeenCalled();
 | |
|         expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
 | |
|         expect(loggerLogSpy).toHaveBeenCalledWith(
 | |
|           'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
 | |
|         expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|         expect(createBuildSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|       it('should respond with 204 if the build did not affect any significant files', async () => {
 | |
|         AFFECTS_SIGNIFICANT_FILES = false;
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
 | |
|         expect(loggerLogSpy).toHaveBeenCalledWith(
 | |
|           'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
 | |
|         expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|         expect(createBuildSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|       it('should respond with 201 if the build is trusted', async () => {
 | |
|         IS_PUBLIC = true;
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(201);
 | |
|       });
 | |
| 
 | |
|       it('should respond with 202 if the build is not trusted', async () => {
 | |
|         IS_PUBLIC = false;
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(202);
 | |
|       });
 | |
| 
 | |
|       it('should not create a preview if the build was not successful', async () => {
 | |
|         BUILD_INFO.success = false;
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|         expect(createBuildSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|       it('should fail if the CircleCI request fails', async () => {
 | |
|         // Note it is important to put the `reject` into `and.callFake`;
 | |
|         // If you just `and.returnValue` the rejected promise
 | |
|         // then you get an "unhandled rejection" message in the console.
 | |
|         getGithubInfoSpy.and.callFake(() => Promise.reject('Test Error'));
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|         expect(createBuildSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|       it('should fail if the Github organisation of the build does not match the configured organisation', async () => {
 | |
|         BUILD_INFO.org = 'bad';
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD)
 | |
|           .expect(500, `Invalid webhook: expected "githubOrg" property to equal "organisation" but got "bad".`);
 | |
|       });
 | |
| 
 | |
|       it('should fail if the Github repo of the build does not match the configured repo', async () => {
 | |
|         BUILD_INFO.repo = 'bad';
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD)
 | |
|           .expect(500, `Invalid webhook: expected "githubRepo" property to equal "repo" but got "bad".`);
 | |
|       });
 | |
| 
 | |
|       it('should fail if the artifact fetch request fails', async () => {
 | |
|         downloadBuildArtifactSpy.and.callFake(() => Promise.reject('Test Error'));
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(downloadBuildArtifactSpy).toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|         expect(createBuildSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|       it('should fail if verifying the PR fails', async () => {
 | |
|         getPrIsTrustedSpy.and.callFake(() => Promise.reject('Test Error'));
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(downloadBuildArtifactSpy).toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).toHaveBeenCalled();
 | |
|         expect(createBuildSpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
|       it('should fail if creating the preview build fails', async () => {
 | |
|         createBuildSpy.and.callFake(() => Promise.reject('Test Error'));
 | |
|         await agent.post(URL).send(BASIC_PAYLOAD).expect(500, 'Test Error');
 | |
|         expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
 | |
|         expect(downloadBuildArtifactSpy).toHaveBeenCalled();
 | |
|         expect(getPrIsTrustedSpy).toHaveBeenCalled();
 | |
|         expect(createBuildSpy).toHaveBeenCalled();
 | |
|       });
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('POST /pr-updated', () => {
 | |
|       const pr = '9';
 | |
|       const url = '/pr-updated';
 | |
|       let bvGetPrIsTrustedSpy: jasmine.Spy;
 | |
|       let bcUpdatePrVisibilitySpy: jasmine.Spy;
 | |
| 
 | |
|       // Helpers
 | |
|       const createRequest = (num: number, action?: string) =>
 | |
|         agent.post(url).send({number: num, action});
 | |
| 
 | |
|       beforeEach(() => {
 | |
|         bvGetPrIsTrustedSpy = spyOn(buildVerifier, 'getPrIsTrusted');
 | |
|         bcUpdatePrVisibilitySpy = spyOn(buildCreator, 'updatePrVisibility');
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 404 for non-POST requests', async () => {
 | |
|         await Promise.all([
 | |
|           agent.get(url).expect(404),
 | |
|           agent.put(url).expect(404),
 | |
|           agent.patch(url).expect(404),
 | |
|           agent.delete(url).expect(404),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 400 for requests without a payload', async () => {
 | |
|         const responseBody = `Missing or empty 'number' field in request: POST ${url} {}`;
 | |
| 
 | |
|         const request1 = agent.post(url);
 | |
|         const request2 = agent.post(url).send();
 | |
| 
 | |
|         await Promise.all([
 | |
|           request1.expect(400, responseBody),
 | |
|           request2.expect(400, responseBody),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should respond with 400 for requests without a \'number\' field', async () => {
 | |
|         const responseBodyPrefix = `Missing or empty 'number' field in request: POST ${url}`;
 | |
| 
 | |
|         const request1 = agent.post(url).send({});
 | |
|         const request2 = agent.post(url).send({number: null});
 | |
| 
 | |
|         await Promise.all([
 | |
|           request1.expect(400, `${responseBodyPrefix} {}`),
 | |
|           request2.expect(400, `${responseBodyPrefix} {"number":null}`),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should call \'BuildVerifier#gtPrIsTrusted()\' with the correct arguments', async () => {
 | |
|         await createRequest(+pr);
 | |
|         expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should propagate errors from BuildVerifier', async () => {
 | |
|         bvGetPrIsTrustedSpy.and.callFake(() => Promise.reject('Test'));
 | |
| 
 | |
|         await createRequest(+pr).expect(500, 'Test');
 | |
| 
 | |
|         expect(bvGetPrIsTrustedSpy).toHaveBeenCalledWith(9);
 | |
|         expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should call \'BuildCreator#updatePrVisibility()\' with the correct arguments', async () => {
 | |
|         bvGetPrIsTrustedSpy.and.callFake((pr2: number) => Promise.resolve(pr2 === 42));
 | |
| 
 | |
|         await createRequest(24);
 | |
|         expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(24, false);
 | |
| 
 | |
|         await createRequest(42);
 | |
|         expect(bcUpdatePrVisibilitySpy).toHaveBeenCalledWith(42, true);
 | |
|       });
 | |
| 
 | |
| 
 | |
|       it('should propagate errors from BuildCreator', async () => {
 | |
|         bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject('Test'));
 | |
|         await createRequest(+pr).expect(500, 'Test');
 | |
|       });
 | |
| 
 | |
| 
 | |
|       describe('on success', () => {
 | |
| 
 | |
|         it('should respond with 200 (action: undefined)', async () => {
 | |
|           bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
 | |
| 
 | |
|           const reqs = [4, 2].map(num => createRequest(num).expect(200, http.STATUS_CODES[200]));
 | |
|           await Promise.all(reqs);
 | |
|         });
 | |
| 
 | |
| 
 | |
|         it('should respond with 200 (action: labeled)', async () => {
 | |
|           bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
 | |
| 
 | |
|           const reqs = [4, 2].map(num => createRequest(num, 'labeled').expect(200, http.STATUS_CODES[200]));
 | |
|           await Promise.all(reqs);
 | |
|         });
 | |
| 
 | |
| 
 | |
|         it('should respond with 200 (action: unlabeled)', async () => {
 | |
|           bvGetPrIsTrustedSpy.and.returnValues(Promise.resolve(true), Promise.resolve(false));
 | |
| 
 | |
|           const reqs = [4, 2].map(num => createRequest(num, 'unlabeled').expect(200, http.STATUS_CODES[200]));
 | |
|           await Promise.all(reqs);
 | |
|         });
 | |
| 
 | |
| 
 | |
|         it('should respond with 200 (and do nothing) if \'action\' implies no visibility change', async () => {
 | |
|           const promises = ['foo', 'notlabeled'].
 | |
|             map(action => createRequest(+pr, action).expect(200, http.STATUS_CODES[200]));
 | |
| 
 | |
|           await Promise.all(promises);
 | |
|           expect(bvGetPrIsTrustedSpy).not.toHaveBeenCalled();
 | |
|           expect(bcUpdatePrVisibilitySpy).not.toHaveBeenCalled();
 | |
|         });
 | |
| 
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
| 
 | |
|     describe('ALL *', () => {
 | |
| 
 | |
|       it('should respond with 404', async () => {
 | |
|         const responseFor = (method: string) => `Unknown resource in request: ${method.toUpperCase()} /some/url`;
 | |
| 
 | |
|         await Promise.all([
 | |
|           agent.get('/some/url').expect(404, responseFor('get')),
 | |
|           agent.put('/some/url').expect(404, responseFor('put')),
 | |
|           agent.post('/some/url').expect(404, responseFor('post')),
 | |
|           agent.patch('/some/url').expect(404, responseFor('patch')),
 | |
|           agent.delete('/some/url').expect(404, responseFor('delete')),
 | |
|         ]);
 | |
|       });
 | |
| 
 | |
|     });
 | |
| 
 | |
|   });
 | |
| 
 | |
| });
 |