# TESTING_STRATEGY.md — NexStock Testing Plan

## Overview

NexStock uses **Pest** as the primary testing framework for PHP, and **Vitest** for Vue component testing. Testing focuses on **correctness of critical business operations** — especially sales, inventory, and tenant isolation.

## Testing Stack

| Tool | Purpose |
|------|---------|
| Pest | PHP unit + feature tests |
| Laravel's HTTP testing | Controller/route testing |
| Pest Browser | End-to-end browser testing |
| Vitest | Vue component unit tests |
| Laravel Dusk (fallback) | Browser automation if needed |
| PHPStan / Larastan | Static type analysis |
| Laravel Pint | Code style enforcement |

## Test Categories

### 1. Unit Tests (`tests/Unit/`)

Fast, isolated tests for:
- Value objects
- DTOs
- Enums
- Utility functions
- Cost calculations (weighted average)
- Business rule validation

```php
it('calculates weighted average cost correctly', function () {
    // Existing: 10 units at $5.00 = $50
    // New: 5 units at $8.00 = $40
    // WAC = $90 / 15 = $6.00
    
    $wac = InventoryCostCalculator::weightedAverage(
        existingQuantity: 10, existingCost: 5.00,
        newQuantity: 5, newCost: 8.00
    );
    
    expect($wac)->toBe(6.00);
});
```

### 2. Feature Tests (`tests/Feature/`)

Integration tests that hit the database and test full request cycles:

#### Multi-Tenancy Tests
```php
describe('Tenant Isolation', function () {
    it('prevents cross-tenant product access', function () {
        $otherBusiness = Business::factory()->create();
        $otherProduct = Product::factory()->for($otherBusiness)->create();
        
        actingAs($this->user) // user belongs to different business
            ->get(route('products.show', $otherProduct))
            ->assertNotFound();
    });
    
    it('prevents cross-tenant product modification', function () {
        $otherBusiness = Business::factory()->create();
        $otherProduct = Product::factory()->for($otherBusiness)->create();
        
        actingAs($this->user)
            ->put(route('products.update', $otherProduct), ['name' => 'Hacked'])
            ->assertNotFound();
    });
    
    it('auto-scopes product queries to current tenant', function () {
        Product::factory()->for($this->business)->count(3)->create();
        Product::factory()->count(5)->create(); // other tenants
        
        actingAs($this->user);
        
        expect(Product::count())->toBe(3);
    });
    
    it('prevents cross-tenant file access');
    it('prevents cross-tenant notification access');
    it('prevents cross-tenant report export');
});
```

#### Product Tests
```php
describe('Products', function () {
    it('creates a product with default variant');
    it('creates a product with multiple variants');
    it('enforces unique SKU within tenant');
    it('allows same SKU across different tenants');
    it('enforces unique barcode within tenant');
    it('updates product details');
    it('archives a product');
    it('validates required fields');
    it('uploads product image');
});
```

#### Inventory Tests
```php
describe('Inventory', function () {
    it('records opening stock balance');
    it('increases stock on purchase receiving');
    it('decreases stock on sale completion');
    it('increases stock on customer return');
    it('records stock adjustment (add)');
    it('records stock adjustment (remove)');
    it('records damage movement');
    it('records loss movement');
    it('calculates weighted average cost on receiving');
    it('maintains correct balance after multiple movements');
    it('creates immutable movement records');
    it('prevents negative stock (if configured)');
    it('detects low stock correctly');
    it('detects out of stock correctly');
    it('reconciles balances from movements');
    it('handles concurrent stock operations with locking');
});
```

#### Sales / POS Tests
```php
describe('Sales', function () {
    it('completes a simple cash sale');
    it('completes a sale with multiple items');
    it('completes a sale with split payment');
    it('deducts stock on sale completion');
    it('generates sequential receipt number');
    it('generates unique receipt numbers per tenant');
    it('stores snapshot data in sale lines');
    it('calculates tax correctly');
    it('applies line-level discount');
    it('applies sale-level discount');
    it('prevents sale without sufficient stock');
    it('handles concurrent sale attempts (locking)');
    it('prevents duplicate sale via idempotency key');
    it('holds and resumes a sale');
    it('records sale payment correctly');
    it('records cost price for profit calculation');
    
    // Atomic transaction test
    it('rolls back entire sale on payment failure', function () {
        // Force payment recording to fail
        // Verify: no sale, no sale lines, no stock deduction, no receipt
    });
});
```

#### Return Tests
```php
describe('Returns', function () {
    it('processes a partial return');
    it('processes a full return');
    it('restores stock on return');
    it('records refund payment');
    it('references original sale');
    it('prevents returning more than sold quantity');
    it('prevents returning already-returned items');
    it('creates inventory movement for return');
    it('generates return number');
});
```

#### Purchase Tests
```php
describe('Purchases', function () {
    it('creates a purchase record');
    it('does not increase stock until received');
    it('increases stock on full receiving');
    it('handles partial receiving');
    it('updates purchase status on receiving');
    it('calculates purchase totals');
    it('records receiving with correct costs');
    it('updates weighted average cost on receiving');
});
```

#### Cash Management Tests
```php
describe('Cash Management', function () {
    it('opens a cash shift');
    it('records cash sale movement');
    it('records cash refund movement');
    it('records cash in');
    it('records cash out');
    it('calculates expected cash at closing');
    it('records variance on close');
    it('prevents opening multiple shifts for same user');
});
```

