77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import {
|
|
createSourceRange,
|
|
diagnose,
|
|
diagnosticSummary,
|
|
sliceSource,
|
|
supportsSyntaxFeature,
|
|
} from "../src/index.ts";
|
|
|
|
describe("syntax contract and security diagnostics", () => {
|
|
test("validates source ranges and summarizes diagnostic codes", () => {
|
|
const range = createSourceRange(2, 6);
|
|
expect(sliceSource("0123456789", range)).toBe("2345");
|
|
expect(() => createSourceRange(-1, 2)).toThrow(RangeError);
|
|
expect(() => createSourceRange(4, 3)).toThrow(RangeError);
|
|
expect(supportsSyntaxFeature("runtime-markers")).toBe(true);
|
|
expect(supportsSyntaxFeature("dynamic-eval")).toBe(false);
|
|
|
|
const summary = diagnosticSummary([
|
|
{ code: "A", severity: "error", message: "a" },
|
|
{ code: "A", severity: "warning", message: "b" },
|
|
{ code: "B", severity: "info", message: "c" },
|
|
]);
|
|
expect(summary).toEqual({ errors: 1, warnings: 1, info: 1, codes: { A: 2, B: 1 } });
|
|
});
|
|
|
|
test("rejects browser secret reads, executable sinks, and sensitive persistence", () => {
|
|
const diagnostics = diagnose(`page store Unsafe {
|
|
client state { apiToken: string = process.env.API_TOKEN }
|
|
persist { storage = "local" include = ["apiToken"] version = 1 }
|
|
functions {
|
|
client function render(raw: string): void { document.write(raw); setTimeout("run()", 1) }
|
|
}
|
|
}`);
|
|
const codes = diagnostics.map((diagnostic) => diagnostic.code);
|
|
expect(codes).toContain("WRN-SEC-SERVER-SECRET-SOURCE");
|
|
expect(codes).toContain("WRN-SEC-DOM-SINK");
|
|
expect(codes).toContain("WRN-SEC-STRING-TIMER");
|
|
expect(codes).toContain("WRN-PERSIST-SENSITIVE");
|
|
});
|
|
|
|
test.each([
|
|
[
|
|
"server output call",
|
|
`component Bad { outputs { save() } functions { server function run(): void { output.save() } } view { <div></div> } }`,
|
|
"WRN-OUTPUT-SERVER-CALL",
|
|
],
|
|
[
|
|
"unknown output call",
|
|
`component Bad { functions { client function run(): void { output.missing() } } view { <button @click="run()">Run</button> } }`,
|
|
"WRN-OUTPUT-UNKNOWN",
|
|
],
|
|
[
|
|
"server API in client function",
|
|
`component Bad { functions { client function run(): void { process.cwd() } } view { <button @click="run()">Run</button> } }`,
|
|
"WRN-CLIENT-SERVER-API",
|
|
],
|
|
[
|
|
"browser API in server function",
|
|
`component Bad { functions { server function run(): void { document.title = "bad" } } view { <div></div> } }`,
|
|
"WRN-SERVER-BROWSER-API",
|
|
],
|
|
[
|
|
"non-serializable shared state",
|
|
`global store Bad { state { values: unknown = new Map() } }`,
|
|
"WRN-STATE-NON-SERIALIZABLE",
|
|
],
|
|
[
|
|
"unknown persisted state",
|
|
`global store Bad { state { value: string = "x" } persist { storage = "local" include = ["missing"] version = 1 } }`,
|
|
"WRN-PERSIST-UNKNOWN-FIELD",
|
|
],
|
|
])("diagnoses %s", (_name, source, expectedCode) => {
|
|
expect(diagnose(source).map((diagnostic) => diagnostic.code)).toContain(expectedCode);
|
|
});
|
|
});
|