-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow optional rewrite rules before computing similarity scores durin…
…g replay (#499) In order to be able to replay the correct tape more accurately, we want to be able to apply some rewrite rules before the similarity score is computed between a request and a recorded tape. The intent of these rewrite rules is to remove random values in both the request and the recorded tapes, so that the tapes match exactly (once the random data has been removed). This PR adds a new varadic CLI argument to allow users to provide zero to many regex-based rewrite rules using sed-style replacement syntax. These regex replacements are performed over all strings that go through similarity scoring.
- Loading branch information
1 parent
a838b21
commit f273960
Showing
7 changed files
with
473 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { RewriteRule, RewriteRules } from "./rewrite"; | ||
|
||
describe("RewriteRule", () => { | ||
describe("without capture groups", () => { | ||
it("applies the expected changes", () => { | ||
const rule = new RewriteRule(/-[a-z0-9]+-?/gi, ""); | ||
expect(rule.apply("")).toEqual(""); | ||
expect(rule.apply("chicken")).toEqual("chicken"); | ||
expect(rule.apply("one-TWO-thRee-fOUr")).toEqual("onethRee"); | ||
}); | ||
}); | ||
|
||
describe("with capture groups", () => { | ||
it("applies the expected changes", () => { | ||
const rule1 = new RewriteRule(/\$([0-9]+)(\.[0-9]+)?/g, "£$1"); | ||
expect(rule1.apply("")).toEqual(""); | ||
expect(rule1.apply("chicken")).toEqual("chicken"); | ||
expect(rule1.apply("They are $5, $7.90, and $1024.9876.")).toEqual( | ||
"They are £5, £7, and £1024." | ||
); | ||
|
||
const rule2 = new RewriteRule( | ||
/-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(@example.com)$/gi, | ||
"$1" | ||
); | ||
expect( | ||
rule2.apply( | ||
"jane.doe-some-test-6f82fbbe-d36a-4c5c-b47b-84100122fbbc@example.com" | ||
) | ||
).toEqual("[email protected]"); | ||
expect( | ||
rule2.apply( | ||
"jane.doe-some-test-6F82FBBE-D36A-4C5C-B47B-84100122FBBC@example.com" | ||
) | ||
).toEqual("[email protected]"); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("RewriteRules", () => { | ||
describe("when there are no rules", () => { | ||
const rules = new RewriteRules(); | ||
|
||
it("does not mutate the given value at all", () => { | ||
expect(rules.apply("chicken")).toEqual("chicken"); | ||
}); | ||
}); | ||
|
||
describe("when there is a simple rewrite rule", () => { | ||
const rules = new RewriteRules().appendRule(new RewriteRule(/dog/, "cat")); | ||
|
||
it("applies the expected changes on basic data types", () => { | ||
expect(rules.apply("The dog dodges the doggie.")).toEqual( | ||
"The cat dodges the doggie." | ||
); | ||
expect(rules.apply(null)).toEqual(null); | ||
expect(rules.apply(undefined)).toEqual(undefined); | ||
expect(rules.apply(123.4)).toEqual(123.4); | ||
expect(rules.apply(true)).toEqual(true); | ||
}); | ||
|
||
it("applies the expected changes on arrays", () => { | ||
const a1 = ["cat", "dog", "fish", "bird"]; | ||
expect(rules.apply(a1)).toEqual(["cat", "cat", "fish", "bird"]); | ||
expect(a1).toEqual(["cat", "dog", "fish", "bird"]); | ||
expect(rules.apply([])).toEqual([]); | ||
}); | ||
|
||
it("applies the expected changes on objects", () => { | ||
const o1 = { dog: "woof", doggie: "wuff", xyz: "I hate dogs" }; | ||
expect(rules.apply(o1)).toEqual({ | ||
cat: "woof", | ||
catgie: "wuff", | ||
xyz: "I hate cats", | ||
}); | ||
expect(o1).toEqual({ dog: "woof", doggie: "wuff", xyz: "I hate dogs" }); | ||
expect(rules.apply({})).toEqual({}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
export class RewriteRule { | ||
find: RegExp; | ||
replace: string; | ||
|
||
constructor(find: RegExp, replace: string) { | ||
this.find = find; | ||
this.replace = replace; | ||
} | ||
|
||
apply(value: string): string { | ||
return value.replace(this.find, this.replace); | ||
} | ||
} | ||
|
||
export class RewriteRules { | ||
private rules: RewriteRule[]; | ||
|
||
constructor() { | ||
this.rules = []; | ||
} | ||
|
||
appendRule(rule: RewriteRule): RewriteRules { | ||
this.rules.push(rule); | ||
return this; | ||
} | ||
|
||
apply<T>(value: T): T { | ||
// Bail early if we have no rules to apply. | ||
if (this.rules.length === 0) { | ||
return value; | ||
} | ||
|
||
return this._apply(value); | ||
} | ||
|
||
private _apply<T>(value: T): T { | ||
if (typeof value === "object" && value !== null) { | ||
// If the object is an array, iterate through each element and call the function recursively | ||
if (Array.isArray(value)) { | ||
return (value.map((v) => this._apply(v)) as any) as T; | ||
} | ||
|
||
// If the object is not an array, create a new object with the same keys, | ||
// and call the function recursively on each value | ||
const oldObj = value as { [key: string]: any }; | ||
const newObj: { [key: string]: any } = {}; | ||
for (const key of Object.keys(oldObj)) { | ||
const newKey = this._apply(key); | ||
const newValue = this._apply(oldObj[key]); | ||
newObj[newKey] = newValue; | ||
} | ||
return newObj as T; | ||
} else if (typeof value === "string") { | ||
let s = value as string; | ||
for (const rule of this.rules) { | ||
s = rule.apply(value); | ||
} | ||
return (s as any) as T; | ||
} else { | ||
return value; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.