#### Permission Tests
```php
describe('Permissions', function () {
    it('allows owner full access');
    it('restricts employee without sell permission');
    it('restricts employee without stock permission');
    it('restricts employee without report permission');
    it('allows employee with specific permission');
    it('restricts employee from managing other employees');
    it('restricts employee from billing');
});
```

#### Billing Tests
```php
describe('Billing', function () {
    it('creates trial subscription on business creation');
    it('trial expires after 14 days');
    it('grace period allows read-only access');
    it('checks entitlements correctly');
    it('enforces user limits');
    it('enforces branch limits');
    it('prevents feature access without entitlement');
});
```

#### Audit Tests
```php
describe('Audit', function () {
    it('logs sale completion');
    it('logs product price change');
    it('logs stock adjustment');
    it('logs return processing');
    it('logs permission change');
    it('logs login event');
    it('includes correct user and tenant');
    it('records old and new values');
});
```

### 3. Browser Tests (`tests/Browser/`)

End-to-end tests for critical user workflows:

```php
describe('E2E: Complete Sale Flow', function () {
    it('completes a full sale via POS interface', function () {
        // Login → Navigate to Sell → Search product → Add to cart
        // → Select payment → Complete → Verify receipt
    });
});

describe('E2E: Onboarding Flow', function () {
    it('completes onboarding wizard', function () {
        // Register → Create business → Set currency → Set timezone
        // → Configure tax → Create branch → Add product → Enter stock
    });
});
```

### 4. Vue Component Tests (`resources/js/**/*.test.ts`)

```typescript
describe('PosCart', () => {
    it('adds product to cart');
    it('updates quantity');
    it('removes item from cart');
    it('calculates subtotal');
    it('applies discount');
    it('calculates tax');
    it('shows total');
});

describe('ProductSearch', () => {
    it('searches by name');
    it('searches by SKU');
    it('searches by barcode');
    it('shows search results');
    it('handles empty results');
});
```

## Critical Test Scenarios

These tests MUST pass before any deployment:

### Sales Integrity
1. ✅ Sale creates sale record + line items + payment + stock deduction atomically
2. ✅ Failed sale rolls back all changes
3. ✅ Duplicate idempotency key returns original sale (no duplicate)
4. ✅ Receipt contains snapshot data unaffected by later product changes
5. ✅ Concurrent sales don't oversell stock

### Inventory Accuracy
1. ✅ `SUM(movements.quantity) == balance.quantity_on_hand` (reconciliation)
2. ✅ WAC calculation correct after receiving
3. ✅ Stock deduction matches sale quantity exactly
4. ✅ Return adds back correct quantity
5. ✅ Adjustment creates traceable movement

### Tenant Isolation
1. ✅ User A cannot see User B's products
2. ✅ User A cannot modify User B's data
3. ✅ User A cannot access User B's files
4. ✅ Queued jobs maintain correct tenant context
5. ✅ Broadcast channels reject cross-tenant listeners

## Test Data Strategy

### Factories
Every model has a factory with sensible defaults:

```php
class ProductFactory extends Factory
{
    public function definition(): array
    {
        return [
            'tenant_id' => Business::factory(),
            'name' => fake()->words(3, true),
            'category_id' => Category::factory(),
            'unit_id' => Unit::factory(),
            'has_variants' => false,
            'is_active' => true,
        ];
    }
}
```

### Seeders
- `DatabaseSeeder` — creates a demo business with sample data
- `SampleDataSeeder` — realistic product catalog for testing
- `PlanSeeder` — creates subscription plans

### Test Helpers
```php
// tests/Pest.php
function createBusinessWithOwner(): array {
    $business = Business::factory()->create();
    $owner = User::factory()->owner()->for($business)->create();
    return [$business, $owner];
}

function createProductWithStock(Business $business, Branch $branch, float $qty = 10, float $cost = 100): Product {
    // Creates product, variant, and opening stock
}
```

## CI Pipeline

```yaml
# .github/workflows/tests.yml
steps:
  - name: PHP Tests
    run: |
      php artisan test --parallel
      
  - name: Static Analysis  
    run: |
      ./vendor/bin/phpstan analyse
      
  - name: Code Style
    run: |
      ./vendor/bin/pint --test
      
  - name: JS Tests
    run: |
      npm run test
      
  - name: Type Check
    run: |
      npx tsc --noEmit
```

## Coverage Targets

| Area | Target |
|------|--------|
| Sales / POS | 95%+ |
| Inventory Engine | 95%+ |
| Tenant Isolation | 100% |
| Purchasing | 90%+ |
| Authentication | 90%+ |
| Permissions | 90%+ |
| Returns | 90%+ |
| Cash Management | 85%+ |
| Billing | 85%+ |
| Reports | 75%+ |

## Running Tests

```bash
# All tests
php artisan test

# Parallel (faster)
php artisan test --parallel

# Specific test file
php artisan test tests/Feature/Sales/CompleteSaleTest.php

# Specific test
php artisan test --filter="completes a simple cash sale"

# With coverage
php artisan test --coverage --min=80

# Browser tests
php artisan dusk

# Vue tests
npm run test
```
