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:
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:
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:
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:
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:
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.
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:
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.
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:
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.
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:
Start a Google Drive to OneDrive folder copy that includes a sibling collision.
Confirm the compatibility dialog appears before any copy begins.
Accept the default rename suggestion and verify the copy completes.
Retry with a skip decision and verify the skipped item is absent from OneDrive.
Retry with an invalid rename target and verify the dialog blocks submission.
Force a rename failure or validation failure and verify Google source renames are rolled back.
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:
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.
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:
Sign in through Clerk and confirm the URL remains on staging.
Connect Google Drive, OneDrive, and Dropbox one at a time.
Confirm each callback and popup/same-tab completion returns to staging and
the account appears in the VM database.
Exercise one approved, non-destructive upload/download path and confirm the
production-mode rclone gateway allows the staging origin.
Confirm an arbitrary preview still shows the UI-only warning and cannot run
OAuth-backed flows.
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.
describe("Service Authentication",()=>{test("Google Drive service authenticates correctly",async()=>{const service =newGoogleDriveService();const result =await service.authenticate("default", mockTokens);expect(result.success).toBe(true);});test("OneDrive service handles authentication errors",async()=>{const service =newOneDriveService();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:
# 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)
Connect a service account, then manually disconnect it via UI
Reload the app → the same account should NOT auto‑reconnect
Manually reconnect the same account → it should connect and clear the block
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:
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
pnpmexec playwright install chromium
Run the credential-free security regression:
pnpm test:e2e:security
Run every discovered suite. Suites without their declared capability skip
safely:
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
# Step through each action$env:PWDEBUG = '1'pnpm test:e2e ----project=chromium
# Interactive test runnerpnpm 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.
describe("File Operations",()=>{test("Upload file to Google Drive",async({ page })=>{awaitauthenticateWithGoogle(page);// Navigate to Google Driveawait page.click('[data-testid="google-drive-tab"]');// Upload fileconst fileInput = page.locator('input[type="file"]');await fileInput.setInputFiles("test-files/sample.pdf");// Verify upload successawaitexpect(page.locator('[data-testid="upload-success"]')).toBeVisible();awaitexpect(page.locator("text=sample.pdf")).toBeVisible();});test("Copy file between services",async({ page })=>{awaitauthenticateWithMultipleServices(page);// Select file in Google Driveawait page.click('[data-testid="google-drive-tab"]');await page.click('[data-testid="file-checkbox-sample.pdf"]');// Copy to Dropboxawait page.click('[data-testid="copy-button"]');await page.selectOption('[data-testid="destination-service"]',"dropbox");await page.click('[data-testid="confirm-copy"]');// Verify copy successawaitexpect(page.locator('[data-testid="copy-success"]')).toBeVisible();});});
Test Data Management
Test File Generation
# create_test_files.pyimport os
from pathlib import Path
defcreate_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
// Cleanup helper for testsexportasyncfunctioncleanupTestData(service:string, accountId:string){const testFiles =awaitlistFiles(service, accountId,{ query:'name contains "test-"',});for(const file of testFiles){awaitdeleteFile(service, accountId, file.id);}}
Performance Testing
Load Testing
describe('Performance Tests',()=>{test('Large file list rendering',async()=>{const startTime =performance.now();// Render component with 1000 filesrender(<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 =awaitPromise.all(uploadPromises);expect(results.every(r => r.success)).toBe(true);});});
Memory Usage Testing
test("Memory usage during large operations",async()=>{const initialMemory = process.memoryUsage().heapUsed;// Perform large operationawaitprocessLargeFileList(generateMockFiles(10000));// Force garbage collectionif(global.gc) global.gc();const finalMemory = process.memoryUsage().heapUsed;const memoryIncrease = finalMemory - initialMemory;// Memory increase should be reasonableexpect(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.tsTest 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:
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.tsTest 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:
Basic Functionality (2 tests)
Returns false when values match initial values
Returns true when values differ from initial values
Multiple Fields (1 test)
Correctly detects changes in forms with 3+ properties
Null Handling (3 tests)
Returns false when initial values are null
Returns false when current values are null
Returns false when both are null
Reset Functionality (2 tests)
Resets dirty state to false when resetDirtyState is called
Maintains reset state until values change again
Different Data Types (3 tests)
Works with string values
Works with number values
Works with boolean values and mixed types
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
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:
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.tsxTest 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:
Rendering (3 tests)
Does not render when isOpen is false
Renders when isOpen is true
Displays warning text correctly
Button Interactions (2 tests)
Calls onConfirm when "Discard Changes" is clicked
Calls onCancel when "Continue Editing" is clicked
Dialog Close Behavior (2 tests)
Calls onClose when dialog is closed via Escape key
# Run all testspnpmtest# Run tests in watch mode (recommended for development)pnpm test:watch
# Run tests with UIpnpm test:ui
# Run tests with coverage reportpnpm test:coverage
# Run E2E testspnpm test:e2e
# Run Storybook testspnpm 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
// Try services sequentially; return on first non-empty result setfor(const account of orderedAccounts){// ... search logic ...if(results.length>0){return results;// ↠EARLY RETURN!}}
After Fix - Parallel Search:
// Search all services in parallelconst searchPromises = serviceAccounts.map(async(account)=>{// ... search logic for each service ...});// Wait for all searches to completeconst searchResults =awaitPromise.all(searchPromises);// Combine all resultsconst allResults:any[]=[];for(const{ account, results, error }of searchResults){if(results.length>0){ allResults.push(...results);}}return allResults;
The following steps require waiting for scheduled time or manual cron trigger:
Wait for Job Execution (Not tested - would require waiting until 17:15)
Verify Job Execution (Not tested - depends on step 4)
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.
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.
Field type: text (stores JSON array of operation IDs)
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
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
Quality Checks ✅
TypeScript compilation: PASSED
ESLint: PASSED
No type errors or linting issues
Manual Testing Required
Test 1: Scheduled Backup Job Cancellation
Test Steps:
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"
Wait for Job to Start:
Navigate to /user/jobs
Wait for job status to change to "running"
Verify job appears in "Running" section
Cancel the Running Job:
Click "Cancel" button for the running job
Observe UI updates to "Cancelled" status
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:
Start a "Run Now" Backup:
Select files/folders to backup
Click "Backup" button
Choose destination
Select "Run Now" option
Click "Backup Items"
Cancel During Execution:
While backup dialog shows progress
Click "Cancel" button
Observe dialog closes
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:
Create Backup with Multiple Files/Folders:
Select 5-10 files and folders
Start backup job
Wait for multiple operations to start
Cancel Job:
Click "Cancel" while multiple operations are running
Observe cancellation process
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:
-- Check operationIds field existsSELECT column_name, data_type
FROM information_schema.columnsWHERE table_name ='backup_jobs'AND column_name ='operation_ids';-- Check cancelled job has operation IDsSELECT id,status, operation_ids
FROM backup_jobs
WHEREstatus='cancelled'ORDERBY created_at DESCLIMIT5;
Rclone Service Verification:
# 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
Partial Backups: Files already copied before cancellation remain in destination
Cleanup: Cancelled jobs don't automatically clean up partial backups
Timing: Very fast operations may complete before cancellation request arrives
Future Enhancements
Automatic Cleanup: Option to delete partial backups on cancellation
Cancellation Confirmation: Dialog to confirm cancellation action
Progress Preservation: Show how much was completed before cancellation
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.
Objective: Verify a supported browser downloads a single file without any ZIP route.
Steps:
Open browser DevTools → Network tab
Select a single file
Click "Download"
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:
Select 2-3 files
Click "Download"
Choose a destination folder
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:
Select a folder
Click "Download"
Choose a destination folder
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:
Open the app in an unsupported browser
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:
Verify the browser is supported by isBrowserDownloadCompatible()
Check for errors during /api/download/resolve
Check whether the destination-folder picker was blocked or cancelled
Issue: Provider-specific streaming fails
Checks:
Verify Google or Dropbox session creation still succeeds
Check authentication/token refresh logs
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.
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
# Run all sync scheduling testspnpm 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 filepnpm test:e2e src/tests/e2e/sync-scheduling.spec.ts
# Run with UIpnpm test:e2e --ui
# Run in headed mode (see browser)pnpm test:e2e --headed
# Run on specific browserpnpm test:e2e --project=chromium
pnpm test:e2e --project=firefox
pnpm test:e2e --project=webkit
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:
Select files/folders from source cloud service
Click "Sync" button to open BatchFileTransferDialog
Configure sync settings:
Sync mode: One-way
Destination service: Google Drive
Destination account: First available
Destination folder: root
Verify schedule is set to "Run now" (default)
Click "Sync Items" to execute immediately
Monitor sync progress
Verify success toast appears
Navigate to Jobs page
Verify no scheduled job was created (immediate operation)
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:
# 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
# Set environment variables and run testE2E_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
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
Running Services:
Next.js application on http://localhost:3000
Rclone service on http://localhost:3001
Authentication:
Current suites use an explicit local E2E_STORAGE_STATE file
Run pnpm casa:e2e:preflight before starting credential-backed tests
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
# Delete cached auth state and try againRemove-Item-LiteralPath '.auth/local-session.json'-ErrorAction SilentlyContinue
pnpm run test:e2e --sync-run-once --headed --project=chromium
Ensure all required fields are filled in the dialog
Verify destination service and account are selected
Sync Doesn't Complete
# Check rclone service is runningcurl http://localhost:3001/health
# Check rclone service logscd 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