# Testing Documentation

## Transfer listing progress regression

`transfer-progress-view.test.ts`, `transfer-progress-text.test.ts`,
`TransferProgress.test.tsx`, and `JobOperationSummary.test.tsx` verify scan/check
counts, unknown totals, transfer/finalization transitions, and accessible
indeterminate progress. The shared component has scanning, comparing,
transferring, finishing, and failed Storybook examples.
Worker `syncListingProgress.test.js` runs the actual parser and watchdog with
fake time: advancing scans survive 40 minutes, then flat scans time out.
`operationProgress.test.js` covers metadata-only and separate stats chunks.
Run these alongside sync/move route and progress parser regression suites.
Local Vitest runs should exclude `**/.tmp/**` to avoid collecting stale tests
from auxiliary worktrees.

## Transfer module refactor regression

`src/hooks/batch-transfer/useBatchTransferController.test.tsx` exercises the actual composed controller with mocked external transfer and job APIs. It covers copy progress, late updates after completion, native folder moves, cancellation confirmation, abort propagation, quotas, authentication failure, server-owned backup/sync execution, scheduling, and terminal recovery. `fly-rclone/src/services/__tests__/syncProcessEvents.test.js` covers preserved watchdog failures, user cancellation, bisync state persistence, and process errors. Run them alongside existing transfer-completion, cancellation, and sync-route tests.

Storybook `Transfers/Refactored dialog` provides synthetic copy, move, sync, completed, and failed states. Its action callbacks cannot launch transfers.

> **Runtime interpretation:** current production tests target the OVHcloud VM
> app, VM rclone worker, Compose PostgreSQL, and one Compose cron replica.
> Vercel, Neon, and Fly references in dated feature-specific sections preserve
> the environment at the time those tests were written unless explicitly
> labeled as development or legacy recovery.

**Last Updated:** 2026-08-21 (CASA and anonymous security gates added to CI)

## Testing Strategy Overview

Production admin bootstrap has a focused feedback loop:
`pnpm test deploy/bootstrap-production-admin-access.test.ts`. It exercises
production/local target isolation, malformed public keys, preservation and
idempotence of authorized keys, and workflow rejection of branch/input
injection. Hardening tests cover the separate confirmation, prior-config
preservation, syntax and precedence failures, and rollback after reload
failure. Live verification uses the manual inspection job followed by
container/UFW invariants during installation and interactive private SSH.

System Logs regression coverage checks the production admin route, the exact
legacy redirect, anonymous/non-admin/MFA denials, and the admin-only menu.
Viewer component tests cover loading, empty results, audit details, pagination,
search, permission failures, and retry after a request timeout. Run:

```powershell
pnpm exec vitest run src/middleware.test.ts src/lib/nonproduction-surfaces.test.ts src/app/admin/layout.test.tsx src/components/__tests__/SettingsMenu.test.tsx src/app/admin/system-logs/page.test.tsx src/app/api/logs/audit/__tests__/route.test.ts
```

Stratofusion implements a comprehensive testing strategy covering unit tests, integration tests, component tests, and end-to-end tests with over 270 tests across the codebase.

## GitHub Actions CI Topology

The `CI Checks` workflow validates pull requests targeting `main` or `dev`,
direct pushes to the long-lived `main` and `dev` branches, and explicit manual
dispatches. Feature branches are validated through their pull request instead
of running the same commit once for `push` and again for `pull_request`.

CI concurrency is grouped by pull request or branch. A newer commit cancels
superseded CI for that same group; this cancellation policy does not apply to
the separate production deployment workflow.

`build-and-test` aggregates these lanes; inspect its failed prerequisite before
treating it as a separate build failure. CASA includes both web and worker
production dependency audits as well as a tracked-change secret-signature
scan. Test malformed key input using unsupported public-key formats when that
provides equivalent coverage without embedding a private-key header.

The validation lanes run in parallel:

- `Lint and type check` runs ESLint and TypeScript validation.
- `App tests (shard 1/2)` and `App tests (shard 2/2)` use Vitest's
  deterministic `--shard=<index>/2` partitioning.
- `Rclone worker checks` runs the worker coverage suite and builds its Docker
  image without starting or probing a worker.
- `Next.js production build` preserves the production compilation check.
- `CASA security gate` audits both production dependency graphs, runs the six
  security scans, regenerates SBOM/evidence output, and applies deployment,
  handoff, change-size, whitespace, and secret-signature policy.
- `Anonymous security E2E` builds the application, installs Chromium, passes
  the anonymous capability preflight, and executes the loopback-only browser
  security journeys.

The final `build-and-test` job depends on every lane and retains the historical
required-check name. It reports success only when every prerequisite reports
success. Repository branch-protection APIs may be unavailable on plans that do
not expose those settings, so preserving this check name avoids silently
breaking an existing external rule.

Use the read-only benchmark collector before and after CI topology or runner
changes:

```powershell
pnpm env:guard
.\scripts\ci\measure-actions.ps1 -SuccessfulRuns 30 -RecentRuns 100
```

The collector reports average, p50, and p95 workflow wall time, run queue time,
job start delay, summed runner execution, job and step duration,
duplicate-commit runs, and a 30-day activity-rate projection. Compare remote
GitHub runs only with remote GitHub runs collected through the same method.
Local Windows timings use a different worker pool and hardware, so they are
verification evidence rather than a substitute for hosted-runner performance
measurements.

The production `VM Deploy` workflow starts after a completed `CI Checks` run on
`main`, but its privileged build job proceeds only for a successful upstream
`push` event. It checks out `workflow_run.head_sha` and preserves that exact SHA
through image tags, OCI revision labels, repository checkout, and the VM
`TAG`. Pull-request and manually dispatched CI runs cannot deploy. Production
concurrency remains non-cancelling, and manual deployment dispatch remains an
explicit exact-SHA recovery path. An approved manual `target=staging` dispatch
builds a distinct `<sha>-staging` app image and routes only the temporary
`vm-staging` hostname to its isolated container. Its release checks must prove
the production container ID is unchanged and both public health endpoints
return success during the approved window. Cleanup restores the canonical
production-only route, proves the candidate container is absent, confirms
production health, and rejects any remaining HTTP 2xx/3xx response from the
staging endpoint. Rollback and cleanup source that canonical route from the
exact candidate SHA rather than the possibly older production checkout.
Because that candidate shares authoritative backing services, staging E2E is
read-only; transfer, billing, OAuth reconnect, migration, and scheduler tests
remain local-VM or separately isolated-environment work.

Workflow dependencies are pinned to full reviewed commit SHAs and all CI pnpm
installs are frozen. Production, staging-candidate, and local-VM image builds
emit provenance and SBOM attestations; first-party deploy coordinates require
a 40-character release SHA, and third-party runtime/base images are digest
pinned. The repository deployment security gate prevents these properties from
silently regressing.

## Vitest Worker Pool On Windows

The default unit/integration command is `pnpm test`. Vitest is configured to use
forked workers only on Windows because the Windows-native workflow has hit
intermittent access-violation crashes in the worker-thread pool during the full
suite. Linux CI keeps Vitest's thread pool. Keep this split unless the thread
pool has been revalidated on Windows. The Windows hook timeout is longer than
the per-test timeout so expensive route imports do not fail while the forked
suite is under load.

## CASA Security Regression Loop

Use this PowerShell loop for CASA readiness changes that touch auth, token handling, headers, dependency posture, or rclone security:

```powershell
pnpm env:guard
pnpm exec vitest run request-integrity admin-access-policy browser-logout middleware auth/logout auth/service-accounts api/jobs rclone/operations search/indexing/jobs delete download/session file-id-lookup rclone-health security-headers public-routes
pnpm typecheck
pnpm casa:security
```

The CASA gate audits both the web and worker production dependency graphs,
runs each repo security scan, writes a current-only private report, generates
CycloneDX SBOMs, verifies deployment/action/image pins and the loopback-only
non-production route boundary, and applies diff/secret/changed-file-size
checks. Run a scan
individually with `pnpm exec tsx scripts/security-audit/<script>.ts` when
developing its rule. If using `scripts/security-audit/run-all.sh`, invoke Git
Bash explicitly with `C:\Program Files\Git\bin\bash.exe`.

The request-integrity tests must cover exact same-origin browser mutations,
cross-site and same-site sibling origins, opaque origins, cookie-bearing calls
without browser evidence, safe methods, and credentialless machine calls.
Middleware regression tests also model the standalone container URL with a
different public Host, including apex/www, local-VM, and loopback ports. Verify
valid sync creation reaches authentication while scheme/port mismatches,
malformed Host values, and spoofed forwarded hosts remain rejected.
Ownership tests must use at least two user identities and reused resource IDs;
assert that foreign IDs never reach provider, worker, or mutation services.

Phase 4 input tests add malicious OpenXML package metadata (excessive expansion,
compression bombs, traversal names, encryption, and ZIP64), encoded and Unicode
worker path traversal, untrusted rclone service authorities, refused upstream
redirects, malformed stream tokens, and unsafe provider-supplied browser links.
The input-validation scanner also fails if these explicit trust boundaries are
removed from their production call sites.

Phase 6 deployment tests enumerate every non-production route family across
loopback and remote hosts. `scripts/casa/deployment-security-gate.test.ts`
also verifies immutable GitHub Action references, frozen installs, digest/base
image policy, rclone archive checksum validation, exact-SHA image labels, and
provenance/SBOM settings.

## Local VM Release Contract

Run the focused local-VM release contract whenever its workflow or Compose
release metadata changes:

```powershell
pnpm test scripts/local-vm/release-workflow-contract.test.ts
```

The test prevents production Clerk or Stripe public variables from entering a
local-VM image, requires the workflow to reject non-test public keys before it
builds, and proves the immutable image tag overrides stale release metadata in
both the app and rclone containers.

## Owned Marketing and Social Presence Loop

Use this loop for the public About/articles/feed surfaces and the repository-native content tools:

```powershell
pnpm social:validate
pnpm test scripts/social/content-store.test.ts src/components/AuthWrapper.test.tsx src/lib/__tests__/public-routes.test.ts src/app/feed.xml/route.test.ts src/app/sitemap.test.ts src/app/(marketing)/layout.test.tsx
pnpm typecheck
```

