/* globals badger:false, constants:false */ (function () { const DOMAIN = "example.com", SUBDOMAIN = "widgets." + DOMAIN, SUBSUBDOMAIN = "cdn." + SUBDOMAIN; let storage = badger.storage, actionMap, snitchMap; QUnit.module("Storage", { before: (assert) => { // can't initialize globally above // as they get initialized too early when run by Selenium actionMap = storage.getStore('action_map'); snitchMap = storage.getStore('snitch_map'); assert.notOk(actionMap.getItem(DOMAIN), "test domain is not yet in action_map"); assert.notOk(snitchMap.getItem(DOMAIN), "test domain is not yet in snitch_map"); } }); QUnit.test("testGetBadgerStorage", function (assert) { assert.ok(actionMap.updateObject instanceof Function, "actionMap is a pbstorage"); }); QUnit.test("test BadgerStorage methods", function (assert) { actionMap.setItem('foo', 'bar'); assert.equal(actionMap.getItem('foo'), 'bar'); assert.ok(actionMap.hasItem('foo')); actionMap.deleteItem('foo'); assert.notOk(actionMap.hasItem('foo')); }); QUnit.test("test user override of default action for domain", function (assert) { badger.saveAction("allow", "pbtest.org"); assert.equal(storage.getAction("pbtest.org"), constants.USER_ALLOW); badger.saveAction("block", "pbtest.org"); assert.equal(storage.getAction("pbtest.org"), constants.USER_BLOCK); badger.saveAction("allow", "pbtest.org"); assert.equal(storage.getAction("pbtest.org"), constants.USER_ALLOW); storage.revertUserAction("pbtest.org"); assert.equal(storage.getAction("pbtest.org"), constants.NO_TRACKING); }); QUnit.test("settings map merging", (assert) => { let settings_map = storage.getStore('settings_map'); // overwrite settings with test values settings_map.setItem('disabledSites', ['example.com']); settings_map.setItem('showCounter', true); // merge settings settings_map.merge({ disabledSites: ['www.nytimes.com'], showCounter: false, }); // verify assert.deepEqual( settings_map.getItem('disabledSites'), ['example.com', 'www.nytimes.com'], "disabled site lists are combined when merging settings" ); assert.ok(!settings_map.getItem('showCounter'), "other settings are overwritten"); }); // previously: // https://github.com/EFForg/privacybadger/pull/1911#issuecomment-379896911 QUnit.test("action map merge copies/breaks references", (assert) => { let data = { dnt: false, heuristicAction: '', nextUpdateTime: 100, userAction: 'user_block' }; actionMap.merge({[DOMAIN]: data}); assert.deepEqual( actionMap.getItem(DOMAIN), data, "test domain was imported"); // set a property on the original object data.userAction = "user_allow"; // this should not affect data in storage assert.equal(actionMap.getItem(DOMAIN).userAction, "user_block", "already imported data should be left alone " + "when modifying object used for import"); }); QUnit.test("action map merge only updates user action", (assert) => { actionMap.setItem(DOMAIN, {dnt: false, heuristicAction: '', nextUpdateTime: 100, userAction: ''}); assert.equal(actionMap.getItem(DOMAIN).nextUpdateTime, 100); let newValue = {dnt: true, heuristicAction: constants.BLOCK, nextUpdateTime: 99, userAction: constants.USER_BLOCK}; actionMap.merge({[DOMAIN]: newValue}); assert.equal(actionMap.getItem(DOMAIN).userAction, constants.USER_BLOCK, "userAction should be merged if it's set"); assert.equal(actionMap.getItem(DOMAIN).heuristicAction, '', 'heuristicAction should never be overwritten'); newValue.userAction = ''; actionMap.merge({[DOMAIN]: newValue}); assert.equal(actionMap.getItem(DOMAIN).userAction, constants.USER_BLOCK, 'blank userAction should not overwrite anything'); }); QUnit.test("action map merge creates new entry if necessary", (assert) => { assert.notOk(actionMap.hasItem('newsite.com')); let newValue = {dnt: false, heuristicAction: constants.BLOCK, nextUpdateTime: 100, userAction: ''}; actionMap.merge({'newsite.com': newValue}); assert.notOk(actionMap.hasItem('newsite.com'), 'action map entry should not be created for heuristicAction alone'); newValue.userAction = constants.USER_BLOCK; actionMap.merge({'newsite.com': newValue}); assert.ok(actionMap.hasItem('newsite.com'), 'action map entry should be created if userAction is set'); actionMap.deleteItem('newsite.com'); newValue.userAction = ''; newValue.dnt = true; actionMap.merge({'newsite.com': newValue}); assert.ok(actionMap.hasItem('newsite.com'), 'action map entry should be created if DNT is set'); }); QUnit.test("action map merge updates with latest DNT info", (assert) => { actionMap.setItem(DOMAIN, {dnt: false, heuristicAction: '', nextUpdateTime: 100, userAction: ''}); // DNT should not be merged if nextUpdateTime is earlier let newValue = {dnt: true, heuristicAction: '', nextUpdateTime: 99, userAction: ''}; actionMap.merge({[DOMAIN]: newValue}); assert.equal(actionMap.getItem(DOMAIN).nextUpdateTime, 100, 'nextUpdateTime should not be changed to an earlier time'); assert.notOk(actionMap.getItem(DOMAIN).dnt, 'DNT value should not be updated by out-of-date information'); // DNT should be merged if it's more up-to-date newValue.nextUpdateTime = 101; actionMap.merge({[DOMAIN]: newValue}); assert.equal(actionMap.getItem(DOMAIN).nextUpdateTime, 101, 'nextUpdateTime should be updated to later time'); assert.ok(actionMap.getItem(DOMAIN).dnt, 'DNT value should be updated with more recent information'); }); QUnit.test("action map merge handles missing nextUpdateTime", (assert) => { let newValue = { dnt: true, heuristicAction: '', userAction: '' }; assert.notOk(newValue.hasOwnProperty('nextUpdateTime'), "nextUpdateTime is indeed missing from the import"); // new DNT domain should be imported actionMap.merge({[DOMAIN]: newValue}); assert.deepEqual( actionMap.getItem(DOMAIN), Object.assign({ nextUpdateTime: 0 }, newValue), "test domain was imported and nextUpdateTime got initialized"); // existing DNT domain should be left alone // as we don't know how fresh the import is newValue.dnt = false; actionMap.merge({[DOMAIN]: newValue}); assert.ok(actionMap.getItem(DOMAIN).dnt, "existing data should be left alone " + "when unable to determine recency of new data"); // now set the timestamp and try again newValue.nextUpdateTime = 200; actionMap.merge({[DOMAIN]: newValue}); assert.notOk(actionMap.getItem(DOMAIN).dnt, "DNT got overriden now that new data seems fresher"); }); QUnit.test("action map merge handles missing userAction", (assert) => { let newValue = { heuristicAction: 'allow', dnt: true, nextUpdateTime: 100 }; // import and check that userAction got initialized actionMap.merge({[DOMAIN]: newValue}); assert.deepEqual( actionMap.getItem(DOMAIN), Object.assign({ userAction: '' }, newValue), "test domain was imported and userAction got initialized"); }); QUnit.test("action map merge handles missing dnt", (assert) => { let newValue = { heuristicAction: 'block', userAction: 'user_allow' }; // import and check that userAction got initialized actionMap.merge({[DOMAIN]: newValue}); assert.deepEqual( actionMap.getItem(DOMAIN), Object.assign({ dnt: false, nextUpdateTime: 0 }, newValue), "test domain was imported and DNT got initialized"); }); QUnit.test("action map merge handles subdomains correctly", (assert) => { actionMap.setItem('testsite.com', {dnt: false, heuristicAction: '', nextUpdateTime: 100, userAction: ''}); let newValue = {dnt: true, heuristicAction: '', nextUpdateTime: 100, userAction: ''}; actionMap.merge({'s1.testsite.com': newValue}); assert.ok(actionMap.hasItem('s1.testsite.com'), 'Subdomains should be merged if they honor DNT'); newValue.dnt = false; actionMap.merge({'s2.testsite.com': newValue}); assert.notOk(actionMap.hasItem('s2.testsite.com'), "Subdomains should not be merged if they don't honor DNT"); }); QUnit.test("snitch map merging", (assert) => { snitchMap.merge({[DOMAIN]: ['firstparty.org']}); assert.ok(snitchMap.getItem(DOMAIN).indexOf('firstparty.org') > -1); // Check to make sure existing and new domain are present snitchMap.merge({[DOMAIN]: ['firstparty2.org']}); assert.ok(snitchMap.getItem(DOMAIN).indexOf('firstparty.org') > -1); assert.ok(snitchMap.getItem(DOMAIN).indexOf('firstparty2.org') > -1); // Verify 'block' status is triggered once TRACKING_THRESHOLD is hit assert.equal(actionMap.getItem(DOMAIN).heuristicAction, "allow"); snitchMap.merge({[DOMAIN]: ["firstparty3.org"]}); assert.equal(actionMap.getItem(DOMAIN).heuristicAction, "block"); }); QUnit.test("blocking cascades", (assert) => { // mark domain for blocking storage.setupHeuristicAction(DOMAIN, constants.BLOCK); // check domain itself assert.equal( storage.getAction(DOMAIN), constants.BLOCK, "domain is marked for blocking directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.BLOCK, "domain is marked for blocking" ); // check that subdomain inherits blocking assert.equal( storage.getAction(SUBDOMAIN), constants.NO_TRACKING, "subdomain is not marked for blocking directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.BLOCK, "subdomain is marked for blocking (via parent domain)" ); // check that subsubdomain inherits blocking assert.equal( storage.getAction(SUBSUBDOMAIN), constants.NO_TRACKING, "subsubdomain is not marked for blocking directly" ); assert.equal( storage.getBestAction(SUBSUBDOMAIN), constants.BLOCK, "subsubdomain is marked for blocking (via grandparent domain)" ); }); QUnit.test("DNT does not cascade", (assert) => { storage.setupDNT(DOMAIN); // check domain itself assert.equal( storage.getAction(DOMAIN), constants.DNT, "domain is marked as DNT directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.DNT, "domain is marked as DNT" ); // check that subdomain does not inherit DNT assert.equal( storage.getAction(SUBDOMAIN), constants.NO_TRACKING, "subdomain is not marked as DNT directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.NO_TRACKING, "subdomain is not marked as DNT (via parent domain)" ); }); QUnit.test("DNT does not return as an action if user has chosen not to", (assert) => { let settings_map = storage.getStore('settings_map'); settings_map.setItem("checkForDNTPolicy", false); storage.setupDNT(DOMAIN); assert.equal( storage.getAction(DOMAIN), constants.NO_TRACKING, "domain is marked as DNT directly, but returns as NO_TRACKING because user has disabled DNT" ); assert.equal( storage.getBestAction(DOMAIN), constants.NO_TRACKING, "domain is marked as DNT, but returns as NO_TRACKING because user has disabled DNT" ); }); QUnit.test("blocking still cascades after domain declares DNT", (assert) => { storage.setupHeuristicAction(DOMAIN, constants.BLOCK); storage.setupDNT(DOMAIN); // check domain itself assert.equal( storage.getAction(DOMAIN, true), constants.BLOCK, "domain is marked for blocking directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.DNT, "domain is marked as DNT" ); // check that subdomain inherits blocking assert.equal( storage.getAction(SUBDOMAIN), constants.NO_TRACKING, "subdomain is not marked for blocking directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.BLOCK, "subdomain is marked for blocking (via parent domain)" ); }); QUnit.test("cascading doesn't work the other way", (assert) => { // mark subdomain for blocking storage.setupHeuristicAction(SUBDOMAIN, constants.BLOCK); // check subdomain itself assert.equal( storage.getAction(SUBDOMAIN), constants.BLOCK, "subdomain is marked for blocking directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.BLOCK, "subdomain is marked for blocking" ); // check that parent domain does not inherit blocking assert.equal( storage.getAction(DOMAIN), constants.NO_TRACKING, "domain is not marked for blocking directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.NO_TRACKING, "domain is not marked for blocking" ); }); QUnit.test("blocking overrules allowing", (assert) => { // mark domain for blocking storage.setupHeuristicAction(DOMAIN, constants.BLOCK); // mark subsubdomain as "allow" (not-yet-over-the-threshold tracker) storage.setupHeuristicAction(SUBSUBDOMAIN, constants.ALLOW); // check domain itself assert.equal( storage.getAction(DOMAIN), constants.BLOCK, "domain is marked for blocking directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.BLOCK, "domain is marked for blocking" ); // check that subsubdomain inherits blocking assert.equal( storage.getAction(SUBSUBDOMAIN), constants.ALLOW, "subdomain is marked as 'allow' directly" ); assert.equal( storage.getBestAction(SUBSUBDOMAIN), constants.BLOCK, "subsubdomain is marked for blocking (via grandparent domain)" ); }); QUnit.test("cookieblocking overrules blocking", (assert) => { // mark domain for cookieblocking storage.setupHeuristicAction(DOMAIN, constants.COOKIEBLOCK); // mark subdomain for blocking storage.setupHeuristicAction(SUBDOMAIN, constants.BLOCK); // check domain itself assert.equal( storage.getAction(DOMAIN), constants.COOKIEBLOCK, "domain is marked for cookieblocking directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.COOKIEBLOCK, "domain is marked for cookieblocking" ); // check that subdomain inherits cookieblocking assert.equal( storage.getAction(SUBDOMAIN), constants.BLOCK, "subdomain is marked for blocking directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.COOKIEBLOCK, "subdomain is marked for cookieblocking (via parent domain)" ); }); QUnit.test("user actions overrule everything else", (assert) => { storage.setupUserAction(DOMAIN, constants.USER_BLOCK); storage.setupHeuristicAction(SUBDOMAIN, constants.COOKIEBLOCK); storage.setupDNT(SUBSUBDOMAIN); // check domain itself assert.equal( storage.getAction(DOMAIN), constants.USER_BLOCK, "domain is marked as userblock directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.USER_BLOCK, "domain is marked as userblock" ); // check subdomain assert.equal( storage.getAction(SUBDOMAIN), constants.COOKIEBLOCK, "subdomain is marked for cookie blocking directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.USER_BLOCK, "subdomain is marked as userblock" ); // check subsubdomain assert.equal( storage.getAction(SUBSUBDOMAIN), constants.DNT, "subsubdomain is marked as DNT directly" ); assert.equal( storage.getBestAction(SUBSUBDOMAIN), constants.USER_BLOCK, "subsubdomain is marked as userblock" ); }); // all three user actions are equally important // but the one closest to the FQDN being checked should win QUnit.test("specificity of rules of equal priority", (assert) => { storage.setupUserAction(DOMAIN, constants.USER_BLOCK); storage.setupUserAction(SUBDOMAIN, constants.USER_ALLOW); storage.setupUserAction(SUBSUBDOMAIN, constants.USER_COOKIEBLOCK); // check domain itself assert.equal( storage.getAction(DOMAIN), constants.USER_BLOCK, "domain is marked as userblock directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.USER_BLOCK, "domain is marked as userblock" ); // check subdomain assert.equal( storage.getAction(SUBDOMAIN), constants.USER_ALLOW, "subdomain is marked as userallow directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.USER_ALLOW, "subdomain is marked as userallow" ); // check subsubdomain assert.equal( storage.getAction(SUBSUBDOMAIN), constants.USER_COOKIEBLOCK, "subsubdomain is marked as usercookieblock directly" ); assert.equal( storage.getBestAction(SUBSUBDOMAIN), constants.USER_COOKIEBLOCK, "subsubdomain is marked as usercookieblock" ); }); QUnit.test("unexpected heuristic actions are ignored", (assert) => { storage.setupHeuristicAction(DOMAIN, "foo"); storage.setupHeuristicAction(SUBDOMAIN, constants.ALLOW); storage.setupHeuristicAction(SUBSUBDOMAIN, "bar"); // check domain itself assert.equal( storage.getAction(DOMAIN), "foo", "domain is marked as 'foo' directly" ); assert.equal( storage.getBestAction(DOMAIN), constants.NO_TRACKING, "best action for domain is 'no tracking'" ); // check subdomain assert.equal( storage.getAction(SUBDOMAIN), constants.ALLOW, "subdomain is marked as 'allow' directly" ); assert.equal( storage.getBestAction(SUBDOMAIN), constants.ALLOW, "best action for subdomain is 'allow'" ); // check subsubdomain assert.equal( storage.getAction(SUBSUBDOMAIN), "bar", "subsubdomain is marked as 'bar' directly" ); assert.equal( storage.getBestAction(SUBSUBDOMAIN), constants.ALLOW, "best action for subsubdomain is 'allow'" ); }); function checkCookieblocking(assert) { assert.equal( storage.getBestAction(SUBDOMAIN), constants.NO_TRACKING, "subdomain is not yet (cookie)blocked" ); assert.ok( storage.wouldGetCookieblocked(SUBDOMAIN), "subdomain would get cookieblocked if blocked" ); // block the subdomain badger.heuristicBlocking.blocklistOrigin(DOMAIN, SUBDOMAIN); assert.equal( storage.getBestAction(SUBDOMAIN), constants.COOKIEBLOCK, "subdomain is cookieblocked" ); assert.ok( storage.wouldGetCookieblocked(SUBDOMAIN), "subdomain would get/is cookieblocked" ); } QUnit.test("checking cookieblock potential for yellowlisted subdomain", (assert) => { assert.notOk( storage.wouldGetCookieblocked(SUBDOMAIN), "subdomain wouldn't get cookieblocked if blocked" ); // add subdomain to yellowlist storage.getStore('cookieblock_list').setItem(SUBDOMAIN, true); checkCookieblocking(assert); }); QUnit.test("checking cookieblock potential for subdomain with yellowlisted base domain", (assert) => { assert.notOk( storage.wouldGetCookieblocked(SUBDOMAIN), "subdomain wouldn't get cookieblocked if blocked" ); // add base domain to yellowlist storage.getStore('cookieblock_list').setItem(DOMAIN, true); checkCookieblocking(assert); }); }());