import { describe, expect, it } from 'vitest';
import { parseUserAgent, getUserDetail, listUserSessions, listUserActivity, getUserPermissionView, countOwnedContent, transferContentOwnership, } from '../../auth/user-admin.js';
describe('parseUserAgent', () => {
    it('identifies common browser/OS pairs', () => {
        expect(parseUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36')).toEqual({ browser: 'Chrome', os: 'Windows', deviceLabel: 'Chrome on Windows' });
        expect(parseUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15')).toEqual({ browser: 'Safari', os: 'macOS', deviceLabel: 'Safari on macOS' });
        expect(parseUserAgent('Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0')).toEqual({ browser: 'Firefox', os: 'Linux', deviceLabel: 'Firefox on Linux' });
    });
    it('degrades unknown agents instead of guessing', () => {
        expect(parseUserAgent(null)).toEqual({
            browser: 'Unknown',
            os: 'Unknown',
            deviceLabel: 'Unknown device',
        });
    });
    it('prefers Edge over Chrome when both tokens are present', () => {
        const r = parseUserAgent('Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36 Edg/124.0');
        expect(r.browser).toBe('Edge');
    });
});
describe('getUserDetail', () => {
    function db(overrides = {}) {
        const user = {
            id: 'u1',
            name: 'Dana Chen',
            email: 'dana@actuatemedia.com',
            role: 'OWNER',
            passwordHash: 'x',
            totpEnabled: true,
            mfaRequired: false,
            forcePasswordReset: false,
            failedLoginCount: 0,
            isActive: true,
            emailVerified: true,
            lastLoginAt: new Date('2026-06-01T00:00:00Z'),
            createdAt: new Date('2026-01-01T00:00:00Z'),
            invitedById: null,
            ...overrides,
        };
        return {
            user: { findUnique: async ({ where }) => (where.id === user.id ? user : null) },
            oAuthAccount: { findMany: async () => [] },
            document: { count: async () => 3 },
        };
    }
    it('serializes a member into the detail shape', async () => {
        const detail = await getUserDetail(db(), 'u1', {
            workspaceDomain: 'actuatemedia.com',
            currentUserId: 'u1',
        });
        expect(detail).toMatchObject({
            id: 'u1',
            roleName: 'Owner',
            status: 'active',
            mfaEnabled: true,
            passwordSet: true,
            ssoOnly: false,
            externalDomain: false,
            contentOwnedCount: 3,
            isCurrentUser: true,
        });
    });
    it('flags external domains and SSO-only accounts', async () => {
        const detail = await getUserDetail({
            user: {
                findUnique: async () => ({
                    id: 'u2',
                    name: 'Sara',
                    email: 'sara@freelance.io',
                    role: 'VIEWER',
                    passwordHash: null,
                    totpEnabled: false,
                    isActive: true,
                    createdAt: new Date(),
                }),
            },
            oAuthAccount: { findMany: async () => [{ provider: 'google' }] },
            document: { count: async () => 0 },
        }, 'u2', { workspaceDomain: 'actuatemedia.com' });
        expect(detail?.externalDomain).toBe(true);
        expect(detail?.ssoOnly).toBe(true);
        expect(detail?.oauthProviders).toEqual(['google']);
    });
    it('returns null for a missing user', async () => {
        expect(await getUserDetail(db(), 'nope')).toBeNull();
    });
    it('derives status from lifecycle columns', async () => {
        const suspended = await getUserDetail(db({ suspendedAt: new Date() }), 'u1');
        expect(suspended?.status).toBe('suspended');
        const locked = await getUserDetail(db({ lockedAt: new Date() }), 'u1');
        expect(locked?.status).toBe('locked');
        const gone = await getUserDetail(db({ isActive: false }), 'u1');
        expect(gone?.status).toBe('deactivated');
    });
});
describe('listUserSessions', () => {
    it('maps sessions, marks the current one, and computes active', async () => {
        const future = new Date(Date.now() + 86400000);
        const past = new Date(Date.now() - 86400000);
        const db = {
            session: {
                findMany: async () => [
                    {
                        id: 's1',
                        userAgent: 'x Chrome/1 Safari',
                        expiresAt: future,
                        createdAt: new Date(),
                        revokedAt: null,
                    },
                    { id: 's2', userAgent: 'x', expiresAt: past, createdAt: new Date(), revokedAt: null },
                    {
                        id: 's3',
                        userAgent: 'x',
                        expiresAt: future,
                        createdAt: new Date(),
                        revokedAt: new Date(),
                    },
                ],
            },
        };
        const sessions = await listUserSessions(db, 'u1', 's1');
        expect(sessions).toHaveLength(3);
        expect(sessions.find((s) => s.id === 's1')?.current).toBe(true);
        expect(sessions.find((s) => s.id === 's1')?.active).toBe(true);
        expect(sessions.find((s) => s.id === 's2')?.active).toBe(false); // expired
        expect(sessions.find((s) => s.id === 's3')?.active).toBe(false); // revoked
    });
});
describe('listUserActivity', () => {
    it('merges actor + target events, de-duplicates, and sorts newest first', async () => {
        const db = {
            auditLog: {
                findMany: async ({ where }) => {
                    if (where.userId === 'u1') {
                        return [
                            { id: 'a1', event: 'login', userId: 'u1', timestamp: new Date('2026-06-03') },
                            { id: 'a2', event: 'role_changed', userId: 'u1', timestamp: new Date('2026-06-01') },
                        ];
                    }
                    // target query
                    return [
                        { id: 'a2', event: 'role_changed', timestamp: new Date('2026-06-01') }, // dup id
                        { id: 'a3', event: 'user_suspended', timestamp: new Date('2026-06-05') },
                    ];
                },
            },
        };
        const events = await listUserActivity(db, 'u1');
        expect(events.map((e) => e.id)).toEqual(['a3', 'a1', 'a2']);
        expect(events.find((e) => e.id === 'a1')?.role).toBe('actor');
        expect(events.find((e) => e.id === 'a3')?.role).toBe('target');
    });
});
describe('getUserPermissionView', () => {
    it('returns role, display label, and derived permissions', () => {
        const view = getUserPermissionView('EDITOR');
        expect(view.roleName).toBe('Editor');
        expect(view.permissions).toContain('content.publish');
        expect(view.permissions).not.toContain('users.invite');
    });
});
describe('countOwnedContent', () => {
    it('sums documents, media, and templates and tolerates missing models', async () => {
        const db = {
            document: { count: async () => 4 },
            media: { count: async () => 2 },
            // contentTemplate intentionally absent → treated as 0
        };
        expect(await countOwnedContent(db, 'u1')).toBe(6);
    });
});
describe('transferContentOwnership', () => {
    it('reassigns owner-of-record fields and returns per-type counts', async () => {
        const calls = [];
        const make = (model, n) => ({
            updateMany: async ({ where, data }) => {
                calls.push({ model, where, data });
                return { count: n };
            },
        });
        const db = {
            document: make('document', 3),
            media: make('media', 1),
            contentTemplate: make('contentTemplate', 2),
        };
        const res = await transferContentOwnership(db, 'from-1', 'to-1');
        expect(res).toEqual({ documents: 3, media: 1, templates: 2, total: 6 });
        // Documents + templates move via createdById; media via uploadedById.
        expect(calls.find((c) => c.model === 'document').data).toEqual({ createdById: 'to-1' });
        expect(calls.find((c) => c.model === 'media').data).toEqual({ uploadedById: 'to-1' });
        expect(calls.find((c) => c.model === 'document').where).toEqual({ createdById: 'from-1' });
    });
    it('is a no-op for content types not present in the schema', async () => {
        const res = await transferContentOwnership({ document: { updateMany: async () => ({ count: 5 }) } }, 'a', 'b');
        expect(res).toEqual({ documents: 5, media: 0, templates: 0, total: 5 });
    });
});
//# sourceMappingURL=user-admin.test.js.map