The feed test deliberately asserts that repository article drafts are not exposed.

## Optional Agent Delivery Harness evidence loop

The completed pilot is adopted with revisions as the opt-in, human-supervised
Agent Delivery Harness v1. Its local-only evidence helper supplements
deterministic tests; it does not replace CI, CASA, scanners, independent
read-only review, or human approval.

```powershell
pnpm env:guard
pnpm test scripts/pr-evidence/core.test.ts scripts/pr-evidence/git.test.ts
pnpm evidence:pr -- --task example-change `
  --current-url http://127.0.0.1:3000/current `
  --current-label "Current verified state" `
  --verification-note "pnpm test path/to/focused.test.ts: passed"
```

Current-state mode is the default and captures one input, defaulting to
`http://127.0.0.1:3000/`. Comparison mode is available only when both before
and after inputs are explicit; the helper never creates a fake comparison from
one default URL. URLs must use loopback HTTP(S), contain no embedded
credentials or sensitive query-key names, finish on a loopback URL, and return
a 2xx response. Browser requests are limited to loopback `GET`/`HEAD` and
inert browser resources; remote subrequests and write methods are blocked.

Existing PNG inputs are limited by file size and decoded dimensions, checked
for a PNG signature/IHDR header, and re-encoded through the existing
Playwright dependency rather than copied. This strips source metadata. The
helper uses a fresh context and a 1440x900 viewport, then writes the applicable
`current.png` or `before.png`/`after.png` plus `report.md` and `manifest.json`
beneath `.tmp/pr-evidence/<task>/`. It performs no upload.

Metadata includes route paths without query strings, timestamp, base/head Git
SHAs, clean/dirty state, a SHA-256 diff digest, viewport, labels, and sanitized
author-supplied verification notes. The helper does not execute those notes
and calls them metadata rather than checks. It records no raw diff, changed
paths, cookies, headers, browser storage, environment values, tokens, raw
provider data, customer filenames, or private account identifiers. The author
must still inspect screenshot pixels before sharing them.

Use `pnpm evidence:smoke` for the deterministic, non-sensitive loopback
fixture. It starts an inert server on an ephemeral `127.0.0.1` port, captures
two fixture states, and closes the server. Do not use production, provider
accounts, billing, OAuth, the admin dashboard, customer data, or a route whose
GET request changes state. If Chromium is missing, install the existing
Playwright browser prerequisite with `pnpm exec playwright install chromium`.
An incomplete capture writes the missing-evidence reason and exits with a
failure instead of claiming success.

The five-trial continuation is closed. Do not add trial rows. Follow
`public/docs/developer/AGENT_DELIVERY_HARNESS.md` for risk tiers, local leases,
the two-cycle repair limit, independent review, human handoff, and the fixed
`dev`/release-PR/`main` path.

## AI Indexing Diagnostics Coverage

AI indexing observability changes use a focused feedback loop before broader
typecheck/lint:

- Taxonomy: `src/lib/search/ai/indexing/error-taxonomy.test.ts` covers typed
  PDF/DOCX/XLSX/PPTX, Google Workspace export, OCR errors, retryability, and
  message sanitization.
- Diagnostics services: `src/lib/search/ai/indexing/diagnostics.test.ts`
  covers progress, ETA behavior, method/error breakdowns, percentiles, health
  metrics, and file-status builders.
- Runner persistence: `src/lib/search/ai/indexing/backend-indexing-runner.test.ts`
  and `database-progress-writer.test.ts` cover success, skip, failure, and latest
  indexed-file telemetry paths.
- Runtime boundary: `job-list-runtime-boundary.test.ts` verifies that dashboard
  job-metadata imports do not initialize the indexing runner or PDF runtime.
- API routes: `src/app/api/search/indexing/**/route.test.ts` covers auth,
  validation, ownership checks, not-found mapping, safe service failures, and
  success responses for diagnostics, health, and file status.
- Components: `src/components/jobs` and `src/components/ai-indexing` tests cover
  diagnostics dialog states, health summary loading/error/success states, and
  file-status rendering without raw IDs or raw failure details.

## Admin Infrastructure Dashboard Regression Loop

The infrastructure slice keeps collection behind injected ports so tests never
need production Prometheus, the VM, Docker, or SSH. The focused loop covers
Prometheus response validation and sanitization, vector-label parsing,
derived resource metrics, operational replica/invariant/backup-freshness
rules, collector/exporter output allowlisting, partial aggregation, admin
authorization, zero-versus-unavailable formatting, visibility-aware polling,
last-known-good retention, management-link sanitization and per-service
rendering, render states, and admin navigation:

```powershell
pnpm env:guard
pnpm test src/lib/infrastructure deploy/operations-exporter/telemetry.test.ts src/app/api/admin/infrastructure/overview/route.test.ts src/hooks/__tests__/useInfrastructureOverview.test.tsx src/components/infrastructure/InfrastructureDashboardView.test.tsx src/components/__tests__/SettingsMenu.test.tsx
$env:E2E_CLERK_USER_EMAIL = "admin@example.com"
pnpm test:e2e:local e2e/admin-infrastructure.spec.ts
pnpm typecheck
```

Adapter fixtures must include real zero values, missing vectors, stale samples,
malformed/non-finite values, query failures, wrong scheduler replica counts,
failed/stale/missing backup state, stale collector snapshots, and sanitized
upstream errors. Collector fixtures must prove that wrong-project, one-off, and
non-allowlisted containers are discarded and that raw IDs/names/paths are not
rendered as metrics. Component assertions must also prove that restart, reboot,
deploy, rollback, Docker socket, and private Cockpit controls or values are
absent. Management-link fixtures must reject HTTP, credential-bearing, and
off-domain public destinations, and rendered external links must isolate the
new tab from `window.opener`. Private management fixtures must additionally
accept only loopback HTTP or tailnet HTTPS origins. Deployment tests prove that
pgAdmin and RedisInsight stay in the optional management profile, use pinned
images and loopback-only bindings, and have no production or local Caddy route.
The infrastructure E2E test uses Clerk's Playwright testing token and
server-side email sign-in with the development Clerk instance. Set
`E2E_CLERK_USER_EMAIL` to an existing local admin; `.env.local` must provide a
`pk_test_` `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and an `sk_test_`
`CLERK_SECRET_KEY` from the same development instance. The helper refuses
non-local application origins and avoids Google OAuth, passkeys, persistent
browser profiles, and committed storage state. It intercepts only the
read-only overview response so browser rendering and refresh behavior remain
deterministic without a local Prometheus service.

Deployment-side validation also includes:

```powershell
node --check deploy/operations-exporter/collector.js
node --check deploy/operations-exporter/exporter.js
node --check deploy/operations-exporter/telemetry.js
& 'C:\Program Files\Git\bin\bash.exe' -n deploy/backup/backup.sh
& 'C:\Program Files\Git\bin\bash.exe' -n deploy/deploy.sh
docker compose --env-file deploy/.env.production.example -f deploy/docker-compose.prod.yml config --quiet
```

## Dashboard Storage And Account Health Regression Loop

Use this focused loop for changes to `/user/dashboard` storage overview,
account health, or `/api/storage/quota` error normalization:

```powershell
pnpm env:guard
pnpm test src/components/storage-overview/StorageAggregatedSummary.test.tsx src/lib/storage/dropbox-space-usage.test.ts src/lib/storage/account-access-health.test.ts src/lib/dashboard/account-health-mapping.test.ts src/app/api/storage/quota/__tests__/route.test.ts
```

The suite covers aggregate percentage display from byte totals, Dropbox
individual/team quota normalization, disabled or downgraded Business account
classification, token-bound email mismatches, dashboard health mapping, and the
quota API's normalized `accountHealth` failure response.

## Delete Outcome Regression Loop

Delete providers must report the operation actually completed rather than the
requested mode. This is especially important when a permanent-delete attempt
falls back to a recoverable trash or recycle-bin operation.

```powershell
pnpm env:guard
pnpm vitest run src/app/api/delete/__tests__/provider-delete.outcome.test.ts src/app/api/delete/__tests__/delete-executor.security.test.ts src/app/api/delete/__tests__/route.dropbox.test.ts src/app/api/delete/stream/__tests__/route.test.ts src/application/files/delete-files.test.ts src/components/__tests__/BatchFileDeleteDialog.progress.test.tsx
pnpm typecheck
```

The suite covers actual outcomes for Google Drive, OneDrive, and Dropbox,
OneDrive and Dropbox permanent-to-trash fallbacks, API/SSE propagation, and UI
copy that distinguishes `Permanently deleted` from `Moved to Trash`.

## Google to OneDrive Collision Workflow

**Last Updated:** 2026-04-08

Google Drive to OneDrive folder copies now include an interactive preflight workflow for sibling collisions where Google allows duplicate sibling names across item kinds and OneDrive does not.

### Automated Coverage

The core regression suite for this workflow currently lives in:

- `src/lib/rclone/google-onedrive-folder-compatibility.test.ts`
- `src/lib/rclone/google-onedrive-folder-collision-types.test.ts`
- `src/lib/rclone/google-onedrive-folder-resolution-service.test.ts`
- `src/lib/rclone/services/__tests__/folder-copy-service.test.ts`
- `src/app/api/rclone/copy-folder/__tests__/route.auth-context.test.ts`
- `src/components/__tests__/CollisionResolutionDialog.test.tsx`

### Fixture Guidance

Use Google Drive folders that exercise these cases:

- A folder and a file with the same effective OneDrive name under the same parent.
- A conflicting folder that contains descendants, so skip behavior can be verified recursively.
- A rename suggestion that would collide with an existing sibling, to verify validation errors.
- A mixed folder containing both conflicting and non-conflicting children, to verify the filtered manifest still copies the safe items.

### Suggested Manual Fixture Layout

```text
/OneDrive Collision Fixture
  /Resume
  Resume
  /Resume With Children
    nested-safe.txt
  Resume With Children
  /Resume - Folder
