| Index: test/synchronizer.js | 
| =================================================================== | 
| --- a/test/synchronizer.js | 
| +++ b/test/synchronizer.js | 
| @@ -12,527 +12,539 @@ | 
| * GNU General Public License for more details. | 
| * | 
| * You should have received a copy of the GNU General Public License | 
| * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. | 
| */ | 
| "use strict"; | 
| +const assert = require("assert"); | 
| let { | 
| - createSandbox, setupTimerAndFetch, setupRandomResult, unexpectedError, | 
| + createSandbox, setupTimerAndFetch, setupRandomResult, | 
| MILLIS_IN_SECOND, MILLIS_IN_HOUR | 
| } = require("./_common"); | 
| let filterStorage = null; | 
| let Prefs = null; | 
| let Subscription = null; | 
| -exports.setUp = function(callback) | 
| -{ | 
| - let globals = Object.assign({}, setupTimerAndFetch.call(this), | 
| - setupRandomResult.call(this)); | 
| - | 
| - let sandboxedRequire = createSandbox({globals}); | 
| - ( | 
| - {filterStorage} = sandboxedRequire("../lib/filterStorage"), | 
| - {Prefs} = sandboxedRequire("./stub-modules/prefs"), | 
| - {Subscription} = sandboxedRequire("../lib/subscriptionClasses"), | 
| - sandboxedRequire("../lib/synchronizer") | 
| - ); | 
| - | 
| - callback(); | 
| -}; | 
| - | 
| -function resetSubscription(subscription) | 
| +describe("Synchronizer", () => | 
| { | 
| - filterStorage.updateSubscriptionFilters(subscription, []); | 
| - subscription.lastCheck = subscription.lastDownload = | 
| - subscription.version = subscription.lastSuccess = | 
| - subscription.expires = subscription.softExpiration = 0; | 
| - subscription.title = ""; | 
| - subscription.homepage = null; | 
| - subscription.errors = 0; | 
| - subscription.downloadStatus = null; | 
| - subscription.requiredVersion = null; | 
| -} | 
| + let runner = {}; | 
| + | 
| + beforeEach(() => | 
| + { | 
| + runner = {}; | 
| + let globals = Object.assign({}, setupTimerAndFetch.call(runner), | 
| + setupRandomResult.call(runner)); | 
| -let initialDelay = 1 / 60; | 
| - | 
| -exports.testOneSubscriptionDownloads = function(test) | 
| -{ | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - let requests = []; | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - requests.push([this.getTimeOffset(), metadata.method, metadata.path]); | 
| - return [200, "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"]; | 
| + let sandboxedRequire = createSandbox({globals}); | 
| + ( | 
| + {filterStorage} = sandboxedRequire("../lib/filterStorage"), | 
| + {Prefs} = sandboxedRequire("./stub-modules/prefs"), | 
| + {Subscription} = sandboxedRequire("../lib/subscriptionClasses"), | 
| + sandboxedRequire("../lib/synchronizer") | 
| + ); | 
| }); | 
| - this.runScheduledTasks(50).then(() => | 
| + function resetSubscription(subscription) | 
| { | 
| - test.deepEqual(requests, [ | 
| - [0 + initialDelay, "GET", "/subscription"], | 
| - [24 + initialDelay, "GET", "/subscription"], | 
| - [48 + initialDelay, "GET", "/subscription"] | 
| - ], "Requests after 50 hours"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| + filterStorage.updateSubscriptionFilters(subscription, []); | 
| + subscription.lastCheck = subscription.lastDownload = | 
| + subscription.version = subscription.lastSuccess = | 
| + subscription.expires = subscription.softExpiration = 0; | 
| + subscription.title = ""; | 
| + subscription.homepage = null; | 
| + subscription.errors = 0; | 
| + subscription.downloadStatus = null; | 
| + subscription.requiredVersion = null; | 
| + } | 
| + | 
| + let initialDelay = 1 / 60; | 
| + | 
| + describe("Downloads", () => | 
| + { | 
| + it("One Subscription", () => | 
| + { | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + filterStorage.addSubscription(subscription); | 
| -exports.testTwoSubscriptionsDownloads = function(test) | 
| -{ | 
| - let subscription1 = Subscription.fromURL("http://example.com/subscription1"); | 
| - filterStorage.addSubscription(subscription1); | 
| + let requests = []; | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + requests.push([runner.getTimeOffset(), metadata.method, metadata.path]); | 
| + return [200, "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"]; | 
| + }); | 
| + | 
| + return runner.runScheduledTasks(50).then(() => | 
| + { | 
| + assert.deepEqual(requests, [ | 
| + [0 + initialDelay, "GET", "/subscription"], | 
| + [24 + initialDelay, "GET", "/subscription"], | 
| + [48 + initialDelay, "GET", "/subscription"] | 
| + ], "Requests after 50 hours"); | 
| + }); | 
| + }); | 
| - let subscription2 = Subscription.fromURL("http://example.com/subscription2"); | 
| - subscription2.expires = | 
| - subscription2.softExpiration = | 
| - (this.currentTime + 2 * MILLIS_IN_HOUR) / MILLIS_IN_SECOND; | 
| - filterStorage.addSubscription(subscription2); | 
| + it("Two Subscriptions", () => | 
| + { | 
| + let subscription1 = Subscription.fromURL("http://example.com/subscription1"); | 
| + filterStorage.addSubscription(subscription1); | 
| - let requests = []; | 
| - let handler = metadata => | 
| - { | 
| - requests.push([this.getTimeOffset(), metadata.method, metadata.path]); | 
| - return [200, "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"]; | 
| - }; | 
| + let subscription2 = Subscription.fromURL("http://example.com/subscription2"); | 
| + subscription2.expires = | 
| + subscription2.softExpiration = | 
| + (runner.currentTime + 2 * MILLIS_IN_HOUR) / MILLIS_IN_SECOND; | 
| + filterStorage.addSubscription(subscription2); | 
| + | 
| + let requests = []; | 
| + let handler = metadata => | 
| + { | 
| + requests.push([runner.getTimeOffset(), metadata.method, metadata.path]); | 
| + return [200, "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"]; | 
| + }; | 
| - this.registerHandler("/subscription1", handler); | 
| - this.registerHandler("/subscription2", handler); | 
| + runner.registerHandler("/subscription1", handler); | 
| + runner.registerHandler("/subscription2", handler); | 
| - this.runScheduledTasks(55).then(() => | 
| + return runner.runScheduledTasks(55).then(() => | 
| + { | 
| + assert.deepEqual(requests, [ | 
| + [0 + initialDelay, "GET", "/subscription1"], | 
| + [2 + initialDelay, "GET", "/subscription2"], | 
| + [24 + initialDelay, "GET", "/subscription1"], | 
| + [26 + initialDelay, "GET", "/subscription2"], | 
| + [48 + initialDelay, "GET", "/subscription1"], | 
| + [50 + initialDelay, "GET", "/subscription2"] | 
| + ], "Requests after 55 hours"); | 
| + }); | 
| + }); | 
| + }); | 
| + | 
| + describe("Subscription Headers", () => | 
| { | 
| - test.deepEqual(requests, [ | 
| - [0 + initialDelay, "GET", "/subscription1"], | 
| - [2 + initialDelay, "GET", "/subscription2"], | 
| - [24 + initialDelay, "GET", "/subscription1"], | 
| - [26 + initialDelay, "GET", "/subscription2"], | 
| - [48 + initialDelay, "GET", "/subscription1"], | 
| - [50 + initialDelay, "GET", "/subscription2"] | 
| - ], "Requests after 55 hours"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| + for (let currentTest of [ | 
| + {header: "[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: null}, | 
| + {header: "[Adblock Plus]", downloadStatus: "synchronize_ok", requiredVersion: null}, | 
| + {header: "(something)[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: null}, | 
| + {header: "[Adblock Plus 0.0.1]", downloadStatus: "synchronize_ok", requiredVersion: "0.0.1"}, | 
| + {header: "[Adblock Plus 99.9]", downloadStatus: "synchronize_ok", requiredVersion: "99.9"}, | 
| + {header: "[Foo]", downloadStatus: "synchronize_invalid_data", requiredVersion: null} | 
| + ]) | 
| + { | 
| + it(currentTest.header, () => | 
| + { | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + filterStorage.addSubscription(subscription); | 
| + | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + return [200, currentTest.header + "\n!Expires: 8 hours\nfoo\n!bar\n\n@@bas\n#bam"]; | 
| + }); | 
| -exports.testSubscriptionHeaders = {}; | 
| + return runner.runScheduledTasks(2).then(() => | 
| + { | 
| + assert.equal(subscription.downloadStatus, currentTest.downloadStatus, "Download status"); | 
| + assert.equal(subscription.requiredVersion, currentTest.requiredVersion, "Required version"); | 
| -for (let currentTest of [ | 
| - {header: "[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: null}, | 
| - {header: "[Adblock Plus]", downloadStatus: "synchronize_ok", requiredVersion: null}, | 
| - {header: "(something)[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: null}, | 
| - {header: "[Adblock Plus 0.0.1]", downloadStatus: "synchronize_ok", requiredVersion: "0.0.1"}, | 
| - {header: "[Adblock Plus 99.9]", downloadStatus: "synchronize_ok", requiredVersion: "99.9"}, | 
| - {header: "[Foo]", downloadStatus: "synchronize_invalid_data", requiredVersion: null} | 
| -]) | 
| -{ | 
| - exports.testSubscriptionHeaders[currentTest.header] = function(test) | 
| + if (currentTest.downloadStatus == "synchronize_ok") | 
| + { | 
| + assert.deepEqual([...subscription.filterText()], ["foo", "!bar", "@@bas", "#bam"], "Resulting subscription filters"); | 
| + } | 
| + else | 
| + { | 
| + assert.deepEqual([...subscription.filterText()], [ | 
| + ], "Resulting subscription filters"); | 
| + } | 
| + }); | 
| + }); | 
| + } | 
| + }); | 
| + | 
| + it("Disable Updates", () => | 
| { | 
| + Prefs.subscriptions_autoupdate = false; | 
| + | 
| let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| filterStorage.addSubscription(subscription); | 
| - this.registerHandler("/subscription", metadata => | 
| + let requests = 0; | 
| + runner.registerHandler("/subscription", metadata => | 
| { | 
| - return [200, currentTest.header + "\n!Expires: 8 hours\nfoo\n!bar\n\n@@bas\n#bam"]; | 
| + requests++; | 
| + throw new Error("Unexpected request"); | 
| }); | 
| - this.runScheduledTasks(2).then(() => | 
| + return runner.runScheduledTasks(50).then(() => | 
| { | 
| - test.equal(subscription.downloadStatus, currentTest.downloadStatus, "Download status"); | 
| - test.equal(subscription.requiredVersion, currentTest.requiredVersion, "Required version"); | 
| - | 
| - if (currentTest.downloadStatus == "synchronize_ok") | 
| - { | 
| - test.deepEqual([...subscription.filterText()], ["foo", "!bar", "@@bas", "#bam"], "Resulting subscription filters"); | 
| - } | 
| - else | 
| - { | 
| - test.deepEqual([...subscription.filterText()], [ | 
| - ], "Resulting subscription filters"); | 
| - } | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| - }; | 
| -} | 
| - | 
| -exports.testsDisabledUpdates = function(test) | 
| -{ | 
| - Prefs.subscriptions_autoupdate = false; | 
| - | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - let requests = 0; | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - requests++; | 
| - throw new Error("Unexpected request"); | 
| + assert.equal(requests, 0, "Request count"); | 
| + }); | 
| }); | 
| - this.runScheduledTasks(50).then(() => | 
| - { | 
| - test.equal(requests, 0, "Request count"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| - | 
| -exports.testExpirationTime = {}; | 
| - | 
| -for (let currentTest of [ | 
| - { | 
| - expiration: "default", | 
| - randomResult: 0.5, | 
| - requests: [0 + initialDelay, 5 * 24 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "1 hours", // Minimal expiration interval | 
| - randomResult: 0.5, | 
| - requests: [0 + initialDelay, 1 + initialDelay, 2 + initialDelay, 3 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "26 hours", | 
| - randomResult: 0.5, | 
| - requests: [0 + initialDelay, 26 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "2 days", | 
| - randomResult: 0.5, | 
| - requests: [0 + initialDelay, 48 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "20 days", // Too large, will be corrected | 
| - randomResult: 0.5, | 
| - requests: [0 + initialDelay, 14 * 24 + initialDelay] | 
| - }, | 
| + describe("Expiration time", () => | 
| { | 
| - expiration: "35 hours", | 
| - randomResult: 0, // Changes interval by factor 0.8 | 
| - requests: [0 + initialDelay, 28 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "35 hours", | 
| - randomResult: 1, // Changes interval by factor 1.2 | 
| - requests: [0 + initialDelay, 42 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "35 hours", | 
| - randomResult: 0.25, // Changes interval by factor 0.9 | 
| - requests: [0 + initialDelay, 32 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "40 hours", | 
| - randomResult: 0.5, | 
| - skipAfter: 5 + initialDelay, | 
| - skip: 10, // Short break should not increase soft expiration | 
| - requests: [0 + initialDelay, 40 + initialDelay] | 
| - }, | 
| + for (let currentTest of [ | 
| + { | 
| + expiration: "default", | 
| + randomResult: 0.5, | 
| + requests: [0 + initialDelay, 5 * 24 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "1 hours", // Minimal expiration interval | 
| + randomResult: 0.5, | 
| + requests: [0 + initialDelay, 1 + initialDelay, 2 + initialDelay, 3 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "26 hours", | 
| + randomResult: 0.5, | 
| + requests: [0 + initialDelay, 26 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "2 days", | 
| + randomResult: 0.5, | 
| + requests: [0 + initialDelay, 48 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "20 days", // Too large, will be corrected | 
| + randomResult: 0.5, | 
| + requests: [0 + initialDelay, 14 * 24 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "35 hours", | 
| + randomResult: 0, // Changes interval by factor 0.8 | 
| + requests: [0 + initialDelay, 28 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "35 hours", | 
| + randomResult: 1, // Changes interval by factor 1.2 | 
| + requests: [0 + initialDelay, 42 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "35 hours", | 
| + randomResult: 0.25, // Changes interval by factor 0.9 | 
| + requests: [0 + initialDelay, 32 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "40 hours", | 
| + randomResult: 0.5, | 
| + skipAfter: 5 + initialDelay, | 
| + skip: 10, // Short break should not increase soft expiration | 
| + requests: [0 + initialDelay, 40 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "40 hours", | 
| + randomResult: 0.5, | 
| + skipAfter: 5 + initialDelay, | 
| + skip: 30, // Long break should increase soft expiration | 
| + requests: [0 + initialDelay, 70 + initialDelay] | 
| + }, | 
| + { | 
| + expiration: "40 hours", | 
| + randomResult: 0.5, | 
| + skipAfter: 5 + initialDelay, | 
| + skip: 80, // Hitting hard expiration, immediate download | 
| + requests: [0 + initialDelay, 85 + initialDelay] | 
| + } | 
| + ]) | 
| + { | 
| + let testId = `"${currentTest.expiration}"`; | 
| + if (currentTest.randomResult != 0.5) | 
| + testId += ` with Math.random() returning ${currentTest.randomResult}`; | 
| + if (currentTest.skip) | 
| + testId += ` skipping ${currentTest.skip} hours after ${currentTest.skipAfter} hours`; | 
| + | 
| + it(testId, () => | 
| + { | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + filterStorage.addSubscription(subscription); | 
| + | 
| + let requests = []; | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + requests.push(runner.getTimeOffset()); | 
| + return [200, "[Adblock]\n!Expires: " + currentTest.expiration + "\nbar"]; | 
| + }); | 
| + | 
| + runner.randomResult = currentTest.randomResult; | 
| + | 
| + let maxHours = Math.round(Math.max.apply(null, currentTest.requests)) + 1; | 
| + return runner.runScheduledTasks(maxHours, currentTest.skipAfter, currentTest.skip).then(() => | 
| + { | 
| + assert.deepEqual(requests, currentTest.requests, "Requests"); | 
| + }); | 
| + }); | 
| + } | 
| + }); | 
| + | 
| + describe("Special Comments", () => | 
| { | 
| - expiration: "40 hours", | 
| - randomResult: 0.5, | 
| - skipAfter: 5 + initialDelay, | 
| - skip: 30, // Long break should increase soft expiration | 
| - requests: [0 + initialDelay, 70 + initialDelay] | 
| - }, | 
| - { | 
| - expiration: "40 hours", | 
| - randomResult: 0.5, | 
| - skipAfter: 5 + initialDelay, | 
| - skip: 80, // Hitting hard expiration, immediate download | 
| - requests: [0 + initialDelay, 85 + initialDelay] | 
| - } | 
| -]) | 
| -{ | 
| - let testId = `"${currentTest.expiration}"`; | 
| - if (currentTest.randomResult != 0.5) | 
| - testId += " with Math.random() returning " + currentTest.randomResult; | 
| - if (currentTest.skip) | 
| - testId += " skipping " + currentTest.skip + " hours after " + currentTest.skipAfter + " hours"; | 
| - exports.testExpirationTime[testId] = function(test) | 
| + for (let [comment, check] of [ | 
| + ["! Homepage: http://example.com/", subscription => | 
| + { | 
| + assert.equal(subscription.homepage, "http://example.com/", "Valid homepage comment"); | 
| + }], | 
| + ["! Homepage: ssh://example.com/", subscription => | 
| + { | 
| + assert.equal(subscription.homepage, null, "Invalid homepage comment"); | 
| + }], | 
| + ["! Title: foo", subscription => | 
| + { | 
| + assert.equal(subscription.title, "foo", "Title comment"); | 
| + assert.equal(subscription.fixedTitle, true, "Fixed title"); | 
| + }], | 
| + ["! Version: 1234", subscription => | 
| + { | 
| + assert.equal(subscription.version, 1234, "Version comment"); | 
| + }] | 
| + ]) | 
| + { | 
| + it(comment, () => | 
| + { | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + filterStorage.addSubscription(subscription); | 
| + | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + return [200, "[Adblock]\n" + comment + "\nfoo\nbar"]; | 
| + }); | 
| + | 
| + return runner.runScheduledTasks(2).then(() => | 
| + { | 
| + check(subscription); | 
| + assert.deepEqual([...subscription.filterText()], ["foo", "bar"], "Special comment not added to filters"); | 
| + }); | 
| + }); | 
| + } | 
| + }); | 
| + | 
| + it("Redirects", () => | 
| { | 
| let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| filterStorage.addSubscription(subscription); | 
| - let requests = []; | 
| - this.registerHandler("/subscription", metadata => | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + return [200, "[Adblock]\n!Redirect: http://example.com/redirected\nbar"]; | 
| + }); | 
| + | 
| + let requests; | 
| + | 
| + return runner.runScheduledTasks(30).then(() => | 
| + { | 
| + assert.equal([...filterStorage.subscriptions()][0], subscription, "Invalid redirect ignored"); | 
| + assert.equal(subscription.downloadStatus, "synchronize_connection_error", "Connection error recorded"); | 
| + assert.equal(subscription.errors, 2, "Number of download errors"); | 
| + | 
| + requests = []; | 
| + | 
| + runner.registerHandler("/redirected", metadata => | 
| + { | 
| + requests.push(runner.getTimeOffset()); | 
| + return [200, "[Adblock]\n! Expires: 8 hours\nbar"]; | 
| + }); | 
| + | 
| + resetSubscription(subscription); | 
| + return runner.runScheduledTasks(15); | 
| + }).then(() => | 
| { | 
| - requests.push(this.getTimeOffset()); | 
| - return [200, "[Adblock]\n!Expires: " + currentTest.expiration + "\nbar"]; | 
| + assert.equal([...filterStorage.subscriptions()][0].url, "http://example.com/redirected", "Redirect followed"); | 
| + assert.deepEqual(requests, [0 + initialDelay, 8 + initialDelay], "Resulting requests"); | 
| + | 
| + runner.registerHandler("/redirected", metadata => | 
| + { | 
| + return [200, "[Adblock]\n!Redirect: http://example.com/subscription\nbar"]; | 
| + }); | 
| + | 
| + subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + resetSubscription(subscription); | 
| + filterStorage.removeSubscription([...filterStorage.subscriptions()][0]); | 
| + filterStorage.addSubscription(subscription); | 
| + | 
| + return runner.runScheduledTasks(2); | 
| + }).then(() => | 
| + { | 
| + assert.equal([...filterStorage.subscriptions()][0], subscription, "Redirect not followed on redirect loop"); | 
| + assert.equal(subscription.downloadStatus, "synchronize_connection_error", "Download status after redirect loop"); | 
| + }); | 
| + }); | 
| + | 
| + it("Fallback", () => | 
| + { | 
| + Prefs.subscriptions_fallbackerrors = 3; | 
| + Prefs.subscriptions_fallbackurl = "http://example.com/fallback?%SUBSCRIPTION%&%RESPONSESTATUS%"; | 
| + | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + filterStorage.addSubscription(subscription); | 
| + | 
| + // No valid response from fallback | 
| + | 
| + let requests = []; | 
| + let fallbackParams; | 
| + let redirectedRequests; | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + requests.push(runner.getTimeOffset()); | 
| + return [404, ""]; | 
| }); | 
| - this.randomResult = currentTest.randomResult; | 
| + return runner.runScheduledTasks(100).then(() => | 
| + { | 
| + assert.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay, 72 + initialDelay, 96 + initialDelay], "Continue trying if the fallback doesn't respond"); | 
| + | 
| + // Fallback giving "Gone" response | 
| - let maxHours = Math.round(Math.max.apply(null, currentTest.requests)) + 1; | 
| - this.runScheduledTasks(maxHours, currentTest.skipAfter, currentTest.skip).then(() => | 
| + resetSubscription(subscription); | 
| + requests = []; | 
| + fallbackParams = null; | 
| + runner.registerHandler("/fallback", metadata => | 
| + { | 
| + fallbackParams = decodeURIComponent(metadata.queryString); | 
| + return [200, "410 Gone"]; | 
| + }); | 
| + | 
| + return runner.runScheduledTasks(100); | 
| + }).then(() => | 
| { | 
| - test.deepEqual(requests, currentTest.requests, "Requests"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| - }; | 
| -} | 
| + assert.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay], "Stop trying if the fallback responds with Gone"); | 
| + assert.equal(fallbackParams, "http://example.com/subscription&404", "Fallback arguments"); | 
| + | 
| + // Fallback redirecting to a missing file | 
| + | 
| + subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + resetSubscription(subscription); | 
| + filterStorage.removeSubscription([...filterStorage.subscriptions()][0]); | 
| + filterStorage.addSubscription(subscription); | 
| + requests = []; | 
| -exports.testSpecialComments = {}; | 
| + runner.registerHandler("/fallback", metadata => | 
| + { | 
| + return [200, "301 http://example.com/redirected"]; | 
| + }); | 
| + return runner.runScheduledTasks(100); | 
| + }).then(() => | 
| + { | 
| + assert.equal([...filterStorage.subscriptions()][0].url, "http://example.com/subscription", "Ignore invalid redirect from fallback"); | 
| + assert.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay, 72 + initialDelay, 96 + initialDelay], "Requests not affected by invalid redirect"); | 
| + | 
| + // Fallback redirecting to an existing file | 
| -for (let [comment, check] of [ | 
| - ["! Homepage: http://example.com/", (test, subscription) => | 
| - { | 
| - test.equal(subscription.homepage, "http://example.com/", "Valid homepage comment"); | 
| - }], | 
| - ["! Homepage: ssh://example.com/", (test, subscription) => | 
| - { | 
| - test.equal(subscription.homepage, null, "Invalid homepage comment"); | 
| - }], | 
| - ["! Title: foo", (test, subscription) => | 
| - { | 
| - test.equal(subscription.title, "foo", "Title comment"); | 
| - test.equal(subscription.fixedTitle, true, "Fixed title"); | 
| - }], | 
| - ["! Version: 1234", (test, subscription) => | 
| - { | 
| - test.equal(subscription.version, 1234, "Version comment"); | 
| - }] | 
| -]) | 
| -{ | 
| - exports.testSpecialComments[comment] = function(test) | 
| + resetSubscription(subscription); | 
| + requests = []; | 
| + redirectedRequests = []; | 
| + runner.registerHandler("/redirected", metadata => | 
| + { | 
| + redirectedRequests.push(runner.getTimeOffset()); | 
| + return [200, "[Adblock]\n!Expires: 1day\nfoo\nbar"]; | 
| + }); | 
| + | 
| + return runner.runScheduledTasks(100); | 
| + }).then(() => | 
| + { | 
| + assert.equal([...filterStorage.subscriptions()][0].url, "http://example.com/redirected", "Valid redirect from fallback is followed"); | 
| + assert.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay], "Stop polling original URL after a valid redirect from fallback"); | 
| + assert.deepEqual(redirectedRequests, [48 + initialDelay, 72 + initialDelay, 96 + initialDelay], "Request new URL after a valid redirect from fallback"); | 
| + | 
| + // Redirect loop | 
| + | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + return [200, "[Adblock]\n! Redirect: http://example.com/subscription2"]; | 
| + }); | 
| + runner.registerHandler("/subscription2", metadata => | 
| + { | 
| + return [200, "[Adblock]\n! Redirect: http://example.com/subscription"]; | 
| + }); | 
| + | 
| + subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + resetSubscription(subscription); | 
| + filterStorage.removeSubscription([...filterStorage.subscriptions()][0]); | 
| + filterStorage.addSubscription(subscription); | 
| + | 
| + return runner.runScheduledTasks(100); | 
| + }).then(() => | 
| + { | 
| + assert.equal([...filterStorage.subscriptions()][0].url, "http://example.com/redirected", "Fallback can still redirect even after a redirect loop"); | 
| + }); | 
| + }); | 
| + | 
| + it("State Fields", () => | 
| { | 
| let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| filterStorage.addSubscription(subscription); | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - return [200, "[Adblock]\n" + comment + "\nfoo\nbar"]; | 
| - }); | 
| - | 
| - this.runScheduledTasks(2).then(() => | 
| + runner.registerHandler("/subscription", metadata => | 
| { | 
| - check(test, subscription); | 
| - test.deepEqual([...subscription.filterText()], ["foo", "bar"], "Special comment not added to filters"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| - }; | 
| -} | 
| - | 
| -exports.testRedirects = function(test) | 
| -{ | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - return [200, "[Adblock]\n!Redirect: http://example.com/redirected\nbar"]; | 
| - }); | 
| - | 
| - let requests; | 
| - | 
| - this.runScheduledTasks(30).then(() => | 
| - { | 
| - test.equal([...filterStorage.subscriptions()][0], subscription, "Invalid redirect ignored"); | 
| - test.equal(subscription.downloadStatus, "synchronize_connection_error", "Connection error recorded"); | 
| - test.equal(subscription.errors, 2, "Number of download errors"); | 
| - | 
| - requests = []; | 
| - | 
| - this.registerHandler("/redirected", metadata => | 
| - { | 
| - requests.push(this.getTimeOffset()); | 
| - return [200, "[Adblock]\n! Expires: 8 hours\nbar"]; | 
| + return [200, "[Adblock]\n! Expires: 2 hours\nfoo\nbar"]; | 
| }); | 
| - resetSubscription(subscription); | 
| - return this.runScheduledTasks(15); | 
| - }).then(() => | 
| - { | 
| - test.equal([...filterStorage.subscriptions()][0].url, "http://example.com/redirected", "Redirect followed"); | 
| - test.deepEqual(requests, [0 + initialDelay, 8 + initialDelay], "Resulting requests"); | 
| + let startTime = runner.currentTime; | 
| + return runner.runScheduledTasks(2).then(() => | 
| + { | 
| + assert.equal(subscription.downloadStatus, "synchronize_ok", "downloadStatus after successful download"); | 
| + assert.equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastDownload after successful download"); | 
| + assert.equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastSuccess after successful download"); | 
| + assert.equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + (1 + initialDelay) * MILLIS_IN_HOUR, "lastCheck after successful download"); | 
| + assert.equal(subscription.errors, 0, "errors after successful download"); | 
| - this.registerHandler("/redirected", metadata => | 
| - { | 
| - return [200, "[Adblock]\n!Redirect: http://example.com/subscription\nbar"]; | 
| - }); | 
| - | 
| - subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - resetSubscription(subscription); | 
| - filterStorage.removeSubscription([...filterStorage.subscriptions()][0]); | 
| - filterStorage.addSubscription(subscription); | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + return [0, ""]; | 
| + }); | 
| - return this.runScheduledTasks(2); | 
| - }).then(() => | 
| - { | 
| - test.equal([...filterStorage.subscriptions()][0], subscription, "Redirect not followed on redirect loop"); | 
| - test.equal(subscription.downloadStatus, "synchronize_connection_error", "Download status after redirect loop"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| - | 
| -exports.testFallback = function(test) | 
| -{ | 
| - Prefs.subscriptions_fallbackerrors = 3; | 
| - Prefs.subscriptions_fallbackurl = "http://example.com/fallback?%SUBSCRIPTION%&%RESPONSESTATUS%"; | 
| + return runner.runScheduledTasks(2); | 
| + }).then(() => | 
| + { | 
| + assert.equal(subscription.downloadStatus, "synchronize_connection_error", "downloadStatus after connection error"); | 
| + assert.equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + (2 + initialDelay) * MILLIS_IN_HOUR, "lastDownload after connection error"); | 
| + assert.equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastSuccess after connection error"); | 
| + assert.equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + (3 + initialDelay) * MILLIS_IN_HOUR, "lastCheck after connection error"); | 
| + assert.equal(subscription.errors, 1, "errors after connection error"); | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - // No valid response from fallback | 
| + runner.registerHandler("/subscription", metadata => | 
| + { | 
| + return [404, ""]; | 
| + }); | 
| - let requests = []; | 
| - let fallbackParams; | 
| - let redirectedRequests; | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - requests.push(this.getTimeOffset()); | 
| - return [404, ""]; | 
| + return runner.runScheduledTasks(24); | 
| + }).then(() => | 
| + { | 
| + assert.equal(subscription.downloadStatus, "synchronize_connection_error", "downloadStatus after download error"); | 
| + assert.equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + (26 + initialDelay) * MILLIS_IN_HOUR, "lastDownload after download error"); | 
| + assert.equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastSuccess after download error"); | 
| + assert.equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + (27 + initialDelay) * MILLIS_IN_HOUR, "lastCheck after download error"); | 
| + assert.equal(subscription.errors, 2, "errors after download error"); | 
| + }); | 
| }); | 
| - this.runScheduledTasks(100).then(() => | 
| - { | 
| - test.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay, 72 + initialDelay, 96 + initialDelay], "Continue trying if the fallback doesn't respond"); | 
| - | 
| - // Fallback giving "Gone" response | 
| - | 
| - resetSubscription(subscription); | 
| - requests = []; | 
| - fallbackParams = null; | 
| - this.registerHandler("/fallback", metadata => | 
| - { | 
| - fallbackParams = decodeURIComponent(metadata.queryString); | 
| - return [200, "410 Gone"]; | 
| - }); | 
| - | 
| - return this.runScheduledTasks(100); | 
| - }).then(() => | 
| - { | 
| - test.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay], "Stop trying if the fallback responds with Gone"); | 
| - test.equal(fallbackParams, "http://example.com/subscription&404", "Fallback arguments"); | 
| - | 
| - // Fallback redirecting to a missing file | 
| - | 
| - subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - resetSubscription(subscription); | 
| - filterStorage.removeSubscription([...filterStorage.subscriptions()][0]); | 
| - filterStorage.addSubscription(subscription); | 
| - requests = []; | 
| - | 
| - this.registerHandler("/fallback", metadata => | 
| - { | 
| - return [200, "301 http://example.com/redirected"]; | 
| - }); | 
| - return this.runScheduledTasks(100); | 
| - }).then(() => | 
| + it("Special Comment Ordering", () => | 
| { | 
| - test.equal([...filterStorage.subscriptions()][0].url, "http://example.com/subscription", "Ignore invalid redirect from fallback"); | 
| - test.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay, 72 + initialDelay, 96 + initialDelay], "Requests not affected by invalid redirect"); | 
| - | 
| - // Fallback redirecting to an existing file | 
| - | 
| - resetSubscription(subscription); | 
| - requests = []; | 
| - redirectedRequests = []; | 
| - this.registerHandler("/redirected", metadata => | 
| - { | 
| - redirectedRequests.push(this.getTimeOffset()); | 
| - return [200, "[Adblock]\n!Expires: 1day\nfoo\nbar"]; | 
| - }); | 
| - | 
| - return this.runScheduledTasks(100); | 
| - }).then(() => | 
| - { | 
| - test.equal([...filterStorage.subscriptions()][0].url, "http://example.com/redirected", "Valid redirect from fallback is followed"); | 
| - test.deepEqual(requests, [0 + initialDelay, 24 + initialDelay, 48 + initialDelay], "Stop polling original URL after a valid redirect from fallback"); | 
| - test.deepEqual(redirectedRequests, [48 + initialDelay, 72 + initialDelay, 96 + initialDelay], "Request new URL after a valid redirect from fallback"); | 
| - | 
| - // Redirect loop | 
| - | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - return [200, "[Adblock]\n! Redirect: http://example.com/subscription2"]; | 
| - }); | 
| - this.registerHandler("/subscription2", metadata => | 
| - { | 
| - return [200, "[Adblock]\n! Redirect: http://example.com/subscription"]; | 
| - }); | 
| - | 
| - subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - resetSubscription(subscription); | 
| - filterStorage.removeSubscription([...filterStorage.subscriptions()][0]); | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| filterStorage.addSubscription(subscription); | 
| - return this.runScheduledTasks(100); | 
| - }).then(() => | 
| - { | 
| - test.equal([...filterStorage.subscriptions()][0].url, "http://example.com/redirected", "Fallback can still redirect even after a redirect loop"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| - | 
| -exports.testStateFields = function(test) | 
| -{ | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - return [200, "[Adblock]\n! Expires: 2 hours\nfoo\nbar"]; | 
| - }); | 
| - | 
| - let startTime = this.currentTime; | 
| - this.runScheduledTasks(2).then(() => | 
| - { | 
| - test.equal(subscription.downloadStatus, "synchronize_ok", "downloadStatus after successful download"); | 
| - test.equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastDownload after successful download"); | 
| - test.equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastSuccess after successful download"); | 
| - test.equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + (1 + initialDelay) * MILLIS_IN_HOUR, "lastCheck after successful download"); | 
| - test.equal(subscription.errors, 0, "errors after successful download"); | 
| - | 
| - this.registerHandler("/subscription", metadata => | 
| + runner.registerHandler("/subscription", metadata => | 
| { | 
| return [0, ""]; | 
| }); | 
| - return this.runScheduledTasks(2); | 
| - }).then(() => | 
| + return runner.runScheduledTasks(1).then(() => | 
| + { | 
| + assert.equal(subscription.title, "http://example.com/subscription", "make sure title was not found"); | 
| + }); | 
| + }); | 
| + | 
| + it("Unkown Special Comments", () => | 
| { | 
| - test.equal(subscription.downloadStatus, "synchronize_connection_error", "downloadStatus after connection error"); | 
| - test.equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + (2 + initialDelay) * MILLIS_IN_HOUR, "lastDownload after connection error"); | 
| - test.equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastSuccess after connection error"); | 
| - test.equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + (3 + initialDelay) * MILLIS_IN_HOUR, "lastCheck after connection error"); | 
| - test.equal(subscription.errors, 1, "errors after connection error"); | 
| + let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| + filterStorage.addSubscription(subscription); | 
| - this.registerHandler("/subscription", metadata => | 
| + runner.registerHandler("/subscription", metadata => | 
| { | 
| - return [404, ""]; | 
| + // To test allowing unknown special comments like `! :`, `!!@#$%^&*() : `, and `! Some Unknown Comment : ` | 
| + return [200, "[Adblock]\n! :\n! !@#$%^&*() :\n! Some Unknown Comment :\n! Title: foobar\nfoo\nbar"]; | 
| }); | 
| - return this.runScheduledTasks(24); | 
| - }).then(() => | 
| - { | 
| - test.equal(subscription.downloadStatus, "synchronize_connection_error", "downloadStatus after download error"); | 
| - test.equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + (26 + initialDelay) * MILLIS_IN_HOUR, "lastDownload after download error"); | 
| - test.equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + initialDelay * MILLIS_IN_HOUR, "lastSuccess after download error"); | 
| - test.equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + (27 + initialDelay) * MILLIS_IN_HOUR, "lastCheck after download error"); | 
| - test.equal(subscription.errors, 2, "errors after download error"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| - | 
| -exports.testSpecialCommentOrdering = function(test) | 
| -{ | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - return [200, "[Adblock]\n! Special Comment: x\n!foo\n! Title: foobar\nfoo\nbar"]; | 
| + return runner.runScheduledTasks(1).then(() => | 
| + { | 
| + assert.equal(subscription.title, "foobar", "make sure title was found"); | 
| + }); | 
| }); | 
| - | 
| - this.runScheduledTasks(1).then(() => | 
| - { | 
| - test.equal(subscription.title, "http://example.com/subscription", "make sure title was not found"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| - | 
| -exports.testUnknownSpecialComments = function(test) | 
| -{ | 
| - let subscription = Subscription.fromURL("http://example.com/subscription"); | 
| - filterStorage.addSubscription(subscription); | 
| - | 
| - this.registerHandler("/subscription", metadata => | 
| - { | 
| - // To test allowing unknown special comments like `! :`, `!!@#$%^&*() : `, and `! Some Unknown Comment : ` | 
| - return [200, "[Adblock]\n! :\n! !@#$%^&*() :\n! Some Unknown Comment :\n! Title: foobar\nfoo\nbar"]; | 
| - }); | 
| - | 
| - this.runScheduledTasks(1).then(() => | 
| - { | 
| - test.equal(subscription.title, "foobar", "make sure title was found"); | 
| - }).catch(unexpectedError.bind(test)).then(() => test.done()); | 
| -}; | 
| +}); |