import { describe, it, expect, vi, beforeEach } from 'vitest';
import { planSlugChangeRedirects, isEmptyPlan } from '../../redirects/auto.js';
import { updateDocument } from '../../actions.js';
import { setActuateConfig } from '../../config/runtime.js';
describe('planSlugChangeRedirects (pure)', () => {
    it('is a no-op when the path did not change', () => {
        const plan = planSlugChangeRedirects({ oldPath: '/about', newPath: '/about', existing: [] });
        expect(isEmptyPlan(plan)).toBe(true);
    });
    it('treats trailing-slash / case-only differences as the same URL', () => {
        const plan = planSlugChangeRedirects({ oldPath: '/About/', newPath: '/about', existing: [] });
        expect(isEmptyPlan(plan)).toBe(true);
    });
    it('plans a single 301 for a simple rename', () => {
        const plan = planSlugChangeRedirects({ oldPath: '/old', newPath: '/new', existing: [] });
        expect(plan.upsert).toEqual({ source: '/old', destination: '/new' });
        expect(plan.deleteIds).toEqual([]);
        expect(plan.repoint).toEqual([]);
    });
    it('never moves the site root', () => {
        const plan = planSlugChangeRedirects({ oldPath: '/', newPath: '/welcome', existing: [] });
        expect(isEmptyPlan(plan)).toBe(true);
    });
    it('flattens chains: rules pointing at the old path repoint to the new path', () => {
        const existing = [
            { id: 'r1', from: '/ancient', to: '/old', enabled: true },
            { id: 'r2', from: '/unrelated', to: '/elsewhere', enabled: true },
        ];
        const plan = planSlugChangeRedirects({ oldPath: '/old', newPath: '/new', existing });
        expect(plan.upsert).toEqual({ source: '/old', destination: '/new' });
        expect(plan.repoint).toEqual([{ id: 'r1', destination: '/new' }]);
    });
    it('drops a stale rule whose source is the now-live new path (A→B then B→A)', () => {
        // A previous rename created /new → /old. Renaming /old back to /new must
        // delete that rule (otherwise the live /new page would redirect away) and
        // add /old → /new without forming a loop.
        const existing = [{ id: 'r1', from: '/new', to: '/old', enabled: true }];
        const plan = planSlugChangeRedirects({ oldPath: '/old', newPath: '/new', existing });
        expect(plan.deleteIds).toEqual(['r1']);
        expect(plan.upsert).toEqual({ source: '/old', destination: '/new' });
    });
    it('normalizes prefixed paths consistently', () => {
        const plan = planSlugChangeRedirects({
            oldPath: '/blog/old-post',
            newPath: '/blog/new-post',
            existing: [],
        });
        expect(plan.upsert).toEqual({ source: '/blog/old-post', destination: '/blog/new-post' });
    });
});
function createMockDB(seedDoc) {
    const docs = new Map([[seedDoc.id, seedDoc]]);
    const redirects = [];
    let rid = 0;
    const redirectDelegate = {
        findMany: vi.fn(async () => redirects.map((r) => ({ ...r }))),
        upsert: vi.fn(async ({ where, create, update }) => {
            const found = redirects.find((r) => r.source === where.source);
            if (found) {
                Object.assign(found, update);
                return { ...found };
            }
            const row = { id: `red-${rid++}`, ...create };
            redirects.push(row);
            return { ...row };
        }),
        update: vi.fn(async ({ where, data }) => {
            const found = redirects.find((r) => r.id === where.id);
            Object.assign(found, data);
            return { ...found };
        }),
        delete: vi.fn(async ({ where }) => {
            const i = redirects.findIndex((r) => r.id === where.id);
            const [removed] = redirects.splice(i, 1);
            return removed;
        }),
    };
    const db = {
        document: {
            findFirst: vi.fn(async ({ where }) => {
                const doc = docs.get(where.id);
                if (!doc)
                    return null;
                if (where.deletedAt === null && doc.deletedAt !== null)
                    return null;
                return { ...doc };
            }),
        },
        redirect: redirectDelegate,
        version: { create: vi.fn(async () => ({})) },
        $transaction: vi.fn(async (fn) => fn({
            document: {
                update: vi.fn(async ({ where, data }) => {
                    const doc = docs.get(where.id);
                    Object.assign(doc, {
                        slug: typeof data.slug === 'string' ? data.slug : doc.slug,
                        status: data.status ?? doc.status,
                        data: data.data ?? doc.data,
                    });
                    return { ...doc };
                }),
            },
            version: { create: vi.fn(async () => ({})) },
            redirect: redirectDelegate,
        })),
        _redirects: redirects,
    };
    return db;
}
function ctxFor(db) {
    return { userId: 'user-1', role: 'ADMIN', db };
}
describe('updateDocument auto-301', () => {
    beforeEach(() => {
        setActuateConfig({});
    });
    it('creates a 301 when a published document changes slug', async () => {
        const db = createMockDB({
            id: 'doc-1',
            collection: 'pages',
            slug: 'old-about',
            status: 'PUBLISHED',
            data: { title: 'About', slug: 'old-about', status: 'PUBLISHED' },
            deletedAt: null,
        });
        await updateDocument('pages', 'doc-1', { slug: 'about', status: 'PUBLISHED' }, ctxFor(db));
        expect(db._redirects).toHaveLength(1);
        expect(db._redirects[0]).toMatchObject({
            source: '/old-about',
            destination: '/about',
            statusCode: 301,
            source_kind: 'slug-change',
        });
    });
    it('honors data.path over slug when resolving the moved URL', async () => {
        const db = createMockDB({
            id: 'doc-1',
            collection: 'pages',
            slug: 'pricing',
            status: 'PUBLISHED',
            data: { title: 'Pricing', slug: 'pricing', path: '/old-pricing', status: 'PUBLISHED' },
            deletedAt: null,
        });
        await updateDocument('pages', 'doc-1', { path: '/pricing', status: 'PUBLISHED' }, ctxFor(db));
        expect(db._redirects[0]).toMatchObject({ source: '/old-pricing', destination: '/pricing' });
    });
    it('does NOT create a redirect for a draft slug change (old URL was never live)', async () => {
        const db = createMockDB({
            id: 'doc-1',
            collection: 'pages',
            slug: 'old',
            status: 'DRAFT',
            data: { title: 'Draft', slug: 'old', status: 'DRAFT' },
            deletedAt: null,
        });
        await updateDocument('pages', 'doc-1', { slug: 'new', status: 'DRAFT' }, ctxFor(db));
        expect(db._redirects).toHaveLength(0);
    });
    it('does NOT create a redirect when the slug is unchanged', async () => {
        const db = createMockDB({
            id: 'doc-1',
            collection: 'pages',
            slug: 'stable',
            status: 'PUBLISHED',
            data: { title: 'Stable', slug: 'stable', status: 'PUBLISHED' },
            deletedAt: null,
        });
        await updateDocument('pages', 'doc-1', { title: 'New Title', status: 'PUBLISHED' }, ctxFor(db));
        expect(db._redirects).toHaveLength(0);
    });
});
//# sourceMappingURL=auto-301.test.js.map