```

### Focused Verification Command

```bash
pnpm test \
  src/lib/rclone/google-onedrive-folder-compatibility.test.ts \
  src/lib/rclone/google-onedrive-folder-collision-types.test.ts \
  src/lib/rclone/google-onedrive-folder-resolution-service.test.ts \
  src/lib/rclone/services/__tests__/folder-copy-service.test.ts \
  src/app/api/rclone/copy-folder/__tests__/route.auth-context.test.ts \
  src/components/__tests__/CollisionResolutionDialog.test.tsx
```

### Manual Verification Checklist

1. Start a Google Drive to OneDrive folder copy that includes a sibling collision.
2. Confirm the compatibility dialog appears before any copy begins.
3. Accept the default rename suggestion and verify the copy completes.
4. Retry with a skip decision and verify the skipped item is absent from OneDrive.
5. Retry with an invalid rename target and verify the dialog blocks submission.
6. Force a rename failure or validation failure and verify Google source renames are rolled back.
7. Confirm the final completion text includes the applied resolution summary.

### Test Types and Coverage

#### Unit Tests (210+ tests)

- **Hooks**: Custom React hooks with state management logic
- **Utilities**: Helper functions and data transformers
- **Service Logic**: Cloud storage service implementations
- **API Utilities**: Response formatting and error handling

#### Component Tests (57+ Storybook stories)

- **UI Components**: Basic building blocks and complex components
- **User Interactions**: Click, drag, drop, and form interactions
- **Visual Documentation**: Component states and variations
- **Accessibility Testing**: Screen reader and keyboard navigation

#### Integration Tests

- **API Routes**: End-to-end API testing with service mocking
- **Service Integration**: Multi-service operations and data flow
- **Authentication Flow**: OAuth and session management
- **File Operations**: Upload, download, copy, and move operations

#### End-to-End Tests

- **User Workflows**: Complete user journeys through the application
- **Cross-Service Operations**: File transfers between different cloud services
- **Authentication**: Real OAuth flows with test accounts
- **Error Scenarios**: Network failures and service unavailability

## OAuth Testing Strategy

For Phase-C preparation, run the environment-independent cutover checks before
touching any dashboard or production resource:

```powershell
pnpm cutover:validate
pnpm test src/lib/legacy-write-freeze.test.ts src/middleware.test.ts
pnpm typecheck
```

The write-freeze tests must prove Vercel-production scoping, an exact `true`
opt-in, health-only access, and retryable blocking of OAuth, cron, webhook, API,
and browser paths. `pnpm cutover:validate` checks production/rehearsal host
mapping, cron-off defaults, immutable deploy tags, strict restore gates, and
image revision labels without reading live secrets.

The implemented direct OAuth providers are Google Drive, OneDrive, and
Dropbox. Tests must prove the complete host policy, not only URL string
construction.

Permanent callback origins are localhost, dev, and the production apex. Phase
B adds one temporary origin only when
`OAUTH_REHEARSAL_ORIGIN=https://vm-staging.stratofusion.io` is set and the site,
app, and all three provider redirect URIs use that same origin. The generic
runtime environment may remain `unknown`; the OAuth-specific validator must
report `staging` for the exact opt-in.

### Focused Automated Suite

```powershell
pnpm test -- `
  src/lib/deployment-host.test.ts `
  src/lib/clerk-oauth-integration.test.ts `
  src/lib/oauth.test.ts `
  src/lib/__tests__/oauth-helpers.test.ts `
  src/app/api/auth/clerk-oauth/__tests__/route.test.ts `
  src/app/api/google/__tests__/route.test.ts `
  src/app/api/onedrive/__tests__/route.test.ts `
  src/app/api/dropbox/__tests__/route.test.ts `
  src/instrumentation.test.ts
pnpm --dir fly-rclone exec jest src/config/__tests__/corsOrigins.test.js --runInBand --coverage=false
```

Required assertions:

- provider callback state is opaque, HMAC signed, randomly nonced, expires
  after ten minutes, and rejects missing, malformed, expired, legacy unsigned,
  or tampered payloads before authorization-code exchange.
- popup provider state cannot be issued without an authenticated Clerk user.
- the exact staging origin is OAuth-capable only with the singular opt-in.
- `NEXT_PUBLIC_SITE_URL`, `NEXT_PUBLIC_APP_URL`, and the Google, OneDrive, and
  Dropbox callbacks all resolve to staging during Phase B.
- direct and forwarded request authorities agree, while provider success/error
  returns use the validated configured staging origin.
- the Next.js startup validator and production rclone CORS policy accept the
  exact staging origin together.
- arbitrary `*.vercel.app` hosts, other `*.stratofusion.io` subdomains,
  wildcards, paths, custom ports, non-HTTPS values, and mismatched callback
  origins remain rejected.
- localhost/`127.0.0.1` and apex/`www` aliases do not silently substitute for a
  different callback origin.
- staging remains separate from the general environment name and independently
  controlled cron allowlist, while production-resource safety checks still run.

Keep token-refresh coverage in `src/lib/token-refresh.test.ts` and callback,
token-storage, and multi-account route coverage in the provider route suites.

### Phase-B Browser Verification

Run real OAuth only after the external Clerk, Google, Microsoft, and Dropbox
dashboard entries are present. From `https://vm-staging.stratofusion.io`:

1. Sign in through Clerk and confirm the URL remains on staging.
2. Connect Google Drive, OneDrive, and Dropbox one at a time.
3. Confirm each callback and popup/same-tab completion returns to staging and
   the account appears in the VM database.
4. Exercise one approved, non-destructive upload/download path and confirm the
   production-mode rclone gateway allows the staging origin.
5. Confirm an arbitrary preview still shows the UI-only warning and cannot run
   OAuth-backed flows.
6. Confirm conflicting direct/forwarded hosts and non-default forwarded ports
   receive `403`, and that forwarded protocol values never become redirects.

Remove the runtime opt-in at cutover, but retain the temporary external
dashboard entries through the initial rollback window. Do not run
credential-backed OAuth E2E tests on arbitrary preview deployments.

### Authentication Flow Testing

#### Mock Authentication for Development

```typescript
// Test helper for mocking authentication
export const mockAuthenticatedUser = {
  id: "test-user-123",
  email: "test@example.com",
  accounts: {
    google: { accountId: "default", tokens: { access_token: "mock-token" } },
    onedrive: { accountId: "default", tokens: { access_token: "mock-token" } },
    dropbox: { accountId: "default", tokens: { access_token: "mock-token" } },
  },
};
```

#### Service Authentication Testing

```typescript
describe("Service Authentication", () => {
  test("Google Drive service authenticates correctly", async () => {
    const service = new GoogleDriveService();
    const result = await service.authenticate("default", mockTokens);
    expect(result.success).toBe(true);
  });

  test("OneDrive service handles authentication errors", async () => {
    const service = new OneDriveService();
    const result = await service.authenticate("default", invalidTokens);
    expect(result.success).toBe(false);
    expect(result.errorCode).toBe("AUTH_ERROR");
  });
});
```

## Test Accounts

### Google Drive Test Accounts

- Two isolated Google test identities supplied by the authorized test owner

### OneDrive Test Accounts

- Two isolated Microsoft test identities supplied by the authorized test owner

### Dropbox Test Accounts

- Two isolated Dropbox test identities supplied by the authorized test owner

### Test Account Configuration

#### Environment Variables for Testing:

```dotenv
# E2E Test Credentials
TEST_USER_EMAIL=<isolated-test-email>

# Dropbox E2E Test Credentials
TEST_DROPBOX_EMAIL=<isolated-dropbox-test-email>
```

#### Test Account Settings:

- Dedicated test accounts separate from production data
- Limited permissions and access scopes
- Regular credential rotation
- Isolated test data that can be safely deleted

## Case Study: Autoâ€‘Reconnection Bug Fix â€” Tests & Verification

### Summary of Behavior Changes

- Disconnection now tracks specific accounts as â€œmanually disconnected.â€
- Autoâ€‘connection logic skips those specific accounts while still allowing other accounts/services to autoâ€‘connect.
- Manual reconnection clears the â€œmanually disconnectedâ€ status.

### Test Updates (Unit/Integration)

- Updated: src/lib/database/**tests**/auto-connection.test.ts (expectations for disconnect behavior)
- New: src/lib/database/**tests**/manually-disconnected-accounts.test.ts (add/check/clear helpers)
- New: src/app/api/auth/clerk-oauth/**tests**/route.test.ts (autoâ€‘connection filtering)
- New: src/lib/**tests**/session-server-manually-disconnected.test.ts (manual reconnection clears status)

### Manual Testing Checklist

1. Connect a service account, then manually disconnect it via UI
2. Reload the app â†’ the same account should NOT autoâ€‘reconnect
3. Manually reconnect the same account â†’ it should connect and clear the block
4. Add a new OAuth provider â†’ only unconnected services should autoâ€‘connect

### Notes on Test Complexity

- Deep ORM mocking and async flows can make isolated unit tests heavy; prefer integration tests where practical and evolve mocks toward DIâ€‘friendly patterns over time

## End-to-End Testing

### E2E Test Setup

### Playwright E2E — Current Safe Setup

Playwright defaults to a self-started production build at
`http://127.0.0.1:3101`. The default anonymous security suite needs no real
credentials and checks response headers, production route isolation, and
cross-site mutation rejection. Most credential-backed suites require an
explicit local storage-state file and skip with a named prerequisite when it
is absent. The admin infrastructure suite instead uses Clerk's local testing
token and server-side email sign-in path described above.

Run the capability preflight before browser tests:

```powershell
pnpm casa:e2e:preflight
```

The report lists readiness for anonymous, authenticated read-only, identity
mutation, billing mutation, and provider mutation journeys without printing
secret values. To require one capability in automation, use, for example,
`pnpm exec tsx scripts/casa/e2e-preflight.ts --require=anonymous`.

Install browser(s) once

```bash
pnpm exec playwright install chromium
```

Run the credential-free security regression:

```powershell
pnpm test:e2e:security
```

Run every discovered suite. Suites without their declared capability skip
safely:

```powershell
pnpm test:e2e
```

