// Imports import * as fs from 'fs'; import * as path from 'path'; import {CmdResult, helper as h} from './helper'; // Tests describe('upload-server (on HTTP)', () => { const hostname = h.uploadHostname; const port = h.uploadPort; const host = `${hostname}:${port}`; const pr = '9'; const sha9 = '9'.repeat(40); const sha0 = '0'.repeat(40); beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000); afterEach(() => h.cleanUp()); describe(`${host}/create-build//`, () => { const curl = `curl -iL --header "X-FILE: ${h.buildsDir}/snapshot.tar.gz"`; it('should disallow non-GET requests', done => { const url = `http://${host}/create-build/${pr}/${sha9}`; const bodyRegex = /^Unsupported method/; Promise.all([ h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405, bodyRegex)), h.runCmd(`curl -iLX POST ${url}`).then(h.verifyResponse(405, bodyRegex)), h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405, bodyRegex)), h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405, bodyRegex)), ]).then(done); }); it('should reject requests without an \'X-FILE\' header', done => { const headers = '--header "X-FILE: "'; const url = `http://${host}/create-build/${pr}/${sha9}`; const bodyRegex = /^Missing or empty 'X-FILE' header/; Promise.all([ h.runCmd(`curl -iL ${url}`).then(h.verifyResponse(400, bodyRegex)), h.runCmd(`curl -iL ${headers} ${url}`).then(h.verifyResponse(400, bodyRegex)), ]).then(done); }); it('should respond with 404 for unknown paths', done => { const cmdPrefix = `${curl} http://${host}`; Promise.all([ h.runCmd(`${cmdPrefix}/foo/create-build/${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/foo-create-build/${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/fooncreate-build/${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/create-build/foo/${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/create-build-foo/${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/create-buildnfoo/${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/create-build/pr${pr}/${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${cmdPrefix}/create-build/${pr}/${sha9}42`).then(h.verifyResponse(404)), ]).then(done); }); it('should reject PRs with leading zeros', done => { h.runCmd(`${curl} http://${host}/create-build/0${pr}/${sha9}`). then(h.verifyResponse(404)). then(done); }); it('should accept SHAs with leading zeros (but not ignore them)', done => { Promise.all([ h.runCmd(`${curl} http://${host}/create-build/${pr}/0${sha9}`).then(h.verifyResponse(404)), h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha0}`).then(h.verifyResponse(500)), ]).then(done); }); it('should not overwrite existing builds', done => { h.createDummyBuild(pr, sha9); expect(h.readBuildFile(pr, sha9, 'index.html')).toContain('index.html'); h.writeBuildFile(pr, sha9, 'index.html', 'My content'); expect(h.readBuildFile(pr, sha9, 'index.html')).toBe('My content'); h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`). then(h.verifyResponse(403, /^Request to overwrite existing directory/)). then(() => expect(h.readBuildFile(pr, sha9, 'index.html')).toBe('My content')). then(done); }); it('should delete the PR directory on error (for new PR)', done => { const prDir = path.join(h.buildsDir, pr); h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`). then(h.verifyResponse(500)). then(() => expect(fs.existsSync(prDir)).toBe(false)). then(done); }); it('should only delete the SHA directory on error (for existing PR)', done => { const prDir = path.join(h.buildsDir, pr); const shaDir = path.join(prDir, sha9); h.createDummyBuild(pr, sha0); h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`). then(h.verifyResponse(500)). then(() => { expect(fs.existsSync(shaDir)).toBe(false); expect(fs.existsSync(prDir)).toBe(true); }). then(done); }); describe('on successful upload', () => { const archivePath = path.join(h.buildsDir, 'snapshot.tar.gz'); let uploadPromise: Promise; beforeEach(() => { h.createDummyArchive(pr, sha9, archivePath); uploadPromise = h.runCmd(`${curl} http://${host}/create-build/${pr}/${sha9}`); }); afterEach(() => h.deletePrDir(pr)); it('should respond with 201', done => { uploadPromise.then(h.verifyResponse(201)).then(done); }); it('should extract the contents of the uploaded file', done => { uploadPromise. then(() => { expect(h.readBuildFile(pr, sha9, 'index.html')).toContain(`uploaded/${pr}`); expect(h.readBuildFile(pr, sha9, 'foo/bar.js')).toContain(`uploaded/${pr}`); }). then(done); }); it(`should create files/directories owned by '${h.serverUser}'`, done => { const shaDir = path.join(h.buildsDir, pr, sha9); const idxPath = path.join(shaDir, 'index.html'); const barPath = path.join(shaDir, 'foo', 'bar.js'); uploadPromise. then(() => Promise.all([ h.runCmd(`find ${shaDir}`), h.runCmd(`find ${shaDir} -user ${h.serverUser}`), ])). then(([{stdout: allFiles}, {stdout: userFiles}]) => { expect(userFiles).toBe(allFiles); expect(userFiles).toContain(shaDir); expect(userFiles).toContain(idxPath); expect(userFiles).toContain(barPath); }). then(done); }); it('should delete the uploaded file', done => { expect(fs.existsSync(archivePath)).toBe(true); uploadPromise. then(() => expect(fs.existsSync(archivePath)).toBe(false)). then(done); }); it('should make the build directory non-writable', done => { const shaDir = path.join(h.buildsDir, pr, sha9); const idxPath = path.join(shaDir, 'index.html'); const barPath = path.join(shaDir, 'foo', 'bar.js'); // See https://github.com/nodejs/node-v0.x-archive/issues/3045#issuecomment-4862588. const isNotWritable = (fileOrDir: string) => { const mode = fs.statSync(fileOrDir).mode; // tslint:disable-next-line: no-bitwise return !(mode & parseInt('222', 8)); }; uploadPromise. then(() => { expect(isNotWritable(shaDir)).toBe(true); expect(isNotWritable(idxPath)).toBe(true); expect(isNotWritable(barPath)).toBe(true); }). then(done); }); }); }); describe(`${host}/health-check`, () => { it('should respond with 200', done => { Promise.all([ h.runCmd(`curl -iL http://${host}/health-check`).then(h.verifyResponse(200)), h.runCmd(`curl -iL http://${host}/health-check/`).then(h.verifyResponse(200)), ]).then(done); }); it('should respond with 404 if the path does not match exactly', done => { Promise.all([ h.runCmd(`curl -iL http://${host}/health-check/foo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL http://${host}/health-check-foo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL http://${host}/health-checknfoo`).then(h.verifyResponse(404)), h.runCmd(`curl -iL http://${host}/foo/health-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL http://${host}/foo-health-check`).then(h.verifyResponse(404)), h.runCmd(`curl -iL http://${host}/foonhealth-check`).then(h.verifyResponse(404)), ]).then(done); }); }); describe(`${host}/*`, () => { it('should respond with 404 for GET requests to unknown URLs', done => { const bodyRegex = /^Unknown resource/; Promise.all([ h.runCmd(`curl -iL http://${host}/index.html`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iL http://${host}/`).then(h.verifyResponse(404, bodyRegex)), h.runCmd(`curl -iL http://${host}`).then(h.verifyResponse(404, bodyRegex)), ]).then(done); }); it('should respond with 405 for non-GET requests to any URL', done => { const bodyRegex = /^Unsupported method/; Promise.all([ h.runCmd(`curl -iLX PUT http://${host}`).then(h.verifyResponse(405, bodyRegex)), h.runCmd(`curl -iLX POST http://${host}`).then(h.verifyResponse(405, bodyRegex)), h.runCmd(`curl -iLX PATCH http://${host}`).then(h.verifyResponse(405, bodyRegex)), h.runCmd(`curl -iLX DELETE http://${host}`).then(h.verifyResponse(405, bodyRegex)), ]).then(done); }); }); });