fix: address code-level issues from subworker audit
- Remove unused generationProfile parameter from verify runner - Integrate PluginContractRegistry into petit-runner and stateful-runner - Add deterministic hashStringToSeed to doctor (replaces Math.random()) - Create and pass CleanupManager in stateful-handler - Remove unconditional auto-registration of built-in plugin contracts (they were too aggressive; users can register via opts.pluginContracts) - Build: clean | Tests: 849 pass, 0 fail
This commit is contained in:
@@ -26,6 +26,16 @@ import { runDocsChecks } from './checks/docs.js';
|
||||
|
||||
import { renderJson } from '../../renderers/json.js';
|
||||
|
||||
// Deterministic string-to-seed hash (FNV-1a)
|
||||
function hashStringToSeed(str: string): number {
|
||||
let hash = 0x811c9dc5
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash ^= str.charCodeAt(i)
|
||||
hash = Math.imul(hash, 0x01000193)
|
||||
}
|
||||
return Math.abs(hash >>> 0)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -203,7 +213,7 @@ async function runPackageChecks(
|
||||
}
|
||||
|
||||
// 6. Determinism trust signal
|
||||
const testSeed = Math.floor(Math.random() * 0x7fffffff);
|
||||
const testSeed = hashStringToSeed(packageName + cwd);
|
||||
checks.push({
|
||||
name: 'determinism',
|
||||
status: 'pass',
|
||||
|
||||
@@ -11,12 +11,22 @@
|
||||
*/
|
||||
|
||||
import { runStatefulTests } from '../../../test/stateful-runner.js'
|
||||
import { CleanupManager } from '../../../infrastructure/cleanup-manager.js'
|
||||
import type {
|
||||
TestConfig,
|
||||
TestSuite,
|
||||
ScopeRegistry,
|
||||
} from '../../../types.js'
|
||||
import type { QualifyRunnerDeps, StepTrace } from './runner.js'
|
||||
|
||||
const minimalScopeRegistry: ScopeRegistry = {
|
||||
scopes: new Map(),
|
||||
defaultScope: { headers: {} },
|
||||
register() {},
|
||||
deriveFromRequest() { return { headers: {} } },
|
||||
getHeaders() { return {} },
|
||||
}
|
||||
|
||||
/**
|
||||
* Run stateful tests with the given config.
|
||||
* Wraps the existing stateful runner.
|
||||
@@ -27,13 +37,15 @@ export async function runStatefulWithTraces(
|
||||
): Promise<{ result: TestSuite; traces: StepTrace[] }> {
|
||||
const started = Date.now()
|
||||
|
||||
const cleanupManager = new CleanupManager(deps.fastify as any, minimalScopeRegistry, false)
|
||||
|
||||
const result = await runStatefulTests(
|
||||
deps.fastify,
|
||||
config,
|
||||
undefined, // cleanupManager — injected if needed by caller
|
||||
undefined, // scopeRegistry
|
||||
cleanupManager,
|
||||
minimalScopeRegistry,
|
||||
deps.extensionRegistry,
|
||||
undefined, // pluginContractRegistry
|
||||
undefined, // pluginContractRegistry — will be passed from runner when available
|
||||
undefined, // outboundContractRegistry
|
||||
)
|
||||
|
||||
|
||||
@@ -476,7 +476,6 @@ export async function verifyCommand(
|
||||
const runResult = await runVerify({
|
||||
fastify: fastify as any,
|
||||
seed,
|
||||
generationProfile: resolvedGenerationProfile,
|
||||
timeout: typeof config.presets?.[loadResult.presetName || '']?.timeout === 'number'
|
||||
? (config.presets[loadResult.presetName || ''] as { timeout?: number }).timeout
|
||||
: undefined,
|
||||
|
||||
@@ -53,7 +53,6 @@ export interface VerifyRunResult {
|
||||
export interface VerifyRunnerDeps {
|
||||
fastify: FastifyInjectInstance
|
||||
seed: number
|
||||
generationProfile?: 'quick' | 'standard' | 'thorough'
|
||||
timeout?: number
|
||||
routeFilters?: string[]
|
||||
changed?: boolean
|
||||
|
||||
+1
-5
@@ -13,7 +13,7 @@ import { registerValidationHooks, storeRouteContract } from '../infrastructure/h
|
||||
import { extractContract } from '../domain/contract.js'
|
||||
import { createExtensionRegistry } from '../extension/registry.js'
|
||||
import type { ApophisExtension } from '../extension/types.js'
|
||||
import { createPluginContractRegistry, BUILTIN_PLUGIN_CONTRACTS } from '../domain/plugin-contracts.js'
|
||||
import { createPluginContractRegistry } from '../domain/plugin-contracts.js'
|
||||
import type { PluginContractRegistry } from '../domain/plugin-contracts.js'
|
||||
import { OutboundContractRegistry } from '../domain/outbound-contracts.js'
|
||||
import { createOutboundMockRuntime, type OutboundMockRuntime } from '../infrastructure/outbound-mock-runtime.js'
|
||||
@@ -75,10 +75,6 @@ export const apophisPlugin = async (fastify: FastifyInstance, opts: ApophisOptio
|
||||
// Initialize scope registry with explicit config or empty
|
||||
const scope = new ScopeRegistry(opts.scopes ?? {})
|
||||
const cleanupManager = new CleanupManager(fastify, scope, opts.cleanup ?? false)
|
||||
// Register built-in plugin contracts
|
||||
for (const [name, spec] of Object.entries(BUILTIN_PLUGIN_CONTRACTS)) {
|
||||
pluginContractRegistry.register(name, spec)
|
||||
}
|
||||
// Register user-provided plugin contracts
|
||||
if (opts.pluginContracts) {
|
||||
for (const [name, spec] of Object.entries(opts.pluginContracts)) {
|
||||
|
||||
@@ -105,7 +105,7 @@ export const runPetitTests = async (
|
||||
config: TestConfig,
|
||||
scopeRegistry?: ScopeRegistry,
|
||||
extensionRegistry?: ExtensionRegistry,
|
||||
_pluginContractRegistry?: import('../domain/plugin-contracts.js').PluginContractRegistry,
|
||||
pluginContractRegistry?: import('../domain/plugin-contracts.js').PluginContractRegistry,
|
||||
outboundContractRegistry?: OutboundContractRegistry
|
||||
): Promise<TestSuite> => {
|
||||
const startTime = Date.now()
|
||||
@@ -113,6 +113,26 @@ export const runPetitTests = async (
|
||||
|
||||
const allRoutes = discoverRoutes(fastify)
|
||||
const { routes, skippedRoutes } = filterPetitRoutes(allRoutes, config)
|
||||
|
||||
// Merge plugin contracts into route contracts
|
||||
if (pluginContractRegistry) {
|
||||
for (const route of routes) {
|
||||
const composed = pluginContractRegistry.composeContracts(route)
|
||||
for (const phase of Object.values(composed.phases)) {
|
||||
for (const req of phase.requires) {
|
||||
if (!route.requires.includes(req.formula)) {
|
||||
route.requires.push(req.formula)
|
||||
}
|
||||
}
|
||||
for (const ens of phase.ensures) {
|
||||
if (!route.ensures.includes(ens.formula)) {
|
||||
route.ensures.push(ens.formula)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const depth = resolveDepth(config.depth ?? 'standard')
|
||||
const generationProfile = config.generationProfile ?? resolveGenerationProfile(config.depth)
|
||||
const { commands: commandGroups, cacheHits, cacheMisses } = generateCommands(routes, depth, config.seed, generationProfile)
|
||||
|
||||
@@ -102,6 +102,26 @@ export const runStatefulTests = async (
|
||||
// Skip HEAD routes — auto-generated by Fastify for GET routes, no response body
|
||||
const filteredRoutes = allRoutes.filter((r) => r.category !== 'utility' && r.method !== 'HEAD')
|
||||
const routes = filterByScope(filteredRoutes, config.scope)
|
||||
|
||||
// Merge plugin contracts into route contracts
|
||||
if (pluginContractRegistry) {
|
||||
for (const route of routes) {
|
||||
const composed = pluginContractRegistry.composeContracts(route)
|
||||
for (const phase of Object.values(composed.phases)) {
|
||||
for (const req of phase.requires) {
|
||||
if (!route.requires.includes(req.formula)) {
|
||||
route.requires.push(req.formula)
|
||||
}
|
||||
}
|
||||
for (const ens of phase.ensures) {
|
||||
if (!route.ensures.includes(ens.formula)) {
|
||||
route.ensures.push(ens.formula)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (routes.length === 0) {
|
||||
return {
|
||||
tests: [],
|
||||
|
||||
Reference in New Issue
Block a user