export interface HttpFailureContext { role?: string; url: string; status: number; location?: string | null; html?: string; extra?: string; serverLogs?: string; } export class HttpDiagnostics { static clipString(str: string, max = 1200): string { if (str.length <= max) return str; return str.substring(0, max) + `... [clipped ${str.length - max} chars]`; } static formatHttpFailure({ role, url, status, location, html, extra, serverLogs }: HttpFailureContext): string { const lines = [ `HTTP Failure: ${status} for ${url}`, role ? `Role: ${role}` : null, location ? `Location: ${location}` : null, extra ? `Extra: ${extra}` : null, html ? `HTML Body (clipped):\n${this.clipString(html)}` : 'No HTML body provided', serverLogs ? `\n--- Server Log Tail ---\n${serverLogs}` : null, ].filter(Boolean); return lines.join('\n'); } static assertHtmlContains(html: string, mustContain: string | string[], context: HttpFailureContext): void { const targets = Array.isArray(mustContain) ? mustContain : [mustContain]; for (const target of targets) { if (!html.includes(target)) { const message = this.formatHttpFailure({ ...context, extra: `Expected HTML to contain: "${target}"`, html, }); throw new Error(message); } } } static assertHtmlNotContains(html: string, mustNotContain: string | string[], context: HttpFailureContext): void { const targets = Array.isArray(mustNotContain) ? mustNotContain : [mustNotContain]; for (const target of targets) { if (html.includes(target)) { const message = this.formatHttpFailure({ ...context, extra: `Expected HTML NOT to contain: "${target}"`, html, }); throw new Error(message); } } } }