From 54e5856dca6eb99a8be0bed500d943d25617670e Mon Sep 17 00:00:00 2001 From: Aaron Anish Date: Tue, 13 Oct 2026 13:44:51 +0530 Subject: [PATCH] feat(sign): Linked the metadata docs in add-on submission errors --- src/util/submit-addon.js | 19 +++++- tests/unit/test-util/test.submit-addon.js | 83 +++++++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/util/submit-addon.js b/src/util/submit-addon.js index 32b01f98d2..e1879f0398 100644 --- a/src/util/submit-addon.js +++ b/src/util/submit-addon.js @@ -14,6 +14,14 @@ const log = createLogger(import.meta.url); export const defaultAsyncFsReadFile = fsPromises.readFile; +// Appended to client errors (4xx) from submission requests, which are usually +// caused by missing or invalid metadata (e.g. no license for listed versions). +export const SUBMISSION_ERROR_HINT = + 'The add-on metadata may be missing a required field or contain an invalid value. ' + + 'Pass metadata with --amo-metadata, see ' + + 'https://mozilla.github.io/addons-server/topics/api/addons.html#version-create ' + + 'and https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/#sign-and-submit-for-publication'; + export class JwtApiAuth { #apiKey; #apiSecret; @@ -215,6 +223,7 @@ export default class Client { 'POST', JSON.stringify(jsonData), 'Submission failed (1)', + SUBMISSION_ERROR_HINT, ); } @@ -229,6 +238,7 @@ export default class Client { 'PUT', JSON.stringify(jsonData), 'Submission failed (2)', + SUBMISSION_ERROR_HINT, ); } @@ -306,7 +316,13 @@ export default class Client { ); } - async fetchJson(url, method = 'GET', body, errorMsg = 'Bad Request') { + async fetchJson( + url, + method = 'GET', + body, + errorMsg = 'Bad Request', + errorHint, + ) { const response = await this.fetch(url, method, body); if (response.status < 200 || response.status >= 500) { throw new Error( @@ -320,6 +336,7 @@ export default class Client { [ `${errorMsg}: ${response.statusText || response.status}`, JSON.stringify(data, null, 2), + ...(errorHint ? [errorHint] : []), ].join('\n'), ); } diff --git a/tests/unit/test-util/test.submit-addon.js b/tests/unit/test-util/test.submit-addon.js index 0d4e5d5fea..73d5cd8410 100644 --- a/tests/unit/test-util/test.submit-addon.js +++ b/tests/unit/test-util/test.submit-addon.js @@ -21,6 +21,7 @@ import Client, { saveIdToFile, saveUploadUuidToFile, signAddon, + SUBMISSION_ERROR_HINT, } from '../../../src/util/submit-addon.js'; import { withTempDir } from '../../../src/util/temp-dir.js'; import { consoleStream } from '../../../src/util/logger.js'; @@ -819,6 +820,33 @@ describe('util.submit-addon', () => { sinon.match({ method: 'POST', body }), ); }); + + it('includes a metadata hint when the submission is rejected', async () => { + const client = new Client(clientDefaults); + const errorBody = { + version: { + license: [ + 'This field, or custom_license, is required for listed versions.', + ], + }, + }; + mockNodeFetch( + sinon.stub(client, 'nodeFetch'), + new URL('addons/addon/', baseUrl), + 'POST', + [{ body: errorBody, status: 400 }], + ); + + const clientPromise = client.doNewAddonSubmit('some-uuid', {}); + await assert.isRejected( + clientPromise, + [ + 'Submission failed (1): 400', + JSON.stringify(errorBody, null, 2), + SUBMISSION_ERROR_HINT, + ].join('\n'), + ); + }); }); describe('doNewAddonOrVersionSubmit', () => { @@ -872,6 +900,44 @@ describe('util.submit-addon', () => { sinon.match({ method: 'PUT', body }), ); }); + + it('includes a metadata hint when the submission is rejected', async () => { + const client = new Client(clientDefaults); + const guid = '@some-addon-guid'; + const errorBody = { version: { license: ['Invalid value.'] } }; + mockNodeFetch( + sinon.stub(client, 'nodeFetch'), + new URL(`addons/addon/${guid}/`, baseUrl), + 'PUT', + [{ body: errorBody, status: 400 }], + ); + + const clientPromise = client.doNewAddonOrVersionSubmit( + guid, + 'some-uuid', + {}, + ); + await assert.isRejected(clientPromise, SUBMISSION_ERROR_HINT); + }); + + it('does not include the metadata hint on server errors', async () => { + const client = new Client(clientDefaults); + const guid = '@some-addon-guid'; + mockNodeFetch( + sinon.stub(client, 'nodeFetch'), + new URL(`addons/addon/${guid}/`, baseUrl), + 'PUT', + [{ body: {}, status: 500 }], + ); + + const clientPromise = client.doNewAddonOrVersionSubmit( + guid, + 'some-uuid', + {}, + ); + const error = await clientPromise.catch((err) => err); + expect(error.message).to.equal('Submission failed (2): 500.'); + }); }); describe('doFormDataPatch', () => { @@ -1249,6 +1315,23 @@ describe('util.submit-addon', () => { await assert.isRejected(clientPromise, 'Bad Request: 400\n{}'); }); + it('appends the error hint on not ok responses', async () => { + mockNodeFetch(nodeFetchStub, baseUrl, 'GET', [ + { body: {}, status: 400 }, + ]); + const clientPromise = client.fetchJson( + baseUrl, + 'GET', + undefined, + 'Bad Request', + 'Some hint', + ); + await assert.isRejected( + clientPromise, + 'Bad Request: 400\n{}\nSome hint', + ); + }); + it('rejects with a promise on < 100 responses', async () => { mockNodeFetch(nodeFetchStub, baseUrl, 'GET', [ { body: {}, status: 99 },