# MVNexus - Testing Guide

## Overview

This guide provides comprehensive testing strategies and patterns for the MVNexus application, covering both backend (Laravel/PHP) and frontend (React/TypeScript) testing.

## Testing Philosophy

### Testing Principles
- **Test Behavior, Not Implementation**: Focus on what components do, not how they do it
- **Test Pyramid**: Unit > Integration > E2E (70% unit, 20% integration, 10% E2E)
- **Coverage Goals**: Minimum 75% coverage for critical modules, 90%+ for business logic
- **Test Isolation**: Tests should not depend on other tests or external state
- **Fast Feedback**: Unit tests should run in <1s, full suite in <5min

## Backend Testing (Laravel/PHP)

### Tech Stack
- **PHPUnit**: Base test framework
- **Pest**: Modern testing syntax
- **Laravel Testing Utilities**: Feature/Browser testing
- **Faker**: Test data generation

### Test Structure

```
tests/
├── Unit/               # Pure unit tests (no Laravel)
│   ├── Models/
│   ├── Services/
│   ├── Repositories/
│   └── DTOs/
├── Feature/            # Integration tests (with Laravel)
│   ├── API/
│   ├── Policies/
│   ├── Middleware/
│   └── Commands/
└── Pest.php           # Pest configuration
```

### Running Tests

```bash
# Run all tests
php artisan test

# Run specific suite
php artisan test --testsuite=Unit
php artisan test --testsuite=Feature

# Run with coverage
php artisan test --coverage --min=75

# Run specific test file
php artisan test tests/Unit/Services/SettingsResolverTest.php

# Run tests in parallel
php artisan test --parallel
```

### Unit Test Example: Settings Inheritance

```php
<?php

use App\Modules\Settings\Services\SettingsResolver;

test('settings inherit from department to company to global', function () {
    $company = Company::factory()->create();
    $department = Department::factory()->for($company)->create();
    
    $resolver = app(SettingsResolver::class);
    
    // Test hierarchical resolution
    $value = $resolver->resolve('ticket.auto_assign', $department->id);
    expect($value)->toBe(false); // Default value
    
    // Create global setting
    Setting::create([
        'key' => 'ticket.auto_assign',
        'value' => 'true',
        'scope' => 'global',
    ]);
    
    $value = $resolver->resolve('ticket.auto_assign', $department->id);
    expect($value)->toBe(true); // Global override
});
```

### Feature Test Example: API Endpoint

```php
<?php

test('user can create a ticket', function () {
    $user = User::factory()->create();
    $department = Department::factory()->create();
    
    $response = $this->actingAs($user, 'sanctum')
        ->postJson('/api/v1/tickets', [
            'subject' => 'Test Ticket',
            'description' => 'Description',
            'priority_id' => Priority::factory()->create()->id,
            'department_id' => $department->id,
        ]);
    
    $response->assertStatus(201)
        ->assertJsonStructure(['data' => ['id', 'subject']]);
});
```

### Authentication tests (OTP, magic links, invites)

Auth feature tests live under `tests/Feature/Identity/`. Use the **`ActsAsStatefulFrontend`** trait so requests include `Origin` / `Referer` headers required by Sanctum stateful middleware (mirrors the SPA calling with credentials).

```php
<?php

use Tests\Feature\Identity\Concerns\ActsAsStatefulFrontend;
use Illuminate\Support\Facades\Queue;
use App\Modules\Identity\Jobs\SendOtpEmailJob;

class OtpAuthTest extends TestCase
{
    use ActsAsStatefulFrontend;
    use RefreshDatabase;

    public function test_request_otp_success_for_active_user(): void
    {
        Queue::fake();
        $this->createUser('agent@mountainview-eg.com');

        $response = $this->postStatefulJson('/api/v1/auth/request-otp', [
            'email' => 'agent@mountainview-eg.com',
        ]);

        $response->assertOk();
        Queue::assertPushed(SendOtpEmailJob::class);
        $this->assertDatabaseHas('login_otps', [
            'email' => 'agent@mountainview-eg.com',
        ]);
    }
}
```

