# Solution Design Document

## Validation Checklist

- [x] All required sections are complete
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] All context sources are listed with relevance ratings
- [x] Project commands are discovered from actual project files
- [x] Constraints → Strategy → Design → Implementation path is logical
- [x] Architecture pattern is clearly stated with rationale
- [x] Every component in diagram has directory mapping
- [x] Every interface has specification
- [x] Error handling covers all error types
- [x] Quality requirements are specific and measurable
- [x] Every quality requirement has test coverage
- [x] **All architecture decisions confirmed by user**
- [x] Component names consistent across diagrams
- [x] A developer could implement from this design

---

## Constraints

CON-1 **Technology Stack**
- PHP 8.x with PHPUnit 12.2.6 (locked version)
- MySQL multi-tenant databases (global `kiosk_users` + per-store `kiosk_[typeNum]`)
- Redis for queue management and caching (Predis client)
- No Mockery library (PRD decision: use PHPUnit native mocking)

CON-2 **Development Environment**
- Tests must run via `./test.sh` script within 10 minutes for full suite
- Test database `kiosk_test` must mirror production schema (no automated migrations)
- External API credentials (Twilio, Vonage, QuickBooks) NOT available in CI environment
- All external service calls must be mocked - no real API calls in tests

CON-3 **Deployment Requirements**
- GitHub Actions is the CI platform (no external CI services)
- Envoyer webhook triggers production deployment
- 80% coverage threshold on changed files required for PR merge
- Test failures must block deployment (non-zero exit code)

CON-4 **Codebase Constraints**
- Legacy code uses global functions (`dbConnectByName()`, `gen_uuid()`) that are difficult to mock
- Some classes have constructor side effects (database queries on instantiation)
- Store entity is 2,136 lines - cannot be significantly refactored as part of this initiative
- Production code can be modified for dependency injection where necessary for testability

## Implementation Context

**IMPORTANT**: You MUST read and analyze ALL listed context sources to understand constraints, patterns, and existing architecture.

### Required Context Sources

- ICO-1 **Existing Test Infrastructure**
```yaml
# Reference implementation - ComebackCash test patterns
- file: userfrosting/tests/Unit/ComebackCash/Models/CouponTest.php
  relevance: HIGH
  why: "Reference pattern for model unit tests with override factories"

- file: userfrosting/tests/Unit/ComebackCash/Services/CouponServiceTest.php
  relevance: HIGH
  why: "Reference pattern for service tests with PHPUnit mocking"

- file: userfrosting/tests/Integration/ComebackCash/CouponLifecycleTest.php
  relevance: HIGH
  why: "Reference pattern for integration tests"

- file: userfrosting/tests/Employee/Fixtures/EmployeeFixtures.php
  relevance: HIGH
  why: "Existing fixture pattern to follow and extend"

- file: userfrosting/tests/DatabaseTestCase.php
  relevance: HIGH
  why: "Base class for database tests - transaction isolation pattern"

- file: userfrosting/tests/bootstrap.php
  relevance: MEDIUM
  why: "Test environment initialization and configuration"
```

- ICO-2 **Target Modules for Testing**
```yaml
# Tier 1 modules requiring test coverage
- file: userfrosting/src/BuyerKiosk/Backstock/Services/EventService.php
  relevance: HIGH
  sections: [getCurrentPhase(), getBinsToPull(), calculateReadiness()]
  why: "1,326 LOC - complex event lifecycle logic requiring comprehensive tests"

- file: userfrosting/src/BuyerKiosk/SMS/TextMessageService.php
  relevance: HIGH
  why: "External API integration requiring mock infrastructure"

- file: userfrosting/src/BuyerKiosk/Cash/CashBalancer.php
  relevance: HIGH
  why: "Financial calculations requiring precision testing"

- file: userfrosting/src/BuyerKiosk/Core/Store.php
  relevance: MEDIUM
  why: "2,136 LOC - central entity but difficult to test without refactoring"
```

- ICO-3 **Build and CI Configuration**
```yaml
- file: userfrosting/phpunit.xml
  relevance: HIGH
  why: "PHPUnit 12.x configuration - defines test suite structure"

- file: test.sh
  relevance: HIGH
  why: "Test execution script - currently only runs ApiNewBuyTest"

- file: deploy.sh
  relevance: MEDIUM
  why: "Deployment trigger - needs test gate integration"

- file: userfrosting/composer.json
  relevance: MEDIUM
  why: "Dependencies including PHPUnit 12.x and Faker"
```

- ICO-4 **External Documentation**
```yaml
- url: https://docs.phpunit.de/en/12.2/
  relevance: HIGH
  sections: [writing-tests, test-doubles, fixtures]
  why: "PHPUnit 12.x API and best practices"

- url: https://docs.github.com/en/actions
  relevance: MEDIUM
  sections: [workflow-syntax, services, caching]
  why: "GitHub Actions configuration for CI pipeline"
```

### Implementation Boundaries

- **Must Preserve**:
  - Existing 738 tests must continue to pass after infrastructure changes
  - ComebackCash test patterns are reference implementation - do not modify
  - `test.sh` and `deploy.sh` interface (flags, exit codes) for CI compatibility
  - PHPUnit 12.x configuration structure in `phpunit.xml`

