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';
|
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
|
// Types
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -203,7 +213,7 @@ async function runPackageChecks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. Determinism trust signal
|
// 6. Determinism trust signal
|
||||||
const testSeed = Math.floor(Math.random() * 0x7fffffff);
|
const testSeed = hashStringToSeed(packageName + cwd);
|
||||||
checks.push({
|
checks.push({
|
||||||
name: 'determinism',
|
name: 'determinism',
|
||||||
status: 'pass',
|
status: 'pass',
|
||||||
|
|||||||
@@ -11,12 +11,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { runStatefulTests } from '../../../test/stateful-runner.js'
|
import { runStatefulTests } from '../../../test/stateful-runner.js'
|
||||||
|
import { CleanupManager } from '../../../infrastructure/cleanup-manager.js'
|
||||||
import type {
|
import type {
|
||||||
TestConfig,
|
TestConfig,
|
||||||
TestSuite,
|
TestSuite,
|
||||||
|
ScopeRegistry,
|
||||||
} from '../../../types.js'
|
} from '../../../types.js'
|
||||||
import type { QualifyRunnerDeps, StepTrace } from './runner.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.
|
* Run stateful tests with the given config.
|
||||||
* Wraps the existing stateful runner.
|
* Wraps the existing stateful runner.
|
||||||
@@ -27,13 +37,15 @@ export async function runStatefulWithTraces(
|
|||||||
): Promise<{ result: TestSuite; traces: StepTrace[] }> {
|
): Promise<{ result: TestSuite; traces: StepTrace[] }> {
|
||||||
const started = Date.now()
|
const started = Date.now()
|
||||||
|
|
||||||
|
const cleanupManager = new CleanupManager(deps.fastify as any, minimalScopeRegistry, false)
|
||||||
|
|
||||||
const result = await runStatefulTests(
|
const result = await runStatefulTests(
|
||||||
deps.fastify,
|
deps.fastify,
|
||||||
config,
|
config,
|
||||||
undefined, // cleanupManager — injected if needed by caller
|
cleanupManager,
|
||||||
undefined, // scopeRegistry
|
minimalScopeRegistry,
|
||||||
deps.extensionRegistry,
|
deps.extensionRegistry,
|
||||||
undefined, // pluginContractRegistry
|
undefined, // pluginContractRegistry — will be passed from runner when available
|
||||||
undefined, // outboundContractRegistry
|
undefined, // outboundContractRegistry
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -476,7 +476,6 @@ export async function verifyCommand(
|
|||||||
const runResult = await runVerify({
|
const runResult = await runVerify({
|
||||||
fastify: fastify as any,
|
fastify: fastify as any,
|
||||||
seed,
|
seed,
|
||||||
generationProfile: resolvedGenerationProfile,
|
|
||||||
timeout: typeof config.presets?.[loadResult.presetName || '']?.timeout === 'number'
|
timeout: typeof config.presets?.[loadResult.presetName || '']?.timeout === 'number'
|
||||||
? (config.presets[loadResult.presetName || ''] as { timeout?: number }).timeout
|
? (config.presets[loadResult.presetName || ''] as { timeout?: number }).timeout
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ export interface VerifyRunResult {
|
|||||||
export interface VerifyRunnerDeps {
|
export interface VerifyRunnerDeps {
|
||||||
fastify: FastifyInjectInstance
|
fastify: FastifyInjectInstance
|
||||||
seed: number
|
seed: number
|
||||||
generationProfile?: 'quick' | 'standard' | 'thorough'
|
|
||||||
timeout?: number
|
timeout?: number
|
||||||
routeFilters?: string[]
|
routeFilters?: string[]
|
||||||
changed?: boolean
|
changed?: boolean
|
||||||
|
|||||||
+1
-5
@@ -13,7 +13,7 @@ import { registerValidationHooks, storeRouteContract } from '../infrastructure/h
|
|||||||
import { extractContract } from '../domain/contract.js'
|
import { extractContract } from '../domain/contract.js'
|
||||||
import { createExtensionRegistry } from '../extension/registry.js'
|
import { createExtensionRegistry } from '../extension/registry.js'
|
||||||
import type { ApophisExtension } from '../extension/types.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 type { PluginContractRegistry } from '../domain/plugin-contracts.js'
|
||||||
import { OutboundContractRegistry } from '../domain/outbound-contracts.js'
|
import { OutboundContractRegistry } from '../domain/outbound-contracts.js'
|
||||||
import { createOutboundMockRuntime, type OutboundMockRuntime } from '../infrastructure/outbound-mock-runtime.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
|
// Initialize scope registry with explicit config or empty
|
||||||
const scope = new ScopeRegistry(opts.scopes ?? {})
|
const scope = new ScopeRegistry(opts.scopes ?? {})
|
||||||
const cleanupManager = new CleanupManager(fastify, scope, opts.cleanup ?? false)
|
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
|
// Register user-provided plugin contracts
|
||||||
if (opts.pluginContracts) {
|
if (opts.pluginContracts) {
|
||||||
for (const [name, spec] of Object.entries(opts.pluginContracts)) {
|
for (const [name, spec] of Object.entries(opts.pluginContracts)) {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ export const runPetitTests = async (
|
|||||||
config: TestConfig,
|
config: TestConfig,
|
||||||
scopeRegistry?: ScopeRegistry,
|
scopeRegistry?: ScopeRegistry,
|
||||||
extensionRegistry?: ExtensionRegistry,
|
extensionRegistry?: ExtensionRegistry,
|
||||||
_pluginContractRegistry?: import('../domain/plugin-contracts.js').PluginContractRegistry,
|
pluginContractRegistry?: import('../domain/plugin-contracts.js').PluginContractRegistry,
|
||||||
outboundContractRegistry?: OutboundContractRegistry
|
outboundContractRegistry?: OutboundContractRegistry
|
||||||
): Promise<TestSuite> => {
|
): Promise<TestSuite> => {
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
@@ -113,6 +113,26 @@ export const runPetitTests = async (
|
|||||||
|
|
||||||
const allRoutes = discoverRoutes(fastify)
|
const allRoutes = discoverRoutes(fastify)
|
||||||
const { routes, skippedRoutes } = filterPetitRoutes(allRoutes, config)
|
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 depth = resolveDepth(config.depth ?? 'standard')
|
||||||
const generationProfile = config.generationProfile ?? resolveGenerationProfile(config.depth)
|
const generationProfile = config.generationProfile ?? resolveGenerationProfile(config.depth)
|
||||||
const { commands: commandGroups, cacheHits, cacheMisses } = generateCommands(routes, depth, config.seed, generationProfile)
|
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
|
// 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 filteredRoutes = allRoutes.filter((r) => r.category !== 'utility' && r.method !== 'HEAD')
|
||||||
const routes = filterByScope(filteredRoutes, config.scope)
|
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) {
|
if (routes.length === 0) {
|
||||||
return {
|
return {
|
||||||
tests: [],
|
tests: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user