import { tap, expect } from '@git.zone/tstest/tapbundle'; import { AppConnection } from '../ts/reception/classes.appconnection.js'; import { BillingPlan } from '../ts/reception/classes.billingplan.js'; import { Organization } from '../ts/reception/classes.organization.js'; import { OrganizationManager } from '../ts/reception/classes.organizationmanager.js'; import { Role } from '../ts/reception/classes.role.js'; import { User } from '../ts/reception/classes.user.js'; import { UserInvitation } from '../ts/reception/classes.userinvitation.js'; const getNestedValue = (targetArg: any, pathArg: string) => { return pathArg.split('.').reduce((currentArg, keyArg) => currentArg?.[keyArg], targetArg); }; const matchesQuery = (targetArg: any, queryArg: Record) => { return Object.entries(queryArg).every(([keyArg, valueArg]) => { const currentValue = getNestedValue(targetArg, keyArg); if (valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg)) { return Object.entries(valueArg).every(([nestedKeyArg, nestedValueArg]) => currentValue?.[nestedKeyArg] === nestedValueArg); } return currentValue === valueArg; }); }; const attachPersistence = Promise; delete?: () => Promise }>( docArg: TDoc, mapArg: Map ) => { docArg.save = async () => { mapArg.set(docArg.id, docArg); }; docArg.delete = async () => { mapArg.delete(docArg.id); }; mapArg.set(docArg.id, docArg); return docArg; }; const createTestOrganizationManager = () => { const organizations = new Map(); const roles = new Map(); const users = new Map(); const appConnections = new Map(); const invitations = new Map(); const billingPlans = new Map(); const activities: Array<{ userId: string; action: string; description: string }> = []; const alerts: Array<{ eventType: string; organizationId?: string }> = []; const getInstancesFromMap = async (mapArg: Map, queryArg: Record = {}) => { return Array.from(mapArg.values()).filter((docArg) => matchesQuery(docArg, queryArg)); }; const reception = { db: { smartdataDb: {} }, typedrouter: { addTypedRouter: () => undefined }, roleManager: { getRoleForUserAndOrg: async (userArg: User, organizationArg: Organization) => { return Array.from(roles.values()).find((roleArg) => roleArg.data.userId === userArg.id && roleArg.data.organizationId === organizationArg.id) || null; }, getAllRolesForOrg: async (organizationIdArg: string) => { return Array.from(roles.values()).filter((roleArg) => roleArg.data.organizationId === organizationIdArg); }, }, userManager: { CUser: { getInstance: async (queryArg: Record) => { return Array.from(users.values()).find((docArg) => matchesQuery(docArg, queryArg)) || null; }, }, }, activityLogManager: { logActivity: async (userId: string, action: string, description: string) => { activities.push({ userId, action, description }); }, }, alertManager: { createAlertsForEvent: async (optionsArg: { eventType: string; organizationId?: string }) => { alerts.push(optionsArg); return []; }, }, appConnectionManager: { CAppConnection: { getInstances: async (queryArg: Record) => getInstancesFromMap(appConnections, queryArg), }, }, userInvitationManager: { CUserInvitation: { getInstances: async (queryArg: Record) => getInstancesFromMap(invitations, queryArg), }, }, billingPlanManager: { CBillingPlan: { getInstances: async (queryArg: Record) => getInstancesFromMap(billingPlans, queryArg), }, }, } as any; const manager = new OrganizationManager(reception); (manager as any).COrganization = { getInstance: async (queryArg: Record) => { return Array.from(organizations.values()).find((docArg) => matchesQuery(docArg, queryArg)) || null; }, getInstances: async (queryArg: Record) => getInstancesFromMap(organizations, queryArg), }; return { manager, organizations, roles, users, appConnections, invitations, billingPlans, activities, alerts, }; }; const addUser = (usersArg: Map, idArg: string, emailArg: string, connectedOrgsArg: string[] = []) => { const user = new User(); user.id = idArg; user.data = { name: emailArg, username: emailArg, email: emailArg, status: 'active', connectedOrgs: connectedOrgsArg, }; return attachPersistence(user, usersArg); }; const addOrganization = (organizationsArg: Map) => { const organization = new Organization(); organization.id = 'org-1'; organization.data = { name: 'Lossless GmbH', slug: 'lossless', billingPlanId: 'billing-1', roleIds: ['role-owner', 'role-member'], }; return attachPersistence(organization, organizationsArg); }; const addRole = (rolesArg: Map, idArg: string, userIdArg: string, rolesValueArg: string[]) => { const role = new Role(); role.id = idArg; role.data = { userId: userIdArg, organizationId: 'org-1', roles: rolesValueArg, }; return attachPersistence(role, rolesArg); }; tap.test('updates organization settings only with audited confirmation', async () => { const { manager, organizations, roles, users, activities, alerts } = createTestOrganizationManager(); const owner = addUser(users, 'owner-1', 'owner@example.com', ['org-1']); addOrganization(organizations); addRole(roles, 'role-owner', owner.id, ['owner']); await expect(manager.updateOrganizationWithAudit({ user: owner, organizationId: 'org-1', name: 'Lossless Updated', slug: 'lossless-updated', confirmationText: 'wrong', })).rejects.toThrow(); const updatedOrganization = await manager.updateOrganizationWithAudit({ user: owner, organizationId: 'org-1', name: 'Lossless Updated', slug: 'lossless-updated', confirmationText: 'lossless', }); expect(updatedOrganization.data.name).toEqual('Lossless Updated'); expect(updatedOrganization.data.slug).toEqual('lossless-updated'); expect(activities[0].action).toEqual('org_updated'); expect(alerts[0].eventType).toEqual('org_updated'); }); tap.test('deletes organization dependencies only with audited owner confirmation', async () => { const { manager, organizations, roles, users, appConnections, invitations, billingPlans, activities, alerts } = createTestOrganizationManager(); const owner = addUser(users, 'owner-1', 'owner@example.com', ['org-1']); const member = addUser(users, 'member-1', 'member@example.com', ['org-1']); addOrganization(organizations); addRole(roles, 'role-owner', owner.id, ['owner']); addRole(roles, 'role-member', member.id, ['viewer']); const appConnection = new AppConnection(); appConnection.id = 'connection-1'; appConnection.data = { organizationId: 'org-1', appId: 'app-1', appType: 'global', status: 'active', connectedAt: Date.now(), connectedByUserId: owner.id, grantedScopes: ['openid'], }; attachPersistence(appConnection, appConnections); const invitation = new UserInvitation(); invitation.id = 'invitation-1'; invitation.data = { email: 'invite@example.com', token: 'token', status: 'pending', createdAt: Date.now(), expiresAt: Date.now() + 1000, organizationRefs: [{ organizationId: 'org-1', invitedByUserId: owner.id, invitedAt: Date.now(), roles: ['viewer'], }], }; attachPersistence(invitation, invitations); const billingPlan = new BillingPlan(); billingPlan.id = 'billing-1'; billingPlan.data.organizationId = 'org-1'; attachPersistence(billingPlan, billingPlans); await expect(manager.deleteOrganizationWithAudit({ user: owner, organizationId: 'org-1', confirmationText: 'delete wrong', })).rejects.toThrow(); await manager.deleteOrganizationWithAudit({ user: owner, organizationId: 'org-1', confirmationText: 'delete lossless', }); expect(organizations.size).toEqual(0); expect(roles.size).toEqual(0); expect(appConnections.size).toEqual(0); expect(billingPlans.size).toEqual(0); expect(invitation.data.status).toEqual('cancelled'); expect(owner.data.connectedOrgs).toEqual([]); expect(member.data.connectedOrgs).toEqual([]); expect(activities[0].action).toEqual('org_deleted'); expect(alerts[0].eventType).toEqual('org_deleted'); }); tap.test('manages custom role definitions and cleans assignments and mappings on delete', async () => { const { manager, organizations, roles, users, appConnections } = createTestOrganizationManager(); const owner = addUser(users, 'owner-1', 'owner@example.com', ['org-1']); const member = addUser(users, 'member-1', 'member@example.com', ['org-1']); const organization = addOrganization(organizations); addRole(roles, 'role-owner', owner.id, ['owner']); const memberRole = addRole(roles, 'role-member', member.id, ['viewer', 'finance']); const roleDefinitions = await manager.upsertOrgRoleDefinition({ user: owner, organizationId: organization.id, roleDefinition: { key: 'finance', name: 'Finance', description: 'Finance team access', }, }); expect(roleDefinitions).toHaveLength(1); expect(roleDefinitions[0].key).toEqual('finance'); expect(await manager.assertRoleKeysAreValid(organization.id, ['finance'])).toEqual(['finance']); const appConnection = new AppConnection(); appConnection.id = 'connection-1'; appConnection.data = { organizationId: organization.id, appId: 'app-1', appType: 'global', status: 'active', connectedAt: Date.now(), connectedByUserId: owner.id, grantedScopes: ['openid'], roleMappings: [{ orgRoleKey: 'finance', appRoles: ['accountant'], permissions: ['invoices:read'], scopes: ['billing'], }], }; attachPersistence(appConnection, appConnections); await manager.deleteOrgRoleDefinition({ user: owner, organizationId: organization.id, roleKey: 'finance', confirmationText: 'delete role finance', }); expect(organization.data.roleDefinitions).toEqual([]); expect(memberRole.data.roles).toEqual(['viewer']); expect(appConnection.data.roleMappings).toEqual([]); }); export default tap.start();