- **Can Modify**:
  - `test.sh` to run full test suite instead of just ApiNewBuyTest
  - `phpunit.xml` to add coverage configuration and test suites
  - Test directory structure (with namespace updates)
  - Production code to add dependency injection for testability
  - DatabaseTestCase to add multi-database support

- **Must Not Touch**:
  - Production business logic (tests only - no functional changes)
  - Store.php (2,136 LOC) - too risky to refactor; create StoreMock instead
  - External API implementations (Twilio SDK, Vonage) - mock only
  - Database schema - tests adapt to existing schema

### External Interfaces

#### System Context Diagram

```mermaid
graph TB
    subgraph "Test Infrastructure"
        TestRunner[PHPUnit Test Runner]
        Mocks[Mock Library]
        Fixtures[Fixture System]
    end

    subgraph "Developers"
        Dev[Developer] -->|./test.sh| TestRunner
        Dev -->|writes tests| TestRunner
    end

    subgraph "CI/CD"
        GHA[GitHub Actions] -->|triggers| TestRunner
        TestRunner -->|coverage report| GHA
        GHA -->|webhook on pass| Envoyer[Envoyer Deploy]
    end

    subgraph "External Services (Mocked)"
        TestRunner -.->|mocked| Twilio[Twilio API]
        TestRunner -.->|mocked| Vonage[Vonage API]
        TestRunner -.->|mocked| Redis[Redis]
    end

    subgraph "Databases"
        TestRunner -->|transaction isolation| TestDB[(kiosk_test)]
        TestRunner -.->|reference only| ProdDB[(kiosk_*)]
    end
```

#### Interface Specifications

```yaml
# Inbound Interfaces (what calls this system)
inbound:
  - name: "Developer CLI"
    type: Shell
    format: Command line
    authentication: None (local)
    entry_point: ./test.sh
    data_flow: "Test execution commands and flags"

  - name: "GitHub Actions"
    type: HTTPS/Webhook
    format: GitHub Events
    authentication: Repository access
    entry_point: .github/workflows/test.yml
    data_flow: "PR events trigger test runs"

# Outbound Interfaces (what this system calls)
outbound:
  - name: "Envoyer Deployment"
    type: HTTPS
    format: Webhook
    authentication: URL token
    endpoint: https://envoyer.io/deploy/[token]
    data_flow: "Deployment trigger on test pass"
    criticality: HIGH

  - name: "Coverage Badge Service"
    type: HTTPS
    format: JSON/SVG
    authentication: None (public)
    data_flow: "Coverage percentage for README badge"
    criticality: LOW

# Mocked External Services (NOT called in tests)
mocked:
  - name: "Twilio SMS API"
    real_type: HTTPS/SDK
    mock_class: Tests\Mocks\TwilioMock
    why: "No credentials in CI; tests verify message building logic"

  - name: "Vonage SMS API"
    real_type: HTTPS/cURL
    mock_class: Tests\Mocks\VonageMock
    why: "No credentials in CI; tests verify request formatting"

  - name: "Redis Cache"
    real_type: TCP
    mock_class: Tests\Mocks\RedisMock
    why: "Isolation for unit tests; integration tests use real Redis"

# Data Interfaces
data:
  - name: "Test Database"
    type: MySQL
    connection: PDO with transaction wrapping
    database: kiosk_test
    data_flow: "Test data insertion and rollback"
    isolation: "Transaction per test with automatic rollback"

  - name: "Store Databases"
    type: MySQL
    connection: PDO
    pattern: kiosk_[typeNum]
    data_flow: "Multi-tenant store data for integration tests"
```

### Cross-Component Boundaries

- **API Contracts**:
  - `./test.sh` exit codes: 0 = success, non-zero = failure (CI integration)
  - `phpunit.xml` test suite names must remain stable for CI configuration
  - Fixture factory method signatures: `createTestX(array $overrides = [])`

- **Shared Resources**:
  - Test database `kiosk_test` - shared across all integration tests
  - `DatabaseTestCase` - base class for all database-dependent tests
  - `tests/bootstrap.php` - shared initialization for all test suites

- **Breaking Change Policy**:
  - Fixture method signature changes require updating all dependent tests
  - Test directory reorganization must update all namespaces and imports
  - PHPUnit configuration changes require CI workflow updates

### Project Commands

```bash
# Component: Test Infrastructure
Location: userfrosting/

## Environment Setup
Install Dependencies: cd userfrosting && composer install
Environment Variables: Copy .env from project root; ensure DB_TEST_NAME=kiosk_test
Database Setup: MySQL must have kiosk_test database with production schema

# Testing Commands
Run All Tests:       ./test.sh                    # Currently only runs ApiNewBuyTest (TO BE FIXED)
Run with Verbose:    ./test.sh -v                 # Verbose PHPUnit output
Run Single Test:     cd userfrosting && ./vendor/bin/phpunit tests/Unit/ComebackCash/Models/CouponTest.php
Run Test Suite:      cd userfrosting && ./vendor/bin/phpunit --testsuite unit
Run with Coverage:   cd userfrosting && XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html coverage/

# PHPUnit Direct Commands
PHPUnit Version:     cd userfrosting && ./vendor/bin/phpunit --version  # 12.2.6
PHPUnit Help:        cd userfrosting && ./vendor/bin/phpunit --help
Filter Tests:        cd userfrosting && ./vendor/bin/phpunit --filter testMethodName

# Deployment
Deploy (test first): ./deploy.sh                  # Runs test.sh then triggers Envoyer webhook
Deploy Webhook:      curl -X POST https://envoyer.io/deploy/HLY5IPKkoPhwOAZjb31IhiK4fTbA7aJYw3eTZsqa

# Database Operations
Migration Run:       cd userfrosting && php conductor.php run
Test DB Reset:       mysql -u kiosk_db -p kiosk_test < schema.sql  # Manual schema sync

# Composer Operations
Update PHPUnit:      cd userfrosting && composer update phpunit/phpunit
Add Dev Dependency:  cd userfrosting && composer require --dev package/name
Autoload Dump:       cd userfrosting && composer dump-autoload
```

