import Fastify from 'fastify' import apophisPlugin from 'apophis-fastify' import crypto from 'crypto' const fastify = Fastify() await fastify.register(apophisPlugin, { runtime: 'error', // Validate contracts on every request cleanup: true, // Auto-cleanup resources on exit }) // In-memory store for demo const users = new Map() // CREATE — constructor fastify.post('/users', { schema: { 'x-category': 'constructor', 'x-ensures': [ 'status:201', 'response_body(this).id != null', 'response_body(this).email == request_body(this).email', ], body: { type: 'object', properties: { email: { type: 'string', format: 'email' }, name: { type: 'string', minLength: 1 } }, required: ['email', 'name'] }, response: { 201: { type: 'object', properties: { id: { type: 'string' }, email: { type: 'string' }, name: { type: 'string' } } } } } }, async (req, reply) => { const id = `usr-${crypto.createHash('sha256').update(req.body.email).digest('hex').slice(0, 8)}` const user = { id, email: req.body.email, name: req.body.name } users.set(id, user) reply.status(201) return user }) // READ — observer fastify.get('/users/:id', { schema: { 'x-category': 'observer', 'x-requires': ['users:id'], 'x-ensures': [ 'status:200', 'response_body(this).id == request_params(this).id', ], params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] }, response: { 200: { type: 'object', properties: { id: { type: 'string' }, email: { type: 'string' }, name: { type: 'string' } } } } } }, async (req) => { const user = users.get(req.params.id) if (!user) { throw new Error('User not found') } return user }) // UPDATE — mutator fastify.put('/users/:id', { schema: { 'x-category': 'mutator', 'x-requires': ['users:id'], 'x-ensures': [ 'status:200', 'response_body(this).id == request_params(this).id', ], params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] }, body: { type: 'object', properties: { email: { type: 'string', format: 'email' }, name: { type: 'string', minLength: 1 } } }, response: { 200: { type: 'object', properties: { id: { type: 'string' }, email: { type: 'string' }, name: { type: 'string' } } } } } }, async (req) => { const user = users.get(req.params.id) if (!user) { throw new Error('User not found') } const updated = { ...user, email: req.body.email ?? user.email, name: req.body.name ?? user.name, } users.set(req.params.id, updated) return updated }) // DELETE — destructor fastify.delete('/users/:id', { schema: { 'x-category': 'destructor', 'x-requires': ['users:id'], 'x-ensures': ['status:204'], params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } } }, async (req, reply) => { users.delete(req.params.id) reply.status(204) }) await fastify.ready() // Run contract tests (all non-utility routes, property-based) const result = await fastify.apophis.contract({ depth: 'standard' }) console.log('Contract tests:', result.summary) // Run stateful tests (constructor→mutator→destructor sequences) const stateful = await fastify.apophis.stateful({ depth: 'standard', seed: 42 }) console.log('Stateful tests:', stateful.summary) // Validate a single route const check = await fastify.apophis.check('POST', '/users') console.log('POST /users check:', check.ok ? 'PASS' : 'FAIL')