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
+20 -14
View File
@@ -7,7 +7,7 @@
* If the test suite passes, the mutation "survives" — indicating a gap in coverage.
*
* Usage:
* const report = await runMutationTesting(fastify, { depth: 'quick' })
* const report = await runMutationTesting(fastify, { runs: 10 })
* console.log(`Mutation score: ${report.score}%`)
*/
import type { FastifyInstance } from 'fastify'
@@ -44,7 +44,7 @@ export interface MutationReport {
readonly weakContracts: string[] // contracts that survived all mutations
}
export interface MutationConfig {
readonly depth?: TestConfig['depth']
readonly runs?: number
readonly seed?: number
/** Max mutations per contract (default: 5) */
readonly maxMutationsPerContract?: number
@@ -214,7 +214,7 @@ export async function runMutationTesting(
const suite = await runPetitTestsWithMutation(
fastify as unknown as FastifyInjectInstance,
{
depth: config.depth ?? 'quick',
runs: config.runs ?? 10,
seed: config.seed,
},
mutatedContract
@@ -256,20 +256,26 @@ export async function runMutationTesting(
}
/**
* Run petit tests with a mutated contract.
* This is a simplified version that tests a single mutated contract.
* Injects the mutated contract so the runner uses it instead of discovering from Fastify.
*/
async function runPetitTestsWithMutation(
fastify: FastifyInjectInstance,
config: { depth?: TestConfig['depth']; seed?: number },
config: { runs?: number; seed?: number },
mutatedContract: RouteContract
): Promise<TestSuite> {
// For now, run the full suite - the mutated contract will be discovered
// In a real implementation, you'd inject the mutated contract into the discovery
return runPetitTests(fastify, {
depth: config.depth ?? 'quick',
seed: config.seed,
routes: [`${mutatedContract.method} ${mutatedContract.path}`],
})
return runPetitTests(
fastify,
{
runs: config.runs ?? 10,
seed: config.seed,
routes: [`${mutatedContract.method} ${mutatedContract.path}`],
},
undefined,
undefined,
undefined,
undefined,
[mutatedContract]
)
}
/**
* Quick mutation test for a single contract formula.
@@ -279,14 +285,14 @@ export async function testMutation(
fastify: FastifyInstance,
contract: RouteContract,
mutation: Mutation,
config: Pick<MutationConfig, 'depth' | 'seed'> = {}
config: Pick<MutationConfig, 'runs' | 'seed'> = {}
): Promise<boolean> {
const mutatedContract = applyMutation(contract, mutation)
try {
const suite = await runPetitTestsWithMutation(
fastify as unknown as FastifyInjectInstance,
{
depth: config.depth ?? 'quick',
runs: config.runs ?? 10,
seed: config.seed,
},
mutatedContract