## Solution Strategy

- **Architecture Pattern**: Layered Test Architecture
  - **Layer 1: Infrastructure** - Mocks, fixtures, base test cases (reusable across all modules)
  - **Layer 2: Unit Tests** - Isolated class tests using mocks for dependencies
  - **Layer 3: Integration Tests** - Multi-component workflow tests using real database with transaction rollback
  - **Layer 4: CI Pipeline** - GitHub Actions orchestrating test execution and deployment gates

- **Integration Approach**: Extend existing patterns, don't replace
  - ComebackCash test suite is the reference implementation - new tests follow these patterns
  - EmployeeFixtures pattern extended to all modules
  - DatabaseTestCase enhanced (not replaced) for multi-database support
  - `test.sh` modified to run full suite while preserving interface

- **Justification**:
  - Leverages proven patterns already in codebase (ComebackCash: 85% coverage)
  - PHPUnit native mocking avoids new dependencies (no Mockery learning curve)
  - Transaction rollback provides fast, isolated tests without cleanup scripts
  - Incremental adoption - new tests don't require refactoring existing code

- **Key Decisions**:
  1. **PHPUnit Native Mocking** over Mockery - simpler, no new dependency, team familiarity
  2. **Static Fixture Factories** over ORM factories - matches existing EmployeeFixtures pattern
  3. **Transaction Isolation** over database seeding - faster, cleaner, no residual data
  4. **GitHub Actions** over other CI - free for open source, integrates with existing workflow
  5. **Reorganize Tests First** before writing new ones - establishes clear structure for all future tests

## Building Block View

### Components

```mermaid
graph TB
    subgraph "Test Execution Layer"
        TestSH[test.sh]
        PHPUnit[PHPUnit 12.x]
        GHA[GitHub Actions]
    end

    subgraph "Test Infrastructure Layer"
        subgraph "Base Classes"
            TC[TestCase]
            DTC[DatabaseTestCase]
            MTC[ModuleTestCase]
        end

        subgraph "Mock Library"
            PDOMock[PdoMockBuilder]
            TwilioMock[TwilioMock]
            VonageMock[VonageMock]
            RedisMock[RedisMock]
            StoreMock[StoreMock]
        end

        subgraph "Fixture System"
            CoreFix[CoreFixtures]
            BackstockFix[BackstockFixtures]
            SMSFix[SMSFixtures]
            CashFix[CashFixtures]
        end
    end

    subgraph "Test Suites"
        UnitTests[Unit Tests]
        IntegTests[Integration Tests]
    end

    subgraph "External Systems"
        TestDB[(kiosk_test)]
        CoverageRpt[Coverage Reports]
    end

    TestSH --> PHPUnit
    GHA --> PHPUnit
    PHPUnit --> TC
    PHPUnit --> DTC
    DTC --> TestDB
    UnitTests --> PDOMock
    UnitTests --> TwilioMock
    UnitTests --> CoreFix
    IntegTests --> DTC
    IntegTests --> BackstockFix
    PHPUnit --> CoverageRpt
```

### Directory Map

**Component**: Test Infrastructure (Target Structure After Reorganization)
```
userfrosting/tests/
├── bootstrap.php                           # MODIFY: Add coverage configuration
├── DatabaseTestCase.php                    # MODIFY: Add multi-database support
├── Unit/
│   ├── Core/                              # NEW: Core module unit tests
│   │   ├── StoreTest.php
│   │   ├── BuyTest.php
│   │   ├── CustomerTest.php
│   │   └── BuyQueueTest.php               # MOVE: from tests/Unit/BuyQueueTest.php
│   ├── Backstock/                         # NEW: Backstock module unit tests
│   │   ├── Models/
│   │   │   ├── EventTest.php
│   │   │   └── BinTest.php
│   │   └── Services/
│   │       └── EventServiceTest.php
│   ├── SMS/                               # NEW: SMS module unit tests
│   │   ├── TextMessageServiceTest.php
│   │   ├── FloodProtectorTest.php
│   │   └── Providers/
│   │       ├── TwilioTextSenderTest.php
│   │       └── VonageTextSenderTest.php
│   ├── Cash/                              # NEW: Cash module unit tests
│   │   ├── CashActivityTest.php
│   │   ├── CashBalancerTest.php
│   │   └── CashActivityRepositoryTest.php
│   ├── ComebackCash/                      # KEEP: Reference implementation
│   ├── Employee/                          # MOVE: from tests/Employee/Unit/
│   ├── Workbook/                          # MOVE: from tests/Workbook/Unit/
│   └── Autoload/                          # KEEP: PSR-4 autoloading tests
├── Integration/
│   ├── Api/                               # NEW: API endpoint tests
│   │   └── NewBuyApiTest.php              # MOVE: from tests/Unit/ApiNewBuyTest.php
│   ├── Core/                              # NEW: Cross-component workflows
│   │   └── BuyQueueLifecycleTest.php
│   ├── Backstock/                         # NEW: Backstock workflows
│   │   └── EventLifecycleTest.php
│   ├── ComebackCash/                      # KEEP: Existing integration tests
│   ├── Employee/                          # MOVE: from tests/Employee/Integration/
│   └── Workbook/                          # NEW: Workbook workflow tests
├── Fixtures/                              # NEW: Centralized fixture directory
│   ├── Core/
│   │   ├── StoreFixtures.php
│   │   ├── BuyFixtures.php
│   │   └── CustomerFixtures.php
│   ├── Backstock/
│   │   ├── EventFixtures.php
│   │   ├── BinFixtures.php
│   │   └── CategoryFixtures.php
│   ├── SMS/
│   │   └── MessageFixtures.php
│   ├── Cash/
│   │   └── CashActivityFixtures.php
│   ├── Employee/                          # MOVE: from tests/Employee/Fixtures/
│   └── Workbook/                          # MOVE: from tests/Workbook/Fixtures/
└── Mocks/                                 # NEW: Shared mock classes
    ├── PdoMockBuilder.php
    ├── TwilioMock.php
    ├── VonageMock.php
    ├── RedisMock.php
    └── StoreMock.php
```

