From da4a3628d2fe641b89d0422a91e964f73e5f4542 Mon Sep 17 00:00:00 2001 From: Emmanuel Ogbizi <iamogbz+github@gmail.com> Date: Tue, 17 Mar 2020 06:03:04 -0400 Subject: [PATCH] fix: exit successfully when submitting a listed add-on (#91) * chore: add allowed channels constants * chore: tests * test: wip * test: wip use union fs * test: wip use memfs * test: wip * chore: use memfs * test: move mock to setup * chore: ensure caveat only applies for listed and autosign * chore: only check if folder exists --- package.json | 4 +- src/constants.js | 8 +++- src/publish.js | 25 +++++++----- tests/__mocks__/fs.js | 21 +++++++++- tests/__mocks__/sign-addon.js | 1 + tests/__mocks__/web-ext.js | 6 --- tests/prepare.test.js | 4 +- tests/publish.test.js | 72 +++++++++++++++++++++++++++----- tests/setup.js | 2 +- yarn.lock | 77 +++++++++++------------------------ 10 files changed, 132 insertions(+), 88 deletions(-) create mode 100644 tests/__mocks__/sign-addon.js delete mode 100644 tests/__mocks__/web-ext.js diff --git a/package.json b/package.json index a2fd6f1..95de237 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@tophat/commitizen-adapter": "^0.0.11", "@tophat/commitlint-config": "^0.1.2", "@tophat/eslint-config": "^0.6.0", + "@types/jest": "^25.1.4", "aggregate-error": "^3.0.1", "all-contributors-cli": "^6.9.1", "babel-eslint": "^10.0.2", @@ -38,14 +39,15 @@ "memfs": "^3.0.3", "prettier": "^1.18.2", "semantic-release": "^17.0.1", + "unionfs": "^4.4.0", "yarn-deduplicate": "^2.0.0" }, "scripts": { "build": "mkdir -p artifacts; echo 'When changing this remember to update @semantic-release/npm.'", "commit": "git-cz", "lock-check": "yarn-deduplicate --list --fail", - "lock-dedup": "yarn-deduplicate", "lint": "eslint . --ext .js,.ts --max-warnings=0", + "postinstall": "yarn-deduplicate", "release": "semantic-release", "report-coverage": "codecov", "test": "jest", diff --git a/src/constants.js b/src/constants.js index 2cc1e7c..c5dea36 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,6 +1,11 @@ +const allowedChannels = { + LISTED: 'listed', + UNLISTED: 'unlisted', +} + const defaultOptions = { artifactsDir: './artifacts', - channel: 'unlisted', + channel: allowedChannels.UNLISTED, manifestPath: 'manifest.json', sourceDir: 'dist', } @@ -18,6 +23,7 @@ const requiredEnvs = { } module.exports = { + allowedChannels, defaultOptions, requiredEnvs, requiredOptions, diff --git a/src/publish.js b/src/publish.js index d388872..56ae033 100644 --- a/src/publish.js +++ b/src/publish.js @@ -4,6 +4,7 @@ const path = require('path') const webExt = require('web-ext').default const defaultAddonSigner = require('sign-addon') +const { allowedChannels } = require('./constants') const { verifyOptions } = require('./utils') const publish = async options => { @@ -24,18 +25,25 @@ const publish = async options => { const { FIREFOX_API_KEY, FIREFOX_SECRET_KEY } = process.env - let unsignedXpiPath const signAddon = async params => { - unsignedXpiPath = params.xpiPath + const unsignedXpiFile = `unsigned-${targetXpi}` + fs.writeFileSync( + path.join(artifactsDir, unsignedXpiFile), + fs.readFileSync(params.xpiPath), + ) const result = await defaultAddonSigner(params) - if (!result.success && result.errorCode === 'ADDON_NOT_AUTO_SIGNED') { + if ( + channel === allowedChannels.LISTED && + !result.success && + result.errorCode === 'ADDON_NOT_AUTO_SIGNED' + ) { result.success = true - result.downloadedFiles = result.downloadedFiles || [] + result.downloadedFiles = result.downloadedFiles || [unsignedXpiFile] } return result } - const { success, downloadedFiles } = await webExt.cmd.sign( + const { downloadedFiles } = await webExt.cmd.sign( { apiKey: FIREFOX_API_KEY, apiSecret: FIREFOX_SECRET_KEY, @@ -46,14 +54,9 @@ const publish = async options => { }, { signAddon }, ) - if (!success) { - throw new Error( - 'Signing the extension failed. See the console output from web-ext sign for the validation link', - ) - } const [xpiFile] = downloadedFiles fs.renameSync( - xpiFile ? path.join(artifactsDir, xpiFile) : unsignedXpiPath, + path.join(artifactsDir, xpiFile), path.join(artifactsDir, targetXpi), ) } diff --git a/tests/__mocks__/fs.js b/tests/__mocks__/fs.js index e5f40ad..19205a6 100644 --- a/tests/__mocks__/fs.js +++ b/tests/__mocks__/fs.js @@ -1,3 +1,20 @@ -const { fs } = require('memfs') +const path = jest.requireActual('path') +const fs = jest.requireActual('fs') +const { vol } = require('memfs') +const { ufs } = require('unionfs') -module.exports = fs +const { createWriteStream } = ufs +ufs.createWriteStream = (...args) => { + for (const _fs of ufs.fss) { + try { + if (_fs.existsSync(path.dirname(`${args[0]}`))) { + return _fs.createWriteStream(args[0]) + } + } catch (e) { + continue + } + } + return createWriteStream(...args) +} + +module.exports = ufs.use(vol).use(fs) diff --git a/tests/__mocks__/sign-addon.js b/tests/__mocks__/sign-addon.js new file mode 100644 index 0000000..b4bbacb --- /dev/null +++ b/tests/__mocks__/sign-addon.js @@ -0,0 +1 @@ +module.exports = jest.fn() diff --git a/tests/__mocks__/web-ext.js b/tests/__mocks__/web-ext.js deleted file mode 100644 index 8319aef..0000000 --- a/tests/__mocks__/web-ext.js +++ /dev/null @@ -1,6 +0,0 @@ -const mockWebExt = { - cmd: { - sign: jest.fn(), - }, -} -module.exports = { default: mockWebExt } diff --git a/tests/prepare.test.js b/tests/prepare.test.js index bb54ffa..200d6cc 100644 --- a/tests/prepare.test.js +++ b/tests/prepare.test.js @@ -1,4 +1,6 @@ -const { fs, vol } = require('memfs') +const fs = require('fs') + +const { vol } = require('memfs') const { prepare } = require('../src') diff --git a/tests/publish.test.js b/tests/publish.test.js index 66c9ee3..2af8548 100644 --- a/tests/publish.test.js +++ b/tests/publish.test.js @@ -1,23 +1,49 @@ -const { fs, vol } = require('memfs') -const { default: webExt } = require('web-ext') +const fs = require('fs') +const path = require('path') + +const { vol } = require('memfs') +const signAddon = require('sign-addon') const { publish } = require('../src') describe('publish', () => { + const mockManifestJSON = { + manifest_version: 2, + name: 'Mock Extension', + version: '0.0.1', + } const extensionId = '{01234567-abcd-6789-cdef-0123456789ef}' const targetXpi = 'target-extension.xpi' const mockOptions = { artifactsDir: 'mock_artifacts', - manifestPath: 'mock_manifest.json', + channel: 'unlisted', + manifestPath: 'manifest.json', sourceDir: 'mock_source', } const completeOptions = { extensionId, targetXpi, ...mockOptions } + const mockAddonSignFailed = { success: false } + const mockAddonSignSuccess = { success: true, id: extensionId } + const clearMockArtifacts = () => { + const actualFs = jest.requireActual('fs') + if (actualFs.existsSync(mockOptions.artifactsDir)) { + actualFs.rmdirSync(mockOptions.artifactsDir, { recursive: true }) + } + } beforeAll(() => { jest.spyOn(console, 'log') }) + beforeEach(() => { + vol.fromJSON({ + [path.join( + mockOptions.sourceDir, + mockOptions.manifestPath, + )]: JSON.stringify(mockManifestJSON), + }) + }) afterEach(() => { vol.reset() + clearMockArtifacts() jest.clearAllMocks() }) afterAll(() => { @@ -36,25 +62,49 @@ describe('publish', () => { ) }) - it('raises error if signing unsuccessful', () => { - webExt.cmd.sign.mockResolvedValueOnce({ success: false }) + it.each` + signCase | signResults + ${'signing unsuccessful'} | ${mockAddonSignFailed} + ${'auto signing unsuccessful and channel is unlisted'} | ${{ ...mockAddonSignFailed, errorCode: 'ADDON_NOT_AUTO_SIGNED' }} + `('raises error if $signCase', ({ signResults }) => { + signAddon.mockResolvedValueOnce(signResults) return expect(publish(completeOptions)).rejects.toThrow( - 'Signing the extension failed', + 'The extension could not be signed', ) }) + it('uses unsigned xpi if auto signing unsuccessful and channel is listed', async () => { + signAddon.mockResolvedValueOnce({ + ...mockAddonSignFailed, + errorCode: 'ADDON_NOT_AUTO_SIGNED', + }) + const targetXpiPath = path.join(mockOptions.artifactsDir, targetXpi) + expect(fs.existsSync(targetXpiPath)).toBe(false) + await publish({ + ...completeOptions, + channel: 'listed', + }) + expect(fs.existsSync(targetXpiPath)).toBe(true) + }) + it('renames downloaded file to target xpi', async () => { const downloadedFile = 'mock_downloaded.xpi' + const mockFileContent = 'some fake signed xpi' vol.fromJSON({ - [`${mockOptions.artifactsDir}/${downloadedFile}`]: 'some fake signed xpi', + [path.join( + mockOptions.artifactsDir, + downloadedFile, + )]: mockFileContent, }) - webExt.cmd.sign.mockResolvedValueOnce({ - success: true, + signAddon.mockResolvedValueOnce({ + ...mockAddonSignSuccess, downloadedFiles: [downloadedFile], }) - const targetXpiPath = `${mockOptions.artifactsDir}/${targetXpi}` + const targetXpiPath = path.join(mockOptions.artifactsDir, targetXpi) expect(fs.existsSync(targetXpiPath)).toBe(false) await publish(completeOptions) - expect(fs.existsSync(targetXpiPath)).toBe(true) + expect(fs.readFileSync(targetXpiPath).toString()).toEqual( + mockFileContent, + ) }) }) diff --git a/tests/setup.js b/tests/setup.js index 54bed39..57de7c0 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,2 +1,2 @@ jest.mock('fs') -jest.mock('web-ext') +jest.mock('sign-addon') diff --git a/yarn.lock b/yarn.lock index dca3ad2..211b579 100644 --- a/yarn.lock +++ b/yarn.lock @@ -188,17 +188,7 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/helper-replace-supers@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc" - integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-replace-supers@^7.8.6": +"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== @@ -715,14 +705,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" - integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== - dependencies: - regenerator-runtime "^0.13.2" - -"@babel/runtime@^7.8.4": +"@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== @@ -845,20 +828,7 @@ babel-runtime "^6.23.0" lodash "4.17.15" -"@commitlint/load@>6.1.1": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-8.3.4.tgz#6a0832362451b959f6aa47da8e44c2e05572b114" - integrity sha512-B4MylKvT02UE3VHC5098OHxsrgkADUy5AD4Cdkiy7oX/edWypEmvK7Wuns3B9dwluWP/iFM6daoWtpkCVZoRwQ== - dependencies: - "@commitlint/execute-rule" "^8.3.4" - "@commitlint/resolve-extends" "^8.3.4" - babel-runtime "^6.23.0" - chalk "2.4.2" - cosmiconfig "^5.2.0" - lodash "4.17.15" - resolve-from "^5.0.0" - -"@commitlint/load@^8.3.5": +"@commitlint/load@>6.1.1", "@commitlint/load@^8.3.5": version "8.3.5" resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-8.3.5.tgz#3f059225ede92166ba94cf4c48e3d67c8b08b18a" integrity sha512-poF7R1CtQvIXRmVIe63FjSQmN9KDqjRtU5A6hxqXBga87yB2VUJzic85TV6PcQc+wStk52cjrMI+g0zFx+Zxrw== @@ -895,17 +865,6 @@ babel-runtime "^6.23.0" git-raw-commits "^2.0.0" -"@commitlint/resolve-extends@^8.3.4": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-8.3.4.tgz#815b646efbf9bc77c44925f619336da0027d7a68" - integrity sha512-M34RLaAW1eGWgtkVtotHfPaJa+cZIARe8twKItd7RhWs7n/1W2py9GTFIiIEq95LBN1uah5vm1WQHsfLqPZYHA== - dependencies: - "@types/node" "^12.0.2" - import-fresh "^3.0.0" - lodash "4.17.15" - resolve-from "^5.0.0" - resolve-global "^1.0.0" - "@commitlint/resolve-extends@^8.3.5": version "8.3.5" resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-8.3.5.tgz#8fff800f292ac217ae30b1862f5f9a84b278310a" @@ -1392,6 +1351,14 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest@^25.1.4": + version "25.1.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.4.tgz#9e9f1e59dda86d3fd56afce71d1ea1b331f6f760" + integrity sha512-QDDY2uNAhCV7TMCITrxz+MRk1EizcsevzfeS6LykIlq2V1E5oO4wXG8V2ZEd9w7Snxeeagk46YbMgZ8ESHx3sw== + dependencies: + jest-diff "^25.1.0" + pretty-format "^25.1.0" + "@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" @@ -1402,7 +1369,7 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/node@*", "@types/node@^12.0.2": +"@types/node@*": version "12.7.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.4.tgz#64db61e0359eb5a8d99b55e05c729f130a678b04" integrity sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ== @@ -4610,7 +4577,7 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.6.0" -fs-monkey@1.0.0: +fs-monkey@1.0.0, fs-monkey@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.0.tgz#b1fe36b2d8a78463fd0b8fd1463b355952743bd0" integrity sha512-nxkkzQ5Ga+ETriXxIof4TncyMSzrV9jFIF+kGN16nw5CiAdWAnG/2FgM7CHhRenW1EBiDx+r1tf/P78HGKCgnA== @@ -8421,16 +8388,11 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4: +picomatch@^2.0.4, picomatch@^2.0.5: version "2.2.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== -picomatch@^2.0.5: - version "2.0.7" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" - integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== - pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8938,7 +8900,7 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== -regenerator-runtime@0.13.3, regenerator-runtime@^0.13.2: +regenerator-runtime@0.13.3: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== @@ -8953,7 +8915,7 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz#e96bf612a3362d12bb69f7e8f74ffeab25c7ac91" integrity sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g== @@ -10543,6 +10505,13 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +unionfs@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/unionfs/-/unionfs-4.4.0.tgz#b671112e505f70678052345cf5c2a33f0e6edde9" + integrity sha512-N+TuJHJ3PjmzIRCE1d2N3VN4qg/P78eh/nxzwHnzpg3W2Mvf8Wvi7J1mvv6eNkb8neUeSdFSQsKna0eXVyF4+w== + dependencies: + fs-monkey "^1.0.0" + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"