/** * Tenant Access Control Unit Tests * * Tests for the tenant access control functions in src/lib/tenantAccess.ts * Covers: getTenantIdFromHost, tenantScopedPublicRead, authenticatedOnly */ import { describe, it, expect, vi, beforeEach } from 'vitest' import type { PayloadRequest } from 'payload' import { createSuperAdmin, createTenantUser, createTenantUserPrimitive, createMockPayloadRequest, createAnonymousRequest, createMockTenant, executeAccess, hasFullAccess, hasNoAccess, hasFilteredAccess, getTenantIdFromFilter, TEST_TENANTS, TEST_USERS, } from '../../helpers/access-control-test-utils' // ============================================================================ // Import the actual functions to test // ============================================================================ import { getTenantIdFromHost, tenantScopedPublicRead, authenticatedOnly, } from '@/lib/tenantAccess' // ============================================================================ // getTenantIdFromHost Tests // ============================================================================ describe('getTenantIdFromHost', () => { describe('Host Header Extraction', () => { it('extracts tenant ID from valid domain', async () => { const request = createAnonymousRequest('porwoll.de', [TEST_TENANTS.porwoll]) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBe(1) }) it('extracts tenant ID with port in host', async () => { const request = createAnonymousRequest('porwoll.de:3000', [TEST_TENANTS.porwoll]) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBe(1) }) it('extracts tenant ID with www prefix', async () => { const tenant = createMockTenant({ id: 1, domains: [{ domain: 'porwoll.de' }], }) const request = createAnonymousRequest('www.porwoll.de', [tenant]) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBe(1) }) it('handles uppercase domain', async () => { const request = createAnonymousRequest('PORWOLL.DE', [TEST_TENANTS.porwoll]) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBe(1) }) it('returns null for missing host header', async () => { const request = createMockPayloadRequest(null, { tenants: [TEST_TENANTS.porwoll] }) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBeNull() }) it('returns null for unknown domain', async () => { const request = createAnonymousRequest('unknown-domain.com', [TEST_TENANTS.porwoll]) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBeNull() }) it('returns null for empty host header', async () => { const request = createMockPayloadRequest(null, { host: '', tenants: [TEST_TENANTS.porwoll] }) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBeNull() }) }) describe('Multiple Tenants', () => { const allTenants = [TEST_TENANTS.porwoll, TEST_TENANTS.c2s, TEST_TENANTS.gunshin] it('resolves correct tenant from multiple options', async () => { const request = createAnonymousRequest('complexcaresolutions.de', allTenants) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBe(4) // c2s tenant ID }) it('resolves each tenant correctly', async () => { const porwollReq = createAnonymousRequest('porwoll.de', allTenants) const gunshinReq = createAnonymousRequest('gunshin.de', allTenants) const porwollId = await getTenantIdFromHost(porwollReq as unknown as PayloadRequest) const gunshinId = await getTenantIdFromHost(gunshinReq as unknown as PayloadRequest) expect(porwollId).toBe(1) expect(gunshinId).toBe(5) }) }) describe('Error Handling', () => { it('returns null when payload.find throws', async () => { const request = createMockPayloadRequest(null, { host: 'test.com' }) request.payload.find = vi.fn().mockRejectedValue(new Error('Database error')) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBeNull() }) it('returns null for tenant without ID', async () => { const request = createMockPayloadRequest(null, { host: 'test.com' }) request.payload.find = vi.fn().mockResolvedValue({ docs: [{ name: 'Test Tenant' }], // Missing ID totalDocs: 1, }) const tenantId = await getTenantIdFromHost(request as unknown as PayloadRequest) expect(tenantId).toBeNull() }) }) }) // ============================================================================ // tenantScopedPublicRead Tests // ============================================================================ describe('tenantScopedPublicRead', () => { describe('Authenticated Users', () => { it('grants full access to super admin', async () => { const request = createMockPayloadRequest(TEST_USERS.superAdmin) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFullAccess(result)).toBe(true) }) it('grants full access to regular authenticated user', async () => { const request = createMockPayloadRequest(TEST_USERS.porwollUser) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFullAccess(result)).toBe(true) }) it('grants full access to multi-tenant user', async () => { const request = createMockPayloadRequest(TEST_USERS.multiTenantUser) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFullAccess(result)).toBe(true) }) }) describe('Anonymous Users', () => { it('returns tenant filter for valid domain', async () => { const request = createAnonymousRequest('porwoll.de', [TEST_TENANTS.porwoll]) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFilteredAccess(result)).toBe(true) expect(getTenantIdFromFilter(result as Record)).toBe(1) }) it('returns different tenant filter for different domain', async () => { const request = createAnonymousRequest('complexcaresolutions.de', [TEST_TENANTS.c2s]) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasFilteredAccess(result)).toBe(true) expect(getTenantIdFromFilter(result as Record)).toBe(4) }) it('denies access for unknown domain', async () => { const request = createAnonymousRequest('malicious-site.com', [TEST_TENANTS.porwoll]) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasNoAccess(result)).toBe(true) }) it('denies access when no host header', async () => { const request = createMockPayloadRequest(null) const result = await executeAccess(tenantScopedPublicRead, request) expect(hasNoAccess(result)).toBe(true) }) }) describe('Filter Structure', () => { it('returns correct where clause structure', async () => { const request = createAnonymousRequest('gunshin.de', [TEST_TENANTS.gunshin]) const result = await executeAccess(tenantScopedPublicRead, request) expect(result).toEqual({ tenant: { equals: 5, }, }) }) }) }) // ============================================================================ // authenticatedOnly Tests // ============================================================================ describe('authenticatedOnly', () => { describe('Grants Access', () => { it('grants access to super admin', async () => { const request = createMockPayloadRequest(TEST_USERS.superAdmin) const result = await executeAccess(authenticatedOnly, request) expect(result).toBe(true) }) it('grants access to regular user', async () => { const request = createMockPayloadRequest(TEST_USERS.porwollUser) const result = await executeAccess(authenticatedOnly, request) expect(result).toBe(true) }) it('grants access to user with minimal data', async () => { const minimalUser = { id: 99, email: 'minimal@test.com' } const request = createMockPayloadRequest(minimalUser) const result = await executeAccess(authenticatedOnly, request) expect(result).toBe(true) }) }) describe('Denies Access', () => { it('denies access to anonymous user', async () => { const request = createMockPayloadRequest(null) const result = await executeAccess(authenticatedOnly, request) expect(result).toBe(false) }) it('denies access when user is undefined', async () => { const request = createMockPayloadRequest(undefined as unknown as null) const result = await executeAccess(authenticatedOnly, request) expect(result).toBe(false) }) }) }) // ============================================================================ // Edge Cases & Integration Scenarios // ============================================================================ describe('Access Control Integration Scenarios', () => { describe('Tenant Assignment Formats', () => { it('handles tenant object format { tenant: { id } }', () => { const user = createTenantUser([1, 4]) expect(user.tenants).toEqual([{ tenant: { id: 1 } }, { tenant: { id: 4 } }]) }) it('handles tenant primitive format { tenant: number }', () => { const user = createTenantUserPrimitive([1, 4]) expect(user.tenants).toEqual([{ tenant: 1 }, { tenant: 4 }]) }) }) describe('Real-World Scenarios', () => { it('public blog post access from tenant domain', async () => { // Anonymous user visiting porwoll.de/blog const request = createAnonymousRequest('porwoll.de', [TEST_TENANTS.porwoll]) const result = await executeAccess(tenantScopedPublicRead, request) // Should only see porwoll.de posts expect(hasFilteredAccess(result)).toBe(true) expect(getTenantIdFromFilter(result as Record)).toBe(1) }) it('admin editing posts from any tenant', async () => { // Super admin in admin panel const request = createMockPayloadRequest(TEST_USERS.superAdmin) const result = await executeAccess(tenantScopedPublicRead, request) // Should see all posts expect(hasFullAccess(result)).toBe(true) }) it('tenant user creating content', async () => { // User assigned to c2s tenant creating a post const request = createMockPayloadRequest(TEST_USERS.c2sUser) const result = await executeAccess(authenticatedOnly, request) // Should be allowed to create expect(result).toBe(true) }) it('cross-origin attack prevention', async () => { // Request from malicious site const request = createAnonymousRequest('evil-site.com', [ TEST_TENANTS.porwoll, TEST_TENANTS.c2s, TEST_TENANTS.gunshin, ]) const result = await executeAccess(tenantScopedPublicRead, request) // Should be denied expect(hasNoAccess(result)).toBe(true) }) }) })