**Component**: CI/CD Configuration
```
.github/
└── workflows/
    └── test.yml                           # NEW: GitHub Actions workflow

userfrosting/
├── phpunit.xml                            # MODIFY: Add coverage, test suites
└── phpunit.xml.dist                       # NEW: Distribution config template
```

**Component**: Scripts
```
./
├── test.sh                                # MODIFY: Run full suite, not just ApiNewBuyTest
└── deploy.sh                              # KEEP: No changes needed
```

### Interface Specifications

#### Data Storage Changes

**No database schema changes required.** Test infrastructure uses existing schema with transaction isolation.

```yaml
# Test database configuration (no schema changes)
Test Database: kiosk_test
  - Mirror of production schema
  - Transaction isolation for each test
  - No persistent test data between runs
```

#### Internal API Changes

**No application API changes required.** This is test infrastructure only.

#### Application Data Models (Test Infrastructure Classes)

```pseudocode
# Mock Classes (NEW)
CLASS: PdoMockBuilder
  PURPOSE: Fluent builder for PDO mock objects
  METHODS:
    + create(): static PdoMockBuilder
    + expectQuery(sql: string): PdoMockBuilder
    + withParams(params: array): PdoMockBuilder
    + willReturn(data: array): PdoMockBuilder
    + willReturnOnConsecutiveCalls(data: array[]): PdoMockBuilder
    + willThrow(exception: Exception): PdoMockBuilder
    + build(): MockObject<PDO>

CLASS: TwilioMock
  PURPOSE: Mock for Twilio\Rest\Client
  METHODS:
    + expectSend(to: string, body: string): TwilioMock
    + willSucceed(): TwilioMock
    + willFail(error: string): TwilioMock
    + verify(): void

CLASS: StoreMock
  PURPOSE: Factory for Store test doubles
  METHODS:
    + create(overrides: array): Store
    + withTypeNum(typeNum: string): StoreMock
    + withIntegrations(integrations: array): StoreMock
    + withTimezone(tz: string): StoreMock

# Fixture Classes (NEW - Following EmployeeFixtures Pattern)
CLASS: BackstockFixtures
  METHODS:
    + createTestEvent(overrides: array): Event
    + createTestEventRow(overrides: array): array
    + insertTestEvent(db: PDO, overrides: array): int
    + createTestBin(overrides: array): Bin
    + insertTestBin(db: PDO, overrides: array): int

CLASS: CashFixtures
  METHODS:
    + createTestCashActivity(overrides: array): CashActivity
    + insertTestCashActivity(db: PDO, overrides: array): int

CLASS: SMSFixtures
  METHODS:
    + createTestMessage(overrides: array): array
    + createValidPhoneNumber(): string
    + createInvalidPhoneNumber(): string

# Base Test Classes (MODIFIED)
CLASS: DatabaseTestCase (MODIFIED)
  ADDITIONS:
    + connectToStore(typeNum: string): PDO
    + switchDatabase(dbName: string): void
    + assertRowExists(table: string, id: int): void
    + assertRowNotExists(table: string, id: int): void
```

#### Integration Points

```yaml
# Test Infrastructure Integration
- from: test.sh
  to: PHPUnit
  protocol: CLI
  data_flow: "Test execution commands and flags"

- from: PHPUnit
  to: DatabaseTestCase
  protocol: PHP inheritance
  data_flow: "Test lifecycle hooks for transaction management"

- from: GitHub Actions
  to: test.sh
  protocol: Shell execution
  data_flow: "CI triggers test execution"

- from: test.sh
  to: Envoyer
  protocol: HTTPS webhook
  data_flow: "Deployment trigger on test success"
```

### Implementation Examples

**Purpose**: Provide strategic code examples to clarify complex logic, critical algorithms, or integration patterns. These examples are for guidance, not prescriptive implementation.

#### Example: PdoMockBuilder Usage

**Why this example**: PDO mocking is the most complex mock pattern, showing fluent API usage.

```php
<?php
// Example: Testing EventService with mocked database
class EventServiceTest extends TestCase
{
    private EventService $service;
    private MockObject $pdoMock;

    protected function setUp(): void
    {
        parent::setUp();

        // Build PDO mock with expected queries
        $this->pdoMock = PdoMockBuilder::create()
            ->expectQuery('SELECT * FROM bsEvents WHERE id = ?')
            ->withParams([1])
            ->willReturn([
                'id' => 1,
                'name' => 'Holiday Sale',
                'startDate' => '2024-12-01',
                'endDate' => '2024-12-31',
                'buildUpDays' => 7,
                'windDownDays' => 3
            ])
            ->build();

        $this->service = new EventService($this->pdoMock);
    }

    /** @test */
    public function test_getCurrentPhase_returns_active_during_event(): void
    {
        // Business Rule: Event is "active" between startDate and endDate
        $event = $this->service->getEvent(1);

        // Freeze time to middle of event
        $this->travelTo('2024-12-15');

        $this->assertEquals('active', $event->getCurrentPhase());
    }
}
```

#### Example: Fixture Factory Pattern

**Why this example**: Shows the standard fixture pattern matching EmployeeFixtures.

```php
<?php
// Example: BackstockFixtures following established pattern
class BackstockFixtures
{
    /**
     * Create in-memory Event object (no database)
     */
    public static function createTestEvent(array $overrides = []): Event
    {
        $defaults = [
            'id' => 1,
            'name' => 'Test Event',
            'startDate' => (new DateTime('+7 days'))->format('Y-m-d'),
            'endDate' => (new DateTime('+14 days'))->format('Y-m-d'),
            'buildUpDays' => 3,
            'windDownDays' => 2,
            'status' => 'active',
        ];

        return Event::fromRow(array_merge($defaults, $overrides));
    }

    /**
     * Insert event into test database, return ID
     */
    public static function insertTestEvent(PDO $db, array $overrides = []): int
    {
        $row = self::createTestEventRow($overrides);

        $stmt = $db->prepare('
            INSERT INTO bsEvents (name, startDate, endDate, buildUpDays, windDownDays, status)
            VALUES (:name, :startDate, :endDate, :buildUpDays, :windDownDays, :status)
        ');
        $stmt->execute($row);

        return (int) $db->lastInsertId();
    }

    /**
     * Create set of events for common test scenarios
     */
    public static function createTestEventSet(): array
    {
        return [
            'upcoming' => self::createTestEvent(['startDate' => '+30 days', 'status' => 'upcoming']),
            'active' => self::createTestEvent(['startDate' => '-3 days', 'endDate' => '+10 days']),
            'completed' => self::createTestEvent(['endDate' => '-7 days', 'status' => 'completed']),
        ];
    }
}
```

#### Example: Database Transaction Isolation

**Why this example**: Shows how DatabaseTestCase ensures test isolation.

```php
<?php
// Example: Integration test with transaction rollback
class EventLifecycleTest extends DatabaseTestCase
{
    /** @test */
    public function test_event_lifecycle_from_creation_to_completion(): void
    {
        // Arrange: Insert test event and bins (auto-rolled-back after test)
        $eventId = BackstockFixtures::insertTestEvent($this->db, [
            'name' => 'Lifecycle Test Event',
            'startDate' => date('Y-m-d'),
            'endDate' => date('Y-m-d', strtotime('+7 days')),
        ]);

        $binId = BackstockFixtures::insertTestBin($this->db, [
            'eventId' => $eventId,
            'onsite' => 0,  // Stored offsite
        ]);

        // Act: Progress through lifecycle
        $service = new EventService($this->db);
        $service->pullBin($binId);  // Move to onsite
        $service->emptyBin($binId); // Mark as finished

        // Assert: Verify state changes
        $progress = $service->getEventProgress($eventId);
        $this->assertEquals(1, $progress['finished']);
        $this->assertEquals(0, $progress['stored']);

        // Note: Transaction automatically rolled back in tearDown()
        // Database returns to clean state for next test
    }
}
```

#### Example: Test Documentation Pattern (ComebackCash Reference)

**Why this example**: Shows how to document business rules in tests.

```php
<?php
/**
 * Unit tests for Backstock Event phase calculations
 *
 * Tests cover:
 * - Phase determination based on current date vs event dates
 * - Build-up period calculation (before startDate)
 * - Wind-down period calculation (after endDate)
 * - Edge cases at phase boundaries
 *
 * Business Rules (from PRD):
 * - Rule 1: Build-up phase = startDate minus buildUpDays
 * - Rule 2: Wind-down phase = endDate plus 1 day through windDownDays
 * - Rule 3: Active phase = startDate through endDate (inclusive)
 * - Rule 4: Completed = after wind-down period ends
 */
class EventPhaseTest extends TestCase
{
    // =========================================================================
    // Test 1: getCurrentPhase() returns 'upcoming' before build-up
    // =========================================================================

    /** @test */
    public function test_getCurrentPhase_returns_upcoming_before_buildUp(): void
    {
        // Business Rule 1: Build-up starts buildUpDays before startDate
        $event = BackstockFixtures::createTestEvent([
            'startDate' => '2024-12-15',
            'buildUpDays' => 7,  // Build-up starts 2024-12-08
        ]);

        $this->travelTo('2024-12-01');  // Before build-up

        $this->assertEquals('upcoming', $event->getCurrentPhase());
    }
}
```

