182 lines
6.7 KiB
Markdown
182 lines
6.7 KiB
Markdown
|
|
# Feedback for Apophis Team: Real-World Integration Challenges
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
We're integrating Apophis v1.1 into Arbiter, a multi-tenant identity platform with complex auth, graph-based permissions, and LinkedDataFragment responses. The goal is to use Apophis for contract testing of our Fastify routes.
|
||
|
|
|
||
|
|
## Issues Encountered
|
||
|
|
|
||
|
|
### 1. ~~APOSTL Syntax: Mandatory `else` Clause is Undocumented~~ ✅ FIXED in v2.0
|
||
|
|
|
||
|
|
**Status**: Resolved. APOPHIS v2.0 replaced APOSTL with Justin (plain JavaScript expressions).
|
||
|
|
|
||
|
|
**What we wrote (v1.x):**
|
||
|
|
```apostl
|
||
|
|
if response_code(this) == 201 then response_body(this).data.ok == true else T
|
||
|
|
```
|
||
|
|
|
||
|
|
**What v2.0 uses:**
|
||
|
|
```javascript
|
||
|
|
statusCode == 201 ? response.body.data.ok == true : true
|
||
|
|
// or simply:
|
||
|
|
!(statusCode == 201) || response.body.data.ok == true
|
||
|
|
```
|
||
|
|
|
||
|
|
**Resolution**: Justin uses standard JS ternary operators (`? :`) and boolean logic. No mandatory `else` clause, no custom syntax to learn.
|
||
|
|
|
||
|
|
### 2. Unclear Value Proposition vs Fastify Schema Validation
|
||
|
|
|
||
|
|
It took us time to understand what Apophis adds on top of Fastify's built-in JSON Schema validation.
|
||
|
|
|
||
|
|
**Fastify already provides:**
|
||
|
|
- Request body/query/params validation (via Ajv)
|
||
|
|
- Response serialization (via fast-json-stringify)
|
||
|
|
- Error formatting
|
||
|
|
|
||
|
|
**We initially thought Apophis would:**
|
||
|
|
- Validate responses against schemas (it doesn't — Fastify only serializes, doesn't validate responses)
|
||
|
|
- Replace our need for separate test files (it partially does, but only for behavioral contracts)
|
||
|
|
|
||
|
|
**What Apophis actually adds:**
|
||
|
|
- Behavioral contracts (`x-ensures`) for side effects and state changes
|
||
|
|
- Property-based test generation from schemas
|
||
|
|
- Stateful testing (constructor → mutator → destructor sequences)
|
||
|
|
|
||
|
|
**Suggestion:** Clarify in the "Getting Started" docs that Apophis is for *behavioral* contracts, not structural validation. Show a clear comparison table.
|
||
|
|
|
||
|
|
### 3. Testing Authenticated Routes is Underspecified
|
||
|
|
|
||
|
|
Our routes require:
|
||
|
|
- JWT tokens in Authorization header
|
||
|
|
- Tenant context (x-tenant-id header)
|
||
|
|
- Permission checks via graph-based auth
|
||
|
|
- Session cookies
|
||
|
|
|
||
|
|
**The problem:** Apophis generates requests programmatically, but there's no clear pattern for:
|
||
|
|
- Injecting auth tokens into generated requests
|
||
|
|
- Setting up prerequisite state (create user → login → get token → test route)
|
||
|
|
- Handling token refresh or session management
|
||
|
|
|
||
|
|
**We tried:**
|
||
|
|
- Using `scopes` to inject headers, but this is static and can't handle dynamic tokens
|
||
|
|
- Using `x-requires` for preconditions, but it's unclear how to satisfy them
|
||
|
|
|
||
|
|
**Suggestion:** Document a pattern for authenticated routes. Examples:
|
||
|
|
```typescript
|
||
|
|
// Option 1: Dynamic scope setup
|
||
|
|
await app.apophis.scope.register('authed', {
|
||
|
|
headers: async () => ({
|
||
|
|
'Authorization': `Bearer ${await getTestToken()}`
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
// Option 2: Test hooks
|
||
|
|
await app.apophis.contract({
|
||
|
|
beforeEach: async (req) => {
|
||
|
|
req.headers['Authorization'] = await getTestToken()
|
||
|
|
}
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Running Against Real Server is Difficult
|
||
|
|
|
||
|
|
The docs show examples with inline route definitions, but we want to test our actual production routes.
|
||
|
|
|
||
|
|
**Challenges:**
|
||
|
|
- Server bootstraps databases, WAL stores, ledger connections
|
||
|
|
- Routes have complex dependency injection
|
||
|
|
- We need to clean up between tests (file system conflicts, port binding)
|
||
|
|
|
||
|
|
**Example error:**
|
||
|
|
```
|
||
|
|
Error: EEXIST: file already exists, mkdir 'server-data/wal.log'
|
||
|
|
```
|
||
|
|
|
||
|
|
**Suggestion:** Provide a guide for "Testing Existing Fastify Apps" that covers:
|
||
|
|
- Bootstrapping the server in test mode
|
||
|
|
- Cleaning up resources between runs
|
||
|
|
- Configuring Apophis after server creation but before `ready()`
|
||
|
|
|
||
|
|
### 5. Contract Debugging is Hard
|
||
|
|
|
||
|
|
When contracts fail, the output is verbose but not actionable.
|
||
|
|
|
||
|
|
**Example output:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"formula": "statusCode == 400 ? response.body.error != null : true",
|
||
|
|
"context": {
|
||
|
|
"expected": "non-null value",
|
||
|
|
"actual": "undefined (field missing)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Problems:**
|
||
|
|
- We don't see the actual request that was generated
|
||
|
|
- We don't see the full response body
|
||
|
|
- No suggestion for how to fix the contract
|
||
|
|
|
||
|
|
**Suggestion:** Include in failure output:
|
||
|
|
- The generated request (method, path, body)
|
||
|
|
- The full response body
|
||
|
|
- A suggestion like "Field 'error' missing from response. Check your handler returns error details."
|
||
|
|
|
||
|
|
### 6. No Clear CI/CD Pattern
|
||
|
|
|
||
|
|
We want to run Apophis in CI, but:
|
||
|
|
- How do we handle database migrations/seeding?
|
||
|
|
- How do we ensure deterministic runs (seed management)?
|
||
|
|
- How do we fail the build on contract violations?
|
||
|
|
|
||
|
|
**Suggestion:** Add a CI/CD section to docs with GitHub Actions example that shows:
|
||
|
|
```yaml
|
||
|
|
- name: Contract Tests
|
||
|
|
run: |
|
||
|
|
npm run db:migrate:test
|
||
|
|
npm run test:contracts
|
||
|
|
# Exit code should be non-zero if contracts fail
|
||
|
|
```
|
||
|
|
|
||
|
|
## What Works Well
|
||
|
|
|
||
|
|
- Schema-driven test generation is powerful
|
||
|
|
- `x-category` auto-categorization reduces boilerplate
|
||
|
|
- `check()` for single-route validation is useful
|
||
|
|
- Integration with `@fastify/swagger` is seamless
|
||
|
|
|
||
|
|
## Recommendations
|
||
|
|
|
||
|
|
1. ~~**Make `else` optional** in APOSTL conditionals~~ ✅ Fixed in v2.0 — Justin uses standard JS ternary operators
|
||
|
|
2. **Add "Auth Patterns" guide** with examples for JWT, sessions, API keys
|
||
|
|
3. **Improve error messages** with request/response context and fix suggestions
|
||
|
|
4. **Document real-world integration** (existing Fastify apps, not just toy examples)
|
||
|
|
5. **Add CI/CD examples** with database setup and deterministic testing
|
||
|
|
|
||
|
|
## Our Current Workaround
|
||
|
|
|
||
|
|
We're using Apophis for:
|
||
|
|
- Schema discovery and validation
|
||
|
|
- Contract syntax checking
|
||
|
|
- Documentation generation
|
||
|
|
|
||
|
|
But for authenticated routes, we're writing traditional E2E tests with `fastify.inject()` because we can control auth headers and setup/teardown more easily.
|
||
|
|
|
||
|
|
## Update: APOPHIS v2.0 Resolutions
|
||
|
|
|
||
|
|
**APOPHIS v2.0 (released 2026-04-25) addresses all feedback items:**
|
||
|
|
|
||
|
|
1. ✅ **APOSTL `else` clause**: Replaced with Justin (standard JS ternary `? :`)
|
||
|
|
2. ✅ **Value proposition**: Documentation now clearly distinguishes structural vs behavioral validation
|
||
|
|
3. ✅ **Auth patterns**: Extension system allows dynamic header injection via `onBuildRequest` hook
|
||
|
|
4. ✅ **Real-world integration**: Guide added for testing existing Fastify apps with complex bootstrapping
|
||
|
|
5. ✅ **Contract debugging**: Failure output now includes generated request, full response, and fix suggestions
|
||
|
|
6. ✅ **CI/CD patterns**: GitHub Actions example with database migrations and deterministic seeds
|
||
|
|
|
||
|
|
**Recommended next steps for Arbiter integration:**
|
||
|
|
- Migrate contracts from APOSTL to Justin using the [migration guide](docs/getting-started.md#migration-from-v1x)
|
||
|
|
- Use the Extension Plugin System for Arbiter-specific predicates (`graph_check`, `partial_graph`, `budget_check`)
|
||
|
|
- Register Arbiter extension to inject S2S headers and handle preflight/finalize lifecycle
|
||
|
|
|
||
|
|
See `docs/extensions/EXTENSION-PLUGIN-SYSTEM.md` for the Arbiter extension example.
|