# Testing

## 1. Running the automated tests

```bash
php artisan test                                   # whole suite (~1–2 minutes)
php artisan test --testsuite=Unit                  # unit tests only
php artisan test tests/Feature/WorkOrders          # one folder
php artisan test --filter=tenant                   # by name
```

**Test database:** the suite runs against MySQL/MariaDB (not SQLite), so foreign keys, CHECK constraints,
triggers and `ON DUPLICATE KEY` numbering behave exactly as in production. Create the database once:

```sql
CREATE DATABASE sub_reseller_portal_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

`phpunit.xml` sets `DB_DATABASE=sub_reseller_portal_test`. It keeps Argon2id with cheap parameters for speed,
and uses array cache/session/mail and a sync queue. Each test runs in a transaction (`RefreshDatabase`) with
the reference data seeded (roles, permissions, Service Master). Demo data is **not** used; tests build their
own tenants (`main`, `subA`, `subB`) with factories.

## 2. Suite overview

**210 tests / 205 test methods** (one test method uses a data provider), all passing at release.

| Area | File(s) | Tests | Focus |
|---|---|---:|---|
| Authentication | `Auth/AuthenticationTest` | 15 | Login and logout, session regeneration, generic errors, throttling, inactive users and organizations, forced password change, password policy, CSRF configuration |
| Password reset | `Auth/PasswordResetTest` | 7 | Identical responses for known and unknown e-mails, inactive users, valid, invalid and expired (30 min) tokens, all sessions ended after reset, rate limiting |
| Sessions | `Auth/SessionSecurityTest` | 4 | Idle and absolute timeouts; polling does not extend sessions |
| Two-factor | `Auth/TwoFactorTest`, `Unit/TwoFactorAuthenticatorTest` | 8 | Enrolment, challenge, replay protection, recovery codes, admin enforcement, RFC 6238 vectors |
| Administration | `Admin/OrganizationManagementTest`, `UserManagementTest`, `RolesAndSettingsTest` | 26 | Hierarchy, vendor access, privilege levels, role scope, no self-escalation, no escalation by proxy, sessions cut on role change, settings validation |
| Catalog | `Catalog/VendorManagementTest`, `PackageManagementTest`, `ServiceMasterTest` | 26 | Catalog ownership, play type calculation, service details, price history, deactivate instead of delete |
| Customers & sellers | `Customers/CustomerManagementTest`, `SellerManagementTest` | 23 | US validation (state, ZIP, phone), numbering, tenant isolation, seller-login restrictions, soft delete rules, CSV formula neutralization |
| Work orders | `WorkOrders/WorkOrderCreationTest` | 18 | Snapshots, price/address history, history kept after renaming or deactivating master records, numbering, vendor/package/seller/customer rules, ignored client-supplied prices and organization, acceptance scenario |
| Status workflow | `WorkOrders/WorkOrderStatusTest` | 14 | All transitions, required data, permissions, recovery, DB constraints, stale-status protection, Livewire dialog |
| Deadlines & notifications | `WorkOrders/DeadlineTest` | 8 | Deadline states, business-timezone "today", DST, alerts sent once, internal-only notification links |
| Access / IDOR | `WorkOrders/WorkOrderAccessTest` | 7 | Cross-tenant URLs → 404, guessed IDs and numbers, main reseller visibility, seller logins, scoped export |
| Attachments | `WorkOrders/AttachmentSecurityTest` | 7 | Private storage, generated names, executables and disguised scripts rejected, size limit, safe download headers |
| Dashboard | `DashboardTest` | 5 | Tenant-scoped counts, card totals equal the linked lists, every role renders |
| Reports | `ReportsTest` | 8 | All 11 reports render, tenant boundaries, totals match the data, filters, export permission, malicious filters rejected, audit |
| Attack surface | `Security/AttackSurfaceTest` | 11 | Stored XSS escaped, mass assignment, Livewire locked-state tampering, forced transitions, open redirect, audit scoping, logo re-encoding, rate limiting |
| Headers & audit | `Security/SecurityHeadersTest` | 6 | CSP and security headers, HSTS, no debug output, HttpOnly cookie, append-only audit log, no secrets in audit |
| Domain rules (unit) | `Unit/DomainRulesTest` | 12 | Play type and its filter options, status machine, transition permissions, phone/ZIP formats, DST day counting, LIKE escaping |

## 3. Other quality checks

| Check | Command | Result at release |
|---|---|---|
| Code style (Laravel Pint) | `vendor/bin/pint --test` | Clean |
| PHP dependency advisories | `composer audit` | No advisories |
| JS dependency advisories | `npm audit` | 0 vulnerabilities |
| Production asset build | `npm run build` | Succeeds |
| HTTP smoke test | All pages requested as each demo role | 200 for allowed pages, 403/404 for denied; CSP nonce on every script |

There is no code-coverage driver (Xdebug/PCOV) in the default XAMPP setup. Install PCOV and run
`php artisan test --coverage` if coverage figures are needed.

## 4. Manual QA checklist

Automated tests run the server side and Livewire components without a browser. **Real-browser, cross-browser
and assistive-technology testing has not been carried out here** and must be done before go-live. Use this checklist.

### Browsers and devices
- [ ] Latest Chrome, Edge, Firefox (Windows); Safari (macOS and iOS); Chrome (Android)
- [ ] Widths 375 px (phone), 768 px (tablet), 1024 px, 1440 px+: sidebar becomes a drawer, tables scroll
      horizontally, forms stack, no horizontal page scroll
- [ ] Browser zoom 200%: content reflows, nothing is cut off

### Accessibility (WCAG 2.2 AA)
- [ ] Keyboard only: every control is reachable with a visible focus ring; *Skip to main content* works; modals trap
      focus and close with Esc; dropdowns open with Enter/Space
- [ ] Screen reader (NVDA + Firefox/Chrome, VoiceOver + Safari): page titles, landmarks, table headers, form labels,
      inline errors (announced, linked through `aria-describedby`), toasts (live region), badges (text, not color only)
- [ ] Contrast: badges, muted text, focus rings and disabled states meet AA
- [ ] `prefers-reduced-motion`: no essential motion

### Functional walkthrough (demo data, `Portal#Demo2026`)
- [ ] **qcp.seller**: sees only John Smith's customers and orders; creates a customer and a draft; submits it; cannot
      schedule or complete; no Sellers/Reports menu