## Runtime View

### Primary Flow: Developer Runs Tests Locally

1. Developer executes `./test.sh` from project root
2. Script invokes PHPUnit with configured test suite
3. PHPUnit loads `bootstrap.php` (initializes environment, database connections)
4. Each test class executes: setUp() → test methods → tearDown()
5. DatabaseTestCase wraps each test in transaction, rolls back after
6. PHPUnit outputs results and exit code (0=pass, non-zero=fail)

```mermaid
sequenceDiagram
    actor Dev as Developer
    participant Shell as test.sh
    participant PHPUnit as PHPUnit 12.x
    participant Bootstrap as bootstrap.php
    participant DTC as DatabaseTestCase
    participant TestDB as kiosk_test

    Dev->>Shell: ./test.sh
    Shell->>PHPUnit: vendor/bin/phpunit
    PHPUnit->>Bootstrap: require bootstrap.php
    Bootstrap->>TestDB: Connect (PDO)
    Bootstrap-->>PHPUnit: Environment ready

    loop Each Test Class
        PHPUnit->>DTC: setUp()
        DTC->>TestDB: BEGIN TRANSACTION
        PHPUnit->>DTC: testMethod()
        DTC->>TestDB: INSERT/SELECT/UPDATE
        PHPUnit->>DTC: tearDown()
        DTC->>TestDB: ROLLBACK
    end

    PHPUnit-->>Shell: Exit code (0 or 1)
    Shell-->>Dev: Test results
```

### Secondary Flow: CI Pipeline Execution

1. Developer pushes to branch / creates PR on GitHub
2. GitHub Actions triggers `test.yml` workflow
3. Workflow provisions MySQL and Redis services
4. Workflow runs `./test.sh` with coverage enabled
5. On success, coverage report uploaded; on failure, PR blocked
6. On merge to master, Envoyer webhook triggered for deployment

```mermaid
sequenceDiagram
    actor Dev as Developer
    participant GH as GitHub
    participant GHA as GitHub Actions
    participant MySQL as MySQL Service
    participant TestSH as test.sh
    participant Envoyer as Envoyer

    Dev->>GH: Push / Create PR
    GH->>GHA: Trigger workflow
    GHA->>MySQL: Start service container
    GHA->>TestSH: ./test.sh

    alt Tests Pass
        TestSH-->>GHA: Exit 0
        GHA->>GH: ✅ Check passed
        Note over GH: PR can merge
        GH->>Envoyer: Webhook (on merge)
        Envoyer-->>GH: Deploy complete
    else Tests Fail
        TestSH-->>GHA: Exit 1
        GHA->>GH: ❌ Check failed
        Note over GH: PR blocked
    end
```

### Error Handling

- **Test Failure**: PHPUnit displays assertion failure with expected vs actual values; developer fixes code and re-runs
- **Database Connection Error**: Bootstrap logs error and fails fast; developer checks `.env` configuration
- **Timeout (>10 min)**: CI kills job; likely indicates slow tests or infinite loop; developer profiles tests
- **Coverage Below Threshold**: CI fails; developer adds more tests to reach 80% on changed files

## Deployment View

### Test Infrastructure Deployment

Test infrastructure is developer tooling - no production deployment required. However, CI/CD integration needs configuration.

- **Environment**: GitHub Actions runners (ubuntu-latest)
- **Configuration**:
  - `.github/workflows/test.yml` - Workflow definition
  - `userfrosting/phpunit.xml` - PHPUnit configuration
  - Repository secrets for Envoyer webhook URL

- **Dependencies**:
  - MySQL 8.0 service in CI
  - Redis service in CI (for integration tests)
  - PHP 8.x with Xdebug (for coverage)

- **Performance**:
  - Target: Full test suite < 10 minutes
  - Caching: Composer dependencies cached between runs
  - Parallelization: Not implemented initially; add if suite grows

### Deployment Order

1. **Phase 1: Local Infrastructure** (no deployment)
   - Mocks, fixtures, base test cases
   - Test reorganization
   - `test.sh` updates

2. **Phase 2: CI Integration**
   - `.github/workflows/test.yml` added
   - Repository secrets configured
   - First PR triggers workflow

3. **Phase 3: Coverage Gates**
   - Coverage reporting enabled
   - Threshold enforcement in CI
   - Badge added to README

### Rollback Strategy

- **CI Workflow Issues**: Revert `.github/workflows/test.yml` to previous version
- **Test Suite Failures After Reorg**: Namespace aliases in `bootstrap.php` for backward compatibility
- **Coverage Gate Too Strict**: Temporarily lower threshold in workflow, create issue to fix tests

## Cross-Cutting Concepts

### Pattern Documentation

```yaml
# Existing patterns used in this feature
- pattern: userfrosting/tests/Unit/ComebackCash/ (reference implementation)
  relevance: CRITICAL
  why: "All new tests must follow ComebackCash patterns for consistency"

- pattern: userfrosting/tests/Employee/Fixtures/EmployeeFixtures.php
  relevance: HIGH
  why: "Fixture factory pattern to extend for all modules"

# New patterns to be documented
- pattern: @docs/guides/testing-guide.md (NEW)
  relevance: HIGH
  why: "Central documentation for test patterns and conventions"

- pattern: @docs/patterns/test-fixtures.md (NEW)
  relevance: MEDIUM
  why: "Document the fixture factory pattern for reuse"
```

