"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const ability_1 = require("@casl/ability");
const organizationMemberProfile_1 = require("../types/organizationMemberProfile");
const projectMemberRole_1 = require("../types/projectMemberRole");
const projects_1 = require("../types/projects");
const collapseAbilityRules_1 = require("./collapseAbilityRules");
const index_1 = require("./index");
const organizationMemberAbility_1 = tslib_1.__importDefault(require("./organizationMemberAbility"));
const ORG_UUID = 'test-org-uuid';
const USER_UUID = 'test-user-uuid';
const CUSTOM_ROLE_UUID = '11111111-1111-4111-a111-111111111111';
const PERMISSIONS_CONFIG = {
    pat: { enabled: false, allowedOrgRoles: [] },
};
const buildExpected = (role) => {
    const builder = new ability_1.AbilityBuilder(ability_1.Ability);
    (0, organizationMemberAbility_1.default)({
        role,
        member: { organizationUuid: ORG_UUID, userUuid: USER_UUID },
        builder,
        permissionsConfig: PERMISSIONS_CONFIG,
    });
    // getUserAbilityBuilder collapses rules before returning, so the reference
    // set must be collapsed the same way for an apples-to-apples comparison.
    builder.rules = (0, collapseAbilityRules_1.collapseAbilityRules)(builder.rules);
    return builder.build().rules;
};
const ruleSetEqual = (a, b) => {
    expect(a.length).toBe(b.length);
    expect(JSON.stringify(a.slice().sort())).toBe(JSON.stringify(b.slice().sort()));
};
describe('getUserAbilityBuilder — org-level role resolution', () => {
    describe('Backwards compatibility with system roles', () => {
        it.each([
            organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
            organizationMemberProfile_1.OrganizationMemberRole.VIEWER,
            organizationMemberProfile_1.OrganizationMemberRole.INTERACTIVE_VIEWER,
            organizationMemberProfile_1.OrganizationMemberRole.EDITOR,
            organizationMemberProfile_1.OrganizationMemberRole.DEVELOPER,
            organizationMemberProfile_1.OrganizationMemberRole.ADMIN,
        ])('%s user without roleUuid uses the system role path (unchanged)', (role) => {
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: undefined,
                },
                projectProfiles: [],
                permissionsConfig: PERMISSIONS_CONFIG,
            });
            ruleSetEqual(builder.build().rules, buildExpected(role));
        });
    });
    describe('Custom-roles feature flag', () => {
        it('falls through to the system role path when customRolesEnabled=false (even if roleUuid is set)', () => {
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.ADMIN,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: CUSTOM_ROLE_UUID,
                },
                projectProfiles: [],
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: { [CUSTOM_ROLE_UUID]: ['view:Dashboard'] },
                customRolesEnabled: false, // gate off
            });
            ruleSetEqual(builder.build().rules, buildExpected(organizationMemberProfile_1.OrganizationMemberRole.ADMIN));
        });
        it('falls through to the system role path when customRolesEnabled=true but the role has no scopes loaded', () => {
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.ADMIN,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: CUSTOM_ROLE_UUID,
                },
                projectProfiles: [],
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: {}, // no scopes for this role
                customRolesEnabled: true,
            });
            ruleSetEqual(builder.build().rules, buildExpected(organizationMemberProfile_1.OrganizationMemberRole.ADMIN));
        });
    });
    describe('Org-level custom role active', () => {
        it('uses the scope-derived path when roleUuid + customRolesEnabled + scopes are all present', () => {
            // A custom role granting only view:Dashboard. Admin's normal
            // abilities should NOT appear (e.g. manage:InviteLink).
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.ADMIN, // ignored — custom role wins
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: CUSTOM_ROLE_UUID,
                },
                projectProfiles: [],
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: { [CUSTOM_ROLE_UUID]: ['view:Dashboard'] },
                customRolesEnabled: true,
            });
            const ability = builder.build();
            // Custom role grants what the scope says
            expect(ability.rules.find((r) => r.subject === 'Dashboard')).toBeDefined();
            // Admin abilities are NOT present
            expect(ability.rules.find((r) => r.subject === 'InviteLink')).toBeUndefined();
            expect(ability.rules.find((r) => r.subject === 'Organization')).toBeUndefined();
        });
        it('different custom roles produce different abilities (smoke)', () => {
            const READ_ONLY_UUID = '22222222-2222-4222-a222-222222222222';
            const EDIT_UUID = '33333333-3333-4333-a333-333333333333';
            const { builder: readOnlyBuilder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: READ_ONLY_UUID,
                },
                projectProfiles: [],
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: {
                    [READ_ONLY_UUID]: ['view:Dashboard'],
                    [EDIT_UUID]: ['view:Dashboard', 'manage:Dashboard'],
                },
                customRolesEnabled: true,
            });
            const { builder: editBuilder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: EDIT_UUID,
                },
                projectProfiles: [],
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: {
                    [READ_ONLY_UUID]: ['view:Dashboard'],
                    [EDIT_UUID]: ['view:Dashboard', 'manage:Dashboard'],
                },
                customRolesEnabled: true,
            });
            const readOnlyRules = readOnlyBuilder.build().rules;
            const editRules = editBuilder.build().rules;
            // Edit role has manage rules; readOnly does not
            expect(editRules.some((r) => r.action === 'manage')).toBe(true);
            expect(readOnlyRules.some((r) => r.action === 'manage')).toBe(false);
        });
    });
    describe('Project profile resolution still works alongside org-level custom roles', () => {
        it('combines org system role with project-level system role', () => {
            const PROJECT_UUID = 'test-project-uuid';
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: undefined,
                },
                projectProfiles: [
                    {
                        projectUuid: PROJECT_UUID,
                        role: 'admin', // ProjectMemberRole.ADMIN
                        userUuid: USER_UUID,
                        roleUuid: undefined,
                    },
                ],
                permissionsConfig: PERMISSIONS_CONFIG,
            });
            const { rules } = builder.build();
            // Org member abilities (minimal) plus project admin abilities
            expect(rules.length).toBeGreaterThan(0);
        });
        it('returns collapsed rules for multi-project system role grants', () => {
            const projectUuids = Array.from({ length: 125 }, (_, i) => `project-${i}`);
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: undefined,
                },
                projectProfiles: [
                    ...projectUuids,
                    projectUuids[0], // duplicate direct + group style grant
                ].map((projectUuid) => ({
                    projectUuid,
                    role: projectMemberRole_1.ProjectMemberRole.ADMIN,
                    userUuid: USER_UUID,
                    roleUuid: undefined,
                })),
                permissionsConfig: PERMISSIONS_CONFIG,
            });
            const ability = builder.build();
            const mergedDashboardRule = ability.rules.find((rule) => rule.subject === 'Dashboard' &&
                rule.action === 'view' &&
                Boolean(rule.conditions?.projectUuid?.$in));
            if (!mergedDashboardRule) {
                throw new Error('Expected Dashboard rule to be collapsed');
            }
            expect(mergedDashboardRule.conditions.projectUuid.$in).toEqual([...projectUuids].sort());
            expect(ability.rules.length).toBeLessThan(100);
            expect(ability.can('manage', (0, ability_1.subject)('Dashboard', { projectUuid: 'project-0' }))).toBe(true);
            expect(ability.can('manage', (0, ability_1.subject)('Dashboard', { projectUuid: 'not-granted' }))).toBe(false);
        });
        it('returns collapsed rules for repeated custom project scopes', () => {
            const projectUuids = Array.from({ length: 10 }, (_, i) => `custom-project-${i}`);
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: undefined,
                },
                projectProfiles: projectUuids.map((projectUuid) => ({
                    projectUuid,
                    role: projectMemberRole_1.ProjectMemberRole.VIEWER,
                    userUuid: USER_UUID,
                    roleUuid: CUSTOM_ROLE_UUID,
                    projectType: projects_1.ProjectType.PREVIEW,
                    projectCreatedByUserUuid: USER_UUID,
                })),
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: {
                    [CUSTOM_ROLE_UUID]: [
                        'view:Dashboard',
                        'manage:Dashboard@self',
                    ],
                },
                customRolesEnabled: true,
                isEnterprise: true,
            });
            const ability = builder.build();
            const mergedViewDashboardRule = ability.rules.find((rule) => rule.subject === 'Dashboard' &&
                rule.action === 'view' &&
                Boolean(rule.conditions?.projectUuid?.$in));
            if (!mergedViewDashboardRule) {
                throw new Error('Expected custom Dashboard rule to collapse');
            }
            expect(mergedViewDashboardRule.conditions.projectUuid.$in).toEqual(projectUuids);
            expect(ability.can('view', (0, ability_1.subject)('Dashboard', {
                projectUuid: 'custom-project-0',
                inheritsFromOrgOrProject: true,
            }))).toBe(true);
            expect(ability.can('view', (0, ability_1.subject)('Dashboard', { projectUuid: 'not-granted' }))).toBe(false);
            expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
                projectUuid: 'custom-project-1',
                inheritsFromOrgOrProject: true,
                access: [],
            }))).toBe(true);
        });
    });
    describe('selfPreview scopes via project profiles', () => {
        it('grants manage:Dashboard@self only in self-created preview projects', () => {
            const { builder } = (0, index_1.getUserAbilityBuilder)({
                user: {
                    role: organizationMemberProfile_1.OrganizationMemberRole.MEMBER,
                    organizationUuid: ORG_UUID,
                    userUuid: USER_UUID,
                    roleUuid: undefined,
                },
                projectProfiles: [
                    {
                        projectUuid: 'prod-project',
                        role: 'viewer', // ProjectMemberRole.VIEWER
                        userUuid: USER_UUID,
                        roleUuid: CUSTOM_ROLE_UUID,
                        projectType: projects_1.ProjectType.DEFAULT,
                        projectCreatedByUserUuid: null,
                    },
                    {
                        projectUuid: 'preview-project',
                        role: 'viewer', // ProjectMemberRole.VIEWER
                        userUuid: USER_UUID,
                        roleUuid: CUSTOM_ROLE_UUID,
                        projectType: projects_1.ProjectType.PREVIEW,
                        projectCreatedByUserUuid: USER_UUID,
                    },
                ],
                permissionsConfig: PERMISSIONS_CONFIG,
                customRoleScopes: {
                    [CUSTOM_ROLE_UUID]: ['manage:Dashboard@self'],
                },
                customRolesEnabled: true,
                isEnterprise: true,
            });
            const ability = builder.build();
            // Public/shared space in the own preview → editable
            expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
                organizationUuid: ORG_UUID,
                projectUuid: 'preview-project',
                inheritsFromOrgOrProject: true,
                access: [],
            }))).toBe(true);
            // Private space the user isn't a member of (copied verbatim from
            // prod) → invisible + uneditable, even in the own preview
            expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
                organizationUuid: ORG_UUID,
                projectUuid: 'preview-project',
                inheritsFromOrgOrProject: false,
                access: [],
            }))).toBe(false);
            // Nothing in production
            expect(ability.can('manage', (0, ability_1.subject)('Dashboard', {
                organizationUuid: ORG_UUID,
                projectUuid: 'prod-project',
                inheritsFromOrgOrProject: true,
                access: [],
            }))).toBe(false);
        });
    });
});
//# sourceMappingURL=getUserAbilityBuilder.test.js.map