[scenario] OAuth basic & PRM variation (#11)
* readme for auth tests * wip prm test * add example helper for local oauth w/o browser * working through dangling timeout issue * add better test function * pretty printing * add mcp method * some more logging tweaks * refactor logger into 1 class * use common request logger * fix base scenario * some renaming * refactor into helpers * refactor to support second test * rm readme * add comment about www-authenticate
This commit is contained in:
parent
78d6bec42a
commit
69f02a4557
1 changed files with 1020 additions and 33 deletions
123
src/scenarios/client/auth/helpers/testClient.ts
Normal file
123
src/scenarios/client/auth/helpers/testClient.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { getScenario } from '../../../../scenarios/index.js';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
const CLIENT_TIMEOUT = 10000; // 10 seconds for client to complete
|
||||
|
||||
export async function runClientAgainstScenario(
|
||||
clientPath: string,
|
||||
scenarioName: string,
|
||||
expectedFailureSlugs: string[] = []
|
||||
): Promise<void> {
|
||||
const scenario = getScenario(scenarioName);
|
||||
if (!scenario) {
|
||||
throw new Error(`Scenario ${scenarioName} not found`);
|
||||
}
|
||||
|
||||
// Start the scenario server
|
||||
const urls = await scenario.start();
|
||||
const serverUrl = urls.serverUrl;
|
||||
|
||||
try {
|
||||
// Run the client
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const clientProcess = spawn('npx', ['tsx', clientPath, serverUrl], {
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
clientProcess.stdout?.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
clientProcess.stderr?.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
clientProcess.kill('SIGTERM');
|
||||
reject(
|
||||
new Error(
|
||||
`Client failed to complete within ${CLIENT_TIMEOUT}ms\nStdout: ${stdout}\nStderr: ${stderr}`
|
||||
)
|
||||
);
|
||||
}, CLIENT_TIMEOUT);
|
||||
|
||||
clientProcess.on('exit', (code) => {
|
||||
clearTimeout(timeout);
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Client exited with code ${code}\nStdout: ${stdout}\nStderr: ${stderr}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
clientProcess.on('error', (error) => {
|
||||
clearTimeout(timeout);
|
||||
reject(
|
||||
new Error(
|
||||
`Failed to start client: ${error.message}\nStdout: ${stdout}\nStderr: ${stderr}`
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Get checks from the scenario
|
||||
const checks = scenario.getChecks();
|
||||
|
||||
// Verify checks were returned
|
||||
if (checks.length === 0) {
|
||||
throw new Error('No checks returned from scenario');
|
||||
}
|
||||
|
||||
// Filter out INFO checks
|
||||
const nonInfoChecks = checks.filter((c) => c.status !== 'INFO');
|
||||
|
||||
// Check for expected failures
|
||||
if (expectedFailureSlugs.length > 0) {
|
||||
// Verify that the expected failures are present
|
||||
for (const slug of expectedFailureSlugs) {
|
||||
const check = checks.find((c) => c.id === slug);
|
||||
if (!check) {
|
||||
throw new Error(`Expected failure check ${slug} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that only the expected checks failed
|
||||
const failures = nonInfoChecks.filter((c) => c.status === 'FAILURE');
|
||||
const failureSlugs = failures.map((c) => c.id);
|
||||
if (
|
||||
failureSlugs.sort().join(',') !== expectedFailureSlugs.sort().join(',')
|
||||
) {
|
||||
throw new Error(
|
||||
`Expected failures ${expectedFailureSlugs.sort().join(', ')} but got ${failureSlugs.sort().join(', ')}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Default: expect all checks to pass
|
||||
const failures = nonInfoChecks.filter((c) => c.status === 'FAILURE');
|
||||
if (failures.length > 0) {
|
||||
const failureMessages = failures
|
||||
.map((c) => `${c.name}: ${c.errorMessage || c.description}`)
|
||||
.join('\n ');
|
||||
throw new Error(`Scenario failed with checks:\n ${failureMessages}`);
|
||||
}
|
||||
|
||||
// All non-INFO checks should be SUCCESS
|
||||
const successes = nonInfoChecks.filter((c) => c.status === 'SUCCESS');
|
||||
if (successes.length !== nonInfoChecks.length) {
|
||||
throw new Error(
|
||||
`Expected all checks to pass but got ${successes.length}/${nonInfoChecks.length}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Stop the scenario server
|
||||
await scenario.stop();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue