139 lines
4.7 KiB
TypeScript
139 lines
4.7 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import * as fc from "fast-check";
|
|
import {
|
|
GrantManager,
|
|
InMemoryPartitionStore,
|
|
KeyRing,
|
|
mergePolicies,
|
|
PartitionManager,
|
|
partitionId,
|
|
TokenManager,
|
|
userId,
|
|
} from "../src/index.js";
|
|
|
|
describe("property — grant expiry", () => {
|
|
it("grant check returns allowed false after advancing clock beyond TTL", {
|
|
timeout: 30000,
|
|
}, () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer({ min: 1, max: 100_000 }),
|
|
fc.integer({ min: 1, max: 100 }),
|
|
(ttl, offset) => {
|
|
let now = 0;
|
|
const m = new GrantManager({ clock: () => now });
|
|
const grant = m.create({
|
|
grantorId: userId("a"),
|
|
granteeId: userId("b"),
|
|
partitionId: partitionId("p"),
|
|
permissions: ["read"],
|
|
expiresAt: ttl,
|
|
});
|
|
now = ttl + offset;
|
|
const r = m.check(grant.id, userId("b"), "read");
|
|
expect(r.allowed).toBe(false);
|
|
}
|
|
),
|
|
{ numRuns: 50 }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("property — token revocation finality", () => {
|
|
it("revoked token validate always returns valid false", {
|
|
timeout: 30000,
|
|
}, () => {
|
|
fc.assert(
|
|
fc.property(fc.integer({ min: 1, max: 100 }), (validateCount) => {
|
|
const m = new TokenManager({
|
|
clock: () => 0,
|
|
masterKey: new Uint8Array(32).fill(0xee),
|
|
});
|
|
const t = m.create({
|
|
partitionId: partitionId("p"),
|
|
createdBy: userId("a"),
|
|
permissions: ["read"],
|
|
});
|
|
m.revoke(t.id);
|
|
for (let i = 0; i < validateCount; i += 1) {
|
|
const r = m.validate(t.id);
|
|
expect(r.valid).toBe(false);
|
|
}
|
|
}),
|
|
{ numRuns: 30 }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("property — policy merge commutativity", () => {
|
|
it("mergePolicies on disjoint allowedActions yields union", {
|
|
timeout: 30000,
|
|
}, () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.uniqueArray(fc.constantFrom("read", "write" as const), {
|
|
minLength: 0,
|
|
maxLength: 2,
|
|
}),
|
|
fc.uniqueArray(fc.constantFrom("delete", "share" as const), {
|
|
minLength: 0,
|
|
maxLength: 2,
|
|
}),
|
|
(a, b) => {
|
|
const aP = {
|
|
encryption: true,
|
|
reauthRequired: [],
|
|
allowedActions: a,
|
|
};
|
|
const bP = {
|
|
encryption: true,
|
|
reauthRequired: [],
|
|
allowedActions: b,
|
|
};
|
|
const ab = mergePolicies(aP, bP);
|
|
const ba = mergePolicies(bP, aP);
|
|
// Same set, order may differ.
|
|
expect(new Set(ab.allowedActions)).toEqual(
|
|
new Set(ba.allowedActions)
|
|
);
|
|
expect(new Set(ab.allowedActions)).toEqual(
|
|
new Set([...a, ...b])
|
|
);
|
|
}
|
|
),
|
|
{ numRuns: 50 }
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("property — partition member invariant", () => {
|
|
it("addMember then removeMember restores member count", {
|
|
timeout: 30000,
|
|
}, async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
fc.string({ minLength: 1, maxLength: 16 }),
|
|
async (name) => {
|
|
const manager = new PartitionManager({
|
|
store: new InMemoryPartitionStore(),
|
|
keyRing: new KeyRing(),
|
|
});
|
|
const p = await manager.create({
|
|
name,
|
|
type: "shared",
|
|
ownerId: userId("alice"),
|
|
});
|
|
const before = p.members.length;
|
|
const after1 = await manager.addMember(p.id, userId("bob"));
|
|
const after2 = await manager.removeMember(
|
|
after1.id,
|
|
userId("bob")
|
|
);
|
|
expect(after2.members.length).toBe(before);
|
|
}
|
|
),
|
|
{ numRuns: 25 }
|
|
);
|
|
});
|
|
});
|