/**
 * Theme B (P1-14) — classifyError helper.
 *
 * Maps a probe failure (Error instance, status code, response context)
 * into one of the structured ErrorClass values. Strategies call it from
 * their catch blocks and post-response branches; the rules engine
 * matches on the result.
 */
import { classifyError } from '../classify-error';

describe('classifyError (Theme B)', () => {
    it('classifies wall-clock / socket timeout as TIMEOUT', () => {
        expect(classifyError({ error: new Error('Timeout') })).toBe('TIMEOUT');
        expect(classifyError({ error: new Error('timeout exceeded') })).toBe('TIMEOUT');
        const abort = new Error('aborted') as Error & { code?: string };
        abort.code = 'ABORT_ERR';
        expect(classifyError({ error: abort })).toBe('TIMEOUT');
    });

    it('classifies DNS resolution failures as DNS_FAIL', () => {
        const e = new Error('getaddrinfo ENOTFOUND nope.example') as Error & { code?: string };
        e.code = 'ENOTFOUND';
        expect(classifyError({ error: e })).toBe('DNS_FAIL');

        const e2 = new Error('queryA EAI_AGAIN') as Error & { code?: string };
        e2.code = 'EAI_AGAIN';
        expect(classifyError({ error: e2 })).toBe('DNS_FAIL');
    });

    it('classifies TCP connection refused as TCP_REJECT', () => {
        const e = new Error('connect ECONNREFUSED 1.2.3.4:443') as Error & { code?: string };
        e.code = 'ECONNREFUSED';
        expect(classifyError({ error: e })).toBe('TCP_REJECT');

        const e2 = new Error('connect EHOSTUNREACH') as Error & { code?: string };
        e2.code = 'EHOSTUNREACH';
        expect(classifyError({ error: e2 })).toBe('TCP_REJECT');
    });

    it('classifies TLS handshake failures as TLS_INVALID', () => {
        const e = new Error('CERT_HAS_EXPIRED') as Error & { code?: string };
        e.code = 'CERT_HAS_EXPIRED';
        expect(classifyError({ error: e })).toBe('TLS_INVALID');

        expect(
            classifyError({ error: new Error('Hostname/IP does not match certificate altnames') })
        ).toBe('TLS_INVALID');
    });

    it('classifies status-code failures by range', () => {
        expect(classifyError({ statusCode: 500 })).toBe('HTTP_5XX');
        expect(classifyError({ statusCode: 503 })).toBe('HTTP_5XX');
        expect(classifyError({ statusCode: 404 })).toBe('HTTP_4XX');
        expect(classifyError({ statusCode: 418 })).toBe('HTTP_4XX');
    });

    it('classifies keyword failures as KEYWORD_MISS', () => {
        expect(classifyError({ keywordFailed: true })).toBe('KEYWORD_MISS');
    });

    it('falls back to UNKNOWN when nothing matches', () => {
        expect(classifyError({ error: new Error('something weird') })).toBe('UNKNOWN');
        expect(classifyError({})).toBe('UNKNOWN');
    });

    it('prefers more-specific signals (keyword fail > status code > error code)', () => {
        // A 200 OK with keyword fail should still register as KEYWORD_MISS.
        expect(classifyError({ statusCode: 200, keywordFailed: true })).toBe('KEYWORD_MISS');
        // An error with both ECONNREFUSED and TIMEOUT in message — error code wins.
        const e = new Error('Timeout connect ECONNREFUSED') as Error & { code?: string };
        e.code = 'ECONNREFUSED';
        expect(classifyError({ error: e })).toBe('TCP_REJECT');
    });
});
