Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Version: 1.1.0
Status: 📋 Active
Last Updated: 2025-12-11
This document defines the strategy for generating, managing, and cleaning up test data in the Encore Health OS Platform.

Overview

This document establishes standards for test data generation, management, and cleanup across all platform tests. It is particularly important for features requiring multi-tenant isolation testing, such as PF-30 (Permissions System V2).

Principles

1. Synthetic Data Only

  • NEVER use real patient data (PHI)
  • NEVER use real employee data (PII)
  • NEVER use production data in tests
  • Generate realistic but fake data using libraries like @faker-js/faker

2. Deterministic & Repeatable

  • Tests should produce same results every run
  • Use seeded random generators
  • Document test data assumptions

3. Isolated & Clean

  • Each test creates its own data
  • Tests clean up after themselves
  • No shared mutable state between tests
  • Use transactions when possible for automatic rollback

4. Realistic & Representative

  • Data should reflect real-world scenarios
  • Include edge cases (empty values, maximum lengths, special characters)
  • Model actual business workflows

Planned Sections

Test Data Factory

Location: /tests/utils/test-data-factory.ts Responsibilities:
  • Create test organizations
  • Create test users with roles
  • Create test employees with credentials
  • Create test forms and submissions
  • Create test shifts and timesheets
  • Generate realistic fake data
Example Usage:
import { 
  createTestOrganization,
  createTestUser,
  createTestEmployee,
  createTestForm
} from '@/tests/utils/test-data-factory';

// In test
const org = await createTestOrganization({
  name: 'Test Healthcare Org'
});

const admin = await createTestUser({
  organization_id: org.id,
  role: 'org_admin'
});

const employee = await createTestEmployee({
  organization_id: org.id,
  department_id: dept.id
});

Seed Data Management

Local Development Seeds:
  • Sample organizations
  • Sample users for each role
  • Sample forms and templates
  • Sample employees and credentials
Test Seeds:
  • Minimal data for fast tests
  • Comprehensive data for integration tests
  • Edge case data for boundary tests

Data Cleanup Strategies

Per-Test Cleanup:
afterEach(async () => {
  // Delete test data created in this test
  await cleanupTestData(testDataIds);
});
Transaction-Based Cleanup:
// Use database transactions for automatic rollback
const { data, error } = await supabase.rpc('begin_test_transaction');
// ... test operations ...
await supabase.rpc('rollback_test_transaction');
Cascade Deletes:
  • Configure foreign keys with CASCADE
  • Delete parent records to clean up children

Tenant Isolation Testing

Multi-Tenant Test Setup:
// Create two isolated organizations
const org1 = await createTestOrganization();
const org2 = await createTestOrganization();

const user1 = await createTestUser({ organization_id: org1.id });
const user2 = await createTestUser({ organization_id: org2.id });

// Verify user1 cannot see org2 data
const { data } = await user1Client.from('employees').select('*');
expect(data.every(emp => emp.organization_id === org1.id)).toBe(true);

Performance Test Data

Volume Testing:
  • Generate 1000+ records
  • Measure query performance
  • Test pagination
  • Test search functionality
Realistic Distribution:
  • Model real-world data skew
  • Include inactive records
  • Include historical data

Data Generation Patterns

Employee Records

// Planned implementation
function generateTestEmployee(overrides = {}) {
  return {
    employee_number: faker.string.alphanumeric(6),
    first_name: faker.person.firstName(),
    last_name: faker.person.lastName(),
    email: faker.internet.email(),
    hire_date: faker.date.past(),
    job_title: faker.person.jobTitle(),
    ...overrides
  };
}

Form Submissions

// Planned implementation
function generateTestSubmission(formId, overrides = {}) {
  return {
    form_id: formId,
    submission_data: {
      // Generate data matching form fields
    },
    submitted_by: faker.string.uuid(),
    ...overrides
  };
}

Credentials

// Planned implementation
function generateTestCredential(employeeId, credentialTypeId) {
  return {
    employee_id: employeeId,
    credential_type_id: credentialTypeId,
    issue_date: faker.date.past(),
    expiration_date: faker.date.future(),
    credential_number: faker.string.alphanumeric(10)
  };
}

Compliance & Privacy

PHI/PII Guidelines

  • ❌ Never use real names from production
  • ❌ Never use real SSNs, DoBs, or medical record numbers
  • ❌ Never use real addresses or contact information
  • ✅ Use faker-generated synthetic data
  • ✅ Use obvious fake values (test@example.com)
  • ✅ Document that test data is synthetic

Data Retention

  • Test data in CI: Delete after test run
  • Local test data: Clean up regularly
  • Seed data: Version controlled, clearly marked as fake

Test Environment Setup

Local Development

# Planned scripts
npm run test:seed        # Seed local database
npm run test:clean       # Clean test data
npm run test:reset       # Reset to clean state

CI/CD

  • Ephemeral test databases
  • Automatic cleanup after test run
  • Isolated per-PR environments

Database Fixtures

Fixture Files

