// HTTP-клиент e2e. Тело ответа типизировано как `any` намеренно: тесты проверяют // внешний контракт JSON, а не типы src/. Правила no-unsafe-* отключены только здесь // и в файлах сценариев. /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment */ import pg from 'pg'; import { inject } from 'vitest'; import type { E2EContext } from './context.js'; export interface ApiResponse { status: number; body: any; text: string; headers: Headers; } export interface RequestOptions { token?: string | undefined; body?: unknown; headers?: Record; base?: 'secured' | 'open' | 'ratelimit'; } export function ctx(): E2EContext { return inject('e2e'); } export async function api( method: string, path: string, options: RequestOptions = {}, ): Promise { const context = ctx(); const base = options.base === 'open' ? context.openBaseUrl : options.base === 'ratelimit' ? context.rateLimitBaseUrl : context.baseUrl; const headers: Record = { ...(options.headers ?? {}) }; if (options.token) headers.authorization = `Bearer ${options.token}`; let payload: string | undefined; if (options.body !== undefined) { headers['content-type'] = 'application/json'; payload = JSON.stringify(options.body); } const url = path.startsWith('/api/') || path.startsWith('/staff') ? path : `/api/v1/staff${path}`; const response = await fetch(`${base}${url}`, { method, headers, ...(payload === undefined ? {} : { body: payload }), redirect: 'manual', }); const text = await response.text(); let body: any = null; try { body = text ? JSON.parse(text) : null; } catch { body = null; } return { status: response.status, body, text, headers: response.headers }; } /** Краткое описание ответа для сообщений assert (без токенов). */ export function describeResponse(response: ApiResponse): string { const snippet = response.text.length > 300 ? `${response.text.slice(0, 300)}…` : response.text; return `HTTP ${response.status} ${snippet}`; } /** Запрос к БД ролью приложения внутри tenant-транзакции (RLS действует). */ export async function dbQuery>( sql: string, params: unknown[] = [], ): Promise { const context = ctx(); const client = new pg.Client({ connectionString: context.appDatabaseUrl }); await client.connect(); try { await client.query('BEGIN'); await client.query("SELECT set_config('app.tenant_id', $1, true)", [context.tenantId]); const result = await client.query(sql, params); await client.query('COMMIT'); return result.rows; } finally { await client.end(); } }