Initial public release of Apophis — invariant-driven automated API testing

This commit is contained in:
John Dvorak
2026-03-10 00:00:00 -07:00
parent d278c4b105
commit 3ac1daf7e9
82 changed files with 3902 additions and 1098 deletions
+6 -6
View File
@@ -7,7 +7,7 @@ import apophisPlugin from '../index.js'
import type { TestResult } from '../types.js'
type TestFastifyInstance = FastifyInstance & {
apophis: {
contract: (opts?: { depth?: string; scope?: string; seed?: number }) => Promise<any>
contract: (opts?: { runs?: number; scope?: string; seed?: number }) => Promise<any>
spec: () => Record<string, unknown>
}
}
@@ -44,19 +44,19 @@ test('scope isolation: routes with x-scope are filtered by scope parameter', asy
}, async () => ({ user: true }))
await fastify.ready()
// Test with no scope - should discover all 3 routes
const allResult = await fastify.apophis.contract({ depth: 'quick', scope: undefined })
const allResult = await fastify.apophis.contract({ runs: 10, scope: undefined })
const allPaths = new Set(allResult.tests.map((t: TestResult) => t.name.split(' ')[1]))
assert.ok(allPaths.has('/public'), 'public route should be in all scope')
assert.ok(allPaths.has('/admin'), 'admin route should be in all scope')
assert.ok(allPaths.has('/user'), 'user route should be in all scope')
// Test with admin scope - should only get public + admin
const adminResult = await fastify.apophis.contract({ depth: 'quick', scope: 'admin' })
const adminResult = await fastify.apophis.contract({ runs: 10, scope: 'admin' })
const adminPaths = new Set(adminResult.tests.map((t: TestResult) => t.name.split(' ')[1]))
assert.ok(adminPaths.has('/public'), 'public route should be in admin scope')
assert.ok(adminPaths.has('/admin'), 'admin route should be in admin scope')
assert.ok(!adminPaths.has('/user'), 'user route should NOT be in admin scope')
// Test with user scope - should only get public + user
const userResult = await fastify.apophis.contract({ depth: 'quick', scope: 'user' })
const userResult = await fastify.apophis.contract({ runs: 10, scope: 'user' })
const userPaths = new Set(userResult.tests.map((t: TestResult) => t.name.split(' ')[1]))
assert.ok(userPaths.has('/public'), 'public route should be in user scope')
assert.ok(!userPaths.has('/admin'), 'admin route should NOT be in user scope')
@@ -88,7 +88,7 @@ test('scope isolation: scope headers are passed to requests', async () => {
headers: { 'x-custom-header': 'test-value' },
metadata: {}
})
await fastify.apophis.contract({ depth: 'quick', scope: 'test' })
await fastify.apophis.contract({ runs: 10, scope: 'test' })
assert.strictEqual(receivedHeaders['x-custom-header'], 'test-value', 'scope header should be passed to request')
} finally {
await fastify.close()
@@ -130,7 +130,7 @@ test('scope isolation: non-matching scope returns empty test suite', async () =>
}, async () => ({ ok: true }))
await fastify.ready()
// Test with non-matching scope
const result = await fastify.apophis.contract({ depth: 'quick', scope: 'other' })
const result = await fastify.apophis.contract({ runs: 10, scope: 'other' })
assert.strictEqual(result.tests.length, 0, 'no tests should run for non-matching scope')
assert.strictEqual(result.summary.passed, 0, 'no tests should pass')
assert.strictEqual(result.summary.failed, 0, 'no tests should fail')