import { describe, it, expect, vi } from 'vitest';
import { classifyLink, countMissingAltImages, extractLinks, scanDocument, } from '../../content-health/scanner.js';
const DAY = 24 * 60 * 60 * 1000;
function fixture(overrides = {}) {
    return {
        id: 'doc-1',
        data: {
            metaDescription: 'A perfectly fine meta description.',
            body: '<h1>Title</h1><p>Body text.</p><img src="a.jpg" alt="A">',
            ...overrides.data,
        },
        updatedAt: new Date(Date.now() - 7 * DAY),
        publishedAt: new Date(Date.now() - 7 * DAY),
        ...overrides,
    };
}
describe('countMissingAltImages', () => {
    it('returns 0 when every <img> has an alt attribute', () => {
        const html = '<img src="a.jpg" alt="A"><img src="b.jpg" alt=""><img src="c.jpg" alt="C">';
        expect(countMissingAltImages(html)).toBe(0);
    });
    it('counts <img> tags missing alt regardless of attribute order', () => {
        const html = '<img src="a.jpg"><img alt="ok" src="b.jpg"><IMG SRC="c.jpg">';
        expect(countMissingAltImages(html)).toBe(2);
    });
    it('does not falsely match `alt` substrings in other attributes', () => {
        // The naive regex must not be fooled by `data-default-alt="..."`.
        const html = '<img src="x.jpg" data-default-alt="hi">';
        expect(countMissingAltImages(html)).toBe(1);
    });
    it('handles empty / non-HTML input gracefully', () => {
        expect(countMissingAltImages('')).toBe(0);
        expect(countMissingAltImages('no html here')).toBe(0);
    });
});
describe('extractLinks', () => {
    it('extracts href values across quote styles', () => {
        const html = `<a href="/foo">x</a><a href='/bar'>y</a><a class="x" href="/baz">z</a>`;
        expect(extractLinks(html).sort()).toEqual(['/bar', '/baz', '/foo']);
    });
    it('ignores anchors without href', () => {
        const html = `<a>no href</a><a href="/foo">x</a>`;
        expect(extractLinks(html)).toEqual(['/foo']);
    });
});
describe('classifyLink', () => {
    const origin = 'https://example.com';
    it('treats relative paths as internal', () => {
        expect(classifyLink('/about', origin)).toEqual({
            isInternal: true,
            resolved: 'https://example.com/about',
        });
        expect(classifyLink('./relative', origin).isInternal).toBe(true);
    });
    it('treats same-origin absolute URLs as internal', () => {
        expect(classifyLink('https://example.com/page', origin).isInternal).toBe(true);
    });
    it('classifies cross-origin URLs as external', () => {
        expect(classifyLink('https://other.com/x', origin).isInternal).toBe(false);
    });
    it('rejects non-http schemes and hash-only anchors', () => {
        expect(classifyLink('mailto:a@b.com', origin).resolved).toBeNull();
        expect(classifyLink('tel:1234', origin).resolved).toBeNull();
        expect(classifyLink('javascript:alert(1)', origin).resolved).toBeNull();
        expect(classifyLink('#section', origin).resolved).toBeNull();
        expect(classifyLink('', origin).resolved).toBeNull();
    });
    it('returns nulls when siteOrigin is malformed', () => {
        expect(classifyLink('/about', 'not-a-url').resolved).toBeNull();
    });
});
describe('scanDocument', () => {
    it('produces no issues for a healthy document', async () => {
        const issues = await scanDocument(fixture(), { checkLink: null });
        expect(issues).toEqual([]);
    });
    it('flags missing meta description', async () => {
        const doc = fixture();
        doc.data = { ...doc.data, metaDescription: undefined, seoDescription: undefined };
        const issues = await scanDocument(doc, { checkLink: null });
        expect(issues.map((i) => i.type)).toContain('MISSING_META_DESCRIPTION');
    });
    it('reads meta description from a nested `seo` object', async () => {
        const doc = fixture();
        doc.data = { body: doc.data.body, seo: { description: 'nested ok' } };
        const issues = await scanDocument(doc, { checkLink: null });
        expect(issues.map((i) => i.type)).not.toContain('MISSING_META_DESCRIPTION');
    });
    it('flags missing alt text with the per-document count', async () => {
        const doc = fixture();
        doc.data = {
            metaDescription: 'ok',
            body: '<img src="a.jpg"><img src="b.jpg"><img alt="ok" src="c.jpg">',
        };
        const issues = await scanDocument(doc, { checkLink: null });
        const altIssue = issues.find((i) => i.type === 'MISSING_ALT_TEXT');
        expect(altIssue?.count).toBe(2);
    });
    it('flags outdated content beyond the configured threshold', async () => {
        const doc = fixture({
            updatedAt: new Date(Date.now() - 200 * DAY),
            publishedAt: new Date(Date.now() - 200 * DAY),
        });
        const issues = await scanDocument(doc, { checkLink: null });
        const outdated = issues.find((i) => i.type === 'OUTDATED_CONTENT');
        expect(outdated).toBeDefined();
        expect(outdated?.details?.ageDays).toBeGreaterThan(180);
    });
    it('does NOT flag outdated content when published within the threshold', async () => {
        const doc = fixture({
            updatedAt: new Date(Date.now() - 10 * DAY),
            publishedAt: new Date(Date.now() - 10 * DAY),
        });
        const issues = await scanDocument(doc, { checkLink: null });
        expect(issues.find((i) => i.type === 'OUTDATED_CONTENT')).toBeUndefined();
    });
    it('flags broken internal links via the injected checker', async () => {
        const doc = fixture();
        doc.data = {
            metaDescription: 'ok',
            body: `<a href="/good">a</a><a href="/bad">b</a><a href="https://external.com/x">c</a>`,
        };
        const checkLink = vi.fn(async (url) => !url.endsWith('/bad'));
        const issues = await scanDocument(doc, {
            checkLink,
            siteOrigin: 'https://example.com',
        });
        const broken = issues.find((i) => i.type === 'BROKEN_INTERNAL_LINK');
        expect(broken?.count).toBe(1);
        expect(broken?.details?.urls).toEqual(['https://example.com/bad']);
        // External link must NOT be probed (cross-origin)
        expect(checkLink).not.toHaveBeenCalledWith('https://external.com/x');
    });
    it('skips link checking when no checker or origin is supplied', async () => {
        const doc = fixture();
        doc.data = { metaDescription: 'ok', body: '<a href="/whatever">x</a>' };
        const checker = vi.fn(async () => true);
        await scanDocument(doc, { checkLink: checker, siteOrigin: null });
        expect(checker).not.toHaveBeenCalled();
    });
    it('falls back to publishedAt when computing freshness, but updatedAt otherwise', async () => {
        // updatedAt recent, publishedAt very old → outdated by publishedAt rule.
        const doc = fixture({
            updatedAt: new Date(Date.now() - 1 * DAY),
            publishedAt: new Date(Date.now() - 365 * DAY),
        });
        const issues = await scanDocument(doc, { checkLink: null });
        expect(issues.find((i) => i.type === 'OUTDATED_CONTENT')).toBeDefined();
    });
});
//# sourceMappingURL=scanner.test.js.map