business-as-code
Version:
Primitives for expressing business logic and processes as code
549 lines (494 loc) • 16.5 kB
text/typescript
/**
* Tests for organization.ts - Organizational structure and permissions
*/
import { describe, it, expect } from 'vitest'
import { resolvePermissions, getApprovalChainForRequest, findManager } from '../src/organization.js'
import type {
Organization,
Department,
Team,
Position,
ApprovalChain,
} from '../src/organization.js'
import type { BusinessRole } from '../src/roles.js'
describe('Organization', () => {
// Helper function to create a test organization
const createTestOrganization = (): Organization => ({
id: 'org_acme',
name: 'Acme Corp',
domain: 'acme.com',
industry: 'technology',
roles: [
{
id: 'role_cto',
name: 'CTO',
type: 'cto',
permissions: {
repository: ['read', 'edit', 'manage'],
project: ['manage'],
},
canApprove: ['technical-decision', 'architecture'],
canHandle: ['architecture-review'],
},
{
id: 'role_lead',
name: 'Engineering Lead',
type: 'lead',
permissions: {
repository: ['read', 'edit', 'act:merge'],
project: ['read', 'edit'],
},
canApprove: ['code-review'],
canHandle: ['code-review', 'mentoring'],
},
{
id: 'role_engineer',
name: 'Software Engineer',
type: 'engineer',
permissions: {
repository: ['read', 'edit'],
project: ['read'],
},
canHandle: ['coding', 'testing'],
},
],
departments: [
{
id: 'dept_eng',
name: 'Engineering',
code: 'ENG',
defaultPermissions: {
documentation: ['read'],
},
teams: [
{
id: 'team_platform',
name: 'Platform Team',
departmentId: 'dept_eng',
defaultPermissions: {
infrastructure: ['read'],
},
resources: {
repositories: ['platform-core', 'platform-api'],
},
positions: [
{
id: 'pos_cto',
title: 'CTO',
roleId: 'role_cto',
workerId: 'worker_jane',
status: 'active',
},
{
id: 'pos_lead',
title: 'Engineering Lead',
roleId: 'role_lead',
workerId: 'worker_bob',
reportsTo: 'pos_cto',
status: 'active',
},
{
id: 'pos_eng_1',
title: 'Senior Software Engineer',
roleId: 'role_engineer',
workerId: 'worker_alice',
reportsTo: 'pos_lead',
status: 'active',
additionalPermissions: {
'repository:platform-core': ['manage'],
},
},
{
id: 'pos_eng_2',
title: 'Software Engineer',
roleId: 'role_engineer',
workerId: 'worker_charlie',
reportsTo: 'pos_lead',
status: 'active',
},
],
},
{
id: 'team_product',
name: 'Product Team',
departmentId: 'dept_eng',
positions: [
{
id: 'pos_pm',
title: 'Product Manager',
roleId: 'role_lead',
workerId: 'worker_dave',
status: 'active',
},
],
},
],
},
],
teams: [
{
id: 'team_special',
name: 'Special Projects',
positions: [
{
id: 'pos_special',
title: 'Special Projects Lead',
roleId: 'role_lead',
workerId: 'worker_eve',
status: 'active',
},
],
},
],
approvalChains: [
{
id: 'chain_expense',
name: 'Expense Approval',
type: 'expense',
active: true,
levels: [
{
threshold: 1000,
approvers: [{ type: 'direct-manager' }],
},
{
threshold: 5000,
approvers: [{ type: 'direct-manager' }, { type: 'role', roleId: 'director' }],
},
{
threshold: 25000,
approvers: [{ type: 'direct-manager' }, { type: 'role', roleId: 'cfo' }],
},
],
},
{
id: 'chain_deployment',
name: 'Deployment Approval',
type: 'deployment',
active: true,
levels: [
{
approvers: [{ type: 'role', roleId: 'role_lead' }],
},
],
},
{
id: 'chain_inactive',
name: 'Inactive Chain',
type: 'inactive-type',
active: false,
levels: [
{
approvers: [{ type: 'direct-manager' }],
},
],
},
],
})
describe('resolvePermissions()', () => {
it('should resolve permissions for a position in the org hierarchy', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved).not.toBeNull()
expect(resolved?.workerId).toBe('worker_alice')
expect(resolved?.positionId).toBe('pos_eng_1')
})
it('should include department permissions', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved?.permissions.documentation).toContain('read')
})
it('should include team permissions', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved?.permissions.infrastructure).toContain('read')
})
it('should include role permissions', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved?.permissions.repository).toContain('read')
expect(resolved?.permissions.repository).toContain('edit')
})
it('should include position-specific permissions', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved?.resourcePermissions['repository:platform-core']).toBeDefined()
})
it('should include canHandle from role', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved?.canHandle).toContain('coding')
expect(resolved?.canHandle).toContain('testing')
})
it('should include canApprove from role', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_lead')
expect(resolved?.canApprove).toContain('code-review')
})
it('should track inheritance chain', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
expect(resolved?.inheritanceChain).toContain('department:dept_eng')
expect(resolved?.inheritanceChain).toContain('team:team_platform')
expect(resolved?.inheritanceChain).toContain('role:role_engineer')
expect(resolved?.inheritanceChain).toContain('position:pos_eng_1')
})
it('should return null for non-existent position', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'non_existent')
expect(resolved).toBeNull()
})
it('should find positions in standalone teams', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_special')
expect(resolved).not.toBeNull()
expect(resolved?.workerId).toBe('worker_eve')
})
it('should merge permissions without duplicates', () => {
const org = createTestOrganization()
const resolved = resolvePermissions(org, 'pos_eng_1')
// Both role and team might have 'read' permission
const readCount = resolved?.permissions.repository?.filter((p) => p === 'read').length
expect(readCount).toBe(1)
})
})
describe('getApprovalChainForRequest()', () => {
it('should return approvers for expense requests', () => {
const org = createTestOrganization()
const approvers = getApprovalChainForRequest(org, 'expense', 500)
// The function returns approvers - check that we get some
expect(approvers.length).toBeGreaterThan(0)
expect(approvers[0]?.type).toBe('direct-manager')
})
it('should return approvers based on amount thresholds', () => {
const org = createTestOrganization()
const approvers = getApprovalChainForRequest(org, 'expense', 3000)
// Should return approvers for the appropriate level
expect(approvers.length).toBeGreaterThan(0)
expect(approvers[0]?.type).toBe('direct-manager')
})
it('should return approvers for high amounts', () => {
const org = createTestOrganization()
const approvers = getApprovalChainForRequest(org, 'expense', 50000)
// Should return some approvers
expect(approvers.length).toBeGreaterThan(0)
})
it('should return empty array for non-existent request type', () => {
const org = createTestOrganization()
const approvers = getApprovalChainForRequest(org, 'non-existent', 1000)
expect(approvers).toEqual([])
})
it('should not return approvers from inactive chains', () => {
const org = createTestOrganization()
const approvers = getApprovalChainForRequest(org, 'inactive-type', 1000)
expect(approvers).toEqual([])
})
it('should return approvers when no amount specified', () => {
const org = createTestOrganization()
const approvers = getApprovalChainForRequest(org, 'deployment')
expect(approvers).toHaveLength(1)
expect(approvers[0]?.type).toBe('role')
expect(approvers[0]?.roleId).toBe('role_lead')
})
})
describe('findManager()', () => {
it('should find direct manager', () => {
const org = createTestOrganization()
const manager = findManager(org, 'pos_eng_1')
expect(manager).not.toBeNull()
expect(manager?.id).toBe('pos_lead')
expect(manager?.workerId).toBe('worker_bob')
})
it('should find second-level manager', () => {
const org = createTestOrganization()
const manager = findManager(org, 'pos_lead')
expect(manager).not.toBeNull()
expect(manager?.id).toBe('pos_cto')
expect(manager?.workerId).toBe('worker_jane')
})
it('should return null for position without manager', () => {
const org = createTestOrganization()
const manager = findManager(org, 'pos_cto')
expect(manager).toBeNull()
})
it('should return null for non-existent position', () => {
const org = createTestOrganization()
const manager = findManager(org, 'non_existent')
expect(manager).toBeNull()
})
})
describe('Organization interface', () => {
it('should support full organization structure', () => {
const org: Organization = {
id: 'org_test',
name: 'Test Corp',
domain: 'test.com',
legalName: 'Test Corporation Inc.',
industry: 'technology',
mission: 'To make testing easy',
values: ['quality', 'speed', 'reliability'],
foundedAt: new Date('2020-01-01'),
headquarters: {
street: '123 Main St',
city: 'San Francisco',
state: 'CA',
postalCode: '94105',
country: 'USA',
},
settings: {
defaultCurrency: 'USD',
timezone: 'America/Los_Angeles',
workWeek: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
businessHours: {
start: '09:00',
end: '17:00',
timezone: 'America/Los_Angeles',
},
fiscalYearStart: 1,
language: 'en',
dateFormat: 'MM/DD/YYYY',
},
departments: [],
teams: [],
roles: [],
metadata: { tier: 'enterprise' },
}
expect(org.name).toBe('Test Corp')
expect(org.headquarters?.city).toBe('San Francisco')
expect(org.settings?.timezone).toBe('America/Los_Angeles')
})
})
describe('Department interface', () => {
it('should support full department structure', () => {
const dept: Department = {
id: 'dept_test',
name: 'Test Department',
code: 'TEST',
description: 'Department for testing',
head: { positionId: 'pos_head', roleId: 'role_director' },
parentId: 'dept_parent',
teams: [],
budget: {
annual: 1000000,
currency: 'USD',
period: '2024',
categories: {
salaries: 700000,
tools: 100000,
training: 50000,
},
spent: 250000,
remaining: 750000,
},
costCenter: 'CC-100',
goals: ['Deliver Q1 roadmap', 'Improve team velocity'],
defaultPermissions: {
document: ['read', 'edit'],
},
metadata: { level: 'division' },
}
expect(dept.name).toBe('Test Department')
expect(dept.budget?.annual).toBe(1000000)
})
})
describe('Team interface', () => {
it('should support full team structure', () => {
const team: Team = {
id: 'team_test',
name: 'Test Team',
departmentId: 'dept_eng',
description: 'A team for testing',
lead: { positionId: 'pos_lead', roleId: 'role_lead' },
positions: [],
objectives: ['Ship feature X', 'Reduce tech debt'],
resources: {
repositories: ['repo-1', 'repo-2'],
projects: ['project-1'],
},
defaultPermissions: {
repository: ['read', 'edit'],
},
channels: {
slack: '#team-test',
email: 'team-test@acme.com',
},
metadata: { squad: true },
}
expect(team.name).toBe('Test Team')
expect(team.resources?.repositories).toContain('repo-1')
})
})
describe('Position interface', () => {
it('should support full position structure', () => {
const position: Position = {
id: 'pos_test',
title: 'Senior Engineer',
roleId: 'role_engineer',
workerId: 'worker_123',
workerType: 'human',
teamId: 'team_platform',
reportsTo: 'pos_lead',
directReports: ['pos_jr_1', 'pos_jr_2'],
additionalPermissions: {
'repository:special': ['manage'],
},
startDate: new Date('2023-01-15'),
endDate: undefined,
status: 'active',
fte: 1.0,
location: 'San Francisco',
workModel: 'hybrid',
compensation: {
baseSalary: 180000,
currency: 'USD',
frequency: 'annual',
bonusTarget: 15,
equity: {
type: 'rsu',
amount: 10000,
vestingSchedule: '4 years with 1 year cliff',
},
band: 'L5',
},
metadata: { promoted: true },
}
expect(position.title).toBe('Senior Engineer')
expect(position.compensation?.baseSalary).toBe(180000)
})
})
describe('ApprovalChain interface', () => {
it('should support full approval chain structure', () => {
const chain: ApprovalChain = {
id: 'chain_test',
name: 'Test Approval Chain',
type: 'purchase',
levels: [
{
threshold: 1000,
approvers: [{ type: 'direct-manager' }],
requiredApprovals: 1,
approvalMode: 'any',
slaHours: 24,
},
{
threshold: 10000,
approvers: [{ type: 'direct-manager' }, { type: 'role', roleId: 'finance_manager' }],
requiredApprovals: 2,
approvalMode: 'sequential',
slaHours: 48,
},
],
escalation: {
afterHours: 48,
escalateTo: 'skip-level-manager',
maxEscalations: 2,
},
active: true,
metadata: { version: 2 },
}
expect(chain.name).toBe('Test Approval Chain')
expect(chain.levels).toHaveLength(2)
expect(chain.escalation?.afterHours).toBe(48)
})
})
})