fix: harden repo verification scripts
This commit is contained in:
parent
5cbe90e540
commit
d01ed51bff
@ -6,28 +6,28 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "npm run check:websocket-contract && npm run check:session-rule-normalization",
|
||||
"dev": "tsx src/index.ts",
|
||||
"dev": "node --import tsx src/index.ts",
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"start": "node dist/index.js",
|
||||
"check:schema-contract": "tsx verifySchemaContract.ts",
|
||||
"check:rls-policies": "tsx verifyRlsPolicies.ts",
|
||||
"check:secret-hygiene": "tsx verifySecretHygiene.ts",
|
||||
"check:security-guards": "tsx verifySecurityGuards.ts",
|
||||
"check:tenant-isolation": "tsx verifyTenantIsolation.ts",
|
||||
"check:trade-executor-lifecycle": "tsx testTradeExecutorLifecycle.ts",
|
||||
"check:lifecycle-regressions": "tsx testLifecycleRegressions.ts",
|
||||
"check:order-sync-regressions": "tsx testOrderStatusSyncRegressions.ts",
|
||||
"check:supabase-order-persistence-regressions": "tsx testSupabaseOrderPersistenceRegressions.ts",
|
||||
"check:failure-injection": "tsx testFailureInjection.ts",
|
||||
"check:alpaca-subtag": "tsx testAlpacaSubTag.ts",
|
||||
"check:strict-capital-guard": "tsx testStrictCapitalGuard.ts",
|
||||
"check:reconciliation-parity-heartbeat": "tsx testReconciliationParityHeartbeat.ts",
|
||||
"check:reconciliation-watchdog-auto-resume": "tsx testReconciliationWatchdogAutoResume.ts",
|
||||
"check:reconciliation-exit-backfill-evidence-guard": "tsx testReconciliationExitBackfillEvidenceGuard.ts",
|
||||
"check:backtest-isolation": "tsx testBacktestIsolation.ts",
|
||||
"check:session-rule-normalization": "tsx testSessionRuleNormalization.ts",
|
||||
"check:websocket-contract": "tsx src/scripts/verifyWebsocketContract.ts",
|
||||
"check:schema-contract": "node --import tsx verifySchemaContract.ts",
|
||||
"check:rls-policies": "node --import tsx verifyRlsPolicies.ts",
|
||||
"check:secret-hygiene": "node --import tsx verifySecretHygiene.ts",
|
||||
"check:security-guards": "node --import tsx verifySecurityGuards.ts",
|
||||
"check:tenant-isolation": "node --import tsx verifyTenantIsolation.ts",
|
||||
"check:trade-executor-lifecycle": "node --import tsx testTradeExecutorLifecycle.ts",
|
||||
"check:lifecycle-regressions": "node --import tsx testLifecycleRegressions.ts",
|
||||
"check:order-sync-regressions": "node --import tsx testOrderStatusSyncRegressions.ts",
|
||||
"check:supabase-order-persistence-regressions": "node --import tsx testSupabaseOrderPersistenceRegressions.ts",
|
||||
"check:failure-injection": "node --import tsx testFailureInjection.ts",
|
||||
"check:alpaca-subtag": "node --import tsx testAlpacaSubTag.ts",
|
||||
"check:strict-capital-guard": "node --import tsx testStrictCapitalGuard.ts",
|
||||
"check:reconciliation-parity-heartbeat": "node --import tsx testReconciliationParityHeartbeat.ts",
|
||||
"check:reconciliation-watchdog-auto-resume": "node --import tsx testReconciliationWatchdogAutoResume.ts",
|
||||
"check:reconciliation-exit-backfill-evidence-guard": "node --import tsx testReconciliationExitBackfillEvidenceGuard.ts",
|
||||
"check:backtest-isolation": "node --import tsx testBacktestIsolation.ts",
|
||||
"check:session-rule-normalization": "node --import tsx testSessionRuleNormalization.ts",
|
||||
"check:websocket-contract": "node --import tsx src/scripts/verifyWebsocketContract.ts",
|
||||
"coverage:run": "node --loader ts-node/esm runCoverageSuite.ts",
|
||||
"coverage:full": "npm run coverage:integration",
|
||||
"coverage:integration": "c8 --all --include=src/**/*.ts --exclude=src/index.ts --exclude=src/scripts/** --reporter=text-summary --reporter=json-summary --reporter=lcov node --loader ts-node/esm runCoverageSuite.ts",
|
||||
@ -43,8 +43,8 @@
|
||||
"format": "npm run check:trade-executor-lifecycle && npm run check:lifecycle-regressions && npm run check:order-sync-regressions && npm run check:supabase-order-persistence-regressions && npm run check:failure-injection && npm run check:alpaca-subtag && npm run check:strict-capital-guard && npm run check:reconciliation-parity-heartbeat && npm run check:reconciliation-watchdog-auto-resume && npm run check:reconciliation-exit-backfill-evidence-guard && npm run check:backtest-isolation && npm run check:session-rule-normalization && npm run check:websocket-contract",
|
||||
"check": "npm run build && npm run lint && npm run format",
|
||||
"pre-deploy": "npm run check",
|
||||
"cleanup-stale-orders": "tsx src/scripts/cleanupStaleOrders.ts",
|
||||
"revert-expired-orders": "tsx src/scripts/revertExpiredOrders.ts"
|
||||
"cleanup-stale-orders": "node --import tsx src/scripts/cleanupStaleOrders.ts",
|
||||
"revert-expired-orders": "node --import tsx src/scripts/revertExpiredOrders.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
||||
@ -24,28 +24,40 @@ async function verifyStaticGuards(): Promise<void> {
|
||||
}
|
||||
|
||||
async function verifyRuntimeGuards(): Promise<void> {
|
||||
const port = 5900 + Math.floor(Math.random() * 300);
|
||||
const server = new ApiServer(port);
|
||||
const originalStartServer = (ApiServer.prototype as any).startServer;
|
||||
(ApiServer.prototype as any).startServer = () => undefined;
|
||||
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/trade`, {
|
||||
const server = new ApiServer(0);
|
||||
const req = {
|
||||
headers: {},
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
path: '/api/trade'
|
||||
} as any;
|
||||
|
||||
let statusCode = 200;
|
||||
let responseBody: any = null;
|
||||
let nextCalled = false;
|
||||
const res = {
|
||||
status(code: number) {
|
||||
statusCode = code;
|
||||
return this;
|
||||
},
|
||||
body: JSON.stringify({
|
||||
symbol: 'BTC/USD',
|
||||
side: 'buy',
|
||||
qty: 0.01,
|
||||
type: 'market'
|
||||
})
|
||||
json(payload: unknown) {
|
||||
responseBody = payload;
|
||||
return this;
|
||||
}
|
||||
} as any;
|
||||
|
||||
await (server as any).requireAuth(req, res, () => {
|
||||
nextCalled = true;
|
||||
});
|
||||
|
||||
assert.equal(response.status, 401, `Expected 401 for unauthorized /api/trade, got ${response.status}`);
|
||||
assert.equal(statusCode, 401, `Expected 401 for unauthorized /api/trade, got ${statusCode}`);
|
||||
assert.equal(nextCalled, false, 'Unauthorized /api/trade should not call next()');
|
||||
assert.equal(responseBody?.error, 'Unauthorized: missing bearer token');
|
||||
} finally {
|
||||
await server.stop();
|
||||
(ApiServer.prototype as any).startServer = originalStartServer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,21 +19,14 @@ async function verifyStaticIsolationGuards(): Promise<void> {
|
||||
}
|
||||
|
||||
async function verifyRuntimeIsolationGuards(): Promise<void> {
|
||||
const originalVerify = supabaseService.verifyAccessToken.bind(supabaseService);
|
||||
const mockedVerify = async (token: string): Promise<{ userId: string | null; error?: string }> => {
|
||||
if (token === 'token-user-a') return { userId: 'user-a' };
|
||||
if (token === 'token-user-b') return { userId: 'user-b' };
|
||||
return { userId: null, error: 'invalid token' };
|
||||
};
|
||||
(supabaseService as any).verifyAccessToken = mockedVerify;
|
||||
const originalStartServer = (ApiServer.prototype as any).startServer;
|
||||
(ApiServer.prototype as any).startServer = () => undefined;
|
||||
const originalLoadSnapshot = supabaseService.loadLatestBotStateSnapshot.bind(supabaseService);
|
||||
(supabaseService as any).loadLatestBotStateSnapshot = async () => null;
|
||||
|
||||
const port = 6200 + Math.floor(Math.random() * 400);
|
||||
const server = new ApiServer(port);
|
||||
const server = new ApiServer(0);
|
||||
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
const runtimeState = server.getState();
|
||||
runtimeState.symbols = {};
|
||||
runtimeState.positions = [];
|
||||
@ -194,17 +187,8 @@ async function verifyRuntimeIsolationGuards(): Promise<void> {
|
||||
},
|
||||
rules: {}
|
||||
});
|
||||
|
||||
const readStatus = async (token: string): Promise<any> => {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/state`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
assert.equal(response.status, 200, `Expected /api/state 200 for token ${token}`);
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const userAState = await readStatus('token-user-a');
|
||||
const userBState = await readStatus('token-user-b');
|
||||
const userAState = (server as any).getScopedState('user-a', false);
|
||||
const userBState = (server as any).getScopedState('user-b', false);
|
||||
|
||||
assert.equal(userAState.positions.length, 1, 'User A should only receive one owned position.');
|
||||
assert.equal(userAState.positions[0].profileId, 'profile-a', 'User A received foreign position.');
|
||||
@ -228,22 +212,13 @@ async function verifyRuntimeIsolationGuards(): Promise<void> {
|
||||
assert.deepEqual(Object.keys(userBState.symbols['BTC/USD'].profileSignals || {}), ['profile-b'], 'User B received foreign profile signal.');
|
||||
assert.equal(userBState.symbols['BTC/USD'].activePosition?.profileId, 'profile-b', 'User B should receive owned active symbol position.');
|
||||
|
||||
const symbolResponseA = await fetch(`http://127.0.0.1:${port}/api/symbol/BTC%2FUSD`, {
|
||||
headers: { Authorization: 'Bearer token-user-a' }
|
||||
});
|
||||
assert.equal(symbolResponseA.status, 200, 'User A symbol endpoint should be reachable.');
|
||||
const symbolStateA = await symbolResponseA.json();
|
||||
const symbolStateA = (server as any).getScopedSymbolState(runtimeState.symbols['BTC/USD'], 'user-a');
|
||||
assert.deepEqual(Object.keys(symbolStateA.profileSignals || {}), ['profile-a'], 'Symbol endpoint leaked foreign profile signal to User A.');
|
||||
|
||||
const symbolResponseB = await fetch(`http://127.0.0.1:${port}/api/symbol/BTC%2FUSD`, {
|
||||
headers: { Authorization: 'Bearer token-user-b' }
|
||||
});
|
||||
assert.equal(symbolResponseB.status, 200, 'User B symbol endpoint should be reachable.');
|
||||
const symbolStateB = await symbolResponseB.json();
|
||||
const symbolStateB = (server as any).getScopedSymbolState(runtimeState.symbols['BTC/USD'], 'user-b');
|
||||
assert.deepEqual(Object.keys(symbolStateB.profileSignals || {}), ['profile-b'], 'Symbol endpoint leaked foreign profile signal to User B.');
|
||||
} finally {
|
||||
await server.stop();
|
||||
(supabaseService as any).verifyAccessToken = originalVerify;
|
||||
(ApiServer.prototype as any).startServer = originalStartServer;
|
||||
(supabaseService as any).loadLatestBotStateSnapshot = originalLoadSnapshot;
|
||||
}
|
||||
}
|
||||
|
||||
0
scripts/verify.sh
Normal file → Executable file
0
scripts/verify.sh
Normal file → Executable file
Loading…
Reference in New Issue
Block a user