Fix broken install button due to circular imports (#3448)

This commit is contained in:
Kumar McMillan 2017-10-11 16:58:48 -05:00 коммит произвёл GitHub
Родитель d8df872597
Коммит 22a452c770
10 изменённых файлов: 694 добавлений и 656 удалений

Просмотреть файл

@ -228,6 +228,7 @@
"chalk": "^2.0.1",
"cheerio": "^1.0.0-rc.2",
"chokidar-cli": "^1.2.0",
"circular-dependency-plugin": "^4.2.1",
"codecov": "^2.3.0",
"concurrently": "^3.4.0",
"content-security-policy-parser": "^0.1.0",

Просмотреть файл

@ -27,10 +27,12 @@ import {
} from 'core/constants';
import { withInstallHelpers } from 'core/installAddon';
import {
getClientCompatibility as _getClientCompatibility,
sanitizeHTML,
sanitizeUserHTML,
} from 'core/utils';
import {
getClientCompatibility as _getClientCompatibility,
} from 'core/utils/compatibility';
import { getAddonIconUrl } from 'core/imageUtils';
import translate from 'core/i18n/translate';
import log from 'core/logger';

Просмотреть файл

@ -15,7 +15,7 @@ import log from 'core/logger';
import { getThemeData } from 'core/themePreview';
import {
getClientCompatibility as _getClientCompatibility,
} from 'core/utils';
} from 'core/utils/compatibility';
import Button from 'ui/components/Button';
import './styles.scss';

Просмотреть файл

@ -0,0 +1,137 @@
/* global window */
import { oneLine } from 'common-tags';
import mozCompare from 'mozilla-version-comparator';
import {
ADDON_TYPE_EXTENSION,
ADDON_TYPE_OPENSEARCH,
INCOMPATIBLE_FIREFOX_FOR_IOS,
INCOMPATIBLE_NO_OPENSEARCH,
INCOMPATIBLE_NOT_FIREFOX,
INCOMPATIBLE_OVER_MAX_VERSION,
INCOMPATIBLE_UNDER_MIN_VERSION,
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
} from 'core/constants';
import { findInstallURL } from 'core/installAddon';
import log from 'core/logger';
export function getCompatibleVersions({ _log = log, addon, clientApp } = {}) {
let maxVersion = null;
let minVersion = null;
if (
addon && addon.current_version && addon.current_version.compatibility
) {
if (addon.current_version.compatibility[clientApp]) {
maxVersion = addon.current_version.compatibility[clientApp].max;
minVersion = addon.current_version.compatibility[clientApp].min;
} else if (addon.type === ADDON_TYPE_OPENSEARCH) {
_log.info(oneLine`addon is type ${ADDON_TYPE_OPENSEARCH}; no
compatibility info found but this is expected.`, { addon, clientApp });
} else {
_log.error(
'addon found with no compatibility info for valid clientApp',
{ addon, clientApp }
);
}
}
return { maxVersion, minVersion };
}
export function isCompatibleWithUserAgent({
_log = log, _window = typeof window !== 'undefined' ? window : {},
addon, maxVersion, minVersion, userAgentInfo,
} = {}) {
// If the userAgent is false there was likely a programming error.
if (!userAgentInfo) {
throw new Error('userAgentInfo is required');
}
const { browser, os } = userAgentInfo;
// We need a Firefox browser compatible with add-ons (Firefox for iOS does
// not currently support add-ons).
if (browser.name === 'Firefox' && os.name === 'iOS') {
return { compatible: false, reason: INCOMPATIBLE_FIREFOX_FOR_IOS };
}
if (browser.name === 'Firefox') {
// Do version checks, if this add-on has minimum or maximum version
// requirements.
// The mozilla-version-comparator API is quite strange; a result of
// `1` means the first argument is higher in version than the second.
//
// Being over the maxVersion, oddly, is not actually a reason to
// disable the install button or mark the add-on as incompatible
// with this version of Firefox. But we log the version mismatch
// here so it's not totally silent and a future developer isn't as
// confused by this as tofumatt was.
// See: https://github.com/mozilla/addons-frontend/issues/2074#issuecomment-286983423
if (maxVersion && mozCompare(browser.version, maxVersion) === 1) {
if (addon.current_version.is_strict_compatibility_enabled) {
return { compatible: false, reason: INCOMPATIBLE_OVER_MAX_VERSION };
}
_log.info(oneLine`maxVersion ${maxVersion} for add-on lower than
browser version ${browser.version}, but add-on still marked as
compatible because we largely ignore maxVersion. See:
https://github.com/mozilla/addons-frontend/issues/2074`);
}
// A result of `-1` means the second argument is a lower version than the
// first.
if (minVersion && mozCompare(browser.version, minVersion) === -1) {
if (minVersion === '*') {
_log.error(oneLine`minVersion of "*" was passed to
isCompatibleWithUserAgent(); bad add-on version data`,
{ browserVersion: browser.version, minVersion }
);
}
// `minVersion` is always respected, regardless of
// `is_strict_compatibility_enabled`'s value.
return { compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION };
}
if (
addon.type === ADDON_TYPE_OPENSEARCH &&
!(_window.external && 'AddSearchProvider' in _window.external)
) {
return { compatible: false, reason: INCOMPATIBLE_NO_OPENSEARCH };
}
// Even if an extension's version is marked compatible,
// we need to make sure it has a matching platform file
// to work around some bugs.
// See https://github.com/mozilla/addons-server/issues/6576
if (
addon.type === ADDON_TYPE_EXTENSION &&
!findInstallURL({
installURLs: addon.installURLs, userAgentInfo,
})
) {
return {
compatible: false,
reason: INCOMPATIBLE_UNSUPPORTED_PLATFORM,
};
}
// If we made it here we're compatible (yay!)
return { compatible: true, reason: null };
}
// This means the client is not Firefox, so it's incompatible.
return { compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX };
}
export function getClientCompatibility({
addon, clientApp, userAgentInfo,
} = {}) {
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp });
const { compatible, reason } = isCompatibleWithUserAgent({
addon, maxVersion, minVersion, userAgentInfo });
return { compatible, maxVersion, minVersion, reason };
}

