Initial public release of Apophis — invariant-driven automated API testing
This commit is contained in:
@@ -243,7 +243,7 @@ test('petit-runner executes tests against real API', async () => {
|
||||
]
|
||||
const fastifyWithRoutes = Object.assign(fastify, { routes: mockRoutes })
|
||||
const result = await runPetitTests(fastifyWithRoutes as any, {
|
||||
depth: 'quick',
|
||||
runs: 10,
|
||||
scope: undefined,
|
||||
seed: undefined
|
||||
})
|
||||
@@ -367,7 +367,7 @@ test('full integration: plugin + routes + test execution', async () => {
|
||||
const createUserContract = contracts.find(c => c.path === '/users' && c.method === 'POST')
|
||||
assert.ok(createUserContract, 'create user contract should exist')
|
||||
assert.strictEqual(createUserContract.category, 'constructor')
|
||||
const testResult = await fastify.apophis.contract({ depth: 'quick' })
|
||||
const testResult = await fastify.apophis.contract({ runs: 10 })
|
||||
assert.ok(Array.isArray(testResult.tests), 'tests should be an array')
|
||||
assert.ok(testResult.tests.length > 0, 'tests should not be empty')
|
||||
await fastify.apophis.cleanup()
|
||||
@@ -439,7 +439,7 @@ test('mode filtering: stateful mode only runs constructor/mutator routes', async
|
||||
}, async () => ({ status: 'ok' }))
|
||||
await fastify.ready()
|
||||
// Run in stateful mode
|
||||
const result = await fastify.apophis.contract({ depth: 'quick' })
|
||||
const result = await fastify.apophis.contract({ runs: 10 })
|
||||
// In stateful mode, utility routes should be excluded
|
||||
// The test should only run constructor and mutator routes
|
||||
assert.ok(Array.isArray(result.tests), 'tests should be an array')
|
||||
@@ -474,7 +474,7 @@ test('failing contract produces ContractViolation with suggestion', async () =>
|
||||
return { status: 'created' } // Returns 200, not 201
|
||||
})
|
||||
await fastify.ready()
|
||||
const result = await fastify.apophis.contract({ depth: 'quick' })
|
||||
const result = await fastify.apophis.contract({ runs: 10 })
|
||||
// Find the failing test
|
||||
const failingTests = result.tests.filter(t => !t.ok)
|
||||
assert.ok(failingTests.length > 0, 'should have at least one failing test')
|
||||
@@ -647,7 +647,7 @@ test('integration: contract routes option limits tested routes', async () => {
|
||||
}, async () => ({ ok: true }))
|
||||
await fastify.ready()
|
||||
const result = await fastify.apophis.contract({
|
||||
depth: 'quick',
|
||||
runs: 10,
|
||||
routes: ['GET /included'],
|
||||
})
|
||||
const includedTests = result.tests.filter(t => t.name.includes('GET /included'))
|
||||
@@ -673,7 +673,7 @@ test('integration: contract variants are tagged and run in declared order', asyn
|
||||
}, async () => ({ ok: true }))
|
||||
await fastify.ready()
|
||||
const result = await fastify.apophis.contract({
|
||||
depth: 'quick',
|
||||
runs: 10,
|
||||
variants: [
|
||||
{ name: 'json', headers: { accept: 'application/json' } },
|
||||
{ name: 'xml', headers: { accept: 'application/xml' } },
|
||||
@@ -715,7 +715,7 @@ test('integration: variant headers override scope headers', async () => {
|
||||
}, async () => ({ ok: true }))
|
||||
await fastify.ready()
|
||||
const result = await fastify.apophis.contract({
|
||||
depth: 'quick',
|
||||
runs: 10,
|
||||
variants: [
|
||||
{ name: 'xml', headers: { accept: 'application/xml' } },
|
||||
],
|
||||
@@ -744,7 +744,7 @@ test('integration: route-level x-variants are extracted and executed', async ()
|
||||
}, async () => ({ ok: true }))
|
||||
await fastify.ready()
|
||||
// No call-site variants; route-level variants should drive execution
|
||||
const result = await fastify.apophis.contract({ depth: 'quick' })
|
||||
const result = await fastify.apophis.contract({ runs: 10 })
|
||||
const jsonTests = result.tests.filter((t) => t.name.includes('[variant:json]'))
|
||||
const xmlTests = result.tests.filter((t) => t.name.includes('[variant:xml]'))
|
||||
assert.ok(jsonTests.length > 0, 'route json variant should produce tests')
|
||||
@@ -753,3 +753,40 @@ test('integration: route-level x-variants are extracted and executed', async ()
|
||||
await fastify.close()
|
||||
}
|
||||
})
|
||||
|
||||
test('integration: inferred contracts are guarded by status code', async () => {
|
||||
const fastify = Fastify() as unknown as TestFastifyInstance
|
||||
try {
|
||||
await fastify.register(swagger, {})
|
||||
await fastify.register(apophisPlugin, {})
|
||||
fastify.get('/status-guarded', {
|
||||
schema: {
|
||||
'x-category': 'observer',
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: { status: { type: 'string', const: 'success' } },
|
||||
required: ['status']
|
||||
},
|
||||
404: {
|
||||
type: 'object',
|
||||
properties: { error: { type: 'string' } },
|
||||
required: ['error']
|
||||
}
|
||||
}
|
||||
} as Record<string, unknown>
|
||||
}, async (request, reply) => {
|
||||
// Return 404 to verify the 200-schema const doesn't fail
|
||||
reply.status(404)
|
||||
return { error: 'not found' }
|
||||
})
|
||||
await fastify.ready()
|
||||
const result = await fastify.apophis.contract({ runs: 10 })
|
||||
// Should pass because the inferred const contract is guarded:
|
||||
// response_code(this) == 200 => response_body(this).status == "success"
|
||||
// The 404 response doesn't trigger the antecedent, so the implication holds.
|
||||
assert.strictEqual(result.summary.failed, 0, 'inferred 200-schema const should not fail on 404')
|
||||
} finally {
|
||||
await fastify.close()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user