UNPKG

@mmomtchev/node-pre-gyp-github

Version:

A node-pre-gyp module which provides the ability to publish to GitHub releases.

304 lines (273 loc) 13 kB
'use strict'; const chai = require('chai'); const chaiAsPromised = require('chai-as-promised'); const expect = chai.expect; chai.use(chaiAsPromised); let fs = require('fs'); const Index = require('../index.js'); const index = new Index(); const stage_dir = index.stage_dir; const {Octokit} = require('@octokit/rest'); const octokit = new Octokit({auth: 'secret'}); const sinon = require('sinon'); const reset_index = function (index_string_ref) { delete require.cache[require.resolve(index_string_ref)]; return require(index_string_ref); }; const sandbox = sinon.createSandbox(); const reset_mocks = function () { sandbox.restore(); process.env.NODE_PRE_GYP_GITHUB_TOKEN = 'secret'; fs = reset_index('fs'); fs.readFileSync = function () { return '{"name":"test","version":"0.0.1","repository": {"url":"git+https://github.com/test/test.git"},"binary":{"host":"https://github.com/test/test/releases/download/","remote_path":"{version}"}}'; }; index.stage_dir = stage_dir; Index.prototype.octokit = function () { return octokit; }; sandbox.stub(octokit.repos, 'listReleases').callsFake(function () { return Promise.resolve({data: [{tag_name: '0.0.0', assets: [{name: 'filename'}]}]}); }); sandbox.stub(octokit.repos, 'createRelease').callsFake(function () { return Promise.resolve({data: {tag_name: '0.0.1', draft: true, assets: [{}]}}); }); sandbox.stub(octokit.repos, 'uploadReleaseAsset').callsFake(function () { return Promise.resolve(); }); sandbox.stub(octokit.repos, 'listReleaseAssets').callsFake(function () { return Promise.resolve({ data: [ {state: 'uploaded', id: 1}, {state: 'starter', id: 2} ] }); }); sandbox.stub(octokit.repos, 'deleteReleaseAsset').callsFake(function () { return Promise.reject(); }); }; describe('Publishes packages to GitHub Releases', function () { describe('Publishes without an error under all options', function () { let log, err; beforeEach(() => { log = console.log; err = console.error; reset_mocks(); fs.promises.readdir = function () { return Promise.resolve(['filename']); }; fs.promises.readFile = function () { return {}; }; console.log = function () {}; console.error = function () {}; }); afterEach(() => { console.log = log; console.error = err; }); describe('should publish without error in all scenarios', function () { it('when a release already exists', () => { const tests = []; tests.push(expect(index.publish()).to.be.fulfilled); tests.push(expect(index.publish({draft: false, verbose: false})).to.be.fulfilled); tests.push(expect(index.publish({draft: false, verbose: true})).to.be.fulfilled); tests.push(expect(index.publish({draft: true, verbose: false})).to.be.fulfilled); tests.push(expect(index.publish({draft: true, verbose: true})).to.be.fulfilled); return Promise.all(tests); }); it('when a release does not already exist', () => { const tests = []; octokit.repos.listReleases = function () { return Promise.resolve({data: []}); }; octokit.repos.createRelease = function () { return Promise.resolve({data: {draft: false}}); }; tests.push(expect(index.publish()).to.be.fulfilled); tests.push(expect(index.publish({draft: false, verbose: false})).to.be.fulfilled); tests.push(expect(index.publish({draft: false, verbose: true})).to.be.fulfilled); octokit.repos.createRelease = function () { return Promise.resolve({data: {draft: true}}); }; tests.push(expect(index.publish({draft: true, verbose: false})).to.be.fulfilled); tests.push(expect(index.publish({draft: true, verbose: true})).to.be.fulfilled); fs.readFileSync = function () { return '{"version":"0.0.1","repository": {"url":"git+https://github.com/test/test.git"},"binary":{"host":"https://github.com/test/test/releases/download/","remote_path":"{version}"}}'; }; tests.push(expect(index.publish()).to.be.fulfilled); return Promise.all(tests); }); }); }); describe('Throws an error when node-pre-gyp-github is not configured properly', function () { let log, err; beforeEach(() => { log = console.log; err = console.error; reset_mocks(); console.log = function () {}; console.error = function () {}; }); afterEach(() => { console.log = log; console.error = err; }); it('should throw an error when missing repository.url in package.json', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.readFileSync = function () { return '{}'; }; return expect(index.publish(options)).to.be.rejectedWith( 'Missing repository.url in package.json' ); }); it('should throw an error when a correctly formatted GitHub repository.url is not found in package.json', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.readFileSync = function () { return '{"repository": {"url":"bad_format_url"}}'; }; return expect(index.publish(options)).to.be.rejectedWith( 'A correctly formatted GitHub repository.url was not found within package.json' ); }); it('should throw an error when missing binary.host in package.json', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.readFileSync = function () { return '{"repository": {"url":"git+https://github.com/test/test.git"}}'; }; return expect(index.publish(options)).to.be.rejectedWith( 'Missing binary.host in package.json' ); }); it('should throw an error when binary.host does not begin with the correct url', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.readFileSync = function () { return '{"repository": {"url":"git+https://github.com/test/test.git"},"binary":{"host":"bad_format_binary"}}'; }; return expect(index.publish(options)).to.be.rejectedWith( /^binary.host in package.json should begin with:/i ); }); it('should throw an error when the NODE_PRE_GYP_GITHUB_TOKEN environment variable is not found', function () { const options = {draft: true, verbose: false}; reset_mocks(); process.env.NODE_PRE_GYP_GITHUB_TOKEN = ''; return expect(index.publish(options)).to.be.rejectedWith( 'NODE_PRE_GYP_GITHUB_TOKEN environment variable not found' ); }); it('should throw an error when octokit.repos.listReleases returns an error', function () { const options = {draft: true, verbose: false}; reset_mocks(); octokit.repos.listReleases.restore(); sandbox.stub(octokit.repos, 'listReleases').callsFake(function () { return Promise.reject(new Error('listReleases error')); }); return expect(index.publish(options)).to.be.rejectedWith('listReleases error'); }); it('should throw an error when octokit.repos.createRelease returns an error', function () { const options = {draft: true, verbose: false}; reset_mocks(); octokit.repos.listReleases = function () { return Promise.resolve({data: []}); }; octokit.repos.createRelease = function () { return Promise.reject(new Error('createRelease error')); }; return expect(index.publish(options)).to.be.rejectedWith('createRelease error'); }); it('should throw an error when the stage directory structure is missing', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.promises.readdir = function () { return Promise.reject(new Error('readdir Error')); }; return expect(index.publish(options)).to.be.rejectedWith('readdir Error'); }); it('should throw an error when there are no files found within the stage directory', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.promises.readdir = function () { return Promise.resolve([]); }; return expect(index.publish(options)).to.be.rejectedWith( /^No files found within the stage directory:/i ); }); it('should throw an error when a staged file already exists in the current release', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.promises.readdir = function () { return Promise.resolve(['filename']); }; octokit.repos.listReleases = function () { return Promise.resolve({data: [{tag_name: '0.0.1', assets: [{name: 'filename'}]}]}); }; return expect(index.publish(options)).to.be.rejectedWith( /^Staged file .* found but it already exists in release .*. If you would like to replace it, you must first manually delete it within GitHub./i ); }); it('should throw an error when github.releases.uploadAsset returns an error 3 times', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.promises.readdir = function () { return Promise.resolve(['filename']); }; let counter = 0; let deleted = []; octokit.repos.uploadReleaseAsset = function () { counter++; return Promise.reject(new Error('uploadAsset error')); }; octokit.repos.deleteReleaseAsset = function (asset) { deleted.push(asset.asset_id); return Promise.resolve(); }; return expect(index.publish(options)) .to.be.rejectedWith('uploadAsset error') .then(() => { expect(counter).to.equal(3); expect(deleted).to.deep.equal([2, 2, 2]); }); }); it('should work correctly when deleting partial assets fails', function () { const options = {draft: true, verbose: false}; reset_mocks(); fs.promises.readdir = function () { return Promise.resolve(['filename']); }; let counter = 0; octokit.repos.uploadReleaseAsset = function () { counter++; return Promise.reject(new Error('uploadAsset error')); }; return expect(index.publish(options)) .to.be.rejectedWith('uploadAsset error') .then(() => { expect(counter).to.equal(3); }); }); }); describe('Verify backwards compatible with any breaking changes made within the same MINOR version.', function () { it("should publish even when package.json's binary.remote_path property is not provided and instead the version is hard coded within binary.host", function () { const options = {draft: false, verbose: false}; reset_mocks(); fs.readFileSync = function () { return '{"name":"test","version":"0.0.1","repository": {"url":"git+https://github.com/test/test.git"},"binary":{"host":"https://github.com/test/test/releases/download/0.0.1"}}'; }; fs.readdir = function () { return Promise.reoslve(['filename']); }; octokit.reposcreateRelease = function () { return Promise.resolve({data: {tag_name: '0.0.1', draft: false, assets: [{}]}}); }; return expect(index.publish(options)).to.be.fulfilled; }); }); });