Files
clawsec/skills/clawsec-suite/test/fuzz_semver_scope_suppression.test.mjs
T
davida-ps 938eb929f3 feat: add property-based fuzz tests for advisory parsing, semver matc… (#69)
* feat: add property-based fuzz tests for advisory parsing, semver matching, and suppression config

* fix(ci): install deps before fuzz test jobs
2026-02-25 17:48:48 +02:00

138 lines
4.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Property-based fuzz tests for semver matching, advisory scope, and suppression matching.
*
* Run: node skills/clawsec-suite/test/fuzz_semver_scope_suppression.test.mjs
*/
import assert from "node:assert/strict";
import fc from "fast-check";
import { advisoryAppliesToOpenclaw } from "../hooks/clawsec-advisory-guardian/lib/advisory_scope.mjs";
import { isAdvisorySuppressed } from "../hooks/clawsec-advisory-guardian/lib/suppression.mjs";
import { compareSemver, parseSemver, versionMatches } from "../hooks/clawsec-advisory-guardian/lib/version.mjs";
const semverCoreArb = fc.tuple(
fc.integer({ min: 0, max: 999 }),
fc.integer({ min: 0, max: 999 }),
fc.integer({ min: 0, max: 999 }),
);
const semverArb = semverCoreArb.map(([major, minor, patch]) => `${major}.${minor}.${patch}`);
const idArb = fc.string({ minLength: 1, maxLength: 24 });
const skillArb = fc.string({ minLength: 1, maxLength: 24 });
function runSemverProperties() {
fc.assert(
fc.property(semverCoreArb, ([major, minor, patch]) => {
const version = `v${major}.${minor}.${patch}`;
assert.deepEqual(parseSemver(version), [major, minor, patch]);
}),
{ numRuns: 250 },
);
fc.assert(
fc.property(semverArb, semverArb, (left, right) => {
const leftVsRight = compareSemver(left, right);
const rightVsLeft = compareSemver(right, left);
assert.notEqual(leftVsRight, null);
assert.notEqual(rightVsLeft, null);
assert.equal(leftVsRight, -rightVsLeft);
}),
{ numRuns: 250 },
);
fc.assert(
fc.property(semverArb, semverArb, (left, right) => {
const compared = compareSemver(left, right);
assert.notEqual(compared, null);
assert.equal(versionMatches(left, `>=${right}`), compared >= 0);
assert.equal(versionMatches(left, `<=${right}`), compared <= 0);
assert.equal(versionMatches(left, `>${right}`), compared > 0);
assert.equal(versionMatches(left, `<${right}`), compared < 0);
assert.equal(versionMatches(left, `=${right}`), compared === 0);
}),
{ numRuns: 250 },
);
}
function runAdvisoryScopeProperties() {
fc.assert(
fc.property(fc.string(), (application) => {
const normalized = application.trim().toLowerCase();
const expected = normalized === "" || normalized === "openclaw" || normalized === "all";
assert.equal(advisoryAppliesToOpenclaw({ application }), expected);
}),
{ numRuns: 250 },
);
fc.assert(
fc.property(fc.array(fc.string(), { maxLength: 8 }), (applications) => {
const normalized = applications
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean);
const expected =
normalized.length === 0 || normalized.includes("openclaw") || normalized.includes("all");
assert.equal(advisoryAppliesToOpenclaw({ application: applications }), expected);
}),
{ numRuns: 250 },
);
assert.equal(advisoryAppliesToOpenclaw({}), true);
assert.equal(advisoryAppliesToOpenclaw({ application: null }), true);
}
function runSuppressionProperties() {
fc.assert(
fc.property(idArb, skillArb, (id, skill) => {
const match = {
advisory: { id },
skill: { name: skill.toUpperCase() },
};
const suppressions = [
{
checkId: id,
skill: skill.toLowerCase(),
reason: "fuzz",
suppressedAt: "2026-02-25",
},
];
assert.equal(isAdvisorySuppressed(match, suppressions), true);
}),
{ numRuns: 250 },
);
fc.assert(
fc.property(idArb, idArb, skillArb, (targetId, otherId, skill) => {
const differentId = targetId === otherId ? `${otherId}-x` : otherId;
const match = {
advisory: { id: targetId },
skill: { name: skill },
};
const suppressions = [
{
checkId: differentId,
skill,
reason: "fuzz",
suppressedAt: "2026-02-25",
},
];
assert.equal(isAdvisorySuppressed(match, suppressions), false);
}),
{ numRuns: 250 },
);
}
try {
console.log("=== ClawSec Semver/Scope/Suppression Fuzz Properties ===\n");
runSemverProperties();
runAdvisoryScopeProperties();
runSuppressionProperties();
console.log("=== Results: all fuzz properties passed ===");
} catch (error) {
console.error("Fuzz property test failed:");
console.error(error);
process.exit(1);
}