import { createServer, type Server } from 'node:http' import type { BackendTarget } from '@dbmole/core' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { createEsBackend } from '../../../src/es/backend.js' import type { ConnectionConfig } from '../../../src/schema.js' let server: Server let port: number const seen: { method?: string; url?: string; auth?: string; body?: string }[] = [] beforeAll(async () => { server = createServer((req, res) => { let body = '' req.on('data', (c) => { body += c }) req.on('end', () => { seen.push({ method: req.method, url: req.url, auth: req.headers.authorization, body }) if (req.url?.startsWith('/_fail')) { res.writeHead(404, { 'content-type': 'application/json' }) res.end(JSON.stringify({ error: 'index_not_found' })) return } if (req.url?.startsWith('/_text')) { res.writeHead(200, { 'content-type': 'text/plain' }) res.end('plain text body') return } if (req.url?.startsWith('/_empty')) { res.writeHead(200, { 'content-type': 'text/plain' }) res.end('') return } if (req.url?.startsWith('/_brace')) { // looks like JSON by leading brace but is not parseable res.writeHead(200, { 'content-type': 'text/plain' }) res.end('{not json') return } res.writeHead(200, { 'content-type': 'application/json' }) res.end(JSON.stringify({ ok: true, echo: body || null })) }) }) await new Promise((r) => server.listen(0, '127.0.0.1', () => r())) port = (server.address() as { port: number }).port }) afterAll(async () => { await new Promise((r) => server.close(() => r())) }) const target = (cfg: Partial = {}): BackendTarget => ({ config: { name: 'x', host: '127.0.0.1', type: 'elasticsearch', scheme: 'http', verifyTls: false, user: 'u', password: 'p', readonly: false, ...cfg } as ConnectionConfig, connectHost: '127.0.0.1', connectPort: port, serverName: '127.0.0.1' }) describe('createEsBackend', () => { it('sends basic auth and parses JSON', async () => { const b = createEsBackend(target()) const res = await b.request('GET', '/_cluster/health') expect(res.status).toBe(200) expect(res.body).toEqual({ ok: true, echo: null }) expect(seen.at(-1)?.auth).toMatch(/^Basic /) await b.dispose() }) it('sends ApiKey auth when configured', async () => { const b = createEsBackend(target({ user: undefined, password: undefined, apiKey: 'abc==' })) await b.request('GET', '/') expect(seen.at(-1)?.auth).toBe('ApiKey abc==') await b.dispose() }) it('returns status+body on 4xx without throwing', async () => { const b = createEsBackend(target()) const res = await b.request('GET', '/_fail') expect(res.status).toBe(404) expect(res.body).toEqual({ error: 'index_not_found' }) await b.dispose() }) it('serializes object bodies as JSON and sends string bodies as-is', async () => { const b = createEsBackend(target()) await b.request('POST', '/idx/_search', { body: { query: { match_all: {} } } }) expect(JSON.parse(seen.at(-1)?.body ?? '{}')).toEqual({ query: { match_all: {} } }) await b.request('POST', '/_bulk', { body: '{"index":{}}\n{"a":1}\n' }) expect(seen.at(-1)?.body).toBe('{"index":{}}\n{"a":1}\n') await b.dispose() }) it('forwards query params', async () => { const b = createEsBackend(target()) await b.request('GET', '/_cat/indices', { params: { format: 'json', v: true } }) expect(seen.at(-1)?.url).toContain('format=json') await b.dispose() }) it('sends no auth header when neither credentials nor apiKey are present', async () => { const b = createEsBackend( target({ user: undefined, password: undefined, apiKey: undefined }) ) const res = await b.request('get', 'idx/_search') expect(res.status).toBe(200) expect(seen.at(-1)?.auth).toBeUndefined() // path without leading slash is normalized expect(seen.at(-1)?.url).toBe('/idx/_search') await b.dispose() }) it('decodes plain text bodies and empty bodies as null', async () => { const b = createEsBackend(target()) const text = await b.request('GET', '/_text') expect(text.body).toBe('plain text body') const empty = await b.request('GET', '/_empty') expect(empty.body).toBeNull() await b.dispose() }) it('falls back to raw text when a JSON-looking body fails to parse', async () => { const b = createEsBackend(target()) const res = await b.request('GET', '/_brace') expect(res.status).toBe(200) expect(res.body).toBe('{not json') await b.dispose() }) it('configures TLS servername for https origins', async () => { // https target: factory must take the connect-config branch without a request const b = createEsBackend( target({ scheme: 'https', verifyTls: true, host: 'es.example.com' }) ) expect(typeof b.request).toBe('function') await b.dispose() }) })