Use Firefox schemas for manifest validation (#1238)
Fixes #1131. Fixes #1196. * Add comments about FLAG_PATTERN_REWRITES
This commit is contained in:
Родитель
ce9bc680d9
Коммит
7e59f35dfb
|
@ -27,6 +27,3 @@ env:
|
|||
- secure: kxnTz51/kPI6y4vU8y222rX1B0csVWXU3pvJVzKxgBuoOtvSsebqsjPXkBT67j0anf7yg/N9qIFXf/8J2QWOAs3mYS0viONppGsiAUqla9QwpEJG89equl/760J5RIiSswsMTI/Rd1CM53AF7lUudF5+htXt9VkWvOAbJzkCTAdsqJZgNIQYDezkRxEwpmaIVZNzh/wsjW7x8as4sAeq3qZeVGRA6skEJiOIoR780EFOQqVz+BH+17NreFFM60ekkLBTzjCjkRyWCZmNl8FMz/F/E3qN/21y6ggqfBJf9qGO9GJ3xAr5FhGmyllMP4xKyQeBlUkbzJHWFnAvJFOQ+4dlVech9X3RFczQwMpnVT/m6rQyYy7xQXw+2f6iiAXf4biPsWJgEOBuJwgNIIGAWTbxvH7k5xa5iDbKFntw0TIzAopNZI6JBd5kUQHri0544q2akHRZzcpa+0ZBqOiqMMoL3pkws2L8E8VYKofL3vmP/2o298Ah0KYZWsl6Oc5Ev/vfQLn+cGROpgoeoURW601kFpJ4+uLNhQwdxavEI0KnRLK9OZKik0mHzy/LX/xp6aKl1jREc04pONzSHqQtkhSLmjauQoF0e6UXWCPdH4LW1bd1Xw0BhvyOjSiy2gOyRfj9WHunn9pb4oDsYQbswDIlew6wQR6uKoc7d7/rUDI=
|
||||
- secure: h4pyX3Ak/ZpKyMWUKOcdng6Psq1FqAsTG0ErXUwxWQ16ijHZZL5jlcMnDpH9ctRjyaS8s/3dfa2dRAuPCt9cQOBXZBWNCGekr5kpVq8eaYDJ1SOzEvm8CPtNkqof46ifYN+KDFtqodg83M9Mm2ab0MZgvCP7JJd072SnafhQvXyZ4rOucocnbAp31BFtHhMLlGpI+Vr/1jl88Df1tHDyiPdVjqzNm0z9CGttFkmOpO9Uah+6hKKslDZGtFiRxnf4rpiWSYKkkWe9pa1330V37Cfmz8T4k4c8irg2Hi3ShojAmgRNuwq6Dg8skzy61R172FWpb6KhmT/usRX15FQ005gkM4NXJ3oRNORgsjiUtCKHDFlCJiFO/suqIcWxvEPvQjBJgAL+efxxku2S9wUGoLk3mw2KCHuw+Q/tj+kaqtO5XbtUH3h43M6UqC37E+ZLMnQnQx4I7YcsfCY54pwxbwgRDm/k/5Egs596fKxUJO4VBY04FdB1tqxleVG1GN3fv6qfcZaUDj3VPEQ1d5jFzHcGm5QVEbsyfh/FSdgUmMTuWtC8orqIZ8Rt774HJjzjUw9SkiBCQMk5dI5vi8vMbf3JHQVqQW+LdDW+Z5qd4ZlVYqPOK/FBspq54qpIFIA+6mmmUmzJOzpmEokNhoDq3gBGBDD+gqXJpQ6Vi5qe1/E=
|
||||
- secure: R5a9OOHyki7lihwtbWuWaIBv7nwKY8lCGRmYaSiZ5rKZ5ZSkaoC55eq3JsFqW1sQTxsiri6UJKBXoxJJiY1veV5DVuER6ZRoYd3Vr55BjKMBBFSp5uTFrzX7Mtbfncuf5/tVbwoDxDgc/AwahbWvR/gV9yUDGrbMrgU1/jE0Otjw+LXEqc0+nINoMVyfYFT9Elt1zcl6iKIKS+LDO0S9B5jQpxyIudC2bh/k8uaSKYRaBG9srqMuPJKXOdcMxmtOLkjcr7pKT8+LL7Y6U9i/tpsYnXHv/hz6m084Wz/Eu634wPjEfOTExoXgH1frxGF24ZgmdSfo6xm3fjI+1FPyyB7kp+y4r/pyySjHtqWJgIIUR1no22ISA0z6vy/mtQ0IXPgqoXoF96F2U2hyqVp9Mu5IXASiJXGvafW/bq4cstwRNibC8llmT5QJwJFZLs47qwCroLnyapr8Zbx6+4vzqTX6jQDURy8b95ydewtMhmwLeTeOhX+3qWnYqkG/+thhAkk1bjnuZkFgLMx9ae7GaZJSXelxhLR3pSpr9yGQ/WGnWjeumk3bN/gM8c94VCv2WDFBqb4bOjYsHZk+gGkh4mr1MznrjHcf6K7QJf9eVOXGwq4F/AMAqNkYcx3qj02b2PRrZ1WvF4MPGOw2PHREBLUzIiJmYl/iopT76XBo+f8=
|
||||
matrix:
|
||||
- USE_FIREFOX_SCHEMAS=
|
||||
- USE_FIREFOX_SCHEMAS=true
|
||||
|
|
|
@ -8,6 +8,21 @@ import request from 'request';
|
|||
import tar from 'tar';
|
||||
|
||||
const FLAG_PATTERN_REGEX = /^\(\?[im]*\)(.*)/;
|
||||
/* There are some patterns in the Firefox schemas that have case insensitive
|
||||
* flags set. These are marked with (?i) at the beginning of them. The JSON
|
||||
* Schema spec does not support flags so this object defines rewritten versions
|
||||
* of patterns without the flags. Since these need to be managed by hand, the
|
||||
* code that detects a flag in a pattern will throw if there is no rewritten
|
||||
* pattern for it, preventing updates to the schemas until it is fixed. */
|
||||
/* eslint-disable max-len */
|
||||
export const FLAG_PATTERN_REWRITES = {
|
||||
// Extension ID, UUID format.
|
||||
'(?i)^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$':
|
||||
'^\\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\}$',
|
||||
// Extension ID, email format.
|
||||
'(?i)^[a-z0-9-._]*@[a-z0-9-._]+$': '^[a-zA-Z0-9-._]*@[a-zA-Z0-9-._]+$',
|
||||
};
|
||||
/* eslint-enable max-len */
|
||||
const UNRECOGNIZED_PROPERTY_REFS = [
|
||||
'UnrecognizedProperty',
|
||||
'manifest#/types/UnrecognizedProperty',
|
||||
|
@ -31,11 +46,13 @@ export const inner = {};
|
|||
// use in Firefox only. We shouldn't import these schemas.
|
||||
export const ignoredSchemas = ['omnibox_internal'];
|
||||
|
||||
function stripFlagsFromPattern(value) {
|
||||
// TODO: Fix these patterns and remove this code.
|
||||
const matches = FLAG_PATTERN_REGEX.exec(value);
|
||||
if (matches) {
|
||||
return matches[1];
|
||||
function rewritePatternFlags(value) {
|
||||
if (FLAG_PATTERN_REGEX.test(value)) {
|
||||
const rewritten = FLAG_PATTERN_REWRITES[value];
|
||||
if (!rewritten) {
|
||||
throw new Error(`pattern ${value} must be rewritten`);
|
||||
}
|
||||
return rewritten;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -120,7 +137,7 @@ export function rewriteValue(key, value) {
|
|||
} else if (key === 'id') {
|
||||
return undefined;
|
||||
} else if (key === 'pattern') {
|
||||
return stripFlagsFromPattern(value);
|
||||
return rewritePatternFlags(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import ajv from 'ajv';
|
||||
import URL from 'url-parse';
|
||||
import { isRelativeURL, isValidVersionString } from './formats';
|
||||
import schemaObject from 'schema/imported/manifest.json';
|
||||
import schemas from './imported';
|
||||
|
||||
|
||||
function isURL(value) {
|
||||
const url = new URL(value);
|
||||
return ['http:', 'https:'].includes(url.protocol);
|
||||
}
|
||||
|
||||
function isSecureURL(value) {
|
||||
const url = new URL(value);
|
||||
return url.protocol === 'https:';
|
||||
}
|
||||
|
||||
function isStrictRelativeUrl(value) {
|
||||
return !value.startsWith('//') && isRelativeURL(value);
|
||||
}
|
||||
|
||||
var validator = ajv({
|
||||
allErrors: true,
|
||||
errorDataPath: 'property',
|
||||
jsonPointers: true,
|
||||
verbose: true,
|
||||
schemas,
|
||||
});
|
||||
|
||||
validator.addFormat('versionString', isValidVersionString);
|
||||
validator.addFormat('relativeUrl', isRelativeURL);
|
||||
validator.addFormat('strictRelativeUrl', isStrictRelativeUrl);
|
||||
validator.addFormat('url', isURL);
|
||||
validator.addFormat('secureUrl', isSecureURL);
|
||||
validator.addFormat('deprecated', () => false);
|
||||
|
||||
export default validator.compile(schemaObject);
|
|
@ -60,7 +60,7 @@
|
|||
},
|
||||
"homepage_url": {
|
||||
"type": "string",
|
||||
"format": null,
|
||||
"format": "ignore",
|
||||
"preprocess": "localize",
|
||||
"oneOf": [
|
||||
{
|
||||
|
@ -315,11 +315,11 @@
|
|||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$"
|
||||
"pattern": "^\\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\}$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-._]*@[a-z0-9-._]+$"
|
||||
"pattern": "^[a-zA-Z0-9-._]*@[a-zA-Z0-9-._]+$"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import ajv from 'ajv';
|
||||
import { isRelativeURL, isValidVersionString } from './formats';
|
||||
import schemaObject from 'schema/manifest-schema.json';
|
||||
|
||||
|
||||
var validator = ajv({
|
||||
allErrors: true,
|
||||
errorDataPath: 'property',
|
||||
jsonPointers: true,
|
||||
verbose: true,
|
||||
});
|
||||
validator.addFormat('versionString', isValidVersionString);
|
||||
validator.addFormat('relativeURL', isRelativeURL);
|
||||
|
||||
export default validator.compile(schemaObject);
|
|
@ -1,224 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "http://jsonschema.net",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"applications": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gecko": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "id is the extension ID. Optional. See https://developer.mozilla.org/Add-ons/Install_Manifests#id",
|
||||
"pattern": "^{[A-Fa-f0-9]{8}-([A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}}$|^[A-Za-z0-9-._]*\\@[A-Za-z0-9-._]+$",
|
||||
"type": "string"
|
||||
},
|
||||
"strict_min_version": {
|
||||
"default": "42a1",
|
||||
"description": "Minimum version of Gecko to support. Defaults to '42a1'. (Requires Gecko 45)",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{1,3}(\\.[a-z0-9]+)+$"
|
||||
},
|
||||
"strict_max_version": {
|
||||
"default": "*",
|
||||
"description": "Maximum version of Gecko to support. Defaults to '*'. (Requires Gecko 45)",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{1,3}(\\.[a-z0-9*]+)+$"
|
||||
},
|
||||
"update_url": {
|
||||
"description": "Link to an add-on update manifest. (Requires Gecko 45)",
|
||||
"type": "string",
|
||||
"pattern": "^https://.*$"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of the extension. This is used to identify the extension in the browser's user interface and on sites like addons.mozilla.org.",
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 45
|
||||
},
|
||||
"homepage_url": {
|
||||
"description": "The URL of the homepage for this add-on. The add-on management page will contain a link to this URL.",
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^__MSG_.*?__$"
|
||||
}
|
||||
]
|
||||
},
|
||||
"icons": {
|
||||
"description": "Icons to be shown in buttons and the add-on management page.",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[1-9]\\d*$": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"background": {
|
||||
"description": "This key specifies how background scripts will work.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"scripts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"format": "relativeURL"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"type": "string",
|
||||
"format": "relativeURL"
|
||||
},
|
||||
"persistent": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"manifest_version": {
|
||||
"description": "This key specifies the version of manifest.json used by this extension. Currently, this must always be 2.",
|
||||
"minimum": 2,
|
||||
"maximum": 2,
|
||||
"type": "integer"
|
||||
},
|
||||
"version": {
|
||||
"$ref": "#/definitions/versionString"
|
||||
},
|
||||
"permissions": {
|
||||
"description": "Permissions allowed for the extension",
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"activeTab",
|
||||
"alarms",
|
||||
"bookmarks",
|
||||
"clipboardWrite",
|
||||
"contextMenus",
|
||||
"cookies",
|
||||
"downloads",
|
||||
"downloads.open",
|
||||
"downloads.shelf",
|
||||
"history",
|
||||
"idle",
|
||||
"nativeMessaging",
|
||||
"notifications",
|
||||
"storage",
|
||||
"tabs",
|
||||
"webNavigation",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
"<all_urls>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^(https?|file|ftp|app|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^file:///.*$"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"web_accessible_resources": {
|
||||
"description": "Resources accessible to the extension",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"incognito": {
|
||||
"description": "The behaviour in incognito or private browsing mode",
|
||||
"type": "string",
|
||||
"pattern": "^spanning$"
|
||||
},
|
||||
"author": {
|
||||
"description": "Legacy field from Chrome. Use \"developer\" instead.",
|
||||
"type": "string"
|
||||
},
|
||||
"commands": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"suggested_key": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"$ref": "#/definitions/KeyName"
|
||||
},
|
||||
"mac": {
|
||||
"$ref": "#/definitions/KeyName"
|
||||
},
|
||||
"linux": {
|
||||
"$ref": "#/definitions/KeyName"
|
||||
},
|
||||
"windows": {
|
||||
"$ref": "#/definitions/KeyName"
|
||||
},
|
||||
"chromeos": {
|
||||
"type": "string"
|
||||
},
|
||||
"android": {
|
||||
"type": "string"
|
||||
},
|
||||
"ios": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"deprecated": "Unknown platform name"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"manifest_version",
|
||||
"version"
|
||||
],
|
||||
"definitions": {
|
||||
"versionString": {
|
||||
"format": "versionString",
|
||||
"type": "string",
|
||||
"description": "Version string must be a string comprising one to four dot-separated integers (0-65535). E.g: 1.2.3."
|
||||
},
|
||||
"KeyName": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^\\s*(Alt|Ctrl|Command|MacCtrl)\\s*\\+\\s*(Shift\\s*\\+\\s*)?([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)\\s*$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^\\s*((Alt|Ctrl|Command|MacCtrl)\\s*\\+\\s*)?(Shift\\s*\\+\\s*)?(F[1-9]|F1[0-2])\\s*$"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^(MediaNextTrack|MediaPlayPause|MediaPrevTrack|MediaStop)$"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,8 +33,8 @@
|
|||
]
|
||||
},
|
||||
"homepage_url": {
|
||||
"format": "ignore",
|
||||
"type": "string",
|
||||
"format": null,
|
||||
"oneOf": [
|
||||
{
|
||||
"format": "url"
|
||||
|
|
|
@ -1,3 +1,38 @@
|
|||
export default process.env.USE_FIREFOX_SCHEMAS
|
||||
? require('./firefox-validator').default
|
||||
: require('./linter-validator').default;
|
||||
import ajv from 'ajv';
|
||||
import URL from 'url-parse';
|
||||
import { isRelativeURL, isValidVersionString } from './formats';
|
||||
import schemaObject from 'schema/imported/manifest.json';
|
||||
import schemas from './imported';
|
||||
|
||||
function isURL(value) {
|
||||
const url = new URL(value);
|
||||
return ['http:', 'https:'].includes(url.protocol);
|
||||
}
|
||||
|
||||
function isSecureURL(value) {
|
||||
const url = new URL(value);
|
||||
return url.protocol === 'https:';
|
||||
}
|
||||
|
||||
function isStrictRelativeUrl(value) {
|
||||
return !value.startsWith('//') && isRelativeURL(value);
|
||||
}
|
||||
|
||||
var validator = ajv({
|
||||
allErrors: true,
|
||||
errorDataPath: 'property',
|
||||
jsonPointers: true,
|
||||
verbose: true,
|
||||
schemas,
|
||||
});
|
||||
|
||||
validator.addFormat('versionString', isValidVersionString);
|
||||
validator.addFormat('relativeUrl', isRelativeURL);
|
||||
validator.addFormat('strictRelativeUrl', isStrictRelativeUrl);
|
||||
validator.addFormat('url', isURL);
|
||||
validator.addFormat('secureUrl', isSecureURL);
|
||||
validator.addFormat('deprecated', () => false);
|
||||
validator.addFormat('contentSecurityPolicy', () => true);
|
||||
validator.addFormat('ignore', () => true);
|
||||
|
||||
export default validator.compile(schemaObject);
|
||||
|
|
|
@ -12,8 +12,7 @@ describe('/background', () => {
|
|||
validate(manifest);
|
||||
assertHasMatchingError(validate.errors, {
|
||||
dataPath: '/background/scripts/0',
|
||||
// TODO(FxSchema): Switch to just strictRelativeUrl.
|
||||
message: /should match format "(relativeURL|strictRelativeUrl)"/,
|
||||
message: /should match format "strictRelativeUrl"/,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -45,8 +44,7 @@ describe('/background', () => {
|
|||
validate(manifest);
|
||||
assertHasMatchingError(validate.errors, {
|
||||
dataPath: '/background/page',
|
||||
// TODO(FxSchema): Switch to just strictRelativeUrl.
|
||||
message: /should match format "(relativeURL|strictRelativeUrl)"/,
|
||||
message: /should match format "strictRelativeUrl"/,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import request from 'request';
|
|||
import tar from 'tar';
|
||||
|
||||
import {
|
||||
FLAG_PATTERN_REWRITES,
|
||||
fetchSchemas,
|
||||
filterSchemas,
|
||||
foldSchemas,
|
||||
|
@ -126,9 +127,30 @@ describe('firefox schema import', () => {
|
|||
assert.equal(rewriteValue('type', 'string'), 'string');
|
||||
});
|
||||
|
||||
it('strips flags from patterns', () => {
|
||||
assert.equal(rewriteValue('pattern', '(?i)^abc$'), '^abc$');
|
||||
assert.equal(rewriteValue('pattern', '^foo(?i)bar$'), '^foo(?i)bar$');
|
||||
describe('pattern rewriting', () => {
|
||||
const originalPattern = '(?i)foo';
|
||||
|
||||
before(() => {
|
||||
FLAG_PATTERN_REWRITES[originalPattern] = 'sup';
|
||||
});
|
||||
|
||||
after(() => {
|
||||
delete FLAG_PATTERN_REWRITES[originalPattern];
|
||||
});
|
||||
|
||||
it('throws on an unknown pattern with flags', () => {
|
||||
assert.throws(
|
||||
() => rewriteValue('pattern', '(?i)^abc$'),
|
||||
'pattern (?i)^abc$ must be rewritten');
|
||||
});
|
||||
|
||||
it('rewrites known patterns', () => {
|
||||
assert.equal(rewriteValue('pattern', originalPattern), 'sup');
|
||||
});
|
||||
|
||||
it('does not rewrite unknown patterns without flags', () => {
|
||||
assert.equal(rewriteValue('pattern', 'abc(?i)def'), 'abc(?i)def');
|
||||
});
|
||||
});
|
||||
|
||||
it('updates $ref to JSON pointer', () => {
|
||||
|
|
|
@ -34,8 +34,7 @@ describe('/homepage_url', () => {
|
|||
validate(manifest);
|
||||
assertHasMatchingError(validate.errors, {
|
||||
dataPath: '/homepage_url',
|
||||
// TODO(FxSchema): Switch to just '... "url"'.
|
||||
message: /should match format "ur[il]"/,
|
||||
message: /should match format "url"/,
|
||||
});
|
||||
assertHasMatchingError(validate.errors, {
|
||||
message: 'should match pattern "^__MSG_.*?__$"',
|
||||
|
|
|
@ -20,13 +20,7 @@ describe('/incognito', () => {
|
|||
validate(manifest);
|
||||
assertHasMatchingError(validate.errors, {
|
||||
dataPath: '/incognito',
|
||||
message: new RegExp(
|
||||
'(' +
|
||||
// TODO(FxSchema): Switch to just this string.
|
||||
'should be equal to one of the allowed values' +
|
||||
'|' +
|
||||
'should match pattern "\\^spanning\\$"' +
|
||||
')'),
|
||||
message: 'should be equal to one of the allowed values',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче