chore: crush git history - reborn from consolidation on 2026-03-10
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* APOPHIS configuration for broken-behavior fixture.
|
||||
*/
|
||||
|
||||
export default {
|
||||
mode: "verify",
|
||||
profiles: {
|
||||
quick: {
|
||||
name: "quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
routes: ["POST /users"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
environments: {
|
||||
local: {
|
||||
name: "local",
|
||||
allowVerify: true,
|
||||
allowObserve: true,
|
||||
allowQualify: false,
|
||||
allowChaos: false,
|
||||
allowBlocking: true,
|
||||
requireSink: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Broken behavior fixture: POST /users returns 201 but GET /users/{id} returns 404.
|
||||
* This is the canonical "wow" failure for APOPHIS CLI acceptance tests.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
import apophisPlugin from "../../../index.js";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
// Register swagger (required by APOPHIS)
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Broken API", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
// Register APOPHIS plugin for route discovery
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
app.post(
|
||||
"/users",
|
||||
{
|
||||
schema: {
|
||||
description: "Create a user",
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name"],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Behavioral contract: created resource must be retrievable
|
||||
"x-ensures": [
|
||||
"response_code(GET /users/{response_body(this).id}) == 200",
|
||||
],
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { name } = request.body;
|
||||
const id = `usr-${Date.now()}`;
|
||||
reply.status(201);
|
||||
return { id, name };
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/users/:id",
|
||||
{
|
||||
schema: {
|
||||
description: "Get a user by ID",
|
||||
params: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
404: {
|
||||
type: "object",
|
||||
properties: {
|
||||
error: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { id } = request.params;
|
||||
// BUG: Always returns 404, even for resources that were just created
|
||||
reply.status(404);
|
||||
return { error: `User ${id} not found` };
|
||||
}
|
||||
);
|
||||
|
||||
export default app;
|
||||
|
||||
// Start server if run directly
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
||||
await app.ready();
|
||||
await app.listen({ port: 3000 });
|
||||
console.log("Broken behavior app running on http://localhost:3000");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-broken-behavior",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* LEGACY APOPHIS configuration (old-style, for migration tests).
|
||||
* This uses deprecated field names that should be detected by `apophis migrate`.
|
||||
*/
|
||||
|
||||
export default {
|
||||
// Deprecated: 'mode' used to be 'testMode'
|
||||
testMode: "verify",
|
||||
|
||||
// Deprecated: 'profiles' used to be 'testProfiles'
|
||||
testProfiles: {
|
||||
quick: {
|
||||
name: "quick",
|
||||
// Deprecated: 'preset' used to be 'usesPreset'
|
||||
usesPreset: "safe-ci",
|
||||
// Deprecated: 'routes' used to be 'routeFilter'
|
||||
routeFilter: ["GET /legacy"],
|
||||
},
|
||||
},
|
||||
|
||||
// Deprecated: 'presets' used to be 'testPresets'
|
||||
testPresets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
// Deprecated: 'depth' used to be 'testDepth'
|
||||
testDepth: "quick",
|
||||
// Deprecated: 'timeout' used to be 'maxDuration'
|
||||
maxDuration: 5000,
|
||||
},
|
||||
},
|
||||
|
||||
// Deprecated: 'environments' used to be 'envPolicies'
|
||||
envPolicies: {
|
||||
local: {
|
||||
name: "local",
|
||||
// Deprecated: 'allowVerify' used to be 'canVerify'
|
||||
canVerify: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Legacy config fixture: old-style config for migration tests.
|
||||
* Uses deprecated field names and structure.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Legacy App", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
app.get("/legacy", async () => ({ status: "legacy" }));
|
||||
|
||||
export default app;
|
||||
|
||||
// Start server if run directly
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
||||
await app.ready();
|
||||
await app.listen({ port: 3000 });
|
||||
console.log("Legacy config app running on http://localhost:3000");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-legacy-config",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Root-level APOPHIS config for monorepo.
|
||||
* Packages can override with their own configs.
|
||||
*/
|
||||
|
||||
export default {
|
||||
mode: "verify",
|
||||
profiles: {
|
||||
"api-quick": {
|
||||
name: "api-quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
},
|
||||
"web-quick": {
|
||||
name: "web-quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@apophis/fixture-monorepo",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test --workspaces"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* API package in monorepo fixture.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "API Package", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
app.get("/health", async () => ({ status: "ok" }));
|
||||
|
||||
app.post(
|
||||
"/users",
|
||||
{
|
||||
schema: {
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name"],
|
||||
properties: { name: { type: "string" } },
|
||||
},
|
||||
"x-ensures": [
|
||||
"response_code(GET /users/{response_body(this).id}) == 200",
|
||||
],
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const id = `usr-${Date.now()}`;
|
||||
reply.status(201);
|
||||
return { id, name: request.body.name };
|
||||
}
|
||||
);
|
||||
|
||||
app.get("/users/:id", async (request) => ({
|
||||
id: request.params.id,
|
||||
name: "Test User",
|
||||
}));
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@apophis/fixture-monorepo-api",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Web package in monorepo fixture.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Web Package", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
app.get("/", async () => ({ message: "Hello from web" }));
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@apophis/fixture-monorepo-web",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* APOPHIS configuration for observe-config fixture.
|
||||
*/
|
||||
|
||||
export default {
|
||||
mode: "observe",
|
||||
profiles: {
|
||||
"staging-observe": {
|
||||
name: "staging-observe",
|
||||
mode: "observe",
|
||||
preset: "observe-safe",
|
||||
routes: ["/health", "/events"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"observe-safe": {
|
||||
name: "observe-safe",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: true,
|
||||
},
|
||||
},
|
||||
environments: {
|
||||
staging: {
|
||||
name: "staging",
|
||||
allowVerify: true,
|
||||
allowObserve: true,
|
||||
allowQualify: false,
|
||||
allowChaos: false,
|
||||
allowBlocking: false,
|
||||
requireSink: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Observe config fixture: app with observe configuration and sink setup.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Observe App", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
app.get("/health", async () => ({ status: "ok" }));
|
||||
|
||||
app.post(
|
||||
"/events",
|
||||
{
|
||||
schema: {
|
||||
description: "Record an event",
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["type", "payload"],
|
||||
properties: {
|
||||
type: { type: "string" },
|
||||
payload: { type: "object" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
received: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const id = `evt-${Date.now()}`;
|
||||
reply.status(201);
|
||||
return { id, received: true };
|
||||
}
|
||||
);
|
||||
|
||||
export default app;
|
||||
|
||||
// Start server if run directly
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
||||
await app.ready();
|
||||
await app.listen({ port: 3000 });
|
||||
console.log("Observe config app running on http://localhost:3000");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-observe-config",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Fastify app that attempts duplicate APOPHIS plugin registration.
|
||||
* Doctor should detect the duplicate and warn, not fail hard.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
import apophisPlugin from "/home/johndvorak/Business/workspace/Apophis/dist/index.js";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Duplicate Plugin Test", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
// First registration
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
// Second registration (duplicate) - this should be handled gracefully
|
||||
// In real Fastify this would throw "decorator already added"
|
||||
// But doctor should detect pre-registration and skip its own attempt
|
||||
|
||||
app.get(
|
||||
"/health",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check",
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => ({ status: "ok" })
|
||||
);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@apophis/fixture-plugin-duplicate",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Fastify app without APOPHIS plugin registered.
|
||||
* Doctor should detect plugin is missing and warn.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "No Plugin Test", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
// NOTE: APOPHIS plugin is NOT registered here
|
||||
|
||||
app.get(
|
||||
"/health",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check",
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => ({ status: "ok" })
|
||||
);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@apophis/fixture-plugin-not-registered",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Fastify from "fastify";
|
||||
import apophisPlugin from "/home/johndvorak/Business/workspace/Apophis/dist/index.js";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Pre-registered Plugin Test", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
// Plugin is already registered here - doctor should detect this
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
app.get(
|
||||
"/health",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check",
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => ({ status: "ok" })
|
||||
);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@apophis/fixture-plugin-pre-registered",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* APOPHIS configuration for protocol-lab fixture.
|
||||
*/
|
||||
|
||||
export default {
|
||||
mode: "qualify",
|
||||
profiles: {
|
||||
"oauth-nightly": {
|
||||
name: "oauth-nightly",
|
||||
mode: "qualify",
|
||||
preset: "deep",
|
||||
routes: ["POST /oauth/authorize", "POST /oauth/token", "GET /api/user"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
deep: {
|
||||
name: "deep",
|
||||
depth: "deep",
|
||||
timeout: 30000,
|
||||
parallel: false,
|
||||
chaos: true,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
environments: {
|
||||
local: {
|
||||
name: "local",
|
||||
allowVerify: true,
|
||||
allowObserve: true,
|
||||
allowQualify: true,
|
||||
allowChaos: true,
|
||||
allowBlocking: true,
|
||||
requireSink: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Protocol lab fixture: OAuth-like multi-step flow app.
|
||||
* Demonstrates stateful testing with multi-step protocols.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
// In-memory token store (for demo only)
|
||||
const tokens = new Map();
|
||||
const authCodes = new Map();
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Protocol Lab", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
// Step 1: Request authorization code
|
||||
app.post(
|
||||
"/oauth/authorize",
|
||||
{
|
||||
schema: {
|
||||
description: "Request authorization code",
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["client_id", "redirect_uri"],
|
||||
properties: {
|
||||
client_id: { type: "string" },
|
||||
redirect_uri: { type: "string" },
|
||||
scope: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
code: { type: "string" },
|
||||
state: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { client_id, redirect_uri } = request.body;
|
||||
const code = `auth-${Date.now()}`;
|
||||
authCodes.set(code, { client_id, redirect_uri, used: false });
|
||||
return { code, state: "xyz" };
|
||||
}
|
||||
);
|
||||
|
||||
// Step 2: Exchange code for token
|
||||
app.post(
|
||||
"/oauth/token",
|
||||
{
|
||||
schema: {
|
||||
description: "Exchange authorization code for access token",
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["code", "client_id", "client_secret"],
|
||||
properties: {
|
||||
code: { type: "string" },
|
||||
client_id: { type: "string" },
|
||||
client_secret: { type: "string" },
|
||||
redirect_uri: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
access_token: { type: "string" },
|
||||
token_type: { type: "string" },
|
||||
expires_in: { type: "number" },
|
||||
},
|
||||
},
|
||||
400: {
|
||||
type: "object",
|
||||
properties: {
|
||||
error: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { code, client_id, client_secret } = request.body;
|
||||
const auth = authCodes.get(code);
|
||||
|
||||
if (!auth || auth.used) {
|
||||
reply.status(400);
|
||||
return { error: "invalid_grant" };
|
||||
}
|
||||
|
||||
if (auth.client_id !== client_id) {
|
||||
reply.status(400);
|
||||
return { error: "invalid_client" };
|
||||
}
|
||||
|
||||
auth.used = true;
|
||||
const token = `tok-${Date.now()}`;
|
||||
tokens.set(token, { client_id, createdAt: Date.now() });
|
||||
|
||||
return {
|
||||
access_token: token,
|
||||
token_type: "Bearer",
|
||||
expires_in: 3600,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Step 3: Use token
|
||||
app.get(
|
||||
"/api/user",
|
||||
{
|
||||
schema: {
|
||||
description: "Get current user with access token",
|
||||
headers: {
|
||||
type: "object",
|
||||
required: ["authorization"],
|
||||
properties: {
|
||||
authorization: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
client_id: { type: "string" },
|
||||
},
|
||||
},
|
||||
401: {
|
||||
type: "object",
|
||||
properties: {
|
||||
error: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const auth = request.headers.authorization;
|
||||
if (!auth || !auth.startsWith("Bearer ")) {
|
||||
reply.status(401);
|
||||
return { error: "invalid_token" };
|
||||
}
|
||||
|
||||
const token = auth.slice(7);
|
||||
const data = tokens.get(token);
|
||||
if (!data) {
|
||||
reply.status(401);
|
||||
return { error: "invalid_token" };
|
||||
}
|
||||
|
||||
return { id: `user-${token}`, client_id: data.client_id };
|
||||
}
|
||||
);
|
||||
|
||||
export default app;
|
||||
|
||||
// Start server if run directly
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
||||
await app.ready();
|
||||
await app.listen({ port: 3000 });
|
||||
console.log("Protocol lab app running on http://localhost:3000");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-protocol-lab",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* APOPHIS configuration for tiny-fastify fixture.
|
||||
*/
|
||||
|
||||
export default {
|
||||
mode: "verify",
|
||||
profiles: {
|
||||
quick: {
|
||||
name: "quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
routes: ["POST /users"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
environments: {
|
||||
local: {
|
||||
name: "local",
|
||||
allowVerify: true,
|
||||
allowObserve: true,
|
||||
allowQualify: false,
|
||||
allowChaos: false,
|
||||
allowBlocking: true,
|
||||
requireSink: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Minimal Fastify app with one route and one behavioral contract.
|
||||
* This is the "hello world" fixture for APOPHIS CLI.
|
||||
*/
|
||||
|
||||
import Fastify from "fastify";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
// Register swagger (required by APOPHIS)
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Tiny API", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
let apophisPlugin;
|
||||
try {
|
||||
({ default: apophisPlugin } = await import("../../../index.js"));
|
||||
} catch {
|
||||
({ default: apophisPlugin } = await import("../../../../dist/index.js"));
|
||||
}
|
||||
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
app.post(
|
||||
"/users",
|
||||
{
|
||||
schema: {
|
||||
description: "Create a user",
|
||||
body: {
|
||||
type: "object",
|
||||
required: ["name"],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
201: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Behavioral contract: created resource must be retrievable
|
||||
"x-ensures": [
|
||||
"response_code(GET /users/{response_body(this).id}) == 200",
|
||||
],
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { name } = request.body;
|
||||
const id = `usr-${Date.now()}`;
|
||||
reply.status(201);
|
||||
return { id, name };
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/users/:id",
|
||||
{
|
||||
schema: {
|
||||
description: "Get a user by ID",
|
||||
params: {
|
||||
type: "object",
|
||||
required: ["id"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
},
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { id } = request.params;
|
||||
// In a real app, this would fetch from DB
|
||||
// For this fixture, we always return the user
|
||||
return { id, name: "Test User" };
|
||||
}
|
||||
);
|
||||
|
||||
export default app;
|
||||
|
||||
// Start server if run directly
|
||||
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
||||
await app.ready();
|
||||
await app.listen({ port: 3000 });
|
||||
console.log("Tiny Fastify app running on http://localhost:3000");
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-tiny-fastify",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
mode: "verify",
|
||||
profiles: {
|
||||
quick: {
|
||||
name: "quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
routes: ["GET /health"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import Fastify from "fastify";
|
||||
import apophisPlugin from "../../../index.js";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Verify No Contracts", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
app.get(
|
||||
"/health",
|
||||
{
|
||||
schema: {
|
||||
description: "Health check route with schema only",
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => ({ status: "ok" }),
|
||||
);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-verify-no-contracts",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
mode: "verify",
|
||||
profiles: {
|
||||
quick: {
|
||||
name: "quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
routes: ["GET /broken"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import Fastify from "fastify";
|
||||
import apophisPlugin from "../../../index.js";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Verify Parse Fail", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
app.get(
|
||||
"/broken",
|
||||
{
|
||||
schema: {
|
||||
description: "Route with invalid behavioral contract",
|
||||
"x-ensures": ["this is not a valid contract!!!"],
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
status: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => ({ status: "ok" }),
|
||||
);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-verify-parse-fail",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
mode: "verify",
|
||||
profiles: {
|
||||
quick: {
|
||||
name: "quick",
|
||||
mode: "verify",
|
||||
preset: "safe-ci",
|
||||
routes: ["GET /slow"],
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
"safe-ci": {
|
||||
name: "safe-ci",
|
||||
depth: "quick",
|
||||
timeout: 5000,
|
||||
parallel: false,
|
||||
chaos: false,
|
||||
observe: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import Fastify from "fastify";
|
||||
import apophisPlugin from "../../../index.js";
|
||||
|
||||
const app = Fastify({ logger: false });
|
||||
|
||||
await app.register(import("@fastify/swagger"), {
|
||||
openapi: {
|
||||
info: { title: "Verify Timeout Route", version: "1.0.0" },
|
||||
},
|
||||
});
|
||||
|
||||
await app.register(apophisPlugin, { runtime: "off" });
|
||||
|
||||
app.get(
|
||||
"/slow",
|
||||
{
|
||||
schema: {
|
||||
description: "Slow route with timeout metadata",
|
||||
"x-timeout": 1,
|
||||
"x-ensures": ["response_code(this) == 200"],
|
||||
response: {
|
||||
200: {
|
||||
type: "object",
|
||||
properties: {
|
||||
ok: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
|
||||
return { ok: true };
|
||||
},
|
||||
);
|
||||
|
||||
export default app;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@apophis/fixture-verify-timeout-route",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"fastify": "^5.0.0",
|
||||
"@fastify/swagger": "^9.0.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user