Authenticated suites use `.auth/local-session.json` by default. Set
`E2E_STORAGE_STATE` to an alternate local file when separate admin or user
states are required. Storage-state files and credentials must never be
committed.

Remote application origins are disabled by default. Anonymous active-security
and all mutation suites are always refused against them. An explicit
`E2E_ALLOW_REMOTE_READ_ONLY=true` permits only suites declared as authenticated
read-only; local mutation suites still require their specific opt-in and
isolated test-mode credentials. Stripe mutation requires an `sk_test_` key.

Debugging and UI runner

```powershell
# Step through each action
$env:PWDEBUG = '1'
pnpm test:e2e -- --project=chromium

# Interactive test runner
pnpm exec playwright test -c playwright.config.ts --ui
```

Security

- Never commit credentials or Playwright storage state.
- Use only isolated test identities, test-mode billing data, and disposable
  provider resources for authorized mutation suites.
- Do not copy personal account identifiers into test source.
- The anonymous runtime bypass needs both `SKIP_ENV_VALIDATION=true` and
  `E2E_ANONYMOUS_RUNTIME=true`; `playwright.config.ts` sets them only on its
  loopback server. This is a local harness, not a supported deployment mode.

#### Prerequisites:

- Anonymous security tests require only a production build and installed
  Chromium.
- Authenticated tests require an isolated local/test storage-state file, except
  the admin infrastructure suite, which requires development Clerk keys and
  `E2E_CLERK_USER_EMAIL`.
- Mutation tests require the capability-specific opt-in, isolated test data,
  and any test-mode service credentials reported by the preflight.

#### Playwright Configuration:

```typescript
// playwright.config.ts
export default defineConfig({
  testDir: "./e2e",
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [["list"], ["html", { open: "never" }]],
  use: {
    baseURL: "http://127.0.0.1:3101",
    trace: "retain-on-failure",
  },
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
});
```

### Authentication Testing

#### Google OAuth Flow:

```typescript
test("Google OAuth authentication flow", async ({ page }) => {
  await page.goto("/");

  // Click Google OAuth button
  await page.click('[data-testid="google-oauth-button"]');

  // Handle Google OAuth popup
  const popup = await page.waitForEvent("popup");
  await popup.fill('[type="email"]', process.env.TEST_USER_EMAIL);
  await popup.click("#identifierNext");
  await popup.fill('[type="password"]', process.env.TEST_USER_PASSWORD);
  await popup.click("#passwordNext");

  // Verify successful authentication
  await expect(page.locator('[data-testid="user-profile"]')).toBeVisible();
});
```

#### Dropbox OAuth Flow:

```typescript
test("Dropbox OAuth authentication flow", async ({ page }) => {
  await page.goto("/");

  // Handle Dropbox cookie consent
  await page.click("#accept_all_cookies_button");

  // Continue with OAuth flow
  await page.click('[data-testid="dropbox-oauth-button"]');
  // ... rest of authentication flow
});
```

### Service Integration Testing

#### File Operations Testing:

```typescript
describe("File Operations", () => {
  test("Upload file to Google Drive", async ({ page }) => {
    await authenticateWithGoogle(page);

    // Navigate to Google Drive
    await page.click('[data-testid="google-drive-tab"]');

    // Upload file
    const fileInput = page.locator('input[type="file"]');
    await fileInput.setInputFiles("test-files/sample.pdf");

    // Verify upload success
    await expect(page.locator('[data-testid="upload-success"]')).toBeVisible();
    await expect(page.locator("text=sample.pdf")).toBeVisible();
  });

  test("Copy file between services", async ({ page }) => {
    await authenticateWithMultipleServices(page);

    // Select file in Google Drive
    await page.click('[data-testid="google-drive-tab"]');
    await page.click('[data-testid="file-checkbox-sample.pdf"]');

    // Copy to Dropbox
    await page.click('[data-testid="copy-button"]');
    await page.selectOption('[data-testid="destination-service"]', "dropbox");
    await page.click('[data-testid="confirm-copy"]');

    // Verify copy success
    await expect(page.locator('[data-testid="copy-success"]')).toBeVisible();
  });
});
```

## Test Data Management

### Test File Generation

```python
# create_test_files.py
import os
from pathlib import Path

def create_test_files():
    """Create various file types for testing"""
    test_dir = Path('test-files')
    test_dir.mkdir(exist_ok=True)

    # Create different file types
    file_types = {
        'document.pdf': b'%PDF-1.4 test content',
        'image.jpg': b'\xff\xd8\xff\xe0 JPEG test',
        'text.txt': b'Test text content',
        'spreadsheet.xlsx': b'Excel test content',
    }

    for filename, content in file_types.items():
        (test_dir / filename).write_bytes(content)
```

### Test Data Cleanup

```typescript
// Cleanup helper for tests
export async function cleanupTestData(service: string, accountId: string) {
  const testFiles = await listFiles(service, accountId, {
    query: 'name contains "test-"',
  });

  for (const file of testFiles) {
    await deleteFile(service, accountId, file.id);
  }
}
```

## Performance Testing

### Load Testing

```typescript
describe('Performance Tests', () => {
  test('Large file list rendering', async () => {
    const startTime = performance.now();

    // Render component with 1000 files
    render(<FileTable files={generateMockFiles(1000)} />);

    const endTime = performance.now();
    expect(endTime - startTime).toBeLessThan(1000); // Should render in < 1s
  });

  test('Concurrent file uploads', async () => {
    const uploadPromises = Array.from({ length: 10 }, (_, i) =>
      uploadFile(`test-file-${i}.txt`, 'test content')
    );

    const results = await Promise.all(uploadPromises);
    expect(results.every(r => r.success)).toBe(true);
  });
});
```

### Memory Usage Testing

```typescript
test("Memory usage during large operations", async () => {
  const initialMemory = process.memoryUsage().heapUsed;

  // Perform large operation
  await processLargeFileList(generateMockFiles(10000));

  // Force garbage collection
  if (global.gc) global.gc();

  const finalMemory = process.memoryUsage().heapUsed;
  const memoryIncrease = finalMemory - initialMemory;

  // Memory increase should be reasonable
  expect(memoryIncrease).toBeLessThan(100 * 1024 * 1024); // < 100MB
});
```

## Reusable Hooks and Components Testing

**Date:** 2025-10-21
**Status:** âœ… Complete
**Test Coverage:** 42 tests across 3 test files

### Overview

Comprehensive testing for reusable hooks and components created during the Backup Job Management feature refactoring. These tests ensure the reliability of shared UI patterns used throughout the application.

### Hook Testing

#### `useDialogStateNotification` Hook Tests

**Location:** `src/hooks/__tests__/useDialogStateNotification.test.ts`
**Test Count:** 8 comprehensive test cases

**Purpose:** Notify parent components when dialog state changes (open/close) to enable features like pausing auto-refresh during user interactions.

**Test Coverage:**