### System-Wide Test Patterns

- **Isolation**: Each test runs in database transaction, rolled back after
- **No Shared State**: Tests must not depend on order of execution
- **Fast Feedback**: Unit tests < 100ms each; integration tests < 1s each
- **Deterministic**: No randomness without seeding; no time-dependent logic without mocking

### Implementation Patterns

#### Fixture Factory Pattern

```php
<?php
// All fixture classes follow this structure
class ModuleFixtures
{
    // In-memory object creation (no database)
    public static function createTestX(array $overrides = []): X
    {
        $defaults = [...];
        return X::fromRow(array_merge($defaults, $overrides));
    }

    // Database insertion (returns ID)
    public static function insertTestX(PDO $db, array $overrides = []): int
    {
        $row = self::createTestXRow($overrides);
        // INSERT and return lastInsertId
    }

    // Batch creation for scenarios
    public static function createTestXSet(): array
    {
        return [
            'scenario1' => self::createTestX([...]),
            'scenario2' => self::createTestX([...]),
        ];
    }
}
```

#### Mock Builder Pattern

```php
<?php
// Fluent builder for complex mock setup
$mock = MockBuilder::create(SomeClass::class)
    ->expectMethod('doSomething')
    ->withArgs(['arg1', 'arg2'])
    ->willReturn($expectedResult)
    ->build();
```

#### Test Documentation Pattern

```php
<?php
/**
 * Tests for [Component] [Feature]
 *
 * Tests cover:
 * - [Bullet list of test categories]
 *
 * Business Rules:
 * - Rule N: [Description from PRD/SDD]
 */
class FeatureTest extends TestCase
{
    // =====================================================
    // Test N: [Descriptive name of test scenario]
    // =====================================================

    /** @test */
    public function test_descriptive_method_name(): void
    {
        // Arrange: [Setup description]
        // Act: [Action description]
        // Assert: [Verification description]
    }
}
```

### Integration Points

- **Connection Points**:
  - `test.sh` → PHPUnit (CLI)
  - `bootstrap.php` → Database connections
  - GitHub Actions → `test.sh`

- **Data Flow**:
  - Tests create data → Database (in transaction)
  - Database → Fixtures (read patterns)
  - PHPUnit → Coverage reports

- **Events**:
  - PR created → CI workflow triggered
  - Tests pass → Deployment webhook fired
  - Tests fail → PR blocked

## Architecture Decisions

- [x] **ADR-1 PHPUnit Native Mocking**: Use PHPUnit's built-in `createMock()` over Mockery
  - Rationale: No new dependency; team familiar with PHPUnit; sufficient for our needs
  - Trade-offs: Slightly more verbose syntax; fewer advanced features
  - Alternatives rejected: Mockery (learning curve), Prophecy (deprecated in PHPUnit 12)
  - User confirmed: ✓ (PRD approved Mockery rejection)

- [x] **ADR-2 Static Fixture Factories**: Use static factory methods over ORM/Eloquent factories
  - Rationale: Matches existing EmployeeFixtures pattern; no framework dependency; simple and explicit
  - Trade-offs: More boilerplate per entity; manual relationship management
  - Alternatives rejected: Laravel factories (requires Laravel), Faker-only (less structured)
  - User confirmed: ✓ (follows existing pattern)

- [x] **ADR-3 Transaction Isolation**: Wrap each test in transaction with automatic rollback
  - Rationale: Fast cleanup; no residual data; proven pattern in DatabaseTestCase
  - Trade-offs: Cannot test transaction behavior itself; nested transaction issues
  - Alternatives rejected: Database seeding (slow), cleanup scripts (error-prone)
  - User confirmed: ✓ (existing pattern)

- [x] **ADR-4 Test Directory Reorganization**: Centralize Unit/, Integration/, Fixtures/ at top level
  - Rationale: Consistent structure; easier navigation; matches PSR-4 autoloading expectations
  - Trade-offs: Requires moving existing tests; namespace updates needed
  - Alternatives rejected: Keep current scattered structure (inconsistent), per-module test folders (duplicates structure)
  - User confirmed: ✓ (Feature 10 in PRD)

- [x] **ADR-5 Coverage Threshold**: 80% on changed files, 90% overall target
  - Rationale: Industry standard; achievable incrementally; prevents regression
  - Trade-offs: May slow initial PRs; some legacy code difficult to cover
  - Alternatives: 70% (too permissive), 100% (unrealistic)
  - User confirmed: ✓

- [x] **ADR-6 CI Platform**: GitHub Actions over alternatives
  - Rationale: Free for repository; native GitHub integration; sufficient for needs
  - Trade-offs: Vendor lock-in; less customization than self-hosted
  - Alternatives rejected: Jenkins (maintenance overhead), CircleCI (cost), GitLab CI (different platform)
  - User confirmed: ✓

## Quality Requirements

- **Performance**:
  - Full test suite execution: < 10 minutes
  - Individual unit test: < 100ms
  - Individual integration test: < 1 second
  - CI feedback time: < 15 minutes from push to result

