mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 19:05:45 +00:00
72 lines
2.3 KiB
TypeScript
72 lines
2.3 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
|
|
import canonicalize from "canonicalize";
|
|
|
|
import type {
|
|
BrowserScenarioArtifactKind,
|
|
BrowserScenarioDiff,
|
|
BrowserScenarioNormalization,
|
|
BrowserScenarioNormalizationRule,
|
|
} from "./browserScenarioDiffValues.js";
|
|
|
|
const BUILT_IN_RULES = [
|
|
"ignore_step_elapsed_ms",
|
|
"ignore_event_sequence",
|
|
"ignore_event_step_index",
|
|
"compare_screenshot_content_digest",
|
|
"compare_normalized_text_content",
|
|
] as const;
|
|
|
|
/** Canonically order and commit a parsed browser scenario normalization policy. */
|
|
export const commitBrowserScenarioNormalization = (
|
|
input: BrowserScenarioNormalization,
|
|
): BrowserScenarioDiff["normalization"] => {
|
|
const rules = [...input.rules]
|
|
.map((rule) => ({
|
|
...rule,
|
|
artifacts: [...rule.artifacts].sort(),
|
|
}))
|
|
.sort((left, right) => left.rule_id.localeCompare(right.rule_id));
|
|
const commitment = { built_in_rules: [...BUILT_IN_RULES], rules };
|
|
return { ...commitment, sha256: digestCanonicalJson(commitment) };
|
|
};
|
|
|
|
/** Digest one artifact after applying committed exact-literal rules. */
|
|
export const digestNormalizedScenarioValue = (
|
|
value: unknown,
|
|
artifact: BrowserScenarioArtifactKind,
|
|
rules: readonly BrowserScenarioNormalizationRule[],
|
|
): string => digestCanonicalJson(normalize(value, artifact, rules));
|
|
|
|
/** Compute a canonical SHA-256 commitment for JSON-compatible domain data. */
|
|
export const digestCanonicalJson = (value: unknown): string => {
|
|
const encoded = canonicalize(value);
|
|
if (encoded === undefined) throw new TypeError("Expected canonical JSON");
|
|
return createHash("sha256").update(encoded).digest("hex");
|
|
};
|
|
|
|
const normalize = (
|
|
value: unknown,
|
|
artifact: BrowserScenarioArtifactKind,
|
|
rules: readonly BrowserScenarioNormalizationRule[],
|
|
): unknown => {
|
|
if (artifact === "screenshot") return value;
|
|
if (typeof value === "string")
|
|
return rules
|
|
.filter(({ artifacts }) => artifacts.includes(artifact))
|
|
.reduce(
|
|
(normalized, rule) =>
|
|
normalized.split(rule.match).join(rule.replacement),
|
|
value,
|
|
);
|
|
if (Array.isArray(value))
|
|
return value.map((item) => normalize(item, artifact, rules));
|
|
if (typeof value !== "object" || value === null) return value;
|
|
return Object.fromEntries(
|
|
Object.entries(value).map(([key, item]) => [
|
|
key,
|
|
normalize(item, artifact, rules),
|
|
]),
|
|
);
|
|
};
|