Location: /tests/fixtures/ Structure:
/tests/fixtures/
  ├── organizations.json
  ├── users.json
  ├── employees.json
  ├── forms.json
  └── credentials.json

Loading Fixtures

// Planned implementation
import { loadFixture } from '@/tests/utils/fixture-loader';

const org = await loadFixture('organizations', 'default-org');
const admin = await loadFixture('users', 'org-admin');

Snapshot Testing

When to Use

  • Form field configurations
  • Automation rule schemas
  • Report query results
  • API response formats

Snapshot Updates

  • Review snapshot changes in PRs
  • Update snapshots intentionally
  • Document breaking changes

PF-30 Permissions System Test Data

Multi-Tenant Permission Testing

The PF-30 Permissions System V2 requires comprehensive test data for validating:
  • Cross-organization isolation (users cannot see other org’s roles/permissions)
  • Role-based access control (org_admin-only operations)
  • Permission inheritance (custom roles inheriting from base system roles)
  • Site-scoped role assignments
  • Expiration-based access control

Test Organization Setup

// Standard test organization structure for PF-30 tests
const testSetup = {
  // Primary test organization
  org_a: {
    id: 'org-a-uuid',
    name: 'Test Organization A',
    permissions_v2_enabled: true // Historical - V2 is mandatory, column exists for compatibility
  },
  
  // Secondary organization for isolation testing
  org_b: {
    id: 'org-b-uuid', 
    name: 'Test Organization B',
    permissions_v2_enabled: true
  },
  
  // Sites within org_a
  sites: {
    site_a_org_a: { id: 'site-a-uuid', organization_id: 'org-a-uuid' },
    site_b_org_a: { id: 'site-b-uuid', organization_id: 'org-a-uuid' }
  }
};

Test User Personas

User IDRoleOrganizationPurpose
admin_org_aorg_adminorg_aCan create/manage custom roles
staff_org_astafforg_aRegular user, cannot manage roles
site_admin_org_asite_admin (site_a)org_aSite-scoped permissions test
user_org_bstafforg_bCross-org isolation testing

Custom Role Test Data

// Sample custom roles for testing
const testRoles = [
  {
    name: 'HR Recruiter',
    organization_id: 'org-a-uuid',
    base_role: 'staff',
    description: 'Can manage candidates and job postings'
  },
  {
    name: 'AP Clerk',
    organization_id: 'org-a-uuid',
    base_role: 'finance_staff',
    description: 'Can process bills and payments'
  }
];

Permission Assignment Test Data

// Sample permission assignments for testing
const testPermissions = [
  // HR Recruiter permissions
  { role: 'HR Recruiter', permissions: ['hr.candidates.view', 'hr.candidates.create', 'hr.applications.view'] },
  // AP Clerk permissions
  { role: 'AP Clerk', permissions: ['fa.bills.view', 'fa.bills.create', 'fa.payments.view'] }
];

Site-Scoped Assignment Test Data

// Site-scoped role assignments for testing
const siteScopes = [
  {
    user_id: 'user-1-uuid',
    role_id: 'hr-recruiter-role-uuid',
    site_ids: ['site-a-uuid'], // Limited to site A only
    expires_at: null
  },
  {
    user_id: 'user-2-uuid',
    role_id: 'hr-recruiter-role-uuid',
    site_ids: null, // All sites (org-wide)
    expires_at: '2025-12-31T23:59:59Z' // Temporary access
  }
];

RLS Test Scenarios

ScenarioActorActionExpected Result
Cross-org isolationuser_org_bView org_a custom rolesEmpty result (no access)
Role creationadmin_org_aCreate custom roleSuccess
Role creationstaff_org_aCreate custom rolePermission denied
Permission assignmentadmin_org_aAssign permission to roleSuccess
Self-privilege escalationstaff_org_aAssign self higher permissionsPermission denied
Site-scoped accesssite_admin_org_aAccess site_b dataPermission denied
Expired assignmentuser with expired roleAccess protected resourcePermission denied

Cleanup Strategy for PF-30 Tests

// Cleanup order (respects FK constraints)
const cleanupOrder = [
  'pf_user_role_assignments',  // Delete assignments first
  'pf_role_permissions',       // Then permission mappings
  'pf_custom_roles',           // Then custom roles
  // pf_module_permissions is system-wide, rarely needs cleanup
];

async function cleanupPF30TestData(testOrgId: string) {
  for (const table of cleanupOrder) {
    await supabase.from(table)
      .delete()
      .eq('organization_id', testOrgId);
  }
}

References

  • /tests/utils/test-data-factory.ts - Data factory implementation
  • /tests/utils/supabase-test-client.ts - Test client utilities
  • /docs/testing/index.md - Testing overview
  • /constitution.md - Security and privacy requirements (Section 5.7)
  • /specs/pf/PF-30-permissions-system-v2.md - Permissions System V2 specification
  • /specs/pf/PF-30-permissions-system-v2-PLAN.md - Implementation plan with test scenarios

Maintained by: Platform QA Team
Questions? Raise in #engineering Slack