**Related suites:**
- `OtpAuthTest.php` — request/verify OTP, cooldowns, trusted devices, `auth_sessions`
- `MagicLinkAuthTest.php` — magic link request/verify, single-use tokens
- `UserInviteTest.php` — admin invites and `accept-invite`
- `AuthSessionTest.php` — list/revoke sessions when authenticated

**Tips:**
- Set `config(['otp.allowed_email_domain' => '…'])` in `setUp()` to match test emails.
- Use `Queue::fake()` to avoid real Mailgun sends; assert jobs (`SendOtpEmailJob`, `SendMagicLinkEmailJob`).
- For authenticated routes after verify, call `$this->getStatefulJson('/api/v1/auth/me')` or `Sanctum::actingAs($user)` depending on what you are testing.
- Do **not** test Microsoft/OAuth flows — they are not implemented (`sso_identities` is legacy schema only).

### Testing Policies

```php
<?php

test('agent can only view tickets in their department', function () {
    $agent = User::factory()->create();
    $agent->assignRole('agent');
    
    $ownDeptTicket = Ticket::factory()->create([
        'department_id' => $agent->departments->first()->id,
    ]);
    
    $otherDeptTicket = Ticket::factory()->create();
    
    expect($agent->can('view', $ownDeptTicket))->toBeTrue();
    expect($agent->can('view', $otherDeptTicket))->toBeFalse();
});
```

### Testing Middleware

```php
<?php

test('department isolation middleware filters queries', function () {
    $user = User::factory()->create();
    $department = $user->departments->first();
    
    // Create tickets in different departments
    Ticket::factory()->count(5)->create(['department_id' => $department->id]);
    Ticket::factory()->count(3)->create(); // Other departments
    
    $response = $this->actingAs($user, 'sanctum')
        ->getJson('/api/v1/tickets');
    
    $response->assertJsonCount(5, 'data'); // Only own department
});
```

## Frontend Testing (React/TypeScript)

### Tech Stack
- **Vitest**: Fast unit test runner
- **React Testing Library**: Component testing
- **Playwright**: E2E testing
- **MSW** (optional): API mocking

### Test Structure

```
resources/js/src/__tests__/
├── unit/
│   ├── components/        # Component tests
│   ├── hooks/             # Custom hook tests
│   └── utils/             # Utility function tests
├── integration/           # Integration tests
│   ├── features/
│   └── api/
├── e2e/                   # End-to-end tests
│   └── *.spec.ts
└── setup.ts              # Test configuration
```

### Running Tests

```bash
# Unit tests
npm run test

# With coverage
npm run test:coverage

# Watch mode
npm run test:watch

# E2E tests
npm run test:e2e

# E2E in headed mode
npm run test:e2e:headed
```

### Component Test Example

```typescript
import { render } from '@testing-library/react';
import { Button } from '@/shared/components/ui/Button';
import { ThemeProvider } from 'styled-components';
import { lightTheme } from '@/styles/theme';

describe('Button Component', () => {
  it('renders with children text', () => {
    const { getByText } = render(
      <ThemeProvider theme={lightTheme}>
        <Button>Click me</Button>
      </ThemeProvider>
    );
    expect(getByText('Click me')).toBeInTheDocument();
  });

  it('calls onClick handler', () => {
    const handleClick = vi.fn();
    const { getByText } = render(
      <ThemeProvider theme={lightTheme}>
        <Button onClick={handleClick}>Click me</Button>
      </ThemeProvider>
    );
    
    getByText('Click me').click();
    expect(handleClick).toHaveBeenCalledOnce();
  });
});
```

### Custom Hook Test Example

```typescript
import { renderHook, act } from '@testing-library/react';
import { useAuthStore } from '@/features/auth/store/authStore';

describe('useAuthStore', () => {
  it('updates user on login', () => {
    const { result } = renderHook(() => useAuthStore());
    
    const user = { id: '1', name: 'John', email: 'john@example.com' };
    
    act(() => {
      result.current.login(user);
    });
    
    expect(result.current.user).toEqual(user);
    expect(result.current.isAuthenticated).toBe(true);
  });
});
```

### E2E Test Example (Playwright)

For E2E, prefer **API/session setup** or a test OTP hook in non-production environments. The login UI is email → OTP (or magic link from email); there is no OAuth redirect to mock.

```typescript
import { test, expect } from '@playwright/test';

test('login page requests OTP', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel(/email/i).fill('agent@mountainview-eg.com');
  await page.getByRole('button', { name: /send code|continue/i }).click();
  await expect(page.getByText(/check your email|code sent/i)).toBeVisible();
});

test('complete ticket lifecycle', async ({ page }) => {
  // Authenticate via test helper or pre-seeded session cookie before this flow
  await page.goto('/tickets');
  // ... create ticket, assert success
});
```

## Testing Patterns

### 1. Settings Inheritance Testing

```php
// Test Department → Company → Global → Default hierarchy
test('settings resolver follows inheritance chain', function () {
    // Create hierarchy
    $company = Company::factory()->create();
    $dept = Department::factory()->for($company)->create();
    
    // Test at each level
    // 1. Default
    expect($resolver->resolve('key', $dept->id))->toBe('default');
    
    // 2. Global
    Setting::create(['key' => 'key', 'value' => 'global', 'scope' => 'global']);
    expect($resolver->resolve('key', $dept->id))->toBe('global');
    
    // 3. Company
    Setting::create(['key' => 'key', 'value' => 'company', 'scope' => 'company', 'scope_id' => $company->id]);
    expect($resolver->resolve('key', $dept->id))->toBe('company');
    
    // 4. Department
    Setting::create(['key' => 'key', 'value' => 'dept', 'scope' => 'department', 'scope_id' => $dept->id]);
    expect($resolver->resolve('key', $dept->id))->toBe('dept');
});
```

### 2. Department Isolation Testing

```php
test('queries are automatically scoped to user department', function () {
    $user = User::factory()->create();
    $dept = $user->departments->first();
    
    // Create data in user's department
    $ownTickets = Ticket::factory()->count(5)->create(['department_id' => $dept->id]);
    
    // Create data in other departments
    $otherTickets = Ticket::factory()->count(10)->create();
    
    // Act as user and query
    $this->actingAs($user, 'sanctum');
    $tickets = Ticket::all();
    
    // Should only see own department
    expect($tickets)->toHaveCount(5);
    expect($tickets->pluck('id'))->toMatchArray($ownTickets->pluck('id'));
});
```

### 3. SLA Testing

```php
test('SLA breach detection works correctly', function () {
    $policy = SLAPolicy::factory()->create([
        'first_response_time' => 60, // 60 minutes
    ]);
    
    $ticket = Ticket::factory()->create([
        'sla_policy_id' => $policy->id,
        'created_at' => now()->subMinutes(70), // Breached
    ]);
    
    // Run SLA check
    (new SLAService())->checkBreaches();
    
    // Verify breach event created
    $breach = SLABreachEvent::where('ticket_id', $ticket->id)->first();
    expect($breach)->not->toBeNull();
    expect($breach->metric_type)->toBe('first_response_time');
});
```

### 4. RTL/LTR Testing

```typescript
describe('RTL support', () => {
  it('applies correct text direction', () => {
    render(
      <LocaleProvider>
        <Component />
      </LocaleProvider>
    );
    
    const html = document.querySelector('html');
    expect(html).toHaveAttribute('dir', 'ltr');
    
    // Switch to Arabic
    act(() => {
      setLocale('ar');
    });
    
    expect(html).toHaveAttribute('dir', 'rtl');
  });
});
```

### 5. Theme Testing

```typescript
describe('Theme switching', () => {
  it('toggles between light and dark mode', () => {
    const { getByRole } = render(
      <ThemeProvider>
        <ThemeToggle />
      </ThemeProvider>
    );
    
    const body = document.body;
    const initialBg = window.getComputedStyle(body).backgroundColor;
    
    // Toggle theme
    getByRole('button').click();
    
    const newBg = window.getComputedStyle(body).backgroundColor;
    expect(newBg).not.toBe(initialBg);
  });
});
```

