fix: Detect unsupported images used in static themes manifest
This commit is contained in:
Родитель
d17cd9d8d6
Коммит
f810d6186a
|
@ -121,3 +121,13 @@ Rules are sorted by severity.
|
|||
| `MANIFEST_MULTIPLE_DICTS` | error | Multiple dictionaries found |
|
||||
| `MANIFEST_EMPTY_DICTS` | error | Empty `dictionaries` object |
|
||||
| `MANIFEST_DICT_MISSING_ID` | error | Missing `applications.gecko.id` property for a dictionary |
|
||||
|
||||
### Static Theme / manifest.json
|
||||
|
||||
| Message code | Severity | Description |
|
||||
| ------------------------------------ | -------- | ----------------------------------------------------------- |
|
||||
| `MANIFEST_THEME_IMAGE_MIME_MISMATCH` | warning | Theme image file extension should match its mime type |
|
||||
| `MANIFEST_THEME_IMAGE_NOT_FOUND` | error | Theme images must not be missing |
|
||||
| `MANIFEST_THEME_IMAGE_CORRUPTED` | error | Theme images must not be corrupted |
|
||||
| `MANIFEST_THEME_IMAGE_WRONG_EXT` | error | Theme images must have one of the supported file extensions |
|
||||
| `MANIFEST_THEME_IMAGE_WRONG_MIME` | error | Theme images mime type must be a supported format |
|
||||
|
|
13
src/const.js
13
src/const.js
|
@ -109,6 +109,19 @@ export const IMAGE_FILE_EXTENSIONS = [
|
|||
'svg',
|
||||
];
|
||||
|
||||
// Map the image mime to the expected file extensions
|
||||
// (used in the the static theme images validation).
|
||||
export const MIME_TO_FILE_EXTENSIONS = {
|
||||
'image/svg+xml': ['svg'],
|
||||
'image/gif': ['gif'],
|
||||
'image/jpeg': ['jpg', 'jpeg'],
|
||||
'image/png': ['png'],
|
||||
'image/webp': ['webp'],
|
||||
};
|
||||
|
||||
// List of the mime types for the allowed static theme images.
|
||||
export const STATIC_THEME_IMAGE_MIMES = Object.keys(MIME_TO_FILE_EXTENSIONS);
|
||||
|
||||
// A list of magic numbers that we won't allow.
|
||||
export const FLAGGED_FILE_MAGIC_NUMBERS = [
|
||||
[0x4d, 0x5a], // EXE or DLL,
|
||||
|
|
|
@ -259,6 +259,10 @@ export default class Linter {
|
|||
if (manifestParser.parsedJSON.icons) {
|
||||
await manifestParser.validateIcons();
|
||||
}
|
||||
if (manifestParser.isStaticTheme) {
|
||||
await manifestParser.validateStaticThemeImages();
|
||||
}
|
||||
|
||||
this.addonMetadata = manifestParser.getMetadata();
|
||||
} else {
|
||||
_log.warn(
|
||||
|
|
|
@ -296,6 +296,84 @@ export function corruptIconFile({ path }) {
|
|||
};
|
||||
}
|
||||
|
||||
export const MANIFEST_THEME_IMAGE_NOT_FOUND = 'MANIFEST_THEME_IMAGE_NOT_FOUND';
|
||||
export function manifestThemeImageMissing(path, type) {
|
||||
return {
|
||||
code: MANIFEST_THEME_IMAGE_NOT_FOUND,
|
||||
message: i18n.sprintf(
|
||||
'Theme image for "%(type)s" could not be found in the package',
|
||||
{ type }
|
||||
),
|
||||
description: i18n.sprintf(
|
||||
i18n._('Theme image for "%(type)s" could not be found at "%(path)s"'),
|
||||
{ path, type }
|
||||
),
|
||||
file: MANIFEST_JSON,
|
||||
};
|
||||
}
|
||||
|
||||
export const MANIFEST_THEME_IMAGE_CORRUPTED = 'MANIFEST_THEME_IMAGE_CORRUPTED';
|
||||
export function manifestThemeImageCorrupted({ path }) {
|
||||
return {
|
||||
code: MANIFEST_THEME_IMAGE_CORRUPTED,
|
||||
message: i18n._('Corrupted theme image file'),
|
||||
description: i18n.sprintf(
|
||||
i18n._('Theme image file at "%(path)s" is corrupted'),
|
||||
{ path }
|
||||
),
|
||||
file: MANIFEST_JSON,
|
||||
};
|
||||
}
|
||||
|
||||
export const MANIFEST_THEME_IMAGE_WRONG_EXT = 'MANIFEST_THEME_IMAGE_WRONG_EXT';
|
||||
export function manifestThemeImageWrongExtension({ path }) {
|
||||
return {
|
||||
code: MANIFEST_THEME_IMAGE_WRONG_EXT,
|
||||
message: i18n._('Theme image file has an unsupported file extension'),
|
||||
description: i18n.sprintf(
|
||||
i18n._(
|
||||
'Theme image file at "%(path)s" has an unsupported file extension'
|
||||
),
|
||||
{ path }
|
||||
),
|
||||
file: MANIFEST_JSON,
|
||||
};
|
||||
}
|
||||
|
||||
export const MANIFEST_THEME_IMAGE_WRONG_MIME =
|
||||
'MANIFEST_THEME_IMAGE_WRONG_MIME';
|
||||
export function manifestThemeImageWrongMime({ path, mime }) {
|
||||
return {
|
||||
code: MANIFEST_THEME_IMAGE_WRONG_MIME,
|
||||
message: i18n._('Theme image file has an unsupported mime type'),
|
||||
description: i18n.sprintf(
|
||||
i18n._(
|
||||
'Theme image file at "%(path)s" has the unsupported mime type "%(mime)s"'
|
||||
),
|
||||
{ path, mime }
|
||||
),
|
||||
file: MANIFEST_JSON,
|
||||
};
|
||||
}
|
||||
|
||||
export const MANIFEST_THEME_IMAGE_MIME_MISMATCH =
|
||||
'MANIFEST_THEME_IMAGE_MIME_MISMATCH';
|
||||
export function manifestThemeImageMimeMismatch({ path, mime }) {
|
||||
return {
|
||||
code: MANIFEST_THEME_IMAGE_MIME_MISMATCH,
|
||||
message: i18n._(
|
||||
'Theme image file mime type does not match its file extension'
|
||||
),
|
||||
description: i18n.sprintf(
|
||||
i18n._(
|
||||
'Theme image file extension at "%(path)s" does not match its actual mime type "%(mime)s"'
|
||||
),
|
||||
{ path, mime }
|
||||
),
|
||||
file: MANIFEST_JSON,
|
||||
};
|
||||
}
|
||||
|
||||
export const PROP_NAME_MISSING = manifestPropMissing('name');
|
||||
export const PROP_VERSION_MISSING = manifestPropMissing('version');
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ import {
|
|||
IMAGE_FILE_EXTENSIONS,
|
||||
LOCALES_DIRECTORY,
|
||||
MESSAGES_JSON,
|
||||
STATIC_THEME_IMAGE_MIMES,
|
||||
MIME_TO_FILE_EXTENSIONS,
|
||||
} from 'const';
|
||||
import log from 'logger';
|
||||
import * as messages from 'messages';
|
||||
|
@ -392,6 +394,87 @@ export default class ManifestJSONParser extends JSONParser {
|
|||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async validateThemeImage(imagePath, manifestPropName) {
|
||||
const _path = normalizePath(imagePath);
|
||||
const ext = path
|
||||
.extname(imagePath)
|
||||
.substring(1)
|
||||
.toLowerCase();
|
||||
|
||||
const fileExists = this.validateFileExistsInPackage(
|
||||
_path,
|
||||
`theme.images.${manifestPropName}`,
|
||||
messages.manifestThemeImageMissing
|
||||
);
|
||||
|
||||
// No need to validate the image format if the file doesn't exist
|
||||
// on disk.
|
||||
if (!fileExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IMAGE_FILE_EXTENSIONS.includes(ext) || ext === 'webp') {
|
||||
this.collector.addError(
|
||||
messages.manifestThemeImageWrongExtension({ path: _path })
|
||||
);
|
||||
this.isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const info = await getImageMetadata(this.io, _path);
|
||||
if (
|
||||
!STATIC_THEME_IMAGE_MIMES.includes(info.mime) ||
|
||||
info.mime === 'image/webp'
|
||||
) {
|
||||
this.collector.addError(
|
||||
messages.manifestThemeImageWrongMime({
|
||||
path: _path,
|
||||
mime: info.mime,
|
||||
})
|
||||
);
|
||||
this.isValid = false;
|
||||
} else if (!MIME_TO_FILE_EXTENSIONS[info.mime].includes(ext)) {
|
||||
this.collector.addWarning(
|
||||
messages.manifestThemeImageMimeMismatch({
|
||||
path: _path,
|
||||
mime: info.mime,
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
log.debug(
|
||||
`Unexpected error raised while validating theme image "${_path}"`,
|
||||
err.message
|
||||
);
|
||||
this.collector.addError(
|
||||
messages.manifestThemeImageCorrupted({ path: _path })
|
||||
);
|
||||
this.isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
validateStaticThemeImages() {
|
||||
const promises = [];
|
||||
const themeImages = this.parsedJSON.theme && this.parsedJSON.theme.images;
|
||||
|
||||
// The theme.images manifest property is mandatory on Firefox < 60, but optional
|
||||
// on Firefox >= 60.
|
||||
if (themeImages) {
|
||||
for (const prop of Object.keys(themeImages)) {
|
||||
if (Array.isArray(themeImages[prop])) {
|
||||
themeImages[prop].forEach((imagePath) => {
|
||||
promises.push(this.validateThemeImage(imagePath, prop));
|
||||
});
|
||||
} else {
|
||||
promises.push(this.validateThemeImage(themeImages[prop], prop));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
validateFileExistsInPackage(
|
||||
filePath,
|
||||
type,
|
||||
|
@ -401,7 +484,9 @@ export default class ManifestJSONParser extends JSONParser {
|
|||
if (!Object.prototype.hasOwnProperty.call(this.io.files, _path)) {
|
||||
this.collector.addError(messageFunc(_path, type));
|
||||
this.isValid = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
validateContentScriptMatchPattern(matchPattern) {
|
||||
|
|
|
@ -14,12 +14,51 @@ export const fakeMessageData = {
|
|||
message: 'message',
|
||||
};
|
||||
|
||||
export const EMPTY_SVG = Buffer.from('<svg viewbox="0 0 1 1"></svg>');
|
||||
|
||||
export const EMPTY_PNG = Buffer.from(
|
||||
oneLine`iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMA
|
||||
AQAABQABDQottAAAAABJRU5ErkJggg==`,
|
||||
'base64'
|
||||
);
|
||||
|
||||
export const EMPTY_GIF = Buffer.from(
|
||||
'R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
|
||||
'base64'
|
||||
);
|
||||
|
||||
export const EMPTY_APNG = Buffer.from(
|
||||
oneLine`iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAA1BMVEUAAACn
|
||||
ej3aAAAAAXRSTlMAQObYZgAAAA1JREFUCNcBAgD9/wAAAAIAAXdw4VoAAAAY
|
||||
dEVYdFNvZnR3YXJlAGdpZjJhcG5nLnNmLm5ldJb/E8gAAAAASUVORK5CYII=`,
|
||||
'base64'
|
||||
);
|
||||
|
||||
export const EMPTY_JPG = Buffer.from(
|
||||
oneLine`/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQE
|
||||
BAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/
|
||||
wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAA
|
||||
AAAAAAAAAAAAAAD/2gAIAQEAAD8AKp//2Q==`,
|
||||
'base64'
|
||||
);
|
||||
|
||||
export const EMPTY_WEBP = Buffer.from(
|
||||
oneLine`UklGRkAAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAIAAAAAAFZQOCAY
|
||||
AAAAMAEAnQEqAQABAAEAHCWkAANwAP7+BtAA`,
|
||||
'base64'
|
||||
);
|
||||
|
||||
export const EMPTY_TIFF = Buffer.from(
|
||||
oneLine`SUkqABIAAAB42mNgAAAAAgABEQAAAQMAAQAAAAEAAAABAQMAAQAAAAEAAAAC
|
||||
AQMAAgAAAAgACAADAQMAAQAAAAgAAAAGAQMAAQAAAAEAAAAKAQMAAQAAAAEA
|
||||
AAARAQQAAQAAAAgAAAASAQMAAQAAAAEAAAAVAQMAAQAAAAIAAAAWAQMAAQAA
|
||||
AAEAAAAXAQQAAQAAAAoAAAAcAQMAAQAAAAEAAAApAQMAAgAAAAAAAQA9AQMA
|
||||
AQAAAAIAAAA+AQUAAgAAABQBAAA/AQUABgAAAOQAAABSAQMAAQAAAAIAAAAA
|
||||
AAAA/wnXo/////9/4XpU///////MzEz//////5mZmf////9/ZmYm/////+8o
|
||||
XA//////fxsNUP//////VzlU/////w==`,
|
||||
'base64'
|
||||
);
|
||||
|
||||
export function getRuleFiles(ruleType) {
|
||||
const ruleFiles = fs.readdirSync(`src/rules/${ruleType}`);
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import fs from 'fs';
|
||||
|
||||
import { oneLine } from 'common-tags';
|
||||
|
||||
import Linter from 'linter';
|
||||
import ManifestJSONParser from 'parsers/manifestjson';
|
||||
import { PACKAGE_EXTENSION, VALID_MANIFEST_VERSION } from 'const';
|
||||
|
@ -13,6 +15,12 @@ import {
|
|||
validStaticThemeManifestJSON,
|
||||
getStreamableIO,
|
||||
EMPTY_PNG,
|
||||
EMPTY_APNG,
|
||||
EMPTY_GIF,
|
||||
EMPTY_JPG,
|
||||
EMPTY_SVG,
|
||||
EMPTY_TIFF,
|
||||
EMPTY_WEBP,
|
||||
} from '../helpers';
|
||||
|
||||
describe('ManifestJSONParser', () => {
|
||||
|
@ -1536,6 +1544,290 @@ describe('ManifestJSONParser', () => {
|
|||
description: 'Your JSON file could not be parsed.',
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a validation error on missing theme image files', async () => {
|
||||
const propName = 'theme.images.headerURL';
|
||||
const fileName = 'missing-image-file.png';
|
||||
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
images: {
|
||||
headerURL: fileName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{
|
||||
io: { files: {} },
|
||||
}
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
expect(manifestJSONParser.isValid).toEqual(false);
|
||||
assertHasMatchingError(linter.collector.errors, {
|
||||
code: messages.MANIFEST_THEME_IMAGE_NOT_FOUND,
|
||||
message: `Theme image for "${propName}" could not be found in the package`,
|
||||
description: `Theme image for "${propName}" could not be found at "${fileName}"`,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a validation error on theme image file with unsupported file extension', async () => {
|
||||
const files = {
|
||||
'unsupported-image-ext.tiff': '',
|
||||
'unsupported-image-ext.webp': '',
|
||||
};
|
||||
const fileNames = Object.keys(files);
|
||||
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
images: {
|
||||
headerURL: fileNames[0],
|
||||
additional_backgrounds: fileNames[1],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{ io: { files } }
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
expect(manifestJSONParser.isValid).toEqual(false);
|
||||
|
||||
for (const name of fileNames) {
|
||||
assertHasMatchingError(linter.collector.errors, {
|
||||
code: messages.MANIFEST_THEME_IMAGE_WRONG_EXT,
|
||||
message: `Theme image file has an unsupported file extension`,
|
||||
description: `Theme image file at "${name}" has an unsupported file extension`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('adds a validation error on theme image file in unsupported formats', async () => {
|
||||
const files = {
|
||||
'tiff-image-with-png-filext.png': EMPTY_TIFF,
|
||||
'webp-image-with-png-filext.png': EMPTY_WEBP,
|
||||
};
|
||||
const fileNames = Object.keys(files);
|
||||
const fileMimes = ['image/tiff', 'image/webp'];
|
||||
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
images: {
|
||||
headerURL: fileNames[0],
|
||||
addional_backgrounds: fileNames[1],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const fakeIO = getStreamableIO(files);
|
||||
fakeIO.getFileAsStream = jest.fn(fakeIO.getFileAsStream);
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{ io: fakeIO }
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
// Expect getFileAsStream to have been called to read the
|
||||
// image file.
|
||||
expect(fakeIO.getFileAsStream.mock.calls.length).toBe(2);
|
||||
for (let i = 0; i < fileNames; i++) {
|
||||
expect(fakeIO.getFileAsStream.mock.calls[i]).toEqual([
|
||||
fileNames[i],
|
||||
{ encoding: null },
|
||||
]);
|
||||
}
|
||||
|
||||
expect(manifestJSONParser.isValid).toEqual(false);
|
||||
|
||||
for (let i = 0; i < fileNames; i++) {
|
||||
const fileName = fileNames[i];
|
||||
const fileMime = fileMimes[i];
|
||||
assertHasMatchingError(linter.collector.errors, {
|
||||
code: messages.MANIFEST_THEME_IMAGE_WRONG_MIME,
|
||||
message: `Theme image file has an unsupported mime type`,
|
||||
description: `Theme image file at "${fileName}" has the unsupported mime type "${fileMime}"`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('adds a validation warning on supported theme image mime with file extension mismatch', async () => {
|
||||
const fileName = 'png-image-with-gif-filext.gif';
|
||||
const fileMime = 'image/png';
|
||||
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
images: {
|
||||
headerURL: fileName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const files = { [fileName]: EMPTY_PNG };
|
||||
const fakeIO = getStreamableIO(files);
|
||||
fakeIO.getFileAsStream = jest.fn(fakeIO.getFileAsStream);
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{ io: fakeIO }
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
// Expect getFileAsStream to have been called to read the
|
||||
// image file.
|
||||
expect(fakeIO.getFileAsStream.mock.calls.length).toBe(1);
|
||||
expect(fakeIO.getFileAsStream.mock.calls[0]).toEqual([
|
||||
fileName,
|
||||
{ encoding: null },
|
||||
]);
|
||||
|
||||
expect(manifestJSONParser.isValid).toEqual(true);
|
||||
assertHasMatchingError(linter.collector.warnings, {
|
||||
code: messages.MANIFEST_THEME_IMAGE_MIME_MISMATCH,
|
||||
message: `Theme image file mime type does not match its file extension`,
|
||||
description: oneLine`Theme image file extension at "${fileName}"
|
||||
does not match its actual mime type "${fileMime}"`,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a validation error if unable to validate theme images files mime type', async () => {
|
||||
const fileName = 'corrupted-image-file.png';
|
||||
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
images: {
|
||||
headerURL: fileName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Set the image file content as empty, so that the validation is going to be
|
||||
// unable to retrive the file mime type.
|
||||
const files = { [fileName]: '' };
|
||||
const fakeIO = getStreamableIO(files);
|
||||
fakeIO.getFileAsStream = jest.fn(fakeIO.getFileAsStream);
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{ io: fakeIO }
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
// Expect getFileAsStream to have been called to read the
|
||||
// image file.
|
||||
expect(fakeIO.getFileAsStream.mock.calls.length).toBe(1);
|
||||
expect(fakeIO.getFileAsStream.mock.calls[0]).toEqual([
|
||||
fileName,
|
||||
{ encoding: null },
|
||||
]);
|
||||
|
||||
expect(manifestJSONParser.isValid).toEqual(false);
|
||||
assertHasMatchingError(linter.collector.errors, {
|
||||
code: messages.MANIFEST_THEME_IMAGE_CORRUPTED,
|
||||
message: `Corrupted theme image file`,
|
||||
description: `Theme image file at "${fileName}" is corrupted`,
|
||||
});
|
||||
});
|
||||
|
||||
it('validates all image paths when the manifest property value is an array', async () => {
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const imageFiles = [
|
||||
'bg1.svg',
|
||||
'bg2.png',
|
||||
'bg2-apng.png',
|
||||
'bg3.gif',
|
||||
'bg4.jpg',
|
||||
'bg4-1.jpeg',
|
||||
];
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
images: {
|
||||
additional_backgrounds: imageFiles,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const files = {
|
||||
'bg1.svg': EMPTY_SVG,
|
||||
'bg2.png': EMPTY_PNG,
|
||||
'bg2-apng.png': EMPTY_APNG,
|
||||
'bg3.gif': EMPTY_GIF,
|
||||
'bg4.jpg': EMPTY_JPG,
|
||||
'bg4-1.jpeg': EMPTY_JPG,
|
||||
};
|
||||
const fakeIO = getStreamableIO(files);
|
||||
fakeIO.getFileAsStream = jest.fn(fakeIO.getFileAsStream);
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{ io: fakeIO }
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
// Expect getFileAsStream to have been called to read all the image files.
|
||||
expect(fakeIO.getFileAsStream.mock.calls.length).toBe(imageFiles.length);
|
||||
expect(fakeIO.getFileAsStream.mock.calls.map((call) => call[0])).toEqual(
|
||||
imageFiles
|
||||
);
|
||||
|
||||
const { errors, warnings } = linter.collector;
|
||||
expect({ errors, warnings }).toEqual({
|
||||
errors: [],
|
||||
warnings: [],
|
||||
});
|
||||
expect(manifestJSONParser.isValid).toEqual(true);
|
||||
});
|
||||
|
||||
it('considers theme.images as optional', async () => {
|
||||
const linter = new Linter({ _: ['bar'] });
|
||||
const manifest = validStaticThemeManifestJSON({
|
||||
theme: {
|
||||
colors: {
|
||||
accentcolor: '#adb09f',
|
||||
textcolor: '#000',
|
||||
background_tab_text: 'rgba(255, 192, 0, 0)',
|
||||
toolbar_text: 'rgb(255, 255, 255),',
|
||||
toolbar_field_text: 'hsl(120, 100%, 50%)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const manifestJSONParser = new ManifestJSONParser(
|
||||
manifest,
|
||||
linter.collector,
|
||||
{ io: { files: {} } }
|
||||
);
|
||||
|
||||
await manifestJSONParser.validateStaticThemeImages();
|
||||
|
||||
const { errors, warnings } = linter.collector;
|
||||
expect({ errors, warnings }).toEqual({
|
||||
errors: [],
|
||||
warnings: [],
|
||||
});
|
||||
expect(manifestJSONParser.isValid).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('locales', () => {
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Xpi } from 'io';
|
|||
import {
|
||||
fakeMessageData,
|
||||
validManifestJSON,
|
||||
validStaticThemeManifestJSON,
|
||||
EMPTY_PNG,
|
||||
assertHasMatchingError,
|
||||
} from './helpers';
|
||||
|
@ -688,6 +689,51 @@ describe('Linter.getAddonMetadata()', () => {
|
|||
expect(notices.length).toEqual(1);
|
||||
expect(notices[0].code).toEqual(messages.TYPE_NO_MANIFEST_JSON.code);
|
||||
});
|
||||
|
||||
it('should validate static theme images defined in the manifest', async () => {
|
||||
const files = { 'manifest.json': {} };
|
||||
const getFiles = async () => files;
|
||||
|
||||
// Spy the manifest parser.
|
||||
let spyManifestParser;
|
||||
const FakeManifestParser = sinon.spy((...args) => {
|
||||
spyManifestParser = new ManifestJSONParser(...args);
|
||||
spyManifestParser.validateStaticThemeImages = sinon.spy();
|
||||
return spyManifestParser;
|
||||
});
|
||||
|
||||
// Test on a static theme manifest.
|
||||
const addonLinterTheme = new Linter({ _: ['bar'] });
|
||||
addonLinterTheme.io = {
|
||||
getFiles,
|
||||
getFileAsString: async () => {
|
||||
return validStaticThemeManifestJSON({});
|
||||
},
|
||||
};
|
||||
|
||||
const themeMetadata = await addonLinterTheme.getAddonMetadata({
|
||||
ManifestJSONParser: FakeManifestParser,
|
||||
});
|
||||
expect(themeMetadata.type).toEqual(constants.PACKAGE_EXTENSION);
|
||||
sinon.assert.calledOnce(FakeManifestParser);
|
||||
sinon.assert.calledOnce(spyManifestParser.validateStaticThemeImages);
|
||||
|
||||
// Test on a non theme manifest.
|
||||
const addonLinterExt = new Linter({ _: ['bar'] });
|
||||
addonLinterExt.io = {
|
||||
getFiles,
|
||||
getFileAsString: async () => {
|
||||
return validManifestJSON({});
|
||||
},
|
||||
};
|
||||
|
||||
const extMetadata = await addonLinterExt.getAddonMetadata({
|
||||
ManifestJSONParser: FakeManifestParser,
|
||||
});
|
||||
expect(extMetadata.type).toEqual(constants.PACKAGE_EXTENSION);
|
||||
sinon.assert.calledTwice(FakeManifestParser);
|
||||
sinon.assert.notCalled(spyManifestParser.validateStaticThemeImages);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Linter.extractMetadata()', () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче