diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000000000000000000000000000000000000..6c4c4cad56f2f49a9f86908590a723651308d48c --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,56 @@ +module.exports = function(config) { + config.set({ + frameworks: ["mocha", "chai", "sinon-chai", "sinon-chrome"], + + files: [ + "modules/internal/*.js", + "core/constants.js", + "core/files.js", + "core/resources.js", + "core/mappings.js", + "core/shorthands.js", + "core/main.js", + "core/messenger.js", + "core/request-sanitizer.js", + "core/state-manager.js", + "core/interceptor.js", + "core/request-analyzer.js", + + "test/unittests/**/*.test.js" + ], + reporters: ["progress", "html", "coverage", "spec"], + preprocessors: { + "modules/internal/*.js": ["coverage"], + "core/**/*.js": ["coverage"] + }, + + coverageReporter: { reporters: [{ type: "lcov" }] }, + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + browsers: ["ChromeHeadless", "Chrome"], + autoWatch: false, + concurrency: Infinity, + specReporter: { + maxLogLines: 3, + suppressErrorSummary: true, + suppressFailed: false, + suppressPassed: false, + suppressSkipped: true, + showSpecTiming: true, + failFast: false + }, + + htmlReporter: { + outputDir: "karma_coverage", + templatePath: null, + focusOnFailures: true, + namedFiles: false, + pageTitle: null, + urlFriendlyName: false, + reportName: "report-summary", + preserveDescribeNesting: false, + foldAll: false + } + }); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..91f025545633a89ac7f40fa2549dda8929885ea0 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "Decentraleyes", + "version": "1.0.0", + "description": "A web browser extension that emulates Content Delivery Networks to improve your online privacy. It intercepts traffic, finds supported resources locally, and injects them into the environment.", + "author": "Thomas Rientjes", + "license": "MPL-2.0", + "devDependencies": { + "chai": "^4.1.2", + "codecov": "3.0.4", + "karma": "^2.0.5", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-coverage": "^1.1.2", + "karma-html-reporter": "^0.2.7", + "karma-mocha": "^1.3.0", + "karma-sinon-chai": "2.0.2", + "karma-sinon-chrome": "^0.2.0", + "karma-sinon-stub-promise": "^1.0.0", + "karma-spec-reporter": "0.0.32", + "mocha": "^5.2.0", + "sinon": "^6.1.4", + "sinon-chai": "^3.2.0", + "web-ext": "^2.8.0" + }, + "scripts": { + "testUnits": "karma start --single-run --browsers ChromeHeadless karma.conf.js", + "testUnitsWatcher": "karma start --auto-watch --browsers ChromeHeadless karma.conf.js", + "testUnitsChromeWatcher": "karma start --auto-watch --browsers Chrome karma.conf.js" + } +} diff --git a/test/unittests/core/interceptor.test.js b/test/unittests/core/interceptor.test.js new file mode 100644 index 0000000000000000000000000000000000000000..977b036d5f69d6d0b1b4d5391b669e3d8d3c092c --- /dev/null +++ b/test/unittests/core/interceptor.test.js @@ -0,0 +1,147 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Interceptor", function() { + var requestDetails, tabid, tab; + beforeEach(function() { + chrome.flush(); + requestDetails = { type: "", url: "", requestId: "" }; + tabid = 1; + tab = {}; + }); + + it("should return redirectURL for library when handleRequest is called", function() { + // tab.url = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"; + tab.url = + "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"; + var stub = sinon + .stub(requestAnalyzer, "isValidCandidate") + .returns(true); + var stub1 = sinon.stub(requestAnalyzer, "getLocalTarget").returns({ + source: "cdnjs.cloudflare.com", + version: "1.7", + path: "resources/angularjs/1.4.8/angular.min.js.dec" + }); + fileGuard = { secret: "somesecret" }; + chrome.extension.getURL + .withArgs( + "resources/angularjs/1.4.8/angular.min.js.dec" + "somesecret" + ) + .returns( + "chrome:extension://asdsdasd/resources/angularjs/1.4.8/angular.min.js.dec" + + "somesecret" + ); + var res = interceptor.handleRequest(requestDetails, tabid, tab); + stub.restore(); + stub1.restore(); + expect(res).to.be.eql({ + redirectUrl: + "chrome:extension://asdsdasd/resources/angularjs/1.4.8/angular.min.js.decsomesecret" + }); + }); + + it("should cancel unknown cdn request if setting to block unknown is enabled", function() { + // tab.url = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"; + tab.url = + "https://cdnjs.somebadCDN.com/ajax/libs/angular.js/1.4.8/angular.js"; + var stub = sinon + .stub(requestAnalyzer, "isValidCandidate") + .returns(true); + var stub1 = sinon + .stub(requestAnalyzer, "getLocalTarget") + .returns(false); + fileGuard = { secret: "somesecret" }; + interceptor.blockMissing = true; + + var res = interceptor.handleRequest(requestDetails, tabid, tab); + stub.restore(); + stub1.restore(); + expect(res).to.be.eql({ cancel: true }); + }); + + it("should not cancel and let through whitelisted domains or NON-GET requests by returning cancel:false", function() { + // tab.url = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"; + tab.url = + "https://cdnjs.somebadCDN.com/ajax/libs/angular.js/1.4.8/angular.js"; + var stub = sinon + .stub(requestAnalyzer, "isValidCandidate") + .returns(false); + // this Stub emulated isValidCandidate to return true or false. False will be sent for whitelisted and non-get calls + + fileGuard = { secret: "somesecret" }; + interceptor.blockMissing = false; + + var res = interceptor.handleRequest(requestDetails, tabid, tab); + stub.restore(); + expect(res).to.be.eql({ cancel: false }); + }); + + it("should handle tainted domains", function() { + // tab.url = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"; + tab.url = "http://udacity.com/ajax/libs/angular.js/1.4.8/angular.js"; + requestDetails.url = + "http://udacity.com/ajax/libs/angular.js/1.4.8/angular"; + var stub = sinon + .stub(requestAnalyzer, "isValidCandidate") + .returns(true); + // this Stub emulates isValidCandidate to return true or false. False will be sent for whitelisted and non-get calls + + fileGuard = { secret: "somesecret" }; + interceptor.blockMissing = false; + + var res = interceptor.handleRequest(requestDetails, tabid, tab); + stub.restore(); + // code turns http to https.. Need to check with Thomas on why this is done. + expect(res.redirectUrl).to.be.equal( + "https://udacity.com/ajax/libs/angular.js/1.4.8/angular" + ); + interceptor.blockMissing = false; + + tab.url = "https://udacity.com/ajax/libs/angular.js/1.4.8/angular.js"; + requestDetails.url = + "https://udacity.com/ajax/libs/angular.js/1.4.8/angular"; + stub = sinon.stub(requestAnalyzer, "isValidCandidate").returns(true); + // this Stub emulated isValidCandidate to return true or false. False will be sent for whitelisted and non-get calls + + fileGuard = { secret: "somesecret" }; + + var res = interceptor.handleRequest(requestDetails, tabid, tab); + stub.restore(); + // code turns https to http and lets proceed. Need to check with Thomas on why this is done. + expect(res).to.be.eql({ cancel: false }); + }); + + it("should handle XHR request type and null domain", function() { + tab.url = + "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"; + requestDetails.url = + "https://udacity.com/ajax/libs/angular.js/1.4.8/angular"; + + var stub = sinon + .stub(requestAnalyzer, "isValidCandidate") + .returns(true); + var stub1 = sinon.stub(requestAnalyzer, "getLocalTarget").returns({ + source: "cdnjs.cloudflare.com", + version: "1.7", + path: "resources/angularjs/1.4.8/angular.min.js.dec" + }); + requestDetails.type = "xmlhttprequest"; + var res = interceptor.handleRequest(requestDetails, tabid, tab); + }); + + it("storage changed listener function should update as needed", function() { + changes = { + xhrTestDomain: { newValue: "value", oldValue: "old" } + }; + interceptor._handleStorageChanged(changes); + changes = { + blockMissing: { newValue: "value", oldValue: "old" } + }; + interceptor._handleStorageChanged(changes); + }); +}); diff --git a/test/unittests/core/main.test.js b/test/unittests/core/main.test.js new file mode 100644 index 0000000000000000000000000000000000000000..71760ddfe1cbfbd3c03c92f3b823e74a06b27d5c --- /dev/null +++ b/test/unittests/core/main.test.js @@ -0,0 +1,219 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Main.js", function() { + describe("Options initializations", function() { + beforeEach(function() { + chrome.flush(); + var Setting = { + AMOUNT_INJECTED: "amountInjected", + BLOCK_MISSING: "blockMissing", + DISABLE_PREFETCH: "disablePrefetch", + SHOW_ICON_BADGE: "showIconBadge", + SHOW_RELEASE_NOTES: "showReleaseNotes", + STRIP_METADATA: "stripMetadata", + WHITELISTED_DOMAINS: "whitelistedDomains", + XHR_TEST_DOMAIN: "xhrTestDomain" + }; + }); + + it("should get and set values from storage", function() { + var options = { + [Setting.XHR_TEST_DOMAIN]: "https://example.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: true, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + chrome.storage.local.get.yields(options); + main._initializeOptions(); + + assert.ok(chrome.storage.local.set.calledOnce); + assert.ok(chrome.storage.local.set.calledWith(options)); + }); + + it("should disable networkPrediction when disabled via setting", function() { + var options = { + [Setting.XHR_TEST_DOMAIN]: "https://example.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: true, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + chrome.storage.local.get.yields(options); + main._initializeOptions(); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.calledOnce + ); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.calledWith({ + value: false + }) + ); + }); + + it("should not disable networkPrediction when enabled via setting", function() { + var options = { + [Setting.XHR_TEST_DOMAIN]: "https://example.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: false, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + chrome.storage.local.get.yields(options); + main._initializeOptions(); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.notCalled + ); + }); + + it("when options are null, set default options", function() { + chrome.storage.local.get.yields(null); + let defaults = { + [Setting.XHR_TEST_DOMAIN]: "decentraleyes.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: true, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + main._initializeOptions(); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.calledWith({ + value: false + }) + ); + assert.ok(chrome.storage.local.set.calledOnce); + assert.ok(chrome.storage.local.set.calledWith(defaults)); + }); + }); + + describe("Release notes", function() { + beforeEach(function() { + chrome.flush(); + chrome.runtime.OnInstalledReason = { + INSTALL: "INSTALL", + UPDATE: "UPDATE" + }; + }); + it("should not show release notes for minor release install or update", function() { + var details = { + temporary: false, + reason: "INSTALL", + previousVersion: "2.0.0" + }; + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + + details.reason = "UPDATE"; + + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + }); + + it("should show release notes for major release install or update", function() { + var details = { + temporary: false, + reason: "INSTALL", + previousVersion: "3.0.0" + }; + + chrome.storage.local.get.yields({ showReleaseNotes: true }); + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok( + chrome.tabs.create.calledWith({ + url: undefined, // for unit test no need to mock response for getURL + active: false + }) + ); + chrome.flush(); + + details.reason = "UPDATE"; + chrome.storage.local.get.yields({ showReleaseNotes: true }); + + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok( + chrome.tabs.create.calledWith({ + url: undefined, // for unit test no need to mock response for getURL + active: false + }) + ); + // assert.ok(chrome.tabs.create.notCalled); + }); + + it("should not show release notes for major release install or update when setting is disabled", function() { + var details = { + temporary: false, + reason: "INSTALL", + previousVersion: "3.0.0" + }; + + chrome.storage.local.get.yields({ showReleaseNotes: false }); + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok(chrome.tabs.create.notCalled); + chrome.flush(); + + details.reason = "UPDATE"; + chrome.storage.local.get.yields({ showReleaseNotes: false }); + + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok(chrome.tabs.create.notCalled); + }); + + it("should do nothing if details.reason is empty or invalid", function() { + var details = { + temporary: false, + reason: "", + previousVersion: "2.0.0" + }; + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + }); + + it("should not show release notes if addon running in temporary mode like web-ext run", function() { + var details = { + temporary: true, + reason: "UPDATE", + previousVersion: "1.0.0" + }; + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + }); + }); +}); diff --git a/test/unittests/core/messenger.test.js b/test/unittests/core/messenger.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5de17044a4a723d40359a6f3a781281f0220b4e3 --- /dev/null +++ b/test/unittests/core/messenger.test.js @@ -0,0 +1,103 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Messenger.js tests", function() { + var sender = {}; + var sendResponse; + var message = {}; + beforeEach(function() { + chrome.flush(); + var MessageResponse = { + ASYNCHRONOUS: true, + SYNCHRONOUS: false + }; + + //stateManager.addDomainToWhitelist = function() {}; + let a = "gnu.org"; + requestAnalyzer = { + whitelistedDomains: { + "fsf.org": true, + "gnu.org": true + } + }; + message.topic = ""; + message.value = 0; + + stateManager = {}; + stateManager.tabs = [{ injections: 4 }]; + stateManager.removeDomainFromWhitelist = function(domain) {}; + stateManager.addDomainToWhitelist = function(domain) {}; + sender = {}; + sendResponse = sinon.spy(); + }); + + it("should return injections and should not keep listener open i.e return false", function() { + message.topic = "tab:fetch-injections"; + + var response = messenger._handleMessageReceived( + message, + sender, + sendResponse + ); + + sinon.assert.calledOnce(sendResponse); + sinon.assert.calledWith(sendResponse, { value: 4 }); + expect(response).to.be.eql(false); + }); + + it("should return if domain whitelisted and not keep listener open i.e return false", function() { + message.topic = "domain:fetch-is-whitelisted"; + message.value = "gnu.org"; + + var response = messenger._handleMessageReceived( + message, + sender, + sendResponse + ); + + sinon.assert.calledOnce(sendResponse); + sinon.assert.calledWith(sendResponse, { value: true }); + expect(response).to.be.eql(false); + }); + + it("should add domain to whitelist by calling statemanager fn and keep listener open i.e return true ", function() { + message.topic = "whitelist:add-domain"; + message.value = "gnu.org"; + + var stub = sinon.stub(stateManager, "addDomainToWhitelist").returns({ + then: function() {} + }); + + var response = messenger._handleMessageReceived( + message, + sender, + sendResponse + ); + sinon.assert.calledWith(stub, "gnu.org"); + expect(response).to.be.eql(true); + }); + + it("should remove domain to whitelist by calling statemanager fn and keep listener open i.e return true", function() { + message.topic = "whitelist:remove-domain"; + message.value = "gnu.org"; + + var stub = sinon + .stub(stateManager, "removeDomainFromWhitelist") + .returns({ + then: function() {} + }); + + var response = messenger._handleMessageReceived( + message, + sender, + sendResponse + ); + sinon.assert.calledWith(stub, "gnu.org"); + expect(response).to.be.eql(true); + }); +}); diff --git a/test/unittests/core/request-sanitizer.test.js b/test/unittests/core/request-sanitizer.test.js new file mode 100644 index 0000000000000000000000000000000000000000..63bd3c9cf8a29ef46283fd16d406377d61655526 --- /dev/null +++ b/test/unittests/core/request-sanitizer.test.js @@ -0,0 +1,84 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Request Sanitizer", function() { + beforeEach(function() { + chrome.flush(); + }); + it("should attach webrequest listener with Blocking response", function() { + requestSanitizer.enable(); + assert.ok(chrome.webRequest.onBeforeSendHeaders.addListener.calledOnce); + assert.ok( + chrome.webRequest.onBeforeSendHeaders.addListener.calledWith( + requestSanitizer._stripMetadata, + { + urls: stateManager.validHosts + }, + [WebRequest.BLOCKING, WebRequest.HEADERS] + ) + ); + }); + + it("should remove webrequest listener", function() { + requestSanitizer.disable(); + assert.ok( + chrome.webRequest.onBeforeSendHeaders.removeListener.calledOnce + ); + assert.ok( + chrome.webRequest.onBeforeSendHeaders.removeListener.calledWith( + requestSanitizer._stripMetadata, + { + urls: stateManager.validHosts + }, + [WebRequest.BLOCKING, WebRequest.HEADERS] + ) + ); + }); + + it("strip metadata", function() { + requestHeaders = {}; + var a = [ + { name: "Host", value: "web.archive.org" }, + { + name: "User-Agent", + value: + "Save Page Request from WaybackEverywhere Browser Extension" + }, + { name: "Accept", value: "*/*" }, + { name: "Accept-Language", value: "en-GB,en;q=0.5" }, + { name: "Accept-Encoding", value: "gzip, deflate, br" }, + { + name: "Referer", + value: "https://web.archive.org/save/http://www.gnu.org/" + }, + { + name: "Cookie", + value: "have some cookies" + }, + { name: "Connection", value: "keep-alive" }, + { name: "Origin", value: "some origin" } + ]; + requestHeaders.requestHeaders = a; + + var val = requestSanitizer._stripMetadata(requestHeaders); + var stripped = [ + { name: "Host", value: "web.archive.org" }, + { + name: "User-Agent", + value: + "Save Page Request from WaybackEverywhere Browser Extension" + }, + { name: "Accept", value: "*/*" }, + { name: "Accept-Language", value: "en-GB,en;q=0.5" }, + { name: "Accept-Encoding", value: "gzip, deflate, br" }, + + { name: "Connection", value: "keep-alive" } + ]; + expect(val).to.be.eql({ requestHeaders: stripped }); + }); +}); diff --git a/test/unittests/internal/helpers.test.js b/test/unittests/internal/helpers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a122cabe94864e9c149648e3acca5f955968d44c --- /dev/null +++ b/test/unittests/internal/helpers.test.js @@ -0,0 +1,228 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Helpers", function() { + describe("domain name handlers", function() { + beforeEach(function() { + chrome.flush(); + var Address = { + ANY: "*://*/*", + ANY_PATH: "/*", + ANY_PROTOCOL: "*://", + CHROME: "chrome:", + CHROME_EXTENSION: "chrome-extension:", + DECENTRALEYES: "decentraleyes.org", + EXAMPLE: "example.org", + HTTP: "http:", + HTTPS: "https:", + RESOURCE_PATH: "/resources", + ROOT_PATH: "/", + WWW_PREFIX: "www." + }; + }); + + it("should return normalized domain", function() { + let domain = helpers.normalizeDomain("https://GnU.oRG"); + expect(domain).to.be.eql("https://gnu.org"); + domain = helpers.normalizeDomain(" https://gnu.oRG "); + expect(domain).to.be.eql("https://gnu.org"); + domain = helpers.normalizeDomain("www.gnu.org"); + expect(domain).to.be.eql("gnu.org"); + }); + + it("should extract domain from URL", function() { + let url = "https://example.org/something/some/path"; + let domain = helpers.extractDomainFromUrl(url, true); + expect(domain).to.be.eql("example.org"); + }); + + it("should handle chrome pages", function() { + let url = "chrome://newtab"; + let domain = helpers.extractDomainFromUrl(url, false); + expect(domain).to.be.eql(null); + }); + + it("should handle invalid urls to return null", function() { + let url = "invalid"; + let domain = helpers.extractDomainFromUrl(url, false); + expect(domain).to.be.eql(null); + }); + + it("should handle empty evaluated url to return null", function() { + let url = "invalid://something"; + let domain = helpers.extractDomainFromUrl(url, false); + expect(domain).to.be.eql(null); + }); + }); + + describe("Formatters", function() { + beforeEach(function() { + chrome.flush(); + }); + it("should return number as string", function() { + let number = helpers.formatNumber(2); + expect(number).to.be.a("string"); + number = helpers.formatNumber("2"); + expect(number).to.be.undefined; + }); + it("should return version formatted", function() { + let version = helpers.formatVersion("betaVersion"); + expect(version).to.be.eql("BETA"); + version = helpers.formatVersion("1.2.1"); + expect(version).to.be.eql("1.2.1"); + }); + }); + + describe("File name handler", function() { + it("should return correct file names from a path", function() { + var filename = helpers.extractFilenameFromPath( + "file://somepath/a/b/c/settings.json" + ); + expect(filename).to.be.eql("settings.json"); + }); + }); + + describe("Content insertion", function() { + it("should return reverse script direction for selected langauges", function() { + var dir = helpers.determineScriptDirection("ar"); + expect(dir).to.be.eql("rtl"); + }); + it("should return left to right script direction for other langauges", function() { + var dir = helpers.determineScriptDirection("en"); + expect(dir).to.be.eql("ltr"); + }); + }); + + describe("Language support", function() { + it("should return true is in language codeword in array", function() { + var check = helpers.languageIsFullySupported("en"); + expect(check).to.be.eql(true); + }); + it("should return false is not in language codeword in array", function() { + var check = helpers.languageIsFullySupported("ta"); + expect(check).to.be.eql(false); + }); + + // TODO it("add more here ") + }); + + describe("GenerateRandomHexString generator", function() { + it("should use crypto function", function() { + var length = 8; + var val = new Uint8Array(length); + val = crypto.getRandomValues(val); + var spy = sinon.stub(crypto, "getRandomValues").returns(val); + var returned = helpers.generateRandomHexString(); + sinon.assert.calledOnce(spy); + }); + }); + + describe("cdn and resource names", function() { + var list = [ + { name: "angular.min.js.dec", value: "AngularJS" }, + { name: "backbone-min.js.dec", value: "Backbone.js" }, + { name: "dojo.js.dec", value: "Dojo" }, + { name: "ember.min.js.dec", value: "Ember.js" }, + { name: "ext-core.js.dec", value: "Ext Core" }, + { name: "jquery.min.js.dec", value: "jQuery" }, + { name: "jquery-ui.min.js.dec", value: "jQuery UI" }, + { name: "modernizr.min.js.dec", value: "Modernizr" }, + { name: "mootools-yui-compressed.js.dec", value: "MooTools" }, + { name: "prototype.js.dec", value: "Prototype" }, + { name: "scriptaculous.js.dec", value: "Scriptaculous" }, + { name: "swfobject.js.dec", value: "SWFObject" }, + { name: "underscore-min.js.dec", value: "Underscore.js" }, + { name: "webfont.js.dec", value: "Web Font Loader" } + ]; + it("should return correct resource name", function() { + for (res in list) { + var check = helpers.determineResourceName(list[res].name); + expect(check).to.be.eql(list[res].value); + } + }); + + it("should return unknown if resouce is unknown to us", function() { + var check = helpers.determineResourceName("somenewlib.js.dec"); + expect(check).to.be.eql("Unknown"); + }); + + var list2 = [ + { + name: "ajax.googleapis.com", + value: "Google Hosted Libraries" + }, + { + name: "ajax.aspnetcdn.com", + value: "Microsoft Ajax CDN" + }, + { + name: "ajax.microsoft.com", + value: "Microsoft Ajax CDN [Deprecated]" + }, + { + name: "cdnjs.cloudflare.com", + value: "CDNJS (Cloudflare)" + }, + { + name: "code.jquery.com", + value: "jQuery CDN (MaxCDN)" + }, + { + name: "cdn.jsdelivr.net", + value: "jsDelivr (MaxCDN)" + }, + { + name: "yastatic.net", + value: "Yandex CDN" + }, + { + name: "yandex.st", + value: "Yandex CDN [Deprecated]" + }, + { + name: "apps.bdimg.com", + value: "Baidu CDN" + }, + { + name: "libs.baidu.com", + value: "Baidu CDN [Deprecated]" + }, + { + name: "lib.sinaapp.com", + value: "Sina Public Resources" + }, + { + name: "upcdn.b0.upaiyun.com", + value: "UpYun Library" + }, + { + name: "cdn.bootcss.com", + value: "BootCDN" + }, + { + name: "sdn.geekzu.org", + value: "Geekzu Public Service [Mirror]" + }, + { + name: "ajax.proxy.ustclug.org", + value: "USTC Linux User Group [Mirror]" + } + ]; + it("should return correct cdn name", function() { + for (res in list2) { + var check = helpers.determineCdnName(list2[res].name); + expect(check).to.be.eql(list2[res].value); + } + }); + + it("should return unknown if cdn unknown to us", function() { + var check = helpers.determineCdnName("some new bad cdn"); + expect(check).to.be.eql("Unknown"); + }); + }); +}); diff --git a/test/unittests/internal/wrappers.test.js b/test/unittests/internal/wrappers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..60837f72581b5afcfc9f6cece0abf9b9709e2200 --- /dev/null +++ b/test/unittests/internal/wrappers.test.js @@ -0,0 +1,45 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Wrappers", function() { + describe("Wrappers for Browser Action API calls", function() { + const API = chrome.browserAction; + beforeEach(function() { + chrome.flush(); + }); + + it("should set badge background color correctly", function() { + var details = { + color: [74, 130, 108, 255] + }; + wrappers.setBadgeBackgroundColor(details); + assert.ok(API.setBadgeBackgroundColor.calledOnce); + assert.ok(API.setBadgeBackgroundColor.calledWith(details)); + }); + + it("should set Badge Text correctly", function() { + var details = { + tabId: 1, + text: "20" + }; + wrappers.setBadgeText(details); + assert.ok(API.setBadgeText.calledOnce); + assert.ok(API.setBadgeText.calledWith(details)); + }); + + it("should set badge icon correctly", function() { + var details = { + path: "path/action/icon16-disabled.png", + tabId: 3 + }; + wrappers.setIcon(details); + assert.ok(API.setIcon.calledOnce); + assert.ok(API.setIcon.calledWith(details)); + }); + }); +}); diff --git a/test/unittests/main.test.js b/test/unittests/main.test.js new file mode 100644 index 0000000000000000000000000000000000000000..71760ddfe1cbfbd3c03c92f3b823e74a06b27d5c --- /dev/null +++ b/test/unittests/main.test.js @@ -0,0 +1,219 @@ +/* + * Belongs to Decentraleyes. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +describe("Main.js", function() { + describe("Options initializations", function() { + beforeEach(function() { + chrome.flush(); + var Setting = { + AMOUNT_INJECTED: "amountInjected", + BLOCK_MISSING: "blockMissing", + DISABLE_PREFETCH: "disablePrefetch", + SHOW_ICON_BADGE: "showIconBadge", + SHOW_RELEASE_NOTES: "showReleaseNotes", + STRIP_METADATA: "stripMetadata", + WHITELISTED_DOMAINS: "whitelistedDomains", + XHR_TEST_DOMAIN: "xhrTestDomain" + }; + }); + + it("should get and set values from storage", function() { + var options = { + [Setting.XHR_TEST_DOMAIN]: "https://example.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: true, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + chrome.storage.local.get.yields(options); + main._initializeOptions(); + + assert.ok(chrome.storage.local.set.calledOnce); + assert.ok(chrome.storage.local.set.calledWith(options)); + }); + + it("should disable networkPrediction when disabled via setting", function() { + var options = { + [Setting.XHR_TEST_DOMAIN]: "https://example.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: true, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + chrome.storage.local.get.yields(options); + main._initializeOptions(); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.calledOnce + ); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.calledWith({ + value: false + }) + ); + }); + + it("should not disable networkPrediction when enabled via setting", function() { + var options = { + [Setting.XHR_TEST_DOMAIN]: "https://example.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: false, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + chrome.storage.local.get.yields(options); + main._initializeOptions(); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.notCalled + ); + }); + + it("when options are null, set default options", function() { + chrome.storage.local.get.yields(null); + let defaults = { + [Setting.XHR_TEST_DOMAIN]: "decentraleyes.org", + [Setting.SHOW_ICON_BADGE]: true, + [Setting.BLOCK_MISSING]: false, + [Setting.DISABLE_PREFETCH]: true, + [Setting.STRIP_METADATA]: true, + [Setting.WHITELISTED_DOMAINS]: {} + }; + main._initializeOptions(); + assert.ok( + chrome.privacy.network.networkPredictionEnabled.set.calledWith({ + value: false + }) + ); + assert.ok(chrome.storage.local.set.calledOnce); + assert.ok(chrome.storage.local.set.calledWith(defaults)); + }); + }); + + describe("Release notes", function() { + beforeEach(function() { + chrome.flush(); + chrome.runtime.OnInstalledReason = { + INSTALL: "INSTALL", + UPDATE: "UPDATE" + }; + }); + it("should not show release notes for minor release install or update", function() { + var details = { + temporary: false, + reason: "INSTALL", + previousVersion: "2.0.0" + }; + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + + details.reason = "UPDATE"; + + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + }); + + it("should show release notes for major release install or update", function() { + var details = { + temporary: false, + reason: "INSTALL", + previousVersion: "3.0.0" + }; + + chrome.storage.local.get.yields({ showReleaseNotes: true }); + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok( + chrome.tabs.create.calledWith({ + url: undefined, // for unit test no need to mock response for getURL + active: false + }) + ); + chrome.flush(); + + details.reason = "UPDATE"; + chrome.storage.local.get.yields({ showReleaseNotes: true }); + + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok( + chrome.tabs.create.calledWith({ + url: undefined, // for unit test no need to mock response for getURL + active: false + }) + ); + // assert.ok(chrome.tabs.create.notCalled); + }); + + it("should not show release notes for major release install or update when setting is disabled", function() { + var details = { + temporary: false, + reason: "INSTALL", + previousVersion: "3.0.0" + }; + + chrome.storage.local.get.yields({ showReleaseNotes: false }); + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok(chrome.tabs.create.notCalled); + chrome.flush(); + + details.reason = "UPDATE"; + chrome.storage.local.get.yields({ showReleaseNotes: false }); + + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.calledOnce); + assert.ok( + chrome.storage.local.get.calledWith({ + [Setting.SHOW_RELEASE_NOTES]: true + }) + ); + assert.ok(chrome.tabs.create.notCalled); + }); + + it("should do nothing if details.reason is empty or invalid", function() { + var details = { + temporary: false, + reason: "", + previousVersion: "2.0.0" + }; + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + }); + + it("should not show release notes if addon running in temporary mode like web-ext run", function() { + var details = { + temporary: true, + reason: "UPDATE", + previousVersion: "1.0.0" + }; + main._showReleaseNotes(details); + assert.ok(chrome.storage.local.get.notCalled); + assert.ok(chrome.tabs.create.notCalled); + }); + }); +});