## Coverage Requirements

### Backend
- **Controllers**: 80%+ (focus on request validation, authorization)
- **Services**: 90%+ (core business logic)
- **Repositories**: 85%+ (data access patterns)
- **Policies**: 100% (security-critical)
- **Middleware**: 95%+ (cross-cutting concerns)
- **Models**: 70%+ (relationships, scopes, accessors)

### Frontend
- **Components**: 75%+ (UI rendering, user interaction)
- **Hooks**: 85%+ (state management, side effects)
- **Utils**: 90%+ (pure functions, transformations)
- **Stores**: 80%+ (state mutations, async operations)

## CI/CD Integration

### GitHub Actions Workflow

```yaml
name: Tests

on: [push, pull_request]

jobs:
  backend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
      - name: Install dependencies
        run: composer install
      - name: Run tests
        run: php artisan test --coverage --min=75
      
  frontend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install dependencies
        run: npm ci
      - name: Run unit tests
        run: npm run test:coverage
      - name: Run E2E tests
        run: npm run test:e2e
```

## QA Checklist

### Before Release
- [ ] All tests pass (backend & frontend)
- [ ] Coverage meets minimum thresholds
- [ ] No critical security vulnerabilities (via `composer audit`, `npm audit`)
- [ ] Performance benchmarks met (API response <200ms, page load <3s)
- [ ] Accessibility score >90 (Lighthouse)
- [ ] RTL layout works correctly in Arabic
- [ ] Dark mode displays correctly
- [ ] Mobile responsive (tested on 320px, 768px, 1024px, 1920px)
- [ ] Cross-browser testing (Chrome, Firefox, Safari, Edge)
- [ ] Department isolation verified
- [ ] Settings inheritance verified
- [ ] SLA calculations verified
- [ ] Real-time notifications working

### Performance Testing
- API endpoints should respond in <200ms (p95)
- Database queries should execute in <50ms (p95)
- Frontend bundle size <500KB gzipped
- Time to Interactive (TTI) <3s
- Core Web Vitals: LCP <2.5s, FID <100ms, CLS <0.1

### Security Testing
- [ ] CSRF protection enabled
- [ ] XSS prevention (sanitized inputs)
- [ ] SQL injection prevention (parameterized queries)
- [ ] Authorization checks on all routes
- [ ] Sensitive data encrypted
- [ ] HTTPS enforced in production
- [ ] Rate limiting configured
- [ ] Audit logging for sensitive operations

## Best Practices

### DO
- ✅ Write tests before fixing bugs (TDD for bug fixes)
- ✅ Test edge cases and error conditions
- ✅ Use factories for test data generation
- ✅ Mock external dependencies (APIs, email, etc.)
- ✅ Use descriptive test names
- ✅ Keep tests focused (one assertion concept per test)
- ✅ Clean up test data (use transactions or teardown)

### DON'T
- ❌ Test implementation details
- ❌ Depend on test execution order
- ❌ Use production data in tests
- ❌ Skip tests temporarily without a ticket
- ❌ Write tests that are flaky or slow
- ❌ Mock everything (test real integrations when possible)

## Troubleshooting

### Tests Failing Randomly
- Check for race conditions in async tests
- Ensure proper test isolation (database transactions)
- Verify mock/factory data doesn't conflict

### Slow Tests
- Reduce database queries (eager loading)
- Use in-memory SQLite for unit tests
- Parallelize test execution
- Profile slow tests: `php artisan test --profile`

### Coverage Not Increasing
- Check for untested branches (if/else, switch)
- Test error handling paths
- Verify edge cases covered

## Resources

- [Pest Documentation](https://pestphp.com)
- [Laravel Testing](https://laravel.com/docs/11.x/testing)
- [React Testing Library](https://testing-library.com/react)
- [Playwright Documentation](https://playwright.dev)
- [Vitest Documentation](https://vitest.dev)

---

**Next**: See `docs/DEPLOYMENT.md` for deployment guidelines.