Просмотреть файл

@ -1,10 +1,8 @@
/* global window */
/* eslint-disable react/prop-types */
import url from 'url';
import { oneLine } from 'common-tags';
import config from 'config';
import mozCompare from 'mozilla-version-comparator';
import React from 'react';
import { asyncConnect as defaultAsyncConnect } from 'redux-connect';
@ -13,23 +11,15 @@ import { fetchAddon } from 'core/api';
import GenericError from 'core/components/ErrorPage/GenericError';
import NotFound from 'core/components/ErrorPage/NotFound';
import {
ADDON_TYPE_EXTENSION,
ADDON_TYPE_COMPLETE_THEME,
ADDON_TYPE_OPENSEARCH,
ADDON_TYPE_THEME,
API_ADDON_TYPES_MAPPING,
CATEGORY_COLORS,
VISIBLE_ADDON_TYPES_MAPPING,
INCOMPATIBLE_FIREFOX_FOR_IOS,
INCOMPATIBLE_NO_OPENSEARCH,
INCOMPATIBLE_NOT_FIREFOX,
INCOMPATIBLE_OVER_MAX_VERSION,
INCOMPATIBLE_UNDER_MIN_VERSION,
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
} from 'core/constants';
import { AddonTypeNotFound } from 'core/errors';
import log from 'core/logger';
import { findInstallURL } from 'core/installAddon';
import purify from 'core/purify';
@ -335,126 +325,6 @@ export function render404IfConfigKeyIsFalse(
};
}
export function getCompatibleVersions({ _log = log, addon, clientApp } = {}) {
let maxVersion = null;
let minVersion = null;
if (
addon && addon.current_version && addon.current_version.compatibility
) {
if (addon.current_version.compatibility[clientApp]) {
maxVersion = addon.current_version.compatibility[clientApp].max;
minVersion = addon.current_version.compatibility[clientApp].min;
} else if (addon.type === ADDON_TYPE_OPENSEARCH) {
_log.info(oneLine`addon is type ${ADDON_TYPE_OPENSEARCH}; no
compatibility info found but this is expected.`, { addon, clientApp });
} else {
_log.error(
'addon found with no compatibility info for valid clientApp',
{ addon, clientApp }
);
}
}
return { maxVersion, minVersion };
}
export function isCompatibleWithUserAgent({
_log = log, _window = typeof window !== 'undefined' ? window : {},
addon, maxVersion, minVersion, userAgentInfo,
} = {}) {
// If the userAgent is false there was likely a programming error.
if (!userAgentInfo) {
throw new Error('userAgentInfo is required');
}
const { browser, os } = userAgentInfo;
// We need a Firefox browser compatible with add-ons (Firefox for iOS does
// not currently support add-ons).
if (browser.name === 'Firefox' && os.name === 'iOS') {
return { compatible: false, reason: INCOMPATIBLE_FIREFOX_FOR_IOS };
}
if (browser.name === 'Firefox') {
// Do version checks, if this add-on has minimum or maximum version
// requirements.
// The mozilla-version-comparator API is quite strange; a result of
// `1` means the first argument is higher in version than the second.
//
// Being over the maxVersion, oddly, is not actually a reason to
// disable the install button or mark the add-on as incompatible
// with this version of Firefox. But we log the version mismatch
// here so it's not totally silent and a future developer isn't as
// confused by this as tofumatt was.
// See: https://github.com/mozilla/addons-frontend/issues/2074#issuecomment-286983423
if (maxVersion && mozCompare(browser.version, maxVersion) === 1) {
if (addon.current_version.is_strict_compatibility_enabled) {
return { compatible: false, reason: INCOMPATIBLE_OVER_MAX_VERSION };
}
_log.info(oneLine`maxVersion ${maxVersion} for add-on lower than
browser version ${browser.version}, but add-on still marked as
compatible because we largely ignore maxVersion. See:
https://github.com/mozilla/addons-frontend/issues/2074`);
}
// A result of `-1` means the second argument is a lower version than the
// first.
if (minVersion && mozCompare(browser.version, minVersion) === -1) {
if (minVersion === '*') {
_log.error(oneLine`minVersion of "*" was passed to
isCompatibleWithUserAgent(); bad add-on version data`,
{ browserVersion: browser.version, minVersion }
);
}
// `minVersion` is always respected, regardless of
// `is_strict_compatibility_enabled`'s value.
return { compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION };
}
if (
addon.type === ADDON_TYPE_OPENSEARCH &&
!(_window.external && 'AddSearchProvider' in _window.external)
) {
return { compatible: false, reason: INCOMPATIBLE_NO_OPENSEARCH };
}
// Even if an extension's version is marked compatible,
// we need to make sure it has a matching platform file
// to work around some bugs.
// See https://github.com/mozilla/addons-server/issues/6576
if (
addon.type === ADDON_TYPE_EXTENSION &&
!findInstallURL({
installURLs: addon.installURLs, userAgentInfo,
})
) {
return {
compatible: false,
reason: INCOMPATIBLE_UNSUPPORTED_PLATFORM,
};
}
// If we made it here we're compatible (yay!)
return { compatible: true, reason: null };
}
// This means the client is not Firefox, so it's incompatible.
return { compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX };
}
export function getClientCompatibility({
addon, clientApp, userAgentInfo,
} = {}) {
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp });
const { compatible, reason } = isCompatibleWithUserAgent({
addon, maxVersion, minVersion, userAgentInfo });
return { compatible, maxVersion, minVersion, reason };
}
export function getCategoryColor(category) {
if (!category) {
throw new Error('category is required.');

Просмотреть файл

@ -30,9 +30,11 @@ import { withInstallHelpers } from 'core/installAddon';
import themeAction from 'core/themePreview';
import tracking, { getAction } from 'core/tracking';
import {
getClientCompatibility as _getClientCompatibility,
sanitizeHTML,
} from 'core/utils';
import {
getClientCompatibility as _getClientCompatibility,
} from 'core/utils/compatibility';
import LoadingText from 'ui/components/LoadingText';
import 'disco/css/Addon.scss';

Просмотреть файл

@ -0,0 +1,540 @@
import { oneLine } from 'common-tags';
import UAParser from 'ua-parser-js';
import {
ADDON_TYPE_OPENSEARCH,
ADDON_TYPE_THEME,
CLIENT_APP_ANDROID,
CLIENT_APP_FIREFOX,
INCOMPATIBLE_FIREFOX_FOR_IOS,
INCOMPATIBLE_NO_OPENSEARCH,
INCOMPATIBLE_NOT_FIREFOX,
INCOMPATIBLE_OVER_MAX_VERSION,
INCOMPATIBLE_UNDER_MIN_VERSION,
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
OS_MAC,
} from 'core/constants';
import { createInternalAddon } from 'core/reducers/addons';
import {
getCompatibleVersions,
getClientCompatibility,
isCompatibleWithUserAgent,
} from 'core/utils/compatibility';
import { fakeAddon } from 'tests/unit/amo/helpers';
import {
userAgents,
userAgentsByPlatform,
} from 'tests/unit/helpers';
describe(__filename, () => {
describe('isCompatibleWithUserAgent', () => {
it('should throw if no userAgentInfo supplied', () => {
expect(() => {
isCompatibleWithUserAgent({ userAgent: null, reason: null });
}).toThrowError('userAgentInfo is required');
});
it('is incompatible with Android/webkit', () => {
userAgents.androidWebkit.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({ userAgentInfo: UAParser(userAgent) }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
});
it('is incompatible with Chrome Android', () => {
userAgents.chromeAndroid.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({ userAgentInfo: UAParser(userAgent) }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
});
it('is incompatible with Chrome desktop', () => {
userAgents.chrome.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({ userAgentInfo: UAParser(userAgent) }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
});
it('is compatible with Firefox desktop', () => {
userAgents.firefox.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: true, reason: null });
});
});
it('is compatible with Firefox Android', () => {
userAgents.firefoxAndroid.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: true, reason: null });
});
});
it('is compatible with Firefox OS', () => {
userAgents.firefoxOS.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: true, reason: null });
});
});
it('is incompatible with Firefox iOS', () => {
userAgents.firefoxIOS.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_FIREFOX_FOR_IOS });
});
});
it(oneLine`should use a Firefox for iOS reason code even if minVersion is
also not met`, () => {
const userAgentInfo = {
browser: { name: 'Firefox', version: '8.0' },
os: { name: 'iOS' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
minVersion: '9.0',
userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_FIREFOX_FOR_IOS });
});
it('should mark Firefox without window.external as incompatible', () => {
const userAgentInfo = {
browser: { name: 'Firefox' },
os: { name: 'Windows' },
};
const fakeOpenSearchAddon = createInternalAddon({
...fakeAddon, type: ADDON_TYPE_OPENSEARCH,
});
const fakeWindow = {};
expect(isCompatibleWithUserAgent({
_window: fakeWindow, addon: fakeOpenSearchAddon, userAgentInfo }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NO_OPENSEARCH });
});
it('should mark Firefox without OpenSearch support as incompatible', () => {
const userAgentInfo = {
browser: { name: 'Firefox' },
os: { name: 'Windows' },
};
const fakeOpenSearchAddon = createInternalAddon({
...fakeAddon, type: ADDON_TYPE_OPENSEARCH,
});
const fakeWindow = { external: {} };
expect(isCompatibleWithUserAgent({
_window: fakeWindow, addon: fakeOpenSearchAddon, userAgentInfo }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NO_OPENSEARCH });
});
it('should mark Firefox with OpenSearch support as compatible', () => {
const userAgentInfo = {
browser: { name: 'Firefox' },
os: { name: 'Windows' },
};
const fakeOpenSearchAddon = createInternalAddon({
...fakeAddon, type: ADDON_TYPE_OPENSEARCH,
});
const fakeWindow = { external: { AddSearchProvider: sinon.stub() } };
expect(isCompatibleWithUserAgent({
_window: fakeWindow, addon: fakeOpenSearchAddon, userAgentInfo }))
.toEqual({ compatible: true, reason: null });
});
it('should mark non-Firefox UAs as incompatible', () => {
const userAgentInfo = { browser: { name: 'Chrome' } };
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
it('should mark Firefox 10 as incompatible with a minVersion of 10.1', () => {
const userAgentInfo = {
browser: { name: 'Firefox', version: '10.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
minVersion: '10.1',
userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION });
});
it('should mark Firefox 24 as compatible with a maxVersion of 8', () => {
// https://github.com/mozilla/addons-frontend/issues/2074
const userAgentInfo = {
browser: { name: 'Firefox', version: '24.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
is_strict_compatibility_enabled: false,
},
}),
maxVersion: '8',
userAgentInfo,
})).toEqual({ compatible: true, reason: null });
});
it('should mark Firefox as compatible when no min or max version', () => {
const userAgentInfo = {
browser: { name: 'Firefox', version: '10.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: true, reason: null });
});
it('should mark Firefox as compatible with maxVersion of "*"', () => {
// WebExtensions are marked as having a maxVersion of "*" by addons-server
// if their manifests don't contain explicit version information.
const userAgentInfo = {
browser: { name: 'Firefox', version: '54.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
maxVersion: '*',
userAgentInfo,
}))
.toEqual({ compatible: true, reason: null });
});
it('should log warning when minVersion is "*"', () => {
// Note that this should never happen as addons-server will mark a
// WebExtension with no minVersion as having a minVersion of "48".
// Still, we accept it (but it will log a warning).
const fakeLog = { error: sinon.stub() };
const userAgentInfo = {
browser: { name: 'Firefox', version: '54.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
_log: fakeLog,
addon: createInternalAddon(fakeAddon),
minVersion: '*',
userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION });
expect(fakeLog.error.firstCall.args[0])
.toContain('minVersion of "*" was passed to isCompatibleWithUserAgent()');
});
it('is incompatible with empty user agent values', () => {
const userAgentInfo = { browser: { name: '' } };
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
it('is incompatible with non-string user agent values', () => {
const userAgentInfo = { browser: { name: null }, os: { name: null } };
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
it('is incompatible if no matching platform file exists', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
files: [{
...fakeAddon.current_version.files[0],
platform: OS_MAC,
}],
},
});
const userAgentInfo =
UAParser(userAgentsByPlatform.windows.firefox40);
expect(isCompatibleWithUserAgent({ addon, userAgentInfo }))
.toEqual({
compatible: false,
reason: INCOMPATIBLE_UNSUPPORTED_PLATFORM,
});
});
it('allows non-extensions to have mismatching platform files', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
files: [{
...fakeAddon.current_version.files[0],
platform: OS_MAC,
}],
},
type: ADDON_TYPE_THEME,
});
const userAgentInfo =
UAParser(userAgentsByPlatform.windows.firefox40);
expect(isCompatibleWithUserAgent({ addon, userAgentInfo }))
.toMatchObject({ compatible: true });
});
});
describe('getCompatibleVersions', () => {
it('gets the min and max versions', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: {
max: '20.0.*',
min: '11.0.1',
},
},
},
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual('20.0.*');
expect(minVersion).toEqual('11.0.1');
});
it('gets null if the clientApp does not match', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: {
max: '20.0.*',
min: '11.0.1',
},
},
},
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_ANDROID });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('returns null if clientApp has no compatibility', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {},
},
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('returns null if current_version does not exist', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: null,
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('returns null if addon is null', () => {
const { maxVersion, minVersion } = getCompatibleVersions({
addon: null, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('should log info when OpenSearch type is found', () => {
const fakeLog = { info: sinon.stub() };
const openSearchAddon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {},
},
type: ADDON_TYPE_OPENSEARCH,
});
const { maxVersion, minVersion } = getCompatibleVersions({
_log: fakeLog,
addon: openSearchAddon,
clientApp: CLIENT_APP_FIREFOX,
});
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
expect(fakeLog.info.firstCall.args[0])
.toContain(`addon is type ${ADDON_TYPE_OPENSEARCH}`);
});
});
describe('getClientCompatibility', () => {
it('returns true for Firefox (reason undefined when compatibile)', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon(fakeAddon),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: null,
minVersion: null,
reason: null,
});
});
it('returns maxVersion when set', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: { max: '200.0', min: null },
},
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: '200.0',
minVersion: null,
reason: null,
});
});
it('returns minVersion when set', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: { max: null, min: '2.0' },
},
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: null,
minVersion: '2.0',
reason: null,
});
});
it('returns incompatible for non-Firefox UA', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon(fakeAddon),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: null,
minVersion: null,
reason: null,
});
});
it('returns compatible if strict compatibility is off', () => {
const { browser, os } = UAParser(userAgents.firefox[4]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
...fakeAddon.current_version.compatibility,
[CLIENT_APP_FIREFOX]: {
max: '56.*',
min: '24.0',
},
},
files: [{
...fakeAddon.current_version.files[0],
is_webextension: true,
}],
is_strict_compatibility_enabled: false,
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toMatchObject({ compatible: true });
});
it('returns incompatible if strict compatibility enabled', () => {
const { browser, os } = UAParser(userAgents.firefox[5]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
...fakeAddon.current_version.compatibility,
[CLIENT_APP_FIREFOX]: {
max: '56.*',
min: '24.0',
},
},
files: [{
...fakeAddon.current_version.files[0],
is_webextension: false,
}],
is_strict_compatibility_enabled: true,
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toMatchObject({
compatible: false,
reason: INCOMPATIBLE_OVER_MAX_VERSION,
});
});
});
});