- [ ] **qcp.ops**: creates a work order with the quick-customer modal; schedules, reschedules, completes it; the deadline badge updates
- [ ] **bhs.admin**: only the 2 permitted vendors appear; Queens data is never visible (try QCP work order URLs → 404)
- [ ] **main.admin**: sees all sub-resellers, filters the dashboard and reports by organization, changes a package price,
      and existing work orders keep the old price
- [ ] **main.auditor**: read-only everywhere; the audit log is visible; no create/edit buttons
- [ ] **superadmin**: settings (timezone, prefixes, warning days, session timeouts), roles, 2FA enforcement
- [ ] Reports: every report → filters → print preview (*Save as PDF*) → CSV opens correctly in Excel (UTF-8, dates, no formulas)
- [ ] Attachments: upload PDF/JPG/DOCX → download → delete; a renamed `.exe` → rejected
- [ ] Notifications: run `php artisan work-orders:deadline-alerts`; bell count, notification list, links open the work order
- [ ] Session: idle timeout (set to 5 min in Settings) signs out; the login page explains why
- [ ] 2FA: enrol with Google/Microsoft Authenticator, sign in, use a recovery code, regenerate codes
- [ ] Password reset: the link appears in `storage/logs/laravel.log` (`MAIL_MAILER=log`) and expires after 30 minutes
- [ ] Error pages: 403, 404, 419 (expired form), 429 (export 11× in a minute), 500 (production mode only)

### Time zone and dates
- [ ] With the server clock near midnight UTC, "Due Today" matches the **New York** date, not UTC
- [ ] Around the DST change (first Sunday of November / second Sunday of March), the deadline day counts stay correct
- [ ] Dates display as MM/DD/YYYY and prices as $1,234.56

## 5. Writing new tests

* Extend `Tests\TestCase`; call `$this->createTenants()` and use `makeUser(role, organization)` / `superAdmin()`.
* For work orders use `Tests\Concerns\BuildsWorkOrderScenario` (vendor, Triple Play package, seller, customer, `createOrder()`).
* For every new route or action, test the **allowed** role, a **denied** role (403), and a **foreign tenant** (404).
* Use real file bytes for upload tests: MIME detection reads the content, not the extension.
* `#[Url]` properties: `Livewire::withQueryParams([...])`. Lazy components: `Livewire::withoutLazyLoading()`.