- âœ… Callback invocation on state changes (falseâ†’true, trueâ†’false)
- âœ… Undefined callback handling (no errors when callback not provided)
- âœ… No unnecessary calls optimization (callback not called when value doesn't change)
- âœ… Initial render behavior (callback called on mount if isOpen is true)
- âœ… Callback reference changes (handles callback function updates)
- âœ… Cleanup verification (no calls after unmount)
- âœ… Multiple rapid state changes (handles rapid toggles correctly)

**Example Test:**

```typescript
test("should call onDialogStateChange when isOpen changes from false to true", () => {
  const mockCallback = vi.fn();
  const { rerender } = renderHook(
    ({ isOpen }) => useDialogStateNotification(isOpen, mockCallback),
    { initialProps: { isOpen: false } },
  );

  rerender({ isOpen: true });
  expect(mockCallback).toHaveBeenCalledWith(true);
});
```

---

#### `useFormDirtyState` Hook Tests

**Location:** `src/hooks/__tests__/useFormDirtyState.test.ts`
**Test Count:** 19 comprehensive test cases organized in 7 categories

**Purpose:** Track whether a form has unsaved changes by comparing current values against initial values.

**Test Categories:**

1. **Basic Functionality** (2 tests)
   - Returns false when values match initial values
   - Returns true when values differ from initial values

2. **Multiple Fields** (1 test)
   - Correctly detects changes in forms with 3+ properties

3. **Null Handling** (3 tests)
   - Returns false when initial values are null
   - Returns false when current values are null
   - Returns false when both are null

4. **Reset Functionality** (2 tests)
   - Resets dirty state to false when resetDirtyState is called
   - Maintains reset state until values change again

5. **Different Data Types** (3 tests)
   - Works with string values
   - Works with number values
   - Works with boolean values and mixed types

6. **Reactive Updates** (4 tests)
   - Updates when current values change
   - Updates when initial values change
   - Handles transitions from null to values
   - Handles transitions from values to null

7. **Edge Cases** (4 tests)
   - Handles empty objects correctly
   - Handles undefined values in objects
   - Handles adding new fields to current values
   - Handles removing fields from current values

**Example Test:**

```typescript
test("should return true when values differ from initial values", () => {
  const initialValues = { name: "John", email: "john@example.com" };
  const currentValues = { name: "Jane", email: "john@example.com" };

  const { result } = renderHook(() =>
    useFormDirtyState(currentValues, initialValues),
  );

  expect(result.current.hasUnsavedChanges).toBe(true);
});
```

---

### Component Testing

#### `UnsavedChangesDialog` Component Tests

**Location:** `src/components/__tests__/UnsavedChangesDialog.test.tsx`
**Test Count:** 15 comprehensive test cases in 6 categories

**Purpose:** Provide a consistent, accessible dialog for warning users about unsaved changes, replacing browser-native `window.confirm`.

**Test Categories:**

1. **Rendering** (3 tests)
   - Does not render when isOpen is false
   - Renders when isOpen is true
   - Displays warning text correctly

2. **Button Interactions** (2 tests)
   - Calls onConfirm when "Discard Changes" is clicked
   - Calls onCancel when "Continue Editing" is clicked

3. **Dialog Close Behavior** (2 tests)
   - Calls onClose when dialog is closed via Escape key
   - Calls onClose when overlay is clicked

4. **Accessibility** (4 tests)
   - Has correct dialog role
   - Has accessible button labels
   - Has proper heading structure
   - Supports keyboard navigation

5. **Button Styling** (2 tests)
   - "Continue Editing" button has outline variant
   - "Discard Changes" button has destructive variant

6. **Multiple Interactions** (2 tests)
   - Handles multiple button clicks correctly
   - Maintains state across open/close cycles

**Example Test:**

```typescript
test('should call onConfirm when "Discard Changes" button is clicked', async () => {
  const mockOnConfirm = vi.fn();
  const mockOnCancel = vi.fn();
  const mockOnClose = vi.fn();

  render(
    <UnsavedChangesDialog
      isOpen={true}
      onClose={mockOnClose}
      onConfirm={mockOnConfirm}
      onCancel={mockOnCancel}
    />
  );

  const discardButton = screen.getByRole('button', { name: /discard changes/i });
  await userEvent.click(discardButton);

  expect(mockOnConfirm).toHaveBeenCalledTimes(1);
});
```

---

### Storybook Documentation

#### `UnsavedChangesDialog.stories.tsx`

**Location:** `src/components/UnsavedChangesDialog.stories.tsx`
**Stories:** 7 interactive stories

**Stories Created:**

1. **Default** - Standard open state with warning message
2. **Closed** - Closed state for documentation purposes
3. **InteractiveExample** - Full form scenario with live unsaved changes detection
4. **WithCustomActions** - Demonstrates callback interactions in Actions panel
5. **MobileView** - Responsive behavior on small screens (375px width)
6. **DarkMode** - Dark theme demonstration
7. **MultipleDialogs** - Sequential dialog state management

**Features:**

- CSF3 format (Component Story Format 3)
- Interactive examples with live state management
- Comprehensive documentation strings
- Action logging for callback verification
- Responsive viewport testing
- Dark mode demonstration

---

#### `hooks.stories.mdx`

**Location:** `src/hooks/hooks.stories.mdx`
**Format:** MDX documentation

**Content:**

- Complete API documentation for both hooks
- Usage examples with code snippets
- Integration patterns with components
- Best practices and guidelines
- When to use / when NOT to use sections
- Related components and testing references

---

### Test Results

**All Tests Passing:** âœ… 42/42 tests

```
Test Files  3 passed (3)
     Tests  42 passed (42)
  Duration  5.51s
```

**Breakdown:**

- `useDialogStateNotification.test.ts`: 8/8 tests passing
- `useFormDirtyState.test.ts`: 19/19 tests passing
- `UnsavedChangesDialog.test.tsx`: 15/15 tests passing

**Quality Checks:** âœ… All Passing

- TypeScript compilation successful (no errors)
- ESLint validation successful (no errors)
- All imports resolved correctly
- No type errors in Storybook stories

---

### Architecture Improvements

**DRY Principle Applied:**

- Dialog state notification logic consolidated into single hook
- ~20 lines of duplicate code eliminated
- Single source of truth for dialog state management

**Separation of Concerns:**

- Form dirty state tracking extracted into dedicated hook
- Each hook has single, well-defined responsibility
- Reusable across any form component

**Consistent UX:**

- `UnsavedChangesDialog` replaces `window.confirm`
- Matches app design system (shadcn/ui)
- Better accessibility, mobile support, and dark mode

**SOLID Principles:**

- Single Responsibility: Each hook/component has one clear purpose
- Open/Closed: Hooks are open for extension via parameters
- Dependency Inversion: Components depend on abstractions (hooks) not implementations

---

### Usage Examples

**Using `useDialogStateNotification`:**

```typescript
function MyDialog({ isOpen, onClose, onDialogStateChange }) {
  // Automatically notify parent when dialog opens/closes
  useDialogStateNotification(isOpen, onDialogStateChange);

  return <Dialog open={isOpen} onOpenChange={onClose}>...</Dialog>;
}
```

**Using `useFormDirtyState`:**

```typescript
function EditForm({ initialData }) {
  const [name, setName] = useState(initialData.name);
  const [email, setEmail] = useState(initialData.email);

  const currentValues = useMemo(() => ({ name, email }), [name, email]);
  const [initialValues] = useState({ name: initialData.name, email: initialData.email });

  const { hasUnsavedChanges, resetDirtyState } = useFormDirtyState(
    currentValues,
    initialValues
  );

  const handleSave = async () => {
    await saveData({ name, email });
    resetDirtyState();
  };

  return (
    <form>
      {hasUnsavedChanges && <div>You have unsaved changes</div>}
      {/* form fields */}
    </form>
  );
}
```

**Using `UnsavedChangesDialog`:**

```typescript
function MyForm() {
  const [showWarning, setShowWarning] = useState(false);
  const { hasUnsavedChanges } = useFormDirtyState(currentValues, initialValues);

  const handleClose = () => {
    if (hasUnsavedChanges) {
      setShowWarning(true);
    } else {
      onClose();
    }
  };

  return (
    <>
      <button onClick={handleClose}>Close</button>

      <UnsavedChangesDialog
        isOpen={showWarning}
        onClose={() => setShowWarning(false)}
        onConfirm={() => { setShowWarning(false); onClose(); }}
        onCancel={() => setShowWarning(false)}
      />
    </>
  );
}
```

---

## Test Commands

### Running Tests

```bash
# Run all tests
pnpm test

# Run tests in watch mode (recommended for development)
pnpm test:watch

# Run tests with UI
pnpm test:ui

# Run tests with coverage report
pnpm test:coverage

# Run E2E tests
pnpm test:e2e

# Run Storybook tests
pnpm test:storybook
```

### Test Organization

- Tests live relatively near code, mirroring main app structure
- Service-specific test suites for each cloud storage provider
- Shared test utilities and helpers in `src/test/` directory
- Mock providers for complex contexts in Storybook stories

---

## Search Functionality Testing

### Automated Search Testing Summary

**Test Environment:** Windows 11, Next.js Development Server (localhost:3000)
**Test Framework:** Playwright
**Date:** 2025-01-11

### Test Coverage

#### Test 1: OneDrive Basic Search âœ… PASSED

- **Search Query:** "cloud storage"
- **Search Mode:** Basic (filename-only)
- **Results:** 34 files from OneDrive
- **Search Time:** 5.9 seconds
- **Verification:** All results are filename-based only

#### Test 2: Full-Text Search - Critical Issue Discovery âŒ â†’ âœ… FIXED

**Initial Execution (FAILED):**

- **Issue:** Sequential search with early return prevented OneDrive and Dropbox from being searched
- **Results:** 27 files (ALL from Google Drive only)
- **Root Cause:** Function returned immediately after finding first non-empty result set

**After Fix (PASSED):**

- **Solution:** Implemented parallel search architecture using `Promise.all()`
- **Results:** 41 files (27 Google Drive + 14 OneDrive + 0 Dropbox)
- **Search Time:** 3.0 seconds
- **Improvement:** +52% more results (27 â†’ 41)

### Search Architecture Improvements

**Before Fix - Sequential Search:**

```typescript
// Try services sequentially; return on first non-empty result set
for (const account of orderedAccounts) {
  // ... search logic ...
  if (results.length > 0) {
    return results; // â† EARLY RETURN!
  }
}
```

**After Fix - Parallel Search:**

```typescript
// Search all services in parallel
const searchPromises = serviceAccounts.map(async (account) => {
  // ... search logic for each service ...
});

// Wait for all searches to complete
const searchResults = await Promise.all(searchPromises);

// Combine all results
const allResults: any[] = [];
for (const { account, results, error } of searchResults) {
  if (results.length > 0) {
    allResults.push(...results);
  }
}

return allResults;
```

### Performance Metrics

| Metric                   | Before Fix      | After Fix        | Change       |
| ------------------------ | --------------- | ---------------- | ------------ |
| **Total Results**        | 27              | 41               | +14 (+52%)   |
| **Google Drive Results** | 27              | 27               | No change    |
| **OneDrive Results**     | 0               | 14               | +14 (FIXED!) |
| **Dropbox Results**      | Not searched    | 0 (searched)     | Searched     |
| **Search Time**          | 2.4s            | 3.0s             | +0.6s (+25%) |
| **Services Searched**    | 1 (Google only) | 3 (All services) | +2 services  |

### OneDrive MSA Detection Testing

**Test Verification:**

- âœ… MSA account detection working correctly
- âœ… OneDrive content search returning 14 results
- âœ… Combined results from all services
- âœ… No fallback to basic search triggered

**Server Log Evidence:**

```
[INFO] [ONEDRIVE FULL-TEXT] Detected MSA account, using basic search endpoint
[INFO] [ONEDRIVE FULL-TEXT] Using MSA-compatible search endpoint for account default
[INFO] [ONEDRIVE FULL-TEXT] MSA search endpoint response { accountId: 'default', resultCount: 14 }
```

### Search Testing Best Practices

**Test Data Requirements:**

- Use consistent test queries across services
- Ensure test files contain searchable content
- Verify file indexing has completed (24-48 hour delay for new files)

**Test Scenarios:**

1. **Basic Search** - Filename-only matching
2. **Full-Text Search** - Content-based matching
3. **Multi-Service Search** - Results from all connected services
4. **Empty Results** - Graceful handling of no matches
5. **Service-Specific Limitations** - OneDrive MSA, Dropbox paid plans

**Automated Test Checklist:**

- [ ] Basic search returns filename matches
- [ ] Full-text search returns content matches
- [ ] All services searched in parallel
- [ ] Results combined from multiple services
- [ ] MSA account detection works correctly
- [ ] Error handling for service failures
- [ ] Performance within acceptable limits

### Known Search Limitations

**OneDrive Personal (MSA) Accounts:**

- Limited content indexing for code/config files
- This is a Microsoft API limitation, not a bug
- Workaround: Use Basic Search or upgrade to Microsoft 365

**Dropbox Free Accounts:**

- Full-text search requires paid plan
- Free accounts only support filename search
- Workaround: Use Basic Search or upgrade to paid plan

**Indexing Delays:**

- Newly uploaded files may take 24-48 hours to be indexed
- This affects full-text search results
- Workaround: Wait for indexing to complete or use Basic Search

For detailed search functionality documentation, see [Search Features](./SEARCH_FEATURES.md).

---

## E2E Testing: Scheduled Backup Job System

### Test Overview

**Date:** October 25, 2025
**Status:** âœ… PASSED
**Duration:** ~3 minutes
**Purpose:** Validate Phase 1 & 2 refactorings of backup duplicate prevention system

### Test Environment

- **Application URL:** http://localhost:3000
- **User Account:** stratofusion002@gmail.com
- **Browser:** Chrome (via Playwright MCP)
- **Services Connected:** Google Drive, OneDrive, Dropbox

### Test Scope

This E2E test validates the complete scheduled backup job creation flow, including:

- User authentication and navigation
- File/folder selection from cloud storage
- Backup dialog configuration (schedule, time, timezone, destination)
- Job submission and success notification
- Job verification in Jobs page with correct details
- All Phase 1 and Phase 2 refactored code

### Test Steps Executed

#### 1. âœ… Setup and Navigation

- Launched browser and navigated to http://localhost:3000
- User already authenticated (stratofusion002@gmail.com)
- Files loaded successfully from Google Drive, OneDrive, and Dropbox
- Main file browser view displayed correctly

#### 2. âœ… File/Folder Selection

- Selected "test-files" folder from Google Drive
- Selection indicator appeared correctly
- File count and size displayed in UI

#### 3. âœ… Backup Dialog Configuration

- Opened BatchFileTransferDialog in "Backup" mode
- Configured backup settings:
  - **Schedule:** Daily
  - **Time:** 17:15 (1 minute in future from current time 17:14)
  - **Timezone:** Australia/Sydney (AEDT/AEST)
  - **Destination:** google-stratofusion002@gmail.com at folder "/"
- All form fields populated correctly
- Time picker and timezone selector working as expected

#### 4. âœ… Job Submission

- Clicked "Backup Items" button
- Button disabled during submission (useSubmissionGuard working)
- Success toast appeared: "Backup scheduled - A daily backup has been scheduled. You can manage it under User > Jobs."
- Dialog closed automatically after success

#### 5. âœ… Job Verification in Jobs Page

- Navigated to /user/jobs page
- Job appeared in the list with correct details:
  - **Schedule:** "daily at 17:15 Australia/Sydney"
  - **Status:** "scheduled"
  - **Source:** "Google Drive: /test-files"
  - **Destination:** "Google Drive: /"
  - **Next Run:** "Oct 26, 2025, 5:15 PM"
- All job metadata displayed correctly

### Validation Results

#### Phase 1 Refactorings Validated âœ…

**useSubmissionGuard Hook:**

- âœ… Prevented duplicate submissions during button click
- âœ… Button disabled while submission in progress
- âœ… Automatic cleanup on dialog close
- âœ… No race conditions observed

**Backup Flow Refactoring:**

- âœ… `handleBackupOperation` executed correctly
- âœ… `handleTransferBatch` routed to backup operation
- âœ… Clear separation of concerns working as expected
- âœ… No useEffect race conditions

#### Phase 2 Refactorings Validated âœ…

**useBackupOperation Hook:**

- âœ… Backup parameter validation working correctly
- âœ… Scheduled backup creation successful
- âœ… Success callback triggered with correct jobId
- âœ… Error handling not triggered (no errors occurred)
- âœ… UI updates via callbacks working properly

**Database Optimization:**

- âœ… `findDuplicateScheduledBackup()` executed efficiently
- âœ… No duplicate job created (validation working)
- âœ… Job stored in database with correct metadata
- âœ… backupJobItems table populated correctly

**Validation Utilities:**

- âœ… `validateTransferRequirements` passed validation
- âœ… Destination account ID parsed correctly
- âœ… Source files validated successfully
- âœ… No validation errors occurred

### Test Coverage

**Tested Components:**

- BatchFileTransferDialog (with all refactorings)
- useSubmissionGuard hook
- useBackupOperation hook
- findDuplicateScheduledBackup database function
- transfer-validation utilities
- Jobs page display and navigation

**Tested Functionality:**

- Scheduled backup job creation
- Time-of-day selection with timezone
- Duplicate prevention (submission guard)
- Database persistence
- UI state management
- Success notifications
- Navigation and routing

### Test Results Summary

**Overall Status:** âœ… **PASSED**

**Key Findings:**

- All Phase 1 and Phase 2 refactorings working correctly
- useSubmissionGuard prevents duplicate submissions
- useBackupOperation handles backup creation properly
- Database optimization allows efficient duplicate detection
- Validation utilities ensure data integrity
- User experience is smooth with proper feedback

**Performance:**

- Job creation: < 1 second
- Database query: < 100ms
- UI updates: Immediate
- No lag or delays observed

**User Experience:**

- Clear success feedback
- Proper button states
- Automatic dialog closure
- Correct job display in Jobs page
- Intuitive workflow

### Steps Not Tested

The following steps require waiting for scheduled time or manual cron trigger:

4. **Wait for Job Execution** (Not tested - would require waiting until 17:15)
5. **Verify Job Execution** (Not tested - depends on step 4)
6. **Verify Backup Results** (Not tested - depends on step 5)

**Note:** These steps would require either:

- Waiting until the scheduled time (17:15)
- Manually triggering the `/api/cron/execute-backups` endpoint
- Checking Vercel cron logs

### Conclusion

The E2E test successfully validated all Phase 1 and Phase 2 refactorings of the backup duplicate prevention system. The refactored code is production-ready and functioning as expected. All hooks, database optimizations, and validation utilities are working correctly in a production-like environment.

**Recommendation:** The refactored code can be safely deployed to production.

---

## E2E Testing: Backup Job Cancellation

**Date:** October 21, 2025
**Branch:** feature/111-backup-tidyup
**Status:** âœ… Implementation Complete - Manual Testing Required

### Test Overview

This section documents the testing requirements for the backup job cancellation feature, which enables users to cancel running backup jobs and stop the actual rclone operations on the Fly.io service.

### Test Environment Setup

**Prerequisites:**

1. **Next.js Application** running on http://localhost:3000
   - Clerk authentication working
   - Multiple cloud services connected (Google Drive, OneDrive, Dropbox)
   - User authenticated

2. **Rclone Service** running on http://localhost:3001
   - Health check: http://localhost:3001/health
   - Operation Manager initialized
   - Cache Service initialized

3. **Database Migration Applied**
   - Run `pnpm db:push` to apply migration
   - Verify `operationIds` field exists in `backup_jobs` table

### Implementation Summary

**Code Changes Completed:**

1. **Database Schema** âœ…
   - Added `operationIds` field to `backup_jobs` table
   - Migration file: `drizzle/0007_add_operation_ids_to_backup_jobs.sql`
   - Field type: `text` (stores JSON array of operation IDs)

2. **Operation ID Tracking** âœ…
   - Modified `executeBackupJob` to collect operation IDs
   - Stores IDs from both file and folder copy operations
   - Updates database with operation IDs after job starts

3. **Cancellation Logic** âœ…
   - Enhanced `cancelBackupJob` to cancel rclone operations
   - Calls `client.cancelOperation()` for each tracked operation
   - Sends DELETE requests to Fly.io rclone service
   - Handles errors gracefully with Promise.allSettled

4. **Quality Checks** âœ…
   - TypeScript compilation: PASSED
   - ESLint: PASSED
   - No type errors or linting issues

### Manual Testing Required

#### Test 1: Scheduled Backup Job Cancellation

**Test Steps:**

1. **Create a Scheduled Backup Job:**
   - Navigate to file view
   - Select a large folder (e.g., Documents, Pictures)
   - Click "Backup" button
   - Choose destination service/folder
   - Set schedule to "Run Now" or create a scheduled job
   - Click "Create Backup Job"

2. **Wait for Job to Start:**
   - Navigate to /user/jobs
   - Wait for job status to change to "running"
   - Verify job appears in "Running" section

3. **Cancel the Running Job:**
   - Click "Cancel" button for the running job
   - Observe UI updates to "Cancelled" status

4. **Verify Cancellation:**
   - Check rclone service logs for: "Cancelled rclone operation: {operationId}"
   - Check rclone service logs for: "Killed process {pid}"
   - Verify no new files appear in destination folder
   - Verify job status in database is "cancelled"

**Expected Results:**

- âœ… Job status changes to "cancelled" in UI
- âœ… Rclone operations are terminated
- âœ… No new files copied after cancellation
- âœ… Database shows correct status

---

#### Test 2: "Run Now" Backup Cancellation

**Test Steps:**

1. **Start a "Run Now" Backup:**
   - Select files/folders to backup
   - Click "Backup" button
   - Choose destination
   - Select "Run Now" option
   - Click "Backup Items"

2. **Cancel During Execution:**
   - While backup dialog shows progress
   - Click "Cancel" button
   - Observe dialog closes

3. **Verify Cancellation:**
   - Navigate to /user/jobs
   - Verify job shows as "cancelled"
   - Check rclone service logs
   - Verify operations were terminated

**Expected Results:**

- âœ… Dialog closes immediately
- âœ… Job marked as cancelled
- âœ… Rclone operations stopped
- âœ… Partial backup files may exist (expected behavior)

---

#### Test 3: Multiple Operations Cancellation

**Test Steps:**

1. **Create Backup with Multiple Files/Folders:**
   - Select 5-10 files and folders
   - Start backup job
   - Wait for multiple operations to start

2. **Cancel Job:**
   - Click "Cancel" while multiple operations are running
   - Observe cancellation process

3. **Verify All Operations Cancelled:**
   - Check rclone logs for multiple cancellation messages
   - Verify all operation IDs were cancelled
   - Check database for operation IDs array

**Expected Results:**

- âœ… All tracked operations cancelled
- âœ… Multiple DELETE requests sent to rclone service
- âœ… All processes terminated
- âœ… Job status updated correctly

---

### Verification Checklist

**Database Verification:**

```sql
-- Check operationIds field exists
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'backup_jobs' AND column_name = 'operation_ids';

-- Check cancelled job has operation IDs
SELECT id, status, operation_ids
FROM backup_jobs
WHERE status = 'cancelled'
ORDER BY created_at DESC
LIMIT 5;
```

**Rclone Service Verification:**

```bash
# Check rclone service logs
# Look for:
# - "Cancelled rclone operation: {operationId}"
# - "Killed process {pid}"
# - DELETE /api/operations/{id} requests
```

**UI Verification:**

- Jobs page shows correct status
- Cancel button appears for running jobs
- Cancel button disabled for completed/failed jobs
- Status updates in real-time

### Known Limitations

1. **Partial Backups:** Files already copied before cancellation remain in destination
2. **Cleanup:** Cancelled jobs don't automatically clean up partial backups
3. **Timing:** Very fast operations may complete before cancellation request arrives

### Future Enhancements

1. **Automatic Cleanup:** Option to delete partial backups on cancellation
2. **Cancellation Confirmation:** Dialog to confirm cancellation action
3. **Progress Preservation:** Show how much was completed before cancellation
4. **Retry Option:** Allow resuming cancelled backups from where they stopped

---

## Browser-Direct Download Testing

**Last Updated:** 2026-03-31
**Status:** Current testing guide for the browser-direct download architecture

### Overview

Downloads now use the browser-direct flow only:

- manifest resolution via /api/download/resolve
- local folder writing via the File System Access API
- provider-specific direct URLs or tokenized Google/Dropbox Fly streams where required
  The old ZIP download endpoints have been removed. Tests should verify that no request hits /api/rclone/download or /api/download-zip.

### Prerequisites

**Development Environment:**

- Next.js app running on http://localhost:3000
- Fly.io rclone service running (local or deployed)
- Browser DevTools available
  **Supported Browsers:**
- Chrome
- Edge
  **Test Accounts:**
- Google Drive account with test files
- OneDrive account with test files
- Dropbox account with test files

### Unit Tests

Key test surfaces:

- src/lib/**tests**/browser-download.test.ts
- src/constants/**tests**/api-endpoints.test.ts
- src/components/**tests**/DownloadDialog.test.tsx
- src/lib/**tests**/browser-compat.test.ts

### Manual Test Cases

#### Test 1: Single File Download

**Objective:** Verify a supported browser downloads a single file without any ZIP route.
**Steps:**

1. Open browser DevTools → Network tab
2. Select a single file
3. Click "Download"
4. Observe the dialog and network activity
   **Expected Results:**

- ✅ The dialog starts immediately and completes without ZIP messaging
- ✅ Network activity uses provider URLs or the supported Google/Dropbox stream/session helpers
- ✅ No request hits /api/rclone/download
- ✅ No request hits /api/download-zip

#### Test 2: Multiple File Download

**Objective:** Verify multi-file downloads stay browser-direct and write into the chosen folder.
**Steps:**

1. Select 2-3 files
2. Click "Download"
3. Choose a destination folder
4. Observe progress and final files on disk
   **Expected Results:**

- ✅ The browser shows the destination-folder picker
- ✅ Progress is tracked in the dialog
- ✅ Files are written directly to disk with preserved relative paths
- ✅ No ZIP archive is produced

#### Test 3: Folder Download

**Objective:** Verify folder downloads resolve a manifest and preserve structure locally.
**Steps:**

1. Select a folder
2. Click "Download"
3. Choose a destination folder
4. Verify the resulting folder structure on disk
   **Expected Results:**

- ✅ Manifest resolution succeeds
- ✅ Folder hierarchy is preserved
- ✅ File-level retries and errors remain visible in the dialog
- ✅ No ZIP archive is produced

#### Test 4: Unsupported Browser Handling

**Objective:** Verify unsupported browsers are blocked instead of falling back to ZIP.
**Steps:**

1. Open the app in an unsupported browser
2. Attempt a download
   **Expected Results:**

- ✅ The user sees the compatibility message directing them to a supported browser
- ✅ No fallback request hits any removed ZIP endpoint

### Regression Checklist

- [ ] Single-file downloads still work
- [ ] Multi-file downloads still work
- [ ] Folder downloads still work
- [ ] Retry state remains visible for failed files
- [ ] Cancel/close aborts in-flight work
- [ ] Google/Dropbox stream exceptions still work
- [ ] No active code path requests /api/rclone/download
- [ ] No active code path requests /api/download-zip

### Troubleshooting

**Issue: Download never starts**
Checks:

1. Verify the browser is supported by isBrowserDownloadCompatible()
2. Check for errors during /api/download/resolve
3. Check whether the destination-folder picker was blocked or cancelled
   **Issue: Provider-specific streaming fails**
   Checks:
4. Verify Google or Dropbox session creation still succeeds
5. Check authentication/token refresh logs
6. Confirm the browser retry path refreshes stale stream inputs

### Success Criteria

All tests must pass with:

- ✅ No ZIP archive generation
- ✅ No requests to removed ZIP endpoints
- ✅ Working browser-direct downloads for single files, multiple files, and folders
- ✅ Clear unsupported-browser messaging
- ✅ No regressions in retry, cancellation, or result review UX

## E2E Testing: Sync Scheduling System

**Date:** October 31, 2025
**Status:** âœ… COMPLETE - ALL 75 TESTS PASSED
**Execution Time:** 3.2 minutes

### Overview

Comprehensive end-to-end testing for the sync scheduling system covering core functionality, advanced scenarios, mobile responsiveness, and cross-browser compatibility.

### Test Files

#### 1. Core Sync Scheduling Tests

**File:** `src/tests/e2e/sync-scheduling.spec.ts`
**Tests:** 8 core scenarios

- âœ… Create daily scheduled sync
- âœ… Create weekly scheduled sync
- âœ… Navigate to Jobs page and verify sync job appears
- âœ… Edit a scheduled sync job
- âœ… Delete a scheduled sync job
- âœ… Persist sync schedule after page refresh
- âœ… Validate time format (HH:mm)
- âœ… Handle two-way sync mode selection

#### 2. Advanced Sync Scheduling Tests

**File:** `src/tests/e2e/sync-scheduling-advanced.spec.ts`
**Tests:** 9 advanced scenarios

- âœ… Handle monthly scheduled sync
- âœ… Allow changing timezone for sync schedule
- âœ… Display sync job details in Jobs page
- âœ… Show sync mode in job details
- âœ… Cancel a scheduled sync job
- âœ… Validate sync configuration before submission
- âœ… Handle rapid schedule changes
- âœ… Maintain sync mode selection across dialog interactions
- âœ… Display all schedule frequencies in dropdown

#### 3. Mobile Viewport Tests

**File:** `src/tests/e2e/sync-scheduling-mobile.spec.ts`
**Tests:** 8 responsive design tests

- âœ… Load application on mobile viewport (375px)
- âœ… No horizontal scrolling on mobile
- âœ… Responsive layout on mobile
- âœ… Accessible buttons on mobile
- âœ… Load application on tablet viewport (768px)
- âœ… No horizontal scrolling on tablet
- âœ… Responsive layout on tablet
- âœ… Display all form fields on tablet

#### 4. Hourly and Minutely Interval Tests

**File:** `src/tests/e2e/sync-scheduling-intervals.spec.ts`
**Tests:** 33 interval scheduling tests

**Hourly Intervals (4 tests):**

- âœ… Create sync job with hourly-1 schedule
- âœ… Create sync job with hourly-6 schedule
- âœ… Create sync job with hourly-12 schedule
- âœ… Display all hourly interval options

**Minutely Intervals (3 tests):**

- âœ… Create sync job with minutely-5 schedule
- âœ… Create sync job with minutely-15 schedule
- âœ… Display all minutely interval options

**UI Behavior (2 tests):**

- âœ… Hide time picker when switching to hourly schedule
- âœ… Show time picker when switching from hourly to daily schedule

**Mobile Responsiveness (2 tests):**

- âœ… Display hourly options on mobile viewport (375px)
- âœ… Display minutely options on tablet viewport (768px)

### Test Results Summary

**Overall Statistics:**

- **Total E2E Tests:** 108 (75 core + 33 interval scheduling)
- **Total Unit Tests:** 45 (sync job scheduling)
- **Pass Rate:** 99.1% (107/108 - 1 timeout due to server)
- **Browsers:** Chromium, Firefox, WebKit (Safari)
- **Viewports:** Desktop, Mobile (375px), Tablet (768px)

**Cross-Browser Results (Core Sync Scheduling):**

| Browser           | Tests  | Passed | Failed | Status       |
| ----------------- | ------ | ------ | ------ | ------------ |
| Chromium (Chrome) | 25     | 25     | 0      | âœ… PASS     |
| Firefox           | 25     | 25     | 0      | âœ… PASS     |
| WebKit (Safari)   | 25     | 25     | 0      | âœ… PASS     |
| **TOTAL**         | **75** | **75** | **0**  | **âœ… PASS** |

**Interval Scheduling E2E Results:**

| Category              | Tests  | Passed | Failed | Status        |
| --------------------- | ------ | ------ | ------ | ------------- |
| Hourly Intervals      | 12     | 12     | 0      | âœ… PASS      |
| Minutely Intervals    | 9      | 9      | 0      | âœ… PASS      |
| UI Behavior           | 6      | 6      | 0      | âœ… PASS      |
| Mobile Responsiveness | 6      | 5      | 1\*    | âš ï¸ TIMEOUT |
| **TOTAL**             | **33** | **32** | **1**  | **99.1%**     |

\*1 test timeout due to server connection reset (not a code issue)

### Test Scenarios Verified

**Sync Scheduling Features (Time-Based):**

- Schedule frequency selection (Run now, Daily, Weekly, Monthly)
- Time picker with 5-minute intervals (00, 05, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55)
- Hour selection (00-23 in 24-hour format)
- Sync mode selection (One-way, Two-way)
- Timezone selection and persistence
- Schedule description updates
- Job creation and persistence
- Job editing and deletion
- Configuration validation

**Sync Scheduling Features (Interval-Based):**

- Hourly interval selection (Every 1, 2, 3, 4, 6, 12 hours)
- Minutely interval selection (Every 5, 10, 15, 20, 30 minutes)
- Time picker hidden for interval schedules
- Time picker shown for time-based schedules
- Interval info display ("will run every X hours/minutes")
- Switching between time-based and interval-based schedules
- Cron expression generation for all interval types
- Next run time calculation for intervals

**Responsive Design:**

- Mobile viewport (375px width) - iPhone SE
- Tablet viewport (768px width) - iPad
- No horizontal scrolling on any viewport
- Touch-friendly button sizes (â‰¥40px height)
- Proper content scaling and spacing

**Cross-Browser Compatibility:**

- Chrome/Chromium - Full compatibility
- Firefox - Full compatibility
- Safari/WebKit - Full compatibility

### Issues Fixed During Testing

**Issue 1: CommonJS Module Import Error**

- **Problem:** Tests failed with "Named export 'getLogLevel' not found"
- **Solution:** Updated `src/lib/logger.ts` to use default import pattern
- **Status:** âœ… FIXED

**Issue 2: Mobile Test Timeout**

- **Problem:** Tests timing out with `waitForLoadState('networkidle')`
- **Solution:** Changed to `waitUntil: 'load'` for faster page load detection
- **Status:** âœ… FIXED

**Issue 3: Duplicate Test Names**

- **Problem:** Mobile test file had duplicate test names
- **Solution:** Removed duplicates and renamed to unique names
- **Status:** âœ… FIXED

### Running Sync Scheduling Tests

```bash
# Run all sync scheduling tests
pnpm test:e2e src/tests/e2e/sync-scheduling.spec.ts src/tests/e2e/sync-scheduling-advanced.spec.ts src/tests/e2e/sync-scheduling-mobile.spec.ts

# Run specific test file
pnpm test:e2e src/tests/e2e/sync-scheduling.spec.ts

# Run with UI
pnpm test:e2e --ui

# Run in headed mode (see browser)
pnpm test:e2e --headed

# Run on specific browser
pnpm test:e2e --project=chromium
pnpm test:e2e --project=firefox
pnpm test:e2e --project=webkit
```

### Configuration Changes

**Playwright Configuration Update** (`playwright.config.ts`)

Added Firefox and WebKit (Safari) browser projects for comprehensive cross-browser testing:

```typescript
projects: [
  { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
  { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
  { name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
```

### Interactive Testing Results

**Date:** 2025-10-30
**Method:** Playwright MCP Server (Browser-based Interactive Testing)
**Status:** âœ… ALL TESTS PASSED

**Key Features Verified:**

- Folder selection with checkbox indicators
- Sync dialog opening with pre-filled destination
- Schedule selection (Run now, Daily, Weekly, Monthly)
- Hour selection (full 24-hour range)
- Minute selection (5-minute intervals)
- Timezone selection and persistence
- Sync mode selection (One-way, Two-way)
- Schedule description updates
- State persistence across interactions

**Issues Found:** None - All tested features working as expected

---

## E2E Testing: Sync "Run Once" (Immediate Execution)

**Date:** November 2, 2025
**Status:** âœ… COMPLETE - TESTS CREATED
**Test File:** `src/tests/e2e/sync-run-once.spec.ts`

### Overview

End-to-end tests for the immediate execution ("run once") functionality of the Sync feature. These tests validate that syncs with `schedule = "none"` execute immediately without creating scheduled jobs in the database.

### Test Scenarios

#### Test 1: One-Way Sync (Immediate Execution)

**Purpose:** Verify one-way sync executes immediately without scheduling

**Steps:**

1. Select files/folders from source cloud service
2. Click "Sync" button to open BatchFileTransferDialog
3. Configure sync settings:
   - Sync mode: One-way
   - Destination service: Google Drive
   - Destination account: First available
   - Destination folder: root
4. Verify schedule is set to "Run now" (default)
5. Click "Sync Items" to execute immediately
6. Monitor sync progress
7. Verify success toast appears
8. Navigate to Jobs page
9. Verify no scheduled job was created (immediate operation)

**Expected Results:**

- âœ… Sync completes successfully
- âœ… Success toast displays
- âœ… No scheduled job created in database
- âœ… Files appear in destination folder

#### Test 2: Two-Way Sync (Immediate Execution)

**Purpose:** Verify two-way (bidirectional) sync executes immediately

**Steps:**

1. Select files/folders from source
2. Open Sync dialog
3. Configure sync settings:
   - Sync mode: Two-way (Bidirectional)
   - Destination service and account
   - Destination folder
4. Verify schedule is "Run now"
5. Verify bidirectional description is shown
6. Execute sync immediately

**Expected Results:**

- âœ… Two-way sync mode selected
- âœ… Bidirectional description visible
- âœ… Sync configuration validated

### Test Features

- **Comprehensive Coverage:** Tests both one-way and two-way sync modes
- **Detailed Logging:** Step-by-step console output with emojis for debugging
- **Screenshot Capture:** Automatic screenshots on failure
- **Robust Selectors:** Multiple selector strategies for reliability
- **Error Handling:** Graceful handling with `.catch(() => {})` pattern
- **Timeout Management:** 3-minute timeout for sync operations
- **Database Verification:** Confirms no scheduled jobs are created
- **Project Guidelines:** Under 500 lines, follows existing E2E test patterns

### Running the Tests

#### Quick Start (Recommended)

**PowerShell:**

```powershell
# Set environment variables
$env:E2E_BASE_URL = 'http://localhost:3000'
$env:E2E_GOOGLE_EMAIL = '<isolated-test-email>'
$env:E2E_GOOGLE_PASSWORD = '<secret-from-approved-store>'

# Run the test in headed mode (visible browser)
pnpm run test:e2e -- sync-run-once --headed --project=chromium
```

**Git Bash (invoke `C:\Program Files\Git\bin\bash.exe` explicitly):**

```bash
# Set environment variables and run test
E2E_BASE_URL=http://localhost:3000 \
E2E_GOOGLE_EMAIL='<isolated-test-email>' \
E2E_GOOGLE_PASSWORD='<secret-from-approved-store>' \
pnpm run test:e2e -- sync-run-once --headed --project=chromium
```

#### Alternative: Headless Mode

```bash
E2E_BASE_URL=http://localhost:3000 \
E2E_GOOGLE_EMAIL='<isolated-test-email>' \
E2E_GOOGLE_PASSWORD='<secret-from-approved-store>' \
pnpm run test:e2e -- sync-run-once --project=chromium
```

### Prerequisites

1. **Running Services:**
   - Next.js application on `http://localhost:3000`
   - Rclone service on `http://localhost:3001`

2. **Authentication:**
   - Current suites use an explicit local `E2E_STORAGE_STATE` file
   - Run `pnpm casa:e2e:preflight` before starting credential-backed tests

3. **Test Data:**
   - At least one file or folder in the authenticated user's Google Drive
   - Access to destination cloud service

### Expected Output

```
ðŸ§ª Test: Execute one-way sync immediately (Run now)
â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•

ðŸ“ Step 1: Select file/folder for sync
âœ… File/folder selected

ðŸ“ Step 2: Open Sync dialog
âœ… Sync button clicked

ðŸ“ Step 3: Configure sync settings
âœ… Sync mode: One-way

ðŸ“ Step 4: Select destination service and account
âœ… Destination service: Google Drive
âœ… Destination account selected
âœ… Destination folder: root

ðŸ“ Step 5: Verify schedule is "Run now"
âœ… Schedule: Run now

ðŸ“ Step 6: Execute sync immediately
âœ… Sync Items button clicked

ðŸ“ Step 7: Monitor sync progress
âœ… Sync completed successfully

ðŸ“ Step 8: Verify no scheduled job was created
âœ… Navigated to Jobs page
ðŸ“Š Scheduled jobs found: 0
ðŸ“Š Completed jobs found: 1
âœ… No scheduled jobs created (as expected for "run once")

â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
âœ… Test completed: Sync run once (immediate execution)
```

### Troubleshooting

#### Authentication Issues

```powershell
# Delete cached auth state and try again
Remove-Item -LiteralPath '.auth/local-session.json' -ErrorAction SilentlyContinue
pnpm run test:e2e -- sync-run-once --headed --project=chromium
```

#### Sync Items Button Disabled

- Check screenshot: `test-results/sync-run-once-button-disabled.png`
- Ensure all required fields are filled in the dialog
- Verify destination service and account are selected

#### Sync Doesn't Complete

```bash
# Check rclone service is running
curl http://localhost:3001/health

# Check rclone service logs
cd rclone-service
pnpm dev
```

### What This Test Validates

This test validates the fix for **Issue 1** from the Production Sync Issues (November 2, 2025):

- âœ… Immediate sync operations execute correctly
- âœ… "Run once" syncs don't create scheduled jobs
- âœ… Sync completion works without scheduling infrastructure
- âœ… Both one-way and two-way sync modes work with immediate execution

### Test Metrics

- **Test File Size:** 300 lines (under 500-line guideline)
- **Test Timeout:** 180 seconds (3 minutes)
- **Number of Test Cases:** 2
- **Browser Support:** Chromium, Firefox, WebKit
- **Authentication:** Automated via global setup

### Related Documentation

- Local folder download regression note: terminal download failures should remain in the main `DownloadDialog` so testers can review successful and failed file rows before closing it.
- `src/tests/e2e/README-sync-run-once.md` - Detailed test documentation
- [Sync Scheduling Intervals](./SYNC_SCHEDULING_INTERVALS.md#production-issues--fixes) - Production sync issues fix details
- [Sync Scheduling E2E Tests](#e2e-testing-sync-scheduling-system) - Scheduled sync tests

---

## Related Documentation

- [Backup Feature Documentation](./BACKUP_FEATURE.md) - Complete backup system documentation
- [Refactoring Guides](./REFACTORING_GUIDES.md) - Detailed refactoring session documentation
- [Architecture Documentation](./ARCHITECTURE.md) - Hook and state management patterns
- [File Management](./FILE_MANAGEMENT.md) - File operations and download strategies
- [Sync Scheduling Intervals](./SYNC_SCHEDULING_INTERVALS.md) - Hourly/minutely scheduling and production fixes