- **Reliability**:
  - Test pass rate: 99%+ (no flaky tests)
  - Zero false positives (passing tests that should fail)
  - Zero false negatives (failing tests that should pass)
  - Transaction rollback must restore database to pre-test state

- **Usability** (Developer Experience):
  - New test creation: < 15 minutes using fixtures and examples
  - Test failure message: Clear indication of expected vs actual
  - Documentation: Any developer can write tests after reading guide

- **Security**:
  - No production credentials in test code or CI logs
  - Test database isolated from production
  - External API mocks - never call real services

## Risks and Technical Debt

### Known Technical Issues

- **test.sh Only Runs One Test**: Currently only executes `ApiNewBuyTest.php` despite 738 tests existing
- **Store.php Untestable**: 2,136-line god object with constructor side effects; requires StoreMock workaround
- **Global Function Dependencies**: `dbConnectByName()`, `gen_uuid()` used throughout; cannot mock without wrappers
- **No Coverage Configuration**: PHPUnit not configured for Xdebug/coverage generation

### Technical Debt

- **Scattered Test Structure**: Tests in multiple locations (Employee/, Workbook/, Unit/) without consistent pattern
- **Inconsistent Namespaces**: Some tests lack proper namespace declarations
- **Missing Fixtures**: Most modules have no fixture classes; developers create ad-hoc test data
- **No CI Pipeline**: Tests not automatically run on PRs; deployment relies on manual testing

### Implementation Gotchas

- **PHPUnit 12 Breaking Changes**: PHPUnit 12.x removed some 11.x methods; check compatibility
- **Xdebug Mode**: Coverage requires `XDEBUG_MODE=coverage` environment variable
- **Transaction Nesting**: MySQL doesn't support true nested transactions; use savepoints carefully
- **DateTime Mocking**: Tests involving dates need time freezing; consider Carbon or custom ClockInterface
- **Autoloading**: Test classes must follow PSR-4 naming for autoloader to find them
- **Bootstrap Order**: `bootstrap.php` must load `.env` before any class that reads `$_ENV`

## Test Specifications

### Critical Test Scenarios

**Scenario 1: Test Infrastructure Smoke Test**
```gherkin
Given: Test infrastructure is installed (mocks, fixtures, base classes)
When: Developer runs ./test.sh
Then: All 738+ existing tests pass
And: Exit code is 0
And: Execution completes in < 10 minutes
```

**Scenario 2: New Module Test Creation**
```gherkin
Given: Developer wants to test a new service class
When: Developer creates test extending TestCase
And: Developer uses PdoMockBuilder for database mock
And: Developer uses ModuleFixtures for test data
Then: Test runs in isolation
And: Database state unchanged after test
And: Test follows documented patterns
```

**Scenario 3: CI Pipeline Integration**
```gherkin
Given: Developer pushes code to GitHub branch
When: GitHub Actions workflow triggers
Then: MySQL and Redis services start
And: ./test.sh executes full test suite
And: Coverage report generates
And: PR status shows pass/fail
```

**Scenario 4: Test Reorganization Verification**
```gherkin
Given: Tests have been reorganized to new structure
When: PHPUnit runs with updated phpunit.xml
Then: All tests discovered via new namespace paths
And: No "class not found" errors
And: Test count matches pre-reorganization count
```

### Test Coverage Requirements

- **Infrastructure Tests** (verify the test infrastructure itself):
  - PdoMockBuilder creates valid mocks
  - Fixture factories produce valid objects
  - DatabaseTestCase transactions roll back correctly
  - Bootstrap initializes environment correctly

- **Module Test Coverage Targets**:
  - Backstock EventService: 50+ tests (phases, bins, alerts)
  - SMS TextMessageService: 25+ tests (providers, flood protection)
  - Cash CashBalancer: 20+ tests (variance calculations)
  - Core Store: 25+ tests (configuration, integrations)

- **Integration Test Coverage**:
  - Buy queue lifecycle (enter → process → complete)
  - Backstock event lifecycle (create → active → complete)
  - CI pipeline execution (push → test → report)

---

## Glossary

### Domain Terms

| Term | Definition | Context |
|------|------------|---------|
| Fixture | Factory class that creates test data objects | Creates Store, Event, Bin objects for tests |
| Mock | Simulated object that mimics real dependency | PdoMock simulates database; TwilioMock simulates SMS |
| Transaction Isolation | Database technique to contain test changes | Each test runs in transaction, rolled back after |
| Coverage | Percentage of code executed by tests | Target: 80% changed files, 90% overall |

### Technical Terms

| Term | Definition | Context |
|------|------------|---------|
| PHPUnit | PHP testing framework | Version 12.2.6; runs all tests |
| Xdebug | PHP extension for debugging and coverage | Required for coverage reports |
| CI/CD | Continuous Integration/Deployment | GitHub Actions runs tests on PR |
| Envoyer | Deployment platform | Webhook triggers deploy after tests pass |

### Test Pattern Terms

| Term | Definition | Context |
|------|------------|---------|
| Unit Test | Tests single class in isolation | Uses mocks; no database |
| Integration Test | Tests multiple components together | Uses real database with transaction |
| Data Provider | PHPUnit feature for parameterized tests | Run same test with multiple inputs |
| Test Double | Generic term for mock/stub/fake | Any object replacing real dependency |