Просмотреть файл

@ -1,6 +1,5 @@
import url from 'url';
import { oneLine } from 'common-tags';
import React from 'react';
import config from 'config';
import { sprintf } from 'jed';
@ -9,7 +8,6 @@ import {
findRenderedComponentWithType,
} from 'react-addons-test-utils';
import { compose } from 'redux';
import UAParser from 'ua-parser-js';
import * as api from 'core/api';
import {
@ -22,13 +20,6 @@ import {
CATEGORY_COLORS,
CLIENT_APP_ANDROID,
CLIENT_APP_FIREFOX,
INCOMPATIBLE_FIREFOX_FOR_IOS,
INCOMPATIBLE_NO_OPENSEARCH,
INCOMPATIBLE_NOT_FIREFOX,
INCOMPATIBLE_OVER_MAX_VERSION,
INCOMPATIBLE_UNDER_MIN_VERSION,
INCOMPATIBLE_UNSUPPORTED_PLATFORM,
OS_MAC,
validAddonTypes,
} from 'core/constants';
import {
@ -40,12 +31,9 @@ import {
findAddon,
getCategoryColor,
getClientApp,
getClientCompatibility,
getClientConfig,
getCompatibleVersions,
isAddonAuthor,
isAllowedOrigin,
isCompatibleWithUserAgent,
isValidClientApp,
loadAddonIfNeeded,
ngettext,
@ -71,7 +59,6 @@ import {
fakeI18n,
unexpectedSuccess,
userAgents,
userAgentsByPlatform,
} from 'tests/unit/helpers';
@ -324,276 +311,6 @@ describe('isAddonAuthor', () => {
});
});
describe('isCompatibleWithUserAgent', () => {
it('should throw if no userAgentInfo supplied', () => {
expect(() => {
isCompatibleWithUserAgent({ userAgent: null, reason: null });
}).toThrowError('userAgentInfo is required');
});
it('is incompatible with Android/webkit', () => {
userAgents.androidWebkit.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({ userAgentInfo: UAParser(userAgent) }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
});
it('is incompatible with Chrome Android', () => {
userAgents.chromeAndroid.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({ userAgentInfo: UAParser(userAgent) }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
});
it('is incompatible with Chrome desktop', () => {
userAgents.chrome.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({ userAgentInfo: UAParser(userAgent) }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
});
it('is compatible with Firefox desktop', () => {
userAgents.firefox.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: true, reason: null });
});
});
it('is compatible with Firefox Android', () => {
userAgents.firefoxAndroid.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: true, reason: null });
});
});
it('is compatible with Firefox OS', () => {
userAgents.firefoxOS.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: true, reason: null });
});
});
it('is incompatible with Firefox iOS', () => {
userAgents.firefoxIOS.forEach((userAgent) => {
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
userAgentInfo: UAParser(userAgent),
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_FIREFOX_FOR_IOS });
});
});
it(oneLine`should use a Firefox for iOS reason code even if minVersion is
also not met`, () => {
const userAgentInfo = {
browser: { name: 'Firefox', version: '8.0' },
os: { name: 'iOS' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
minVersion: '9.0',
userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_FIREFOX_FOR_IOS });
});
it('should mark Firefox without window.external as incompatible', () => {
const userAgentInfo = {
browser: { name: 'Firefox' },
os: { name: 'Windows' },
};
const fakeOpenSearchAddon = createInternalAddon({
...fakeAddon, type: ADDON_TYPE_OPENSEARCH,
});
const fakeWindow = {};
expect(isCompatibleWithUserAgent({
_window: fakeWindow, addon: fakeOpenSearchAddon, userAgentInfo }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NO_OPENSEARCH });
});
it('should mark Firefox without OpenSearch support as incompatible', () => {
const userAgentInfo = {
browser: { name: 'Firefox' },
os: { name: 'Windows' },
};
const fakeOpenSearchAddon = createInternalAddon({
...fakeAddon, type: ADDON_TYPE_OPENSEARCH,
});
const fakeWindow = { external: {} };
expect(isCompatibleWithUserAgent({
_window: fakeWindow, addon: fakeOpenSearchAddon, userAgentInfo }))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NO_OPENSEARCH });
});
it('should mark Firefox with OpenSearch support as compatible', () => {
const userAgentInfo = {
browser: { name: 'Firefox' },
os: { name: 'Windows' },
};
const fakeOpenSearchAddon = createInternalAddon({
...fakeAddon, type: ADDON_TYPE_OPENSEARCH,
});
const fakeWindow = { external: { AddSearchProvider: sinon.stub() } };
expect(isCompatibleWithUserAgent({
_window: fakeWindow, addon: fakeOpenSearchAddon, userAgentInfo }))
.toEqual({ compatible: true, reason: null });
});
it('should mark non-Firefox UAs as incompatible', () => {
const userAgentInfo = { browser: { name: 'Chrome' } };
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
it('should mark Firefox 10 as incompatible with a minVersion of 10.1', () => {
const userAgentInfo = {
browser: { name: 'Firefox', version: '10.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
minVersion: '10.1',
userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION });
});
it('should mark Firefox 24 as compatible with a maxVersion of 8', () => {
// https://github.com/mozilla/addons-frontend/issues/2074
const userAgentInfo = {
browser: { name: 'Firefox', version: '24.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
is_strict_compatibility_enabled: false,
},
}),
maxVersion: '8',
userAgentInfo,
})).toEqual({ compatible: true, reason: null });
});
it('should mark Firefox as compatible when no min or max version', () => {
const userAgentInfo = {
browser: { name: 'Firefox', version: '10.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: true, reason: null });
});
it('should mark Firefox as compatible with maxVersion of "*"', () => {
// WebExtensions are marked as having a maxVersion of "*" by addons-server
// if their manifests don't contain explicit version information.
const userAgentInfo = {
browser: { name: 'Firefox', version: '54.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon),
maxVersion: '*',
userAgentInfo,
}))
.toEqual({ compatible: true, reason: null });
});
it('should log warning when minVersion is "*"', () => {
// Note that this should never happen as addons-server will mark a
// WebExtension with no minVersion as having a minVersion of "48".
// Still, we accept it (but it will log a warning).
const fakeLog = { error: sinon.stub() };
const userAgentInfo = {
browser: { name: 'Firefox', version: '54.0' },
os: { name: 'Windows' },
};
expect(isCompatibleWithUserAgent({
_log: fakeLog,
addon: createInternalAddon(fakeAddon),
minVersion: '*',
userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_UNDER_MIN_VERSION });
expect(fakeLog.error.firstCall.args[0])
.toContain('minVersion of "*" was passed to isCompatibleWithUserAgent()');
});
it('is incompatible with empty user agent values', () => {
const userAgentInfo = { browser: { name: '' } };
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
it('is incompatible with non-string user agent values', () => {
const userAgentInfo = { browser: { name: null }, os: { name: null } };
expect(isCompatibleWithUserAgent({
addon: createInternalAddon(fakeAddon), userAgentInfo,
}))
.toEqual({ compatible: false, reason: INCOMPATIBLE_NOT_FIREFOX });
});
it('is incompatible if no matching platform file exists', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
files: [{
...fakeAddon.current_version.files[0],
platform: OS_MAC,
}],
},
});
const userAgentInfo =
UAParser(userAgentsByPlatform.windows.firefox40);
expect(isCompatibleWithUserAgent({ addon, userAgentInfo }))
.toEqual({
compatible: false,
reason: INCOMPATIBLE_UNSUPPORTED_PLATFORM,
});
});
it('allows non-extensions to have mismatching platform files', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
files: [{
...fakeAddon.current_version.files[0],
platform: OS_MAC,
}],
},
type: ADDON_TYPE_THEME,
});
const userAgentInfo =
UAParser(userAgentsByPlatform.windows.firefox40);
expect(isCompatibleWithUserAgent({ addon, userAgentInfo }))
.toMatchObject({ compatible: true });
});
});
describe('isValidClientApp', () => {
const _config = new Map();
_config.set('validClientApplications', [
@ -1017,246 +734,6 @@ describe('render404IfConfigKeyIsFalse', () => {
});
});
describe('getCompatibleVersions', () => {
it('gets the min and max versions', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: {
max: '20.0.*',
min: '11.0.1',
},
},
},
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual('20.0.*');
expect(minVersion).toEqual('11.0.1');
});
it('gets null if the clientApp does not match', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: {
max: '20.0.*',
min: '11.0.1',
},
},
},
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_ANDROID });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('returns null if clientApp has no compatibility', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {},
},
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('returns null if current_version does not exist', () => {
const addon = createInternalAddon({
...fakeAddon,
current_version: null,
});
const { maxVersion, minVersion } = getCompatibleVersions({
addon, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('returns null if addon is null', () => {
const { maxVersion, minVersion } = getCompatibleVersions({
addon: null, clientApp: CLIENT_APP_FIREFOX });
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
});
it('should log info when OpenSearch type is found', () => {
const fakeLog = { info: sinon.stub() };
const openSearchAddon = createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {},
},
type: ADDON_TYPE_OPENSEARCH,
});
const { maxVersion, minVersion } = getCompatibleVersions({
_log: fakeLog,
addon: openSearchAddon,
clientApp: CLIENT_APP_FIREFOX,
});
expect(maxVersion).toEqual(null);
expect(minVersion).toEqual(null);
expect(fakeLog.info.firstCall.args[0])
.toContain(`addon is type ${ADDON_TYPE_OPENSEARCH}`);
});
});
describe('getClientCompatibility', () => {
it('returns true for Firefox (reason undefined when compatibile)', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon(fakeAddon),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: null,
minVersion: null,
reason: null,
});
});
it('returns maxVersion when set', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: { max: '200.0', min: null },
},
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: '200.0',
minVersion: null,
reason: null,
});
});
it('returns minVersion when set', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
firefox: { max: null, min: '2.0' },
},
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: null,
minVersion: '2.0',
reason: null,
});
});
it('returns incompatible for non-Firefox UA', () => {
const { browser, os } = UAParser(userAgents.firefox[0]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon(fakeAddon),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toEqual({
compatible: true,
maxVersion: null,
minVersion: null,
reason: null,
});
});
it('returns compatible if strict compatibility is off', () => {
const { browser, os } = UAParser(userAgents.firefox[4]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
...fakeAddon.current_version.compatibility,
[CLIENT_APP_FIREFOX]: {
max: '56.*',
min: '24.0',
},
},
files: [{
...fakeAddon.current_version.files[0],
is_webextension: true,
}],
is_strict_compatibility_enabled: false,
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toMatchObject({ compatible: true });
});
it('returns incompatible if strict compatibility enabled', () => {
const { browser, os } = UAParser(userAgents.firefox[5]);
const userAgentInfo = { browser, os };
expect(getClientCompatibility({
addon: createInternalAddon({
...fakeAddon,
current_version: {
...fakeAddon.current_version,
compatibility: {
...fakeAddon.current_version.compatibility,
[CLIENT_APP_FIREFOX]: {
max: '56.*',
min: '24.0',
},
},
files: [{
...fakeAddon.current_version.files[0],
is_webextension: false,
}],
is_strict_compatibility_enabled: true,
},
}),
clientApp: CLIENT_APP_FIREFOX,
userAgentInfo,
})).toMatchObject({
compatible: false,
reason: INCOMPATIBLE_OVER_MAX_VERSION,
});
});
});
describe('getCategoryColor', () => {
it('throws if category is false-y', () => {
expect(() => {

Просмотреть файл

@ -1,5 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */
import autoprefixer from 'autoprefixer';
import CircularDependencyPlugin from 'circular-dependency-plugin';
import config from 'config';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import webpack from 'webpack';
@ -149,6 +150,10 @@ export function getPlugins({ excludeOtherAppLocales = true } = {}) {
// This swaps the server side window object with a standard browser window.
new webpack.NormalModuleReplacementPlugin(
/core\/window/, 'core/browserWindow.js'),
new CircularDependencyPlugin({
exclude: /node_modules/,
failOnError: true,
}),
];
if (excludeOtherAppLocales) {

Просмотреть файл

@ -1640,6 +1640,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
dependencies:
inherits "^2.0.1"
circular-dependency-plugin@4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-4.2.1.tgz#d3af66e04b3bb3f47300824740b817cea74e38f1"
circular-json@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"