@speedkit/cli 4.26.0 → 4.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +203 -15
- package/dist/commands/auto-prewarm.d.ts +12 -1
- package/dist/commands/auto-prewarm.js +26 -7
- package/dist/commands/generate-pop-config.d.ts +1 -0
- package/dist/commands/generate-pop-config.js +9 -3
- package/dist/commands/prewarm.js +3 -1
- package/dist/commands/query/parameter.d.ts +27 -0
- package/dist/commands/query/parameter.js +28 -0
- package/dist/commands/query/prewarm.d.ts +32 -0
- package/dist/commands/query/prewarm.js +32 -0
- package/dist/helpers/clipboard.d.ts +19 -0
- package/dist/helpers/clipboard.js +70 -0
- package/dist/helpers/evaluate-speed-kit-config.d.ts +8 -18
- package/dist/helpers/evaluate-speed-kit-config.js +8 -6
- package/dist/helpers/evaluate-speed-kit-config.spec.d.ts +1 -0
- package/dist/helpers/evaluate-speed-kit-config.spec.js +78 -0
- package/dist/models/cli-parameters.d.ts +106 -0
- package/dist/models/cli-parameters.js +183 -0
- package/dist/models/cli-parameters.spec.d.ts +1 -0
- package/dist/models/cli-parameters.spec.js +47 -0
- package/dist/services/athena/athena-service-factory.d.ts +4 -1
- package/dist/services/athena/athena-service-factory.js +9 -3
- package/dist/services/athena/athena-service.d.ts +13 -1
- package/dist/services/athena/athena-service.js +27 -5
- package/dist/services/athena/athena-service.spec.d.ts +1 -0
- package/dist/services/athena/athena-service.spec.js +93 -0
- package/dist/services/deploy/handler/install-resource-handler.js +3 -4
- package/dist/services/onboarding/onboarding-service-factory.js +2 -2
- package/dist/services/pop-config/pop-config-service.d.ts +8 -1
- package/dist/services/pop-config/pop-config-service.js +16 -28
- package/dist/services/prewarm/assets/asset-api-client.d.ts +1 -1
- package/dist/services/prewarm/assets/asset-api-client.js +5 -4
- package/dist/services/prewarm/auto-pre-warm-factory.d.ts +20 -2
- package/dist/services/prewarm/auto-pre-warm-factory.js +24 -6
- package/dist/services/prewarm/csv-reader.d.ts +21 -2
- package/dist/services/prewarm/csv-reader.js +71 -9
- package/dist/services/prewarm/csv-reader.spec.d.ts +1 -0
- package/dist/services/prewarm/csv-reader.spec.js +75 -0
- package/dist/services/prewarm/index.d.ts +1 -0
- package/dist/services/prewarm/index.js +1 -0
- package/dist/services/prewarm/pre-warm-factory.d.ts +0 -2
- package/dist/services/prewarm/pre-warm-factory.js +8 -11
- package/dist/services/prewarm/pre-warm-model.d.ts +14 -1
- package/dist/services/prewarm/pre-warm-model.js +0 -1
- package/dist/services/prewarm/pre-warm-service.d.ts +9 -4
- package/dist/services/prewarm/pre-warm-service.js +18 -14
- package/dist/services/prewarm/prewarm-targets.d.ts +12 -0
- package/dist/services/prewarm/prewarm-targets.js +18 -0
- package/dist/services/prewarm/prewarm-targets.spec.d.ts +1 -0
- package/dist/services/prewarm/prewarm-targets.spec.js +29 -0
- package/dist/services/query-builder/error/match-matches-nothing-error.d.ts +11 -0
- package/dist/services/query-builder/error/match-matches-nothing-error.js +17 -0
- package/dist/services/query-builder/queries/device.d.ts +21 -0
- package/dist/services/query-builder/queries/device.js +26 -0
- package/dist/services/query-builder/queries/index.d.ts +6 -0
- package/dist/services/query-builder/queries/index.js +6 -0
- package/dist/services/query-builder/queries/page-filter.d.ts +39 -0
- package/dist/services/query-builder/queries/page-filter.js +135 -0
- package/dist/services/query-builder/queries/page-source.d.ts +30 -0
- package/dist/services/query-builder/queries/page-source.js +79 -0
- package/dist/services/query-builder/queries/parameter-query.d.ts +14 -0
- package/dist/services/query-builder/queries/parameter-query.js +142 -0
- package/dist/services/query-builder/queries/pops-query.d.ts +27 -0
- package/dist/services/query-builder/queries/pops-query.js +65 -0
- package/dist/services/query-builder/queries/prewarm-query.d.ts +18 -0
- package/dist/services/query-builder/queries/prewarm-query.js +227 -0
- package/dist/services/query-builder/queries/query-model.d.ts +97 -0
- package/dist/services/query-builder/queries/query-model.js +19 -0
- package/dist/services/query-builder/queries/spec/golden-configs.d.ts +18 -0
- package/dist/services/query-builder/queries/spec/golden-configs.js +116 -0
- package/dist/services/query-builder/queries/spec/golden.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/golden.spec.js +161 -0
- package/dist/services/query-builder/queries/spec/pops-query.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/pops-query.spec.js +58 -0
- package/dist/services/query-builder/queries/spec/queries.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/queries.spec.js +312 -0
- package/dist/services/query-builder/queries/spec/reported-bugs.spec.d.ts +1 -0
- package/dist/services/query-builder/queries/spec/reported-bugs.spec.js +86 -0
- package/dist/services/query-builder/query-builder-factory.d.ts +2 -2
- package/dist/services/query-builder/query-builder-factory.js +36 -8
- package/dist/services/query-builder/query-builder-model.d.ts +33 -19
- package/dist/services/query-builder/query-builder-model.js +5 -5
- package/dist/services/query-builder/query-builder-service.d.ts +26 -5
- package/dist/services/query-builder/query-builder-service.js +157 -44
- package/dist/services/query-builder/query-command.d.ts +27 -0
- package/dist/services/query-builder/query-command.js +36 -0
- package/dist/services/query-builder/rules/index.d.ts +8 -0
- package/dist/services/query-builder/rules/index.js +8 -0
- package/dist/services/query-builder/rules/match-pattern.d.ts +51 -0
- package/dist/services/query-builder/rules/match-pattern.js +141 -0
- package/dist/services/query-builder/rules/pattern-language.d.ts +59 -0
- package/dist/services/query-builder/rules/pattern-language.js +159 -0
- package/dist/services/query-builder/rules/rule-model.d.ts +107 -0
- package/dist/services/query-builder/rules/rule-model.js +54 -0
- package/dist/services/query-builder/rules/rule-parser.d.ts +33 -0
- package/dist/services/query-builder/rules/rule-parser.js +189 -0
- package/dist/services/query-builder/rules/rule-reducer.d.ts +71 -0
- package/dist/services/query-builder/rules/rule-reducer.js +383 -0
- package/dist/services/query-builder/rules/rule-relevance.d.ts +43 -0
- package/dist/services/query-builder/rules/rule-relevance.js +69 -0
- package/dist/services/query-builder/rules/rule-to-sql.d.ts +51 -0
- package/dist/services/query-builder/rules/rule-to-sql.js +75 -0
- package/dist/services/query-builder/rules/spec/pattern-language.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/pattern-language.spec.js +90 -0
- package/dist/services/query-builder/rules/spec/rule-parser.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-parser.spec.js +150 -0
- package/dist/services/query-builder/rules/spec/rule-reducer.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-reducer.spec.js +169 -0
- package/dist/services/query-builder/rules/spec/rule-relevance.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-relevance.spec.js +88 -0
- package/dist/services/query-builder/rules/spec/rule-to-sql.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/rule-to-sql.spec.js +110 -0
- package/dist/services/query-builder/rules/spec/runtime-semantics.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/runtime-semantics.spec.js +55 -0
- package/dist/services/query-builder/rules/spec/strip-parameters.spec.d.ts +1 -0
- package/dist/services/query-builder/rules/spec/strip-parameters.spec.js +85 -0
- package/dist/services/query-builder/rules/strip-parameters.d.ts +39 -0
- package/dist/services/query-builder/rules/strip-parameters.js +197 -0
- package/dist/services/query-builder/sql/expression.d.ts +73 -0
- package/dist/services/query-builder/sql/expression.js +133 -0
- package/dist/services/query-builder/sql/format.d.ts +14 -0
- package/dist/services/query-builder/sql/format.js +48 -0
- package/dist/services/query-builder/sql/index.d.ts +4 -0
- package/dist/services/query-builder/sql/index.js +4 -0
- package/dist/services/query-builder/sql/predicate.d.ts +66 -0
- package/dist/services/query-builder/sql/predicate.js +124 -0
- package/dist/services/query-builder/sql/regex.d.ts +28 -0
- package/dist/services/query-builder/sql/regex.js +81 -0
- package/dist/services/query-builder/sql/spec/expression.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/expression.spec.js +75 -0
- package/dist/services/query-builder/sql/spec/format.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/format.spec.js +37 -0
- package/dist/services/query-builder/sql/spec/predicate.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/predicate.spec.js +78 -0
- package/dist/services/query-builder/sql/spec/regex.spec.d.ts +1 -0
- package/dist/services/query-builder/sql/spec/regex.spec.js +62 -0
- package/oclif.manifest.json +401 -100
- package/package.json +11 -2
- package/dist/commands/build-parameter-query.d.ts +0 -14
- package/dist/commands/build-parameter-query.js +0 -25
- package/dist/commands/build-prewarm-query.d.ts +0 -16
- package/dist/commands/build-prewarm-query.js +0 -37
- package/dist/helpers/build-query-helper.d.ts +0 -32
- package/dist/helpers/build-query-helper.js +0 -223
- package/dist/helpers/get-parsed-config.d.ts +0 -15
- package/dist/helpers/get-parsed-config.js +0 -62
- package/dist/services/onboarding/dashboard/parameter-query-builder.d.ts +0 -8
- package/dist/services/onboarding/dashboard/parameter-query-builder.js +0 -63
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { conditionLanguage, isInside, languagesDisjoint, outsideOf, } from "../pattern-language.js";
|
|
4
|
+
function condition(...patterns) {
|
|
5
|
+
return {
|
|
6
|
+
patterns: patterns.map((value) => typeof value === "string"
|
|
7
|
+
? { kind: "string", value }
|
|
8
|
+
: { kind: "regexp", value }),
|
|
9
|
+
subject: "pathname",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function languageOf(pattern, bound, version = 2) {
|
|
13
|
+
return conditionLanguage(condition(pattern), version, bound);
|
|
14
|
+
}
|
|
15
|
+
/** Whether the first pattern's matches all fall inside the second's. */
|
|
16
|
+
function inside(narrower, wider, version = 2) {
|
|
17
|
+
const atLeast = languageOf(narrower, "atLeast", version);
|
|
18
|
+
const atMost = languageOf(wider, "atMost", version);
|
|
19
|
+
if (!atLeast || !atMost) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const outside = outsideOf(atMost);
|
|
23
|
+
return outside && isInside(atLeast, outside);
|
|
24
|
+
}
|
|
25
|
+
function disjoint(left, right, version = 2) {
|
|
26
|
+
const first = languageOf(left, "atLeast", version);
|
|
27
|
+
const second = languageOf(right, "atLeast", version);
|
|
28
|
+
return first && second ? languagesDisjoint(first, second) : undefined;
|
|
29
|
+
}
|
|
30
|
+
describe("pattern languages", () => {
|
|
31
|
+
it("should see an anchored pattern inside a shorter one", () => {
|
|
32
|
+
expect(inside(/^\/p\/shoes\//i, /^\/p\//i)).to.equal(true);
|
|
33
|
+
expect(inside(/^\/p\//i, /^\/p\/shoes\//i)).to.equal(false);
|
|
34
|
+
});
|
|
35
|
+
it("should see disjoint prefixes as disjoint", () => {
|
|
36
|
+
expect(disjoint(/^\/p\//i, /^\/checkout/i)).to.equal(true);
|
|
37
|
+
});
|
|
38
|
+
it("should not call overlapping patterns disjoint", () => {
|
|
39
|
+
expect(disjoint(/^\/p\//i, /shoes/i)).to.equal(false);
|
|
40
|
+
});
|
|
41
|
+
it("should read an unanchored pattern as matching anywhere", () => {
|
|
42
|
+
expect(inside(/^\/a\/b/i, /b/i)).to.equal(true);
|
|
43
|
+
expect(disjoint(/^\/a\/b/i, /b/i)).to.equal(false);
|
|
44
|
+
});
|
|
45
|
+
it("should respect the case of a pattern without the i flag", () => {
|
|
46
|
+
expect(disjoint(/^\/Shop/, /^\/shop/)).to.equal(true);
|
|
47
|
+
expect(disjoint(/^\/Shop/i, /^\/shop/)).to.equal(false);
|
|
48
|
+
});
|
|
49
|
+
it("should read a string as a prefix under version 1 and exactly under 2", () => {
|
|
50
|
+
expect(inside("/shop/nike", "/shop", 1)).to.equal(true);
|
|
51
|
+
expect(inside("/shop/nike", "/shop", 2)).to.equal(false);
|
|
52
|
+
});
|
|
53
|
+
it("should ignore the case of a string condition", () => {
|
|
54
|
+
expect(disjoint("/SHOP", "/shop", 2)).to.equal(false);
|
|
55
|
+
});
|
|
56
|
+
it("should union the entries of a condition", () => {
|
|
57
|
+
const language = conditionLanguage(condition(/^\/a/, /^\/b/), 2, "atLeast");
|
|
58
|
+
const outside = language && outsideOf(language);
|
|
59
|
+
const a = languageOf(/^\/a\/1/, "atLeast");
|
|
60
|
+
expect(outside && a && isInside(a, outside)).to.equal(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("pattern languages with an assertion", () => {
|
|
64
|
+
// `(\/|$)` and `\b` have no automaton, so each bound approximates — and the
|
|
65
|
+
// two bounds have to fall on opposite sides of the truth.
|
|
66
|
+
it("should read an assertion as widely as possible for atLeast", () => {
|
|
67
|
+
// /^\/shop(\/|$)/ widened means "starts with /shop", which covers /shopx.
|
|
68
|
+
expect(disjoint(/^\/shop(\/|$)/, /^\/shopx/)).to.equal(false);
|
|
69
|
+
});
|
|
70
|
+
it("should read an assertion as narrowly as possible for atMost", () => {
|
|
71
|
+
// Narrowed, the same pattern covers only /shop/… — not /shop itself.
|
|
72
|
+
expect(inside(/^\/shop$/, /^\/shop(\/|$)/)).to.equal(false);
|
|
73
|
+
});
|
|
74
|
+
it("should still place a widened pattern inside a wider one", () => {
|
|
75
|
+
expect(inside(/\bshop\b/i, /shop/i)).to.equal(true);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe("patterns without any automaton", () => {
|
|
79
|
+
// A back reference to a single character is expressible as a union and
|
|
80
|
+
// `refa` resolves it; one to a group of any length is not.
|
|
81
|
+
it("should resolve a back reference to a single character", () => {
|
|
82
|
+
expect(languageOf(/(\w)\1/, "atLeast")).to.not.equal(undefined);
|
|
83
|
+
});
|
|
84
|
+
it("should give up on a back reference it cannot resolve", () => {
|
|
85
|
+
expect(languageOf(/(\w+)\1/, "atLeast")).to.equal(undefined);
|
|
86
|
+
});
|
|
87
|
+
it("should give up on the whole condition when one entry has none", () => {
|
|
88
|
+
expect(conditionLanguage(condition(/^\/a/, /(\w+)\1/), 2, "atLeast")).to.equal(undefined);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import * as vm from "node:vm";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
import { describe, it } from "mocha";
|
|
4
|
+
import { parseCondition, parseRuleSets, parseRuleSetVersion, parseStripQueryParams, } from "../rule-parser.js";
|
|
5
|
+
describe("parseRuleSetVersion", () => {
|
|
6
|
+
it("should default to version 1", () => {
|
|
7
|
+
expect(parseRuleSetVersion({})).to.equal(1);
|
|
8
|
+
});
|
|
9
|
+
it("should read the declared version", () => {
|
|
10
|
+
expect(parseRuleSetVersion({ ruleSetConfig: { version: 2 } })).to.equal(2);
|
|
11
|
+
});
|
|
12
|
+
it("should read a version given as a string", () => {
|
|
13
|
+
expect(parseRuleSetVersion({ ruleSetConfig: { version: "2" } })).to.equal(2);
|
|
14
|
+
});
|
|
15
|
+
it("should fall back for an unusable version", () => {
|
|
16
|
+
expect(parseRuleSetVersion({ ruleSetConfig: { version: "x" } })).to.equal(1);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("parseCondition", () => {
|
|
20
|
+
it("should wrap a single string", () => {
|
|
21
|
+
expect(parseCondition("/shop")).to.deep.equal([
|
|
22
|
+
{ kind: "string", value: "/shop" },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
it("should wrap a single regular expression", () => {
|
|
26
|
+
const pattern = /^\/p\//i;
|
|
27
|
+
expect(parseCondition(pattern)).to.deep.equal([
|
|
28
|
+
{ kind: "regexp", value: pattern },
|
|
29
|
+
]);
|
|
30
|
+
});
|
|
31
|
+
it("should keep the order of a mixed array", () => {
|
|
32
|
+
expect(parseCondition(["/a", /b/]).map(({ kind }) => kind)).to.deep.equal([
|
|
33
|
+
"string",
|
|
34
|
+
"regexp",
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
it("should return nothing for a missing condition", () => {
|
|
38
|
+
expect(parseCondition(undefined)).to.deep.equal([]);
|
|
39
|
+
});
|
|
40
|
+
it("should recognise a regular expression from another realm", () => {
|
|
41
|
+
// The config is evaluated with `vm.runInNewContext`, so its regular
|
|
42
|
+
// expressions are not `instanceof RegExp` here. Reading them as strings
|
|
43
|
+
// would drop every pattern of every rule.
|
|
44
|
+
const foreign = vm.runInNewContext("/^\\/p\\//i");
|
|
45
|
+
expect(foreign instanceof RegExp).to.equal(false);
|
|
46
|
+
const [pattern] = parseCondition(foreign);
|
|
47
|
+
expect(pattern.kind).to.equal("regexp");
|
|
48
|
+
expect(pattern.value).to.be.instanceOf(RegExp);
|
|
49
|
+
expect(String(pattern.value)).to.equal("/^\\/p\\//i");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe("parseRuleSets", () => {
|
|
53
|
+
it("should read every whitelist rule, not just the first", () => {
|
|
54
|
+
const { whitelist } = parseRuleSets({
|
|
55
|
+
whitelist: [{ host: ["www.shop.de"] }, { host: ["m.shop.de"] }],
|
|
56
|
+
});
|
|
57
|
+
expect(whitelist).to.have.lengthOf(2);
|
|
58
|
+
expect(whitelist[1].source).to.equal("whitelist[1]");
|
|
59
|
+
});
|
|
60
|
+
it("should keep the conditions of a rule together", () => {
|
|
61
|
+
const { enabledSites } = parseRuleSets({
|
|
62
|
+
enabledSites: [{ host: ["www.shop.de"], pathname: [/^\/p\//i] }],
|
|
63
|
+
});
|
|
64
|
+
expect(enabledSites[0].conditions.map(({ subject }) => subject)).to.deep.eq(["host", "pathname"]);
|
|
65
|
+
});
|
|
66
|
+
it("should normalize the content types", () => {
|
|
67
|
+
const { blacklist } = parseRuleSets({
|
|
68
|
+
blacklist: [{ contentType: "Image" }],
|
|
69
|
+
});
|
|
70
|
+
expect(blacklist[0].contentTypes).to.deep.equal(["image"]);
|
|
71
|
+
});
|
|
72
|
+
it("should leave the content types undefined when there are none", () => {
|
|
73
|
+
const { blacklist } = parseRuleSets({ blacklist: [{ pathname: "/a" }] });
|
|
74
|
+
expect(blacklist[0].contentTypes).to.equal(undefined);
|
|
75
|
+
});
|
|
76
|
+
it("should record criteria that are not part of the url", () => {
|
|
77
|
+
const { disabledSites } = parseRuleSets({
|
|
78
|
+
disabledSites: [{ cookie: /loggedIn/, mobile: true, pathname: "/a" }],
|
|
79
|
+
});
|
|
80
|
+
expect(disabledSites[0].criteriaOutsideTheUrl).to.deep.equal([
|
|
81
|
+
"cookie",
|
|
82
|
+
"mobile",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
it("should apply the config version to every rule", () => {
|
|
86
|
+
const { enabledSites } = parseRuleSets({
|
|
87
|
+
enabledSites: [{ pathname: "/a" }],
|
|
88
|
+
ruleSetConfig: { version: 2 },
|
|
89
|
+
});
|
|
90
|
+
expect(enabledSites[0].ruleSetVersion).to.equal(2);
|
|
91
|
+
});
|
|
92
|
+
it("should let a rule override the version", () => {
|
|
93
|
+
const { enabledSites } = parseRuleSets({
|
|
94
|
+
enabledSites: [{ pathname: "/a", ruleSetVersion: 1 }],
|
|
95
|
+
ruleSetConfig: { version: 2 },
|
|
96
|
+
});
|
|
97
|
+
expect(enabledSites[0].ruleSetVersion).to.equal(1);
|
|
98
|
+
});
|
|
99
|
+
it("should survive a config without any rule set", () => {
|
|
100
|
+
const parsed = parseRuleSets({});
|
|
101
|
+
expect(parsed.whitelist).to.deep.equal([]);
|
|
102
|
+
expect(parsed.enabledSites).to.deep.equal([]);
|
|
103
|
+
expect(parsed.stripQueryParams).to.deep.equal([]);
|
|
104
|
+
});
|
|
105
|
+
it("should ignore a rule set that is not an array", () => {
|
|
106
|
+
expect(parseRuleSets({ blacklist: "everything" }).blacklist).to.deep.equal([]);
|
|
107
|
+
});
|
|
108
|
+
it("should keep a rule without conditions", () => {
|
|
109
|
+
const { whitelist } = parseRuleSets({ whitelist: [{}] });
|
|
110
|
+
expect(whitelist).to.have.lengthOf(1);
|
|
111
|
+
expect(whitelist[0].conditions).to.deep.equal([]);
|
|
112
|
+
expect(whitelist[0].matchesNothing).to.equal(true);
|
|
113
|
+
});
|
|
114
|
+
it("should mark a rule with an empty condition as matching nothing", () => {
|
|
115
|
+
const { blacklist } = parseRuleSets({ blacklist: [{ host: [] }] });
|
|
116
|
+
expect(blacklist[0].matchesNothing).to.equal(true);
|
|
117
|
+
});
|
|
118
|
+
it("should mark an empty content type as matching nothing", () => {
|
|
119
|
+
const { blacklist } = parseRuleSets({ blacklist: [{ contentType: [] }] });
|
|
120
|
+
expect(blacklist[0].matchesNothing).to.equal(true);
|
|
121
|
+
});
|
|
122
|
+
it("should not mark a rule with a usable condition", () => {
|
|
123
|
+
const { blacklist } = parseRuleSets({ blacklist: [{ host: ["a.de"] }] });
|
|
124
|
+
expect(blacklist[0].matchesNothing).to.equal(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("parseStripQueryParams", () => {
|
|
128
|
+
it("should read a bare list of parameters", () => {
|
|
129
|
+
const rules = parseStripQueryParams(["gclid", /^utm[_-]/], 2);
|
|
130
|
+
expect(rules).to.have.lengthOf(1);
|
|
131
|
+
expect(rules[0].params).to.have.lengthOf(2);
|
|
132
|
+
expect(rules[0].enabledSites).to.equal(undefined);
|
|
133
|
+
});
|
|
134
|
+
it("should read a single parameter", () => {
|
|
135
|
+
expect(parseStripQueryParams("gclid", 2)[0].params).to.deep.equal([
|
|
136
|
+
{ kind: "string", value: "gclid" },
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
it("should keep the pages a rule is scoped to", () => {
|
|
140
|
+
const rules = parseStripQueryParams([{ enabledSites: [{ pathname: [/^\/p\//i] }], params: ["size"] }], 2);
|
|
141
|
+
expect(rules[0].enabledSites).to.have.lengthOf(1);
|
|
142
|
+
expect(rules[0].enabledSites[0].source).to.equal("stripQueryParams[0].enabledSites[0]");
|
|
143
|
+
});
|
|
144
|
+
it("should skip a rule without parameters", () => {
|
|
145
|
+
expect(parseStripQueryParams([{ enabledSites: [] }], 2)).to.deep.equal([]);
|
|
146
|
+
});
|
|
147
|
+
it("should return nothing when the property is missing", () => {
|
|
148
|
+
expect(parseStripQueryParams(undefined, 2)).to.deep.equal([]);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { parseRuleSets } from "../rule-parser.js";
|
|
4
|
+
import { absorbRules, dropUnreachableRules, findShadowedRules, reductionBudget, } from "../rule-reducer.js";
|
|
5
|
+
function rulesOf(config, version = 2) {
|
|
6
|
+
return parseRuleSets({ ruleSetConfig: { version }, ...config });
|
|
7
|
+
}
|
|
8
|
+
function blacklist(rules, version = 2) {
|
|
9
|
+
return rulesOf({ blacklist: rules }, version).blacklist;
|
|
10
|
+
}
|
|
11
|
+
function enabled(rules, version = 2) {
|
|
12
|
+
return rulesOf({ enabledSites: rules }, version).enabledSites;
|
|
13
|
+
}
|
|
14
|
+
/** The entries left on the first rule's first condition. */
|
|
15
|
+
function patternsOf(rules) {
|
|
16
|
+
return (rules[0]?.conditions[0]?.patterns ?? []).map(({ value }) => String(value));
|
|
17
|
+
}
|
|
18
|
+
describe("absorbRules", () => {
|
|
19
|
+
it("should drop a rule a sibling covers", () => {
|
|
20
|
+
const { notes, rules } = absorbRules(blacklist([{ pathname: [/^\/p\/shoes\//i] }, { pathname: [/^\/p\//i] }]));
|
|
21
|
+
expect(rules).to.have.lengthOf(1);
|
|
22
|
+
expect(rules[0].source).to.equal("blacklist[1]");
|
|
23
|
+
expect(notes[0].reason).to.contain("already covered by blacklist[1]");
|
|
24
|
+
});
|
|
25
|
+
it("should keep the first of two equivalent rules", () => {
|
|
26
|
+
const { rules } = absorbRules(blacklist([{ pathname: [/^\/p\//i] }, { pathname: [/^\/p\//i] }]));
|
|
27
|
+
expect(rules).to.have.lengthOf(1);
|
|
28
|
+
expect(rules[0].source).to.equal("blacklist[0]");
|
|
29
|
+
});
|
|
30
|
+
it("should not compare rules about different parts of the url", () => {
|
|
31
|
+
const { rules } = absorbRules(blacklist([{ host: ["www.shop.de"] }, { pathname: [/^\/p\//i] }]));
|
|
32
|
+
expect(rules).to.have.lengthOf(2);
|
|
33
|
+
});
|
|
34
|
+
it("should keep a rule that is wider on a subject the other constrains", () => {
|
|
35
|
+
const { rules } = absorbRules(blacklist([
|
|
36
|
+
{ host: ["www.shop.de"], pathname: [/^\/p\//i] },
|
|
37
|
+
{ pathname: [/^\/p\//i] },
|
|
38
|
+
]));
|
|
39
|
+
expect(rules).to.have.lengthOf(1);
|
|
40
|
+
expect(rules[0].source).to.equal("blacklist[1]");
|
|
41
|
+
});
|
|
42
|
+
it("should drop a condition entry a sibling entry covers", () => {
|
|
43
|
+
const { rules } = absorbRules(blacklist([{ pathname: [/^\/p\/shoes\//i, /^\/p\//i] }]));
|
|
44
|
+
expect(patternsOf(rules)).to.deep.equal(["/^\\/p\\//i"]);
|
|
45
|
+
});
|
|
46
|
+
it("should keep entries that only overlap", () => {
|
|
47
|
+
const { rules } = absorbRules(blacklist([{ pathname: [/^\/p\//i, /shoes/i] }]));
|
|
48
|
+
expect(patternsOf(rules)).to.have.lengthOf(2);
|
|
49
|
+
});
|
|
50
|
+
it("should absorb an entry with an assertion into a wider one", () => {
|
|
51
|
+
// `\b` has no automaton, but read as widely as possible the entry means
|
|
52
|
+
// "contains p" — which /p/i does cover, so the absorption holds.
|
|
53
|
+
const { rules } = absorbRules(blacklist([{ pathname: [/\bp\b/i, /p/i] }]));
|
|
54
|
+
expect(patternsOf(rules)).to.deep.equal(["/p/i"]);
|
|
55
|
+
});
|
|
56
|
+
it("should not let an entry with an assertion absorb a wider one", () => {
|
|
57
|
+
// Read as narrowly as possible the entry means nothing at all, so it
|
|
58
|
+
// cannot stand in for /p/i.
|
|
59
|
+
const { rules } = absorbRules(blacklist([{ pathname: [/p/i, /\bp\b/i] }]));
|
|
60
|
+
expect(patternsOf(rules)).to.deep.equal(["/p/i"]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("dropUnreachableRules", () => {
|
|
64
|
+
const reachable = [enabled([{ pathname: [/^\/$/, /^\/p\//i] }])];
|
|
65
|
+
it("should drop an entry no enabled page can match", () => {
|
|
66
|
+
const { notes, rules } = dropUnreachableRules(blacklist([{ pathname: [/^\/checkout/i, /^\/p\//i] }]), reachable);
|
|
67
|
+
expect(patternsOf(rules)).to.deep.equal(["/^\\/p\\//i"]);
|
|
68
|
+
expect(notes[0].reason).to.contain("matches no page enabledSites");
|
|
69
|
+
});
|
|
70
|
+
it("should drop the rule once no entry of a condition is left", () => {
|
|
71
|
+
const { notes, rules } = dropUnreachableRules(blacklist([{ pathname: [/^\/checkout/i, /^\/account/i] }]), reachable);
|
|
72
|
+
expect(rules).to.deep.equal([]);
|
|
73
|
+
expect(notes[0].source).to.equal("blacklist[0]");
|
|
74
|
+
});
|
|
75
|
+
it("should keep an unanchored entry that can hit an enabled page", () => {
|
|
76
|
+
const { rules } = dropUnreachableRules(blacklist([{ pathname: [/\.pdf/i] }]), reachable);
|
|
77
|
+
expect(patternsOf(rules)).to.have.lengthOf(1);
|
|
78
|
+
});
|
|
79
|
+
it("should keep an entry when the enabled rules leave the subject free", () => {
|
|
80
|
+
const { rules } = dropUnreachableRules(blacklist([{ host: ["cdn.shop.de"] }]), reachable);
|
|
81
|
+
expect(rules).to.have.lengthOf(1);
|
|
82
|
+
});
|
|
83
|
+
it("should reduce nothing without an including set", () => {
|
|
84
|
+
const { rules } = dropUnreachableRules(blacklist([{ pathname: [/^\/checkout/i] }]), []);
|
|
85
|
+
expect(rules).to.have.lengthOf(1);
|
|
86
|
+
});
|
|
87
|
+
it("should not read an assertion as an exclusion", () => {
|
|
88
|
+
// `(\/|$)` has no automaton. Reading it as "never matches" would make
|
|
89
|
+
// /shop/x look unreachable, and the guard would vanish from the query.
|
|
90
|
+
const { rules } = dropUnreachableRules(blacklist([{ pathname: [/^\/shop\/x/i] }]), [enabled([{ pathname: [/^\/shop(\/|$)/i] }])]);
|
|
91
|
+
expect(rules).to.have.lengthOf(1);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("the reduction budget", () => {
|
|
95
|
+
it("should spend the same comparisons on the same input, twice", () => {
|
|
96
|
+
// The budget counts work, not milliseconds: a busier machine must not
|
|
97
|
+
// reduce a config differently than a quiet one.
|
|
98
|
+
const rules = () => blacklist([{ pathname: [/^\/p\/shoes\//i] }, { pathname: [/^\/p\//i] }]);
|
|
99
|
+
const budget = reductionBudget(3);
|
|
100
|
+
const first = absorbRules(rules(), budget);
|
|
101
|
+
const second = absorbRules(rules(), reductionBudget(3));
|
|
102
|
+
expect(first.rules.map(({ source }) => source)).to.deep.equal(second.rules.map(({ source }) => source));
|
|
103
|
+
expect(first.notes).to.deep.equal(second.notes);
|
|
104
|
+
});
|
|
105
|
+
const spent = () => reductionBudget(0);
|
|
106
|
+
it("should keep every rule once the budget is gone", () => {
|
|
107
|
+
const { notes, rules } = absorbRules(blacklist([{ pathname: [/^\/p\/shoes\//i] }, { pathname: [/^\/p\//i] }]), spent());
|
|
108
|
+
expect(rules).to.have.lengthOf(2);
|
|
109
|
+
expect(notes[0].reason).to.contain("stopped after");
|
|
110
|
+
expect(notes[0].reason).to.contain("comparisons");
|
|
111
|
+
});
|
|
112
|
+
it("should keep every condition entry once the budget is gone", () => {
|
|
113
|
+
const { rules } = dropUnreachableRules(blacklist([{ pathname: [/^\/checkout/i] }]), [enabled([{ pathname: [/^\/p\//i] }])], spent());
|
|
114
|
+
expect(rules).to.have.lengthOf(1);
|
|
115
|
+
expect(patternsOf(rules)).to.have.lengthOf(1);
|
|
116
|
+
});
|
|
117
|
+
it("should report no finding once the budget is gone", () => {
|
|
118
|
+
expect(findShadowedRules(enabled([{ pathname: [/^\/p\/shoes\//i] }]), blacklist([{ pathname: [/^\/p\//i] }]), spent())).to.deep.equal([]);
|
|
119
|
+
});
|
|
120
|
+
it("should reduce normally with comparisons left", () => {
|
|
121
|
+
const { rules } = absorbRules(blacklist([{ pathname: [/^\/p\/shoes\//i] }, { pathname: [/^\/p\//i] }]), reductionBudget(100));
|
|
122
|
+
expect(rules).to.have.lengthOf(1);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
describe("findShadowedRules", () => {
|
|
126
|
+
it("should report an enabled rule an excluding rule covers", () => {
|
|
127
|
+
const notes = findShadowedRules(enabled([{ pathname: [/^\/p\/shoes\//i] }]), blacklist([{ pathname: [/^\/p\//i] }]));
|
|
128
|
+
expect(notes).to.have.lengthOf(1);
|
|
129
|
+
expect(notes[0].source).to.equal("enabledSites[0]");
|
|
130
|
+
expect(notes[0].reason).to.contain("blacklist[0]");
|
|
131
|
+
});
|
|
132
|
+
it("should say nothing about a rule that is only narrowed", () => {
|
|
133
|
+
const notes = findShadowedRules(enabled([{ pathname: [/^\/p\//i] }]), blacklist([{ pathname: [/^\/p\/shoes\//i] }]));
|
|
134
|
+
expect(notes).to.deep.equal([]);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe("disabledScopes as rules", () => {
|
|
138
|
+
it("should read a scope as a case-sensitive path prefix", () => {
|
|
139
|
+
const { disabledScopes } = rulesOf({ disabledScopes: ["/checkout"] });
|
|
140
|
+
expect(disabledScopes).to.have.lengthOf(1);
|
|
141
|
+
expect(patternsOf(disabledScopes)).to.deep.equal(["/^\\/checkout/"]);
|
|
142
|
+
expect(disabledScopes[0].source).to.equal("disabledScopes[0]");
|
|
143
|
+
});
|
|
144
|
+
it("should escape a scope that reads as a pattern", () => {
|
|
145
|
+
const { disabledScopes } = rulesOf({ disabledScopes: ["/a.b"] });
|
|
146
|
+
expect(patternsOf(disabledScopes)).to.deep.equal([String.raw `/^\/a\.b/`]);
|
|
147
|
+
});
|
|
148
|
+
it("should ignore entries that are not a prefix", () => {
|
|
149
|
+
const { disabledScopes } = rulesOf({
|
|
150
|
+
disabledScopes: ["/cart", "", null, 7],
|
|
151
|
+
});
|
|
152
|
+
expect(disabledScopes).to.have.lengthOf(1);
|
|
153
|
+
});
|
|
154
|
+
it("should return nothing when the property is missing", () => {
|
|
155
|
+
expect(rulesOf({}).disabledScopes).to.deep.equal([]);
|
|
156
|
+
});
|
|
157
|
+
it("should cover the sibling paths a service worker scope covers", () => {
|
|
158
|
+
// A scope is a plain prefix, so `/checkout` claims `/checkoutTunnel` too
|
|
159
|
+
// and a page type living there can never be accelerated.
|
|
160
|
+
const notes = findShadowedRules(enabled([{ pathname: [/^\/checkoutTunnel/] }]), rulesOf({ disabledScopes: ["/checkout"] }).disabledScopes);
|
|
161
|
+
expect(notes).to.have.lengthOf(1);
|
|
162
|
+
expect(notes[0].reason).to.contain("disabledScopes[0]");
|
|
163
|
+
});
|
|
164
|
+
it("should not claim a path that only differs in case", () => {
|
|
165
|
+
// Scope matching is case-sensitive, unlike a string condition.
|
|
166
|
+
const notes = findShadowedRules(enabled([{ pathname: [/^\/Checkout/] }]), rulesOf({ disabledScopes: ["/checkout"] }).disabledScopes);
|
|
167
|
+
expect(notes).to.deep.equal([]);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { parseRuleSets } from "../rule-parser.js";
|
|
4
|
+
import { keepRelevantRules } from "../rule-relevance.js";
|
|
5
|
+
function blacklist(...rules) {
|
|
6
|
+
return parseRuleSets({ blacklist: rules }).blacklist;
|
|
7
|
+
}
|
|
8
|
+
function blacklistV2(...rules) {
|
|
9
|
+
return parseRuleSets({ blacklist: rules, ruleSetConfig: { version: 2 } })
|
|
10
|
+
.blacklist;
|
|
11
|
+
}
|
|
12
|
+
describe("keepRelevantRules content types", () => {
|
|
13
|
+
it("should drop a rule that only applies to other content types", () => {
|
|
14
|
+
const result = keepRelevantRules(blacklist({ contentType: ["image", "script"], pathname: "/media" }));
|
|
15
|
+
expect(result.rules).to.deep.equal([]);
|
|
16
|
+
expect(result.dropped[0].reason).to.contain("image, script");
|
|
17
|
+
});
|
|
18
|
+
it("should keep a rule that also applies to documents", () => {
|
|
19
|
+
const result = keepRelevantRules(blacklist({ contentType: ["document"], pathname: "/checkout" }));
|
|
20
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
21
|
+
});
|
|
22
|
+
it("should keep a rule without a content type", () => {
|
|
23
|
+
const result = keepRelevantRules(blacklist({ pathname: "/checkout" }));
|
|
24
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("keepRelevantRules criteria outside the url", () => {
|
|
28
|
+
it("should drop a rule that matches on a cookie only", () => {
|
|
29
|
+
const result = keepRelevantRules(blacklist({ cookie: /loggedIn/ }));
|
|
30
|
+
expect(result.rules).to.deep.equal([]);
|
|
31
|
+
expect(result.dropped[0].reason).to.contain("cookie");
|
|
32
|
+
});
|
|
33
|
+
it("should keep a rule with url conditions and report it as approximated", () => {
|
|
34
|
+
const result = keepRelevantRules(blacklist({ cookie: /loggedIn/, pathname: "/account" }));
|
|
35
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
36
|
+
expect(result.approximated).to.have.lengthOf(1);
|
|
37
|
+
expect(result.approximated[0].reason).to.contain("wider than the rule");
|
|
38
|
+
});
|
|
39
|
+
it("should not report a plain rule as approximated", () => {
|
|
40
|
+
const result = keepRelevantRules(blacklist({ pathname: "/account" }));
|
|
41
|
+
expect(result.approximated).to.deep.equal([]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("keepRelevantRules hosts", () => {
|
|
45
|
+
const hosts = ["www.shop.de", "m.shop.de"];
|
|
46
|
+
it("should drop a rule for a host the query does not cover", () => {
|
|
47
|
+
const result = keepRelevantRules(blacklist({ host: ["cdn.tracking.com"] }), { hosts });
|
|
48
|
+
expect(result.rules).to.deep.equal([]);
|
|
49
|
+
expect(result.dropped[0].reason).to.contain("none of the queried origins");
|
|
50
|
+
});
|
|
51
|
+
it("should keep a rule for a covered host", () => {
|
|
52
|
+
const result = keepRelevantRules(blacklist({ host: ["m.shop.de"] }), {
|
|
53
|
+
hosts,
|
|
54
|
+
});
|
|
55
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
56
|
+
});
|
|
57
|
+
it("should keep a rule whose host pattern matches", () => {
|
|
58
|
+
const result = keepRelevantRules(blacklist({ host: [/shop\.de$/i] }), {
|
|
59
|
+
hosts,
|
|
60
|
+
});
|
|
61
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
62
|
+
});
|
|
63
|
+
it("should treat a host string as a prefix under version 1", () => {
|
|
64
|
+
const result = keepRelevantRules(blacklist({ host: ["www.shop"] }), {
|
|
65
|
+
hosts,
|
|
66
|
+
});
|
|
67
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
68
|
+
});
|
|
69
|
+
it("should treat a host string as exact under version 2", () => {
|
|
70
|
+
const result = keepRelevantRules(blacklistV2({ host: ["www.shop"] }), {
|
|
71
|
+
hosts,
|
|
72
|
+
});
|
|
73
|
+
expect(result.rules).to.deep.equal([]);
|
|
74
|
+
});
|
|
75
|
+
it("should not be confused by a global pattern matched twice", () => {
|
|
76
|
+
const rules = blacklist({ host: [/shop\.de/g] }, { host: [/shop\.de/g] });
|
|
77
|
+
const result = keepRelevantRules(rules, { hosts });
|
|
78
|
+
expect(result.rules).to.have.lengthOf(2);
|
|
79
|
+
});
|
|
80
|
+
it("should keep a rule without a host condition", () => {
|
|
81
|
+
const result = keepRelevantRules(blacklist({ pathname: "/a" }), { hosts });
|
|
82
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
83
|
+
});
|
|
84
|
+
it("should skip the check when no hosts are given", () => {
|
|
85
|
+
const result = keepRelevantRules(blacklist({ host: ["cdn.other.com"] }));
|
|
86
|
+
expect(result.rules).to.have.lengthOf(1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { column, formatPredicate, isFalse, isTrue, leaf, } from "../../sql/index.js";
|
|
4
|
+
import { parseRuleSets } from "../rule-parser.js";
|
|
5
|
+
import { translateRules, urlSubjects } from "../rule-to-sql.js";
|
|
6
|
+
const subjects = urlSubjects(column("url"));
|
|
7
|
+
function enabledSites(rules, version) {
|
|
8
|
+
return parseRuleSets({
|
|
9
|
+
enabledSites: rules,
|
|
10
|
+
...(version ? { ruleSetConfig: { version } } : {}),
|
|
11
|
+
}).enabledSites;
|
|
12
|
+
}
|
|
13
|
+
function sql(rules, version) {
|
|
14
|
+
return formatPredicate(translateRules(enabledSites(rules, version), subjects).predicate);
|
|
15
|
+
}
|
|
16
|
+
describe("urlSubjects", () => {
|
|
17
|
+
it("should extract the host", () => {
|
|
18
|
+
expect(subjects.host).to.equal("url_extract_host(url)");
|
|
19
|
+
});
|
|
20
|
+
it("should read an empty path as the root, like the browser does", () => {
|
|
21
|
+
expect(subjects.path).to.equal("coalesce(nullif(url_extract_path(url), ''), '/')");
|
|
22
|
+
});
|
|
23
|
+
it("should append the query string for the version 1 pathname subject", () => {
|
|
24
|
+
expect(subjects.pathWithSearch).to.equal(`(${subjects.path} || coalesce(nullif(('?' || url_extract_query(url)), '?'), ''))`);
|
|
25
|
+
});
|
|
26
|
+
it("should strip fragment and scheme from the url subject", () => {
|
|
27
|
+
expect(subjects.url).to.equal(String.raw `regexp_replace(regexp_replace(url, '#.*', ''), '^(\w+:)?//', '')`);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe("translateRules string conditions", () => {
|
|
31
|
+
it("should match a pathname as a prefix under version 1", () => {
|
|
32
|
+
expect(sql([{ pathname: "/shop" }])).to.equal(`lower(${subjects.pathWithSearch}) like '/shop%'`);
|
|
33
|
+
});
|
|
34
|
+
it("should match a pathname exactly under version 2", () => {
|
|
35
|
+
expect(sql([{ pathname: "/shop" }], 2)).to.equal(`lower(${subjects.path}) = '/shop'`);
|
|
36
|
+
});
|
|
37
|
+
it("should ignore the case of a host condition", () => {
|
|
38
|
+
expect(sql([{ host: "WWW.Shop.de" }], 2)).to.equal(`lower(${subjects.host}) = 'www.shop.de'`);
|
|
39
|
+
});
|
|
40
|
+
it("should match a url condition against the stripped url", () => {
|
|
41
|
+
// `stripForRules` takes the scheme off, so the config writes the URL
|
|
42
|
+
// without one — and so does the comparison.
|
|
43
|
+
expect(sql([{ url: "www.shop.de/" }], 2)).to.equal(`lower(${subjects.url}) = 'www.shop.de/'`);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("translateRules regular expressions", () => {
|
|
47
|
+
it("should move the i flag into the pattern", () => {
|
|
48
|
+
expect(sql([{ pathname: [/^\/p\//i] }], 2)).to.equal(`regexp_like(${subjects.path}, '(?i)^/p/')`);
|
|
49
|
+
});
|
|
50
|
+
it("should match a version 1 pathname pattern against path and query", () => {
|
|
51
|
+
expect(sql([{ pathname: [/\/$/i] }])).to.equal(`regexp_like(${subjects.pathWithSearch}, '(?i)/$')`);
|
|
52
|
+
});
|
|
53
|
+
it("should report a warning with the rule it came from", () => {
|
|
54
|
+
const { warnings } = translateRules(enabledSites([{ pathname: [/(?<=\/de)\/p\//i] }]), subjects);
|
|
55
|
+
expect(warnings).to.have.lengthOf(1);
|
|
56
|
+
expect(warnings[0]).to.contain("enabledSites[0]");
|
|
57
|
+
expect(warnings[0]).to.contain("lookbehind");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("translateRules structure", () => {
|
|
61
|
+
it("should or the entries of a condition", () => {
|
|
62
|
+
expect(sql([{ pathname: ["/a", "/b"] }], 2)).to.equal([
|
|
63
|
+
`lower(${subjects.path}) = '/a'`,
|
|
64
|
+
`or lower(${subjects.path}) = '/b'`,
|
|
65
|
+
].join("\n"));
|
|
66
|
+
});
|
|
67
|
+
it("should and the conditions of a rule", () => {
|
|
68
|
+
expect(sql([{ host: "www.shop.de", pathname: "/a" }], 2)).to.equal([
|
|
69
|
+
`lower(${subjects.host}) = 'www.shop.de'`,
|
|
70
|
+
`and lower(${subjects.path}) = '/a'`,
|
|
71
|
+
].join("\n"));
|
|
72
|
+
});
|
|
73
|
+
it("should or the rules of a set, keeping each rule's conditions together", () => {
|
|
74
|
+
expect(sql([
|
|
75
|
+
{ host: "www.shop.de", pathname: "/de/" },
|
|
76
|
+
{ host: "www.shop.fr", pathname: "/fr/" },
|
|
77
|
+
], 2)).to.equal([
|
|
78
|
+
"(",
|
|
79
|
+
` lower(${subjects.host}) = 'www.shop.de'`,
|
|
80
|
+
` and lower(${subjects.path}) = '/de/'`,
|
|
81
|
+
")",
|
|
82
|
+
"or (",
|
|
83
|
+
` lower(${subjects.host}) = 'www.shop.fr'`,
|
|
84
|
+
` and lower(${subjects.path}) = '/fr/'`,
|
|
85
|
+
")",
|
|
86
|
+
].join("\n"));
|
|
87
|
+
});
|
|
88
|
+
it("should match nothing for a rule without conditions", () => {
|
|
89
|
+
// The runtime's AndRule returns false when it holds no condition.
|
|
90
|
+
expect(isFalse(translateRules(enabledSites([{}]), subjects).predicate)).to.equal(true);
|
|
91
|
+
});
|
|
92
|
+
it("should match nothing for a rule whose condition is empty", () => {
|
|
93
|
+
// `host: hasWebPSupport ? [] : [...]` is a real config. Reading the empty
|
|
94
|
+
// list as "any host" would make a blacklist exclude everything.
|
|
95
|
+
expect(isFalse(translateRules(enabledSites([{ host: [] }]), subjects).predicate)).to.equal(true);
|
|
96
|
+
});
|
|
97
|
+
it("should keep a rule whose other condition is empty out of the way", () => {
|
|
98
|
+
const { predicate } = translateRules(enabledSites([{ host: [], pathname: [/^\/p\//i] }]), subjects);
|
|
99
|
+
expect(isFalse(predicate)).to.equal(true);
|
|
100
|
+
});
|
|
101
|
+
it("should be true for an empty rule set by default", () => {
|
|
102
|
+
expect(isTrue(translateRules([], subjects).predicate)).to.equal(true);
|
|
103
|
+
});
|
|
104
|
+
it("should use the given meaning of an empty rule set", () => {
|
|
105
|
+
const { predicate } = translateRules([], subjects, {
|
|
106
|
+
whenEmpty: leaf("a"),
|
|
107
|
+
});
|
|
108
|
+
expect(formatPredicate(predicate)).to.equal("a");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|