First public release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { overall, validateReport } from '@extant2000/evidence-record'
|
||||
import {
|
||||
claimRule,
|
||||
evaluatePolicy,
|
||||
evaluateRule,
|
||||
formatReport,
|
||||
toReport,
|
||||
inOverflowRule,
|
||||
orgScopingRule,
|
||||
type SourceFile,
|
||||
} from '../src/index.js'
|
||||
|
||||
const f = (path: string, content: string): SourceFile => ({ path, content })
|
||||
|
||||
// An app that scopes rows by tenant columns.
|
||||
const tenantOrg = orgScopingRule({
|
||||
guards: /requireOrg|resolveOrg|assertContactInOrg/,
|
||||
inlineTenantFilter: /\.eq\((['"])(organization_id|org_id|owner_id|user_id)\1/,
|
||||
})
|
||||
|
||||
// An app with no tenant columns at all, using RBAC capability checks instead.
|
||||
const rbacOrg = orgScopingRule({
|
||||
serviceClients: /createServiceClient|createClient\(/,
|
||||
guards: /requireAuth|requireProjectCap|requireAdminCap|getRequestClient/,
|
||||
})
|
||||
|
||||
describe('orgScopingRule — the divergence that made this a library', () => {
|
||||
it('tenant-column app: a service-role handler with no guard is a violation', () => {
|
||||
const r = evaluateRule(tenantOrg, [
|
||||
f('server/api/x.ts', 'const s = createServiceClient()\ns.from("t").select()'),
|
||||
])
|
||||
expect(r.violations).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('tenant-column app: an inline tenant filter satisfies it', () => {
|
||||
const r = evaluateRule(tenantOrg, [
|
||||
f('server/api/x.ts', `const s = createServiceClient()\ns.from("t").select().eq('org_id', id)`),
|
||||
])
|
||||
expect(r.violations).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('RBAC app: a capability check satisfies it, with NO tenant column', () => {
|
||||
// This is why one shared script could not work. The RBAC app has no org_id
|
||||
// anywhere; under the tenant-column rule this same file would be a false
|
||||
// violation.
|
||||
const rbacFile = f('server/api/y.ts',
|
||||
'await requireProjectCap(event, id, "view")\nconst s = createServiceClient()')
|
||||
expect(evaluateRule(rbacOrg, [rbacFile]).violations).toHaveLength(0)
|
||||
expect(evaluateRule(tenantOrg, [rbacFile]).violations).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('a documented exemption excuses the file and is counted', () => {
|
||||
const r = evaluateRule(tenantOrg, [
|
||||
f('server/api/cron.ts', 'createServiceClient()\n// org-scoping-exempt: nightly cron, cross-tenant by design'),
|
||||
])
|
||||
expect(r.violations).toHaveLength(0)
|
||||
expect(r.exempted).toBe(1)
|
||||
})
|
||||
|
||||
it('ignores files outside the path scope', () => {
|
||||
const r = evaluateRule(tenantOrg, [f('app/pages/x.vue', 'createServiceClient()')])
|
||||
expect(r.considered).toBe(0)
|
||||
expect(r.violations).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('ignores a file that never touches a service-role client', () => {
|
||||
const r = evaluateRule(tenantOrg, [f('server/api/x.ts', 'const s = getRequestClient(event)')])
|
||||
expect(r.considered).toBe(1)
|
||||
expect(r.subject).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('inOverflowRule', () => {
|
||||
const rule = inOverflowRule({ chunkHelpers: /selectInChunks/ })
|
||||
|
||||
it('flags a dynamic .in() with no chunking', () => {
|
||||
const r = evaluateRule(rule, [f('server/x.ts', `supabase.from('t').select().in('id', ids)`)])
|
||||
expect(r.violations).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('accepts a chunked call', () => {
|
||||
const r = evaluateRule(rule, [
|
||||
f('server/x.ts', `selectInChunks(ids, p => supabase.from('t').select().in('id', p))`),
|
||||
])
|
||||
expect(r.violations).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('does NOT flag a literal array — that list is bounded', () => {
|
||||
const r = evaluateRule(rule, [f('server/x.ts', `.in('status', ['a','b'])`)])
|
||||
expect(r.subject).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('claimRule — advertised == implemented', () => {
|
||||
// The classic gap: uptime or SLA language in marketing copy with nothing
|
||||
// in the code that enforces it.
|
||||
const rule = claimRule({
|
||||
id: 'uptime-claim',
|
||||
claimPaths: ['src/views/', 'app/pages/'],
|
||||
claim: /99\.\d+%\s*uptime|uptime\s*SLA/i,
|
||||
enforcedBy: /uptimeCredit|slaEnforcer/,
|
||||
})
|
||||
|
||||
it('flags an uptime claim with no enforcement', () => {
|
||||
const r = evaluateRule(rule, [f('src/views/Pricing.vue', '<p>99.9% uptime SLA</p>')])
|
||||
expect(r.violations).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('passes when enforcement is present', () => {
|
||||
const r = evaluateRule(rule, [
|
||||
f('src/views/Pricing.vue', '<p>99.9% uptime SLA</p>\nimport { uptimeCredit } from "~/lib"'),
|
||||
])
|
||||
expect(r.violations).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('regex state safety', () => {
|
||||
it('a global-flag rule does not skip files via lastIndex', () => {
|
||||
// A `g` regex carries lastIndex between .test() calls. Reusing one across
|
||||
// files makes later files silently pass — a green gate covering nothing.
|
||||
const sticky = orgScopingRule({
|
||||
serviceClients: /createServiceClient/g,
|
||||
guards: /requireOrg/g,
|
||||
})
|
||||
const r = evaluateRule(sticky, [
|
||||
f('server/api/a.ts', 'createServiceClient()'),
|
||||
f('server/api/b.ts', 'createServiceClient()'),
|
||||
f('server/api/c.ts', 'createServiceClient()'),
|
||||
])
|
||||
expect(r.violations).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('evaluatePolicy + formatReport', () => {
|
||||
it('distinguishes "nothing was checked" from a real pass', () => {
|
||||
// "nothing changed that this covers" is not "the policy holds".
|
||||
const nothing = evaluatePolicy([tenantOrg], [f('server/api/x.ts', 'export default 1')])
|
||||
expect(nothing.passed).toBe(true)
|
||||
expect(nothing.vacuous).toBe(true)
|
||||
expect(overall(toReport(nothing))).toBe('not-assessed')
|
||||
const nothingText = formatReport(nothing)
|
||||
expect(nothingText).toContain('NOT ASSESSED')
|
||||
expect(nothingText).toContain('not evidence the policy holds')
|
||||
// The word it replaced needed a glossary.
|
||||
expect(nothingText).not.toContain('VACUOUS')
|
||||
|
||||
const real = evaluatePolicy([tenantOrg], [
|
||||
f('server/api/x.ts', `createServiceClient()\n.eq('org_id', o)`),
|
||||
])
|
||||
expect(real.passed).toBe(true)
|
||||
expect(real.vacuous).toBe(false)
|
||||
expect(overall(toReport(real))).toBe('pass')
|
||||
})
|
||||
|
||||
it('a pass cites the files it actually checked', () => {
|
||||
// A gate that reports "3 files checked" without naming them is an
|
||||
// asserted result, which is the defect this library exists to find.
|
||||
const real = evaluatePolicy([tenantOrg], [
|
||||
f('server/api/x.ts', `createServiceClient()\n.eq('org_id', o)`),
|
||||
])
|
||||
expect(toReport(real).findings[0]!.evidence.length).toBeGreaterThan(0)
|
||||
expect(formatReport(real, { evidence: 'full' })).toContain('server/api/x.ts')
|
||||
})
|
||||
|
||||
it('aggregates violations across rules and prints remedies', () => {
|
||||
const rep = evaluatePolicy([tenantOrg, inOverflowRule({ chunkHelpers: /selectInChunks/ })], [
|
||||
f('server/api/a.ts', 'createServiceClient()'),
|
||||
f('server/b.ts', `.in('id', ids)`),
|
||||
])
|
||||
expect(rep.passed).toBe(false)
|
||||
expect(rep.violations).toHaveLength(2)
|
||||
const text = formatReport(rep, { evidence: 'full' })
|
||||
expect(text).toContain('org-scoping')
|
||||
expect(text).toContain('in-overflow')
|
||||
expect(text).toContain('org-scoping-exempt:')
|
||||
expect(overall(toReport(rep))).toBe('fail')
|
||||
})
|
||||
|
||||
it('cites the line that triggered the rule, not just the file', () => {
|
||||
const rep = evaluatePolicy([tenantOrg], [
|
||||
f('server/api/a.ts', 'const x = 1\nconst y = 2\ncreateServiceClient()'),
|
||||
])
|
||||
expect(rep.violations[0]!.line).toBe(3)
|
||||
expect(rep.violations[0]!.excerpt).toBe('createServiceClient()')
|
||||
expect(formatReport(rep)).toContain('server/api/a.ts:3')
|
||||
})
|
||||
|
||||
it('counts exemptions as evidence rather than hiding them', () => {
|
||||
const rep = evaluatePolicy([tenantOrg], [
|
||||
f('server/api/a.ts', 'createServiceClient() // org-scoping-exempt: cross-tenant cron'),
|
||||
])
|
||||
expect(rep.passed).toBe(true)
|
||||
const text = formatReport(rep, { evidence: 'full' })
|
||||
expect(text).toContain('exempted via')
|
||||
expect(text).toContain('server/api/a.ts')
|
||||
})
|
||||
|
||||
it('every conclusion carries a citation', () => {
|
||||
const rep = evaluatePolicy([tenantOrg, inOverflowRule({ chunkHelpers: /selectInChunks/ })], [
|
||||
f('server/api/a.ts', 'createServiceClient()'),
|
||||
f('server/b.ts', `.in('id', ids)`),
|
||||
])
|
||||
expect(validateReport(toReport(rep))).toEqual([])
|
||||
})
|
||||
|
||||
it('an empty file set is vacuous, never a pass claim', () => {
|
||||
const rep = evaluatePolicy([tenantOrg], [])
|
||||
expect(rep.vacuous).toBe(true)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user