Commit Graph
200 Commits
Author SHA1 Message Date
SnapOtter 5dbbac6c43 test(web): align unit + e2e expectations with section routes 2026-06-20 23:35:37 +08:00
SnapOtter 68dd7dabaa feat(api): section-prefix factory + batch routes via apiToolPath (+section validation) 2026-06-20 11:44:36 +08:00
SnapOtter c17a4a4225 feat(scripts): add segment-anchored tool-path rewrite transform 2026-06-20 11:22:33 +08:00
SnapOtter 9ca901f8d5 feat(shared): add apiToolPath() and section-prefixed routes; drop MODALITY_URL_SLUG 2026-06-20 11:05:44 +08:00
SnapOtter 790747188a feat(shared): add Section concept and toolSection() partition 2026-06-20 10:55:56 +08:00
SnapOtterandGitHub b847dcc2ab test(features): align manifest assertions with deepsafe repo and best-effort extractedSize
Fix the Unit Tests CI job: bundleRepo now asserts deepsafe/feature-bundles (intentional, temporary); extractedSize relaxed to >= 0 (best-effort field, build script does not measure uncompressed size). sha256 + compressedSize remain strict. Full unit suite: 4546 passed.
2026-06-19 19:15:32 +08:00
SnapOtter de8bd79b04 test: update pre-existing stale specs for 2.0 multimodal + validation behavior
Fixes a backlog of integration/unit specs that asserted pre-2.0 behavior and
were failing CI (not caused by recent feature work):
- modality-aware empty-input error is 'No file(s) provided', not /no image/i
  (rotate, border, crop, resize, smart-crop, edge-cases, adversarial-extended,
  api, tool-factory-route)
- input validation rejects pre-enqueue with a clean 400 in 'error' (was a worker
  422 in 'details'): create-zip, extract-zip, merge-csvs
- resolveToolPool defaults unknown tools to the system pool (pool-routing)
- /upload and fetch-urls accept non-image content, validated per-tool at process
  time (api, fetch-urls)
- color-adjust legacy aliases were consolidated into adjust-colors: drop the
  removed-alias tests; retarget the format-preservation tests
- xml-to-csv gracefully converts a single non-repeating record to a 1-row CSV
- dropzone is multimodal; image-only filtering is opt-in via fileFilter
- factory-multi-input: register the synthetic test tools in the catalog so they
  route correctly (file modality for concat; image for the validation-prefix test)

Verified locally: unit 4546 passed, integration 8332 passed, typecheck + lint green.
2026-06-17 15:54:48 +08:00
SnapOtter 1f5b222267 test: fix docker test-image env and container-specific test guards
Make the full pnpm test:docker suite pass the env-dependent tests (~85 failures):
- Dockerfile.test: ENV LD_LIBRARY_PATH=/usr/local/lib so the built libheif 1.21 is not shadowed by the base image's older system libheif (heif-dec failed with an undefined-symbol error -> 'No HEIF decoder found' on 72 HEIF tests); add libjxl-tools (JXL) and ghostscript + the ImageMagick policy.xml EPS allow-edit.
- docker-compose.test.yml: SYNC_WAIT_MS=30000 so sync-wait image tools do not fall back to 202 under single-container contention (10 tests).
- install_feature.py: guard tarfile.extractall(filter='data') behind Python>=3.12 (bookworm ships 3.11); the manual entry guards already protect.
- feature-status.test.ts / docker-file-secrets.test.ts: skip the two cases that cannot hold inside the container (/.dockerenv always present; root bypasses chmod). Verified on host: all still pass.
2026-06-17 14:28:41 +08:00
SnapOtter c483897452 fix(api): drop app-logger import from media-input; update stale errors mock
The full unit suite surfaced two issues from earlier commits on this branch. (1) Importing the app logger into media-input.ts pulled logger.ts -- which builds its pino file transport at module load via join(env.LOG_DIR, ...) -- into the unit-test import graph, throwing at collection time wherever LOG_DIR is unset (integration tests set it; unit tests do not). A low-level modality handler should not depend on the app logger, and a corrupt upload is an expected user error, so drop the import and keep the clean validation message. (2) tool-factory-route.test.ts mocked errors.js without the new friendlyError export; add it.
2026-06-17 14:28:41 +08:00
SnapOtter 9c250d244f test: add regression coverage for the video QA fixes
friendlyError unit test (incl. the false-positive guard); gated integration tests for multi-file video batch and a multi-step video pipeline (regression for the modality-aware batch/pipeline fix). All pass locally; existing image batch (36) and pipeline (37) suites remain green, and the existing gif-to-video webm test now passes with the pix_fmt fix.
2026-06-17 14:28:41 +08:00
SnapOtterandGitHub 17726ae59d docs: multi-modality rebrand, 2.0 architecture accuracy, and full OpenAPI coverage (#254)
* docs: rebrand from image-only to multi-modality across docs and metadata

SnapOtter expanded from image-only to 157 tools across 5 modalities
(image, video, audio, document/PDF, data). Update all product-level
copy, metadata, and i18n that still framed it as an image-only tool.

- README, package.json, root llms.txt: multi-modality framing, 157 tools
- OpenAPI info + tags, generated /llms.txt tagline (docs.ts)
- VitePress docs site: hero, getting-started, architecture, security,
  deployment, configuration, developer, supported-formats
- i18n: 10 product keys across all 21 locales (hero, app description,
  privacy notes, AI features, progress messages, getting-started)
- web/demo/landing meta + privacy copy, COMMUNITY_GUIDE, .env.example

Stale tool counts (53/50+/52/70+/35) corrected to 157 throughout.
Database/container deployment claims left unchanged (out of scope).

* docs: fix stale post-rebrand test assertions and README language list

- tests/e2e-docs/homepage.spec.ts: assert the current docs homepage (file toolkit, 157 tools, 5 modalities) instead of the old image-only strings
- tests/unit/api/docs-route.test.ts: sync the reproduced llms.txt tagline with docs.ts
- README.md: 21 languages with the correct list (add Swedish and Chinese Traditional, drop Czech which is not supported)

* docs: correct 2.0 architecture references (Postgres 17 + Redis 8, 3-container stack)

The docs and metadata still described the 1.x stack (SQLite, single container, p-queue). Update them to the current 2.0 reality.

- README: replace the broken single-container `docker run` quick-start with the real Docker Compose stack (app + Postgres 17 + Redis 8); fix the "no Redis, no Postgres" feature bullet
- package.json: description no longer claims a single container
- apps/docs: rewrite database.md for Postgres; configuration.md DB_PATH -> DATABASE_URL + REDIS_URL; architecture.md SQLite/p-queue/better-sqlite3 -> Postgres/BullMQ/pg and add media-engine + doc-engine; developer/security/deployment/docker-tags/getting-started/contributing compose examples now include postgres + redis; index.md + api/ai.md AI count 16 -> 19
- SECURITY.md: Drizzle (SQLite) -> (PostgreSQL)
- landing: enterprise/FeatureHighlights single-container wording; TrustSignals/ToolGrid 150+ -> 157 (dynamic); Pricing/FAQ 15 -> 19 AI tools

* docs(api): document all video, audio, document, and data tool endpoints in OpenAPI

The spec covered only image tools; the Scalar UI and the generated /llms.txt and /llms-full.txt inherited that gap. Add the 104 missing tool endpoints so the API docs match the code.

- Video: 29 endpoints (most long/async; auto-subtitles is AI)
- Audio: 17 (transcribe-audio is AI)
- Document/PDF: 36 (ocr-pdf is AI; conversions are long/async)
- Data: 10
- Image: 12 newer tools (background-replace, blur-background AI; histogram/lqip-placeholder/sprite-sheet custom responses; barcode-generate uses a JSON body)

Each schema is derived from the tool's Zod validator and executionHint (fast -> 200, long -> 202+SSE, AI adds 501 FeatureNotInstalledError, multi-file inputs as arrays), referencing the existing shared schemas. Tool path entries: 64 -> 168. Spec parses as valid YAML with no duplicate paths and only known $refs.
2026-06-16 18:04:52 +08:00
SnapOtterandGitHub 8eee17aeea fix: handle non-image modalities across uploads, previews, and filenames (#255)
SnapOtter spans five modalities now, but several code paths still assumed image input.

- dropzone: default to accept-all when no fileFilter is given (image tools still pass one); neutral "supported file types" error text instead of "image files"
- automate (pipelines): accept any modality in the file pickers and dropzones; render modality-aware previews (video player, audio waveform, document/data card) instead of always using ImageViewer/BeforeAfterSlider
- filename sanitizer: extend the double-extension allowlist beyond image extensions to video/audio/document/data so e.g. "report.csv.php" becomes "report.csv"; add tests
- thumbnail route: return 422 for non-rasterisable files (audio, data, non-PDF docs) instead of attempting a doomed Sharp decode
- pool: unknown tools fall back to the "system" pool, not the image pool
- a11y labels: "Previous/Next image", "Image viewer/area/controls/drop zone" are now modality-neutral, across all 21 locales
- copy: bulk-rename default, find-duplicates ZIP name, SSRF user-agent, fetch-urls fallback name, file-details MIME label, URL-import placeholder, help dialog
2026-06-16 18:04:48 +08:00
SnapOtterandGitHub 622c9f98a5 fix(ci): resolve pre-existing failures making main red (#250)
After #249 merged, main's CI is red on failures that predate this work (they
were cache-masked on the old branch). This applies only the missing fixes onto
current main (reverts nothing from #246/#248; saml/user-files are already fixed
on main):

- remove 8 dead landing unit tests importing @landing/app and @landing/components
  React paths deleted in the Astro migration
- format tool-factory.ts and json-xml.ts to clear the api lint errors
- accept a 202 async fallback in the image-enhancement large-image test (it
  exceeds the sync window on slower CI runners)
- relax the pipeline no-file assertion to match 'No image file provided' or
  'No file provided'
2026-06-16 16:09:45 +08:00
SnapOtterandGitHub d8cf979d4b fix: resolve 18 QA-discovered bugs across tools, previews, and the AI pipeline (#242)
Exhaustive QA sweep of all 157 tools. Fixes: CSP blob media, csv-excel ExcelJS interop, ocr-pdf segfault, chart-maker upload, non-PDF doc preview, RAW decode, merge-tool multi-file path, html-to-image chromium, ogv/wma/amr/ac3 preview fallbacks, meme/gif/stabilize codecs, nav+home a11y. Plus orphan-format and test-debt cleanup, the AI bundle build script, and a reusable Playwright QA harness under tests/qa/.
2026-06-15 22:26:24 +08:00
SnapOtter 507fc1c356 Merge branch 'feat/tool-first-workflow'
Resolved conflict in worker.ts: kept remote refactored worker
(v2 process, scratch paths, extra outputs, metrics) and re-applied
the auto-save removal from the feature branch.
2026-06-15 14:55:58 +08:00
SnapOtterandGitHub 3fb8164fa5 feat: add OpenTelemetry distributed tracing (enterprise) (#232)
* feat(tracing): add OpenTelemetry dependencies and --import preload flag

* feat(enterprise): add distributed_tracing feature gate

* feat(tracing): add SDK bootstrap with enterprise gating

* fix(tracing): correct test coverage for enterprise-unavailable path and prevent double-init

Test 2 now mocks @snapotter/enterprise to throw an import error, exercising
the catch block in the preload. Test 3 imports with no endpoint so the preload
is a no-op, avoiding leaked SDK from double-initialization. Added idempotency
guard to initTracing() as a safety net.

* feat(tracing): add Pino trace mixin and shared logger

When OTel tracing is active, every Pino log line now includes traceId,
spanId, and traceFlags fields for log-to-trace correlation. The mixin
is a no-op when no SDK is registered (community users).

* feat(tracing): add _otel to ToolJobData and inject trace context at enqueue

Add optional _otel carrier field to ToolJobData for W3C trace context
propagation across BullMQ job boundaries. When an active OTel span exists,
propagation.inject() writes traceparent/tracestate into the job data before
queue.add(). When no SDK is registered (community edition), the carrier
stays empty and _otel remains undefined -- zero overhead.

* feat(tracing): extract trace context and create spans in BullMQ worker

* feat(tracing): inject trace context into Python sidecar calls

* feat(tracing): add trace context extraction to Python sidecar

* feat(tracing): add shutdownTracing to graceful shutdown sequence

* feat(tracing): enrich HTTP spans with tool_id and user_id attributes

* docs: add OpenTelemetry env var documentation to .env.example

* test(tracing): add lifecycle integration tests for trace propagation

* fix(tracing): inject trace context into pipeline and batch flow jobs

* fix(tracing): add sidecar.execute Node-side span and remove unnecessary comment

Wraps PythonDispatcher.run() with a sidecar.execute span on the Node
side so traces show the full round-trip (Node span -> Python span).
Also removes an obvious comment from logger.ts.
2026-06-15 12:53:06 +08:00
SnapOtter f387e98fff feat: tool-first workflow polish -- fix tests, lint cleanup
- Remove obsolete fullscreen redirect test (route deleted in Task 3)
- Update dropzone test strings to match new i18n values
- Fix categories count in i18n locale test (12 -> 24)
- Add chart-maker to no-dropzone exempt list in tool-registry test
- Auto-fix import ordering (Biome) in popular tools route and landing config
2026-06-14 19:44:58 +08:00
SnapOtter 112df13957 test: add enterprise feature test coverage
Tests for SCIM, GDPR lifecycle, legal hold, audit export/archival,
encryption, SIEM forwarding, MFA endpoints, IP allowlist, config
export/import, webhook management, and license validation.
2026-06-14 16:50:35 +08:00
SnapOtter f9ed5d125a Merge branch 'feat/enterprise-on-prem' 2026-06-14 16:10:15 +08:00
SnapOtter e7bef34918 style: apply Biome formatting to enterprise files 2026-06-14 14:53:44 +08:00
SnapOtter db6f7bf38a feat(enterprise): add IP allowlisting with CIDR matching and Redis cache
Enterprise-gated onRequest hook that restricts API access to
configured CIDR ranges using Node 22's native BlockList.

- Plugin (ip-allowlist.ts): builds a BlockList from the ipAllowlist
  setting, caches in-process, syncs across instances via Redis pub/sub.
  Exempt paths for health probes, SCIM, SAML/OIDC callbacks.
  Handles IPv4-mapped IPv6 (::ffff:x.x.x.x) transparently.
- Admin API (enterprise/ip-allowlist.ts): GET/PUT endpoints gated by
  security:manage permission and ip_allowlist feature flag.  Validates
  CIDRs, prevents self-lockout, emits IP_ALLOWLIST_UPDATED audit event.
- 32 unit tests covering CIDR matching, validation, exempt paths, IPv6,
  and edge cases (/0, /32, mapped addresses).
2026-06-13 22:54:06 +08:00
SnapOtter 1787be35fe feat(enterprise): add TOTP MFA with enrollment, verification, and recovery codes 2026-06-13 22:49:00 +08:00
SnapOtter 6920035f5a refactor: extract external auth resolver from OIDC for SAML reuse
Move user resolution logic (match by externalId, auto-link by email,
auto-create with user limit check) into a shared module that both
OIDC and SAML callbacks can use. Includes sanitizeUsername and
findUniqueUsername helpers. Preserves all existing OIDC behavior
and audit events.
2026-06-13 22:20:22 +08:00
SnapOtter b1e94bd988 fix: build script venv handling and lint fixes
- Use /opt/venv directly when --entrypoint bash bypasses entrypoint.sh
- Use sys.executable for all pip calls (not bare pip)
- Override entrypoint in CI workflow to avoid startup banner
- Fix Biome formatting (template literals, try/catch blocks)
2026-06-13 20:48:27 +08:00
SnapOtter de7bbcc18d test: update permission count tests for new enterprise permissions 2026-06-13 17:07:39 +08:00
SnapOtter c1dc27f248 feat(enterprise): add audit log archival with crash-safe state machine 2026-06-13 16:57:20 +08:00
SnapOtter ab88b9ad0d feat: add webhook delivery module with retry and backoff 2026-06-13 16:49:47 +08:00
SnapOtter 895e29e93f feat(enterprise): add tamper-resistant audit mode with HMAC integrity 2026-06-13 16:48:09 +08:00
SnapOtter a4fa3ce2a7 feat: rewrite install_feature.py for pre-built tar bundles 2026-06-13 16:37:00 +08:00
SnapOtter 2520cdd556 feat: add AES-256-GCM encryption at rest for sensitive settings 2026-06-13 16:35:28 +08:00
SnapOtter d1e9536cff feat: extend crash recovery for pre-built bundle artifacts 2026-06-13 16:29:50 +08:00
SnapOtter a4ac7cf7d6 feat: bump feature manifest to v2 with archive metadata 2026-06-13 16:27:24 +08:00
SnapOtter f6d5479334 test: add enterprise feature mock utilities 2026-06-13 16:22:01 +08:00
SnapOtter 86d6f50ea6 feat: add enterprise Phase 1-4 feature flags and new permissions 2026-06-13 16:20:24 +08:00
SnapOtter 492da820f8 fix(web): resolve feature install status sync and mutual exclusivity (#214) 2026-06-13 13:44:37 +08:00
SnapOtter 5397f9b21c fix(ai): broaden SSRF pre-scan regex to cover srcset, poster, formaction, @import 2026-06-13 10:36:12 +08:00
SnapOtter fc718c1684 fix(jobs): strip internal paths from all worker SSE error frames
Closes #71. Several error paths in the worker could leak internal
filesystem paths (/tmp/workspace, /data/ai/venv, /app) through SSE
frames, resultPayload objects, and Redis batch-error lists. The
existing stripInternalPaths call at worker.ts line 345 only covered
the single-file processToolJob catch block.

Wrapped 6 additional call sites:
- processPipelineStep: prevError from DB and catch errorMsg
- processPipelineFinalize: composed errorMsg reaching SSE,
  recordChildOutcome, and resultPayload
- processBatchChild: catch error reaching recordChildOutcome
  and resultPayload
- processBatchFinalize: manifest errorMsg from DB rows
- recordChildOutcome (batch-progress.ts): defense-in-depth
  strip before Redis rpush

Added 10 unit tests for stripInternalPaths covering /tmp, /data,
/app, /opt, /home, /workspace, multi-path messages, safe passthrough,
and pipeline-step wrapping.
2026-06-13 10:36:01 +08:00
SnapOtter 39543d6512 fix(docker): make venv bootstrap upgrade-aware with pip-freeze stamp
On 1.x-to-2.0 upgrade the /data/ai/venv already existed with stale
packages, so the entrypoint skipped the cp from /opt/venv and 2.0
Python tools failed with ImportError (#85).

Dockerfile now writes a SHA-256 of `pip freeze` to /opt/venv/.venv-version
at build time. The entrypoint compares that stamp against the copy on the
volume; on mismatch it does a clean nuke + re-copy and resets
installed.json so the UI correctly shows bundles as needing reinstall.
Models in /data/ai/models survive the refresh, so bundle reinstall only
reruns pip (model downloads are idempotent).

Backward compatible: if the image has no stamp (old build), the
entrypoint falls through to the existing skip-if-exists behavior.
2026-06-13 10:31:22 +08:00
SnapOtter 51666cdd5f feat(tools): 2.0 phase 5 wave 5b - ai pool: ocr-pdf, transcription, background composites (5 tools) (#226) 2026-06-13 10:19:47 +08:00
SnapOtter 6e1b9865f1 feat(tools): 2.0 phase 5 wave 5a - image gap-fill (11 tools) (#225) 2026-06-13 10:19:16 +08:00
SnapOtter fc7c1f850e feat(tools): 2.0 phase 5 wave 4 - office, ebooks, data, archives (14 tools) (#224) 2026-06-13 10:19:11 +08:00
SnapOtter 638288e196 feat(tools): 2.0 phase 5 wave 3b - audio depth (14 tools) (#222) 2026-06-13 10:19:06 +08:00
SnapOtter 5f98b48593 feat(tools): 2.0 phase 5 wave 3a - video depth (22 tools) (#221) 2026-06-13 10:19:00 +08:00
SnapOtter 2f39e38162 feat(tools): 2.0 phase 5 wave 2 - pdf depth (21 tools) (#220) 2026-06-13 10:18:55 +08:00
SnapOtter ae1337901d feat(tools)!: SnapOtter 2.0 phase 4 wave 1: 45 core tools across all modalities (#219) 2026-06-13 10:18:49 +08:00
SnapOtter d647d8ed19 feat(modality)!: SnapOtter 2.0 phase 3 modality framework: media/doc engines, pool routing, display modes (#218) 2026-06-13 10:18:39 +08:00
SnapOtter c451b939c7 feat(jobs)!: SnapOtter 2.0 phase 2 job spine: async queues, worker pools, object storage, admin dashboard (#217) 2026-06-13 10:17:13 +08:00
SnapOtterandGitHub 1c724d5d21 feat(db)!: SnapOtter 2.0 phase 1 foundation: postgres, migrator, compose stack (#216)
* feat(infra): add dev compose stack with postgres and redis

* fix(infra): comment dev env defaults until wired; harden dev compose restart and start_period

* chore(deps): add pg driver and testcontainers for postgres migration

* feat(db): translate schema to drizzle pg-core (timestamptz, boolean, pgEnum, jsonb)

Schema translation (apps/api/src/db/schema.ts):
- sqlite-core -> pg-core, all 10 tables preserved 1:1
- integer(mode:'timestamp') -> timestamp({ withTimezone: true })
- integer(mode:'boolean') -> boolean
- jobs.status text enum -> pgEnum('job_status') with same 4 values
- 7 columns changed from text to jsonb: jobs.inputFiles, jobs.settings,
  pipelines.steps, apiKeys.permissions, roles.permissions,
  auditLog.details, userFiles.toolChain
- settings.value stays text, jobs.error stays text, jobs.progress stays real

jsonb call-site sweep (removed JSON.stringify on writes, JSON.parse on reads):
- apps/api/src/routes/roles.ts: permissions read/write (3 sites)
- apps/api/src/routes/api-keys.ts: permissions write + read (2 sites)
- apps/api/src/routes/audit-log.ts: details read (1 site)
- apps/api/src/routes/pipeline.ts: steps write + read (2 sites)
- apps/api/src/routes/progress.ts: inputFiles write (2 sites)
- apps/api/src/routes/tool-factory.ts: toolChain read + write (2 sites)
- apps/api/src/routes/user-files.ts: toolChain read + write (4 sites)
- apps/api/src/permissions.ts: roles.permissions read (1 site)
- apps/api/src/lib/audit.ts: details write (1 site)
- apps/api/src/plugins/auth.ts: apiKeys.permissions read (1 site)

* refactor(db): type jsonb columns via $type and note raw CTE conversion requirements

* feat(db): archive sqlite migrations and generate postgres baseline

* chore(db): dockerignore legacy migrations, add archive breadcrumb, fix trailing newline

* feat(db): pg pool connection, advisory-locked boot migrations, DATABASE_URL config

* fix(db): friendly fatal on unreachable postgres, idempotent closeDb, lock-key convention note

* refactor(db): async drizzle calls in plugins, lib, permissions

* fix(api): analytics never throws, typed permission guard, single-query session invalidation

* refactor(db): async drizzle calls across all routes and bootstrap

Convert every route file and index.ts from sync SQLite drizzle
patterns to async node-postgres drizzle:

- .all() removed (bare await on select)
- .get() converted to destructured [row] = await ...
- .run() removed (bare await on insert/update/delete)
- .changes replaced with .rowCount (null-guarded) in progress.ts
- sqlite import removed from user-files.ts; raw CTEs converted to
  await db.execute(sql`...`) with postgres-dialect recursive CTEs
- ChainRow types updated: tool_chain is parsed jsonb (string[] | null),
  created_at is Date (timestamptz) with no * 1000 conversion
- All requirePermission() guard calls awaited (security: unawaited
  async guard returns truthy Promise, bypassing permission check)
- All hasEffectivePermission() and getPermissions() calls awaited
- All auditLog() calls awaited (preserves write-before-response order)
- trackEvent() and captureException() left un-awaited (fire-and-forget
  by design, guaranteed never-throw)
- ensureAnonymousUser(), startCleanupCron(), recoverStaleJobs() awaited
  in bootstrap sequence
- ensureInstanceId() and ensureDefaultSettings() made async

Files converted: 14 (index.ts + 12 route files + tools/index.ts)

* fix(db): await async checkStorageQuota in user-files upload/save routes

* fix(db): await checkStorageQuota in save-result route (missed second call site)

* feat(db): sqlite-to-postgres migrator with CLI and first-boot import

* fix(db): migrator error context, honest force semantics, boot-hook fatal, null-variance tests

* test: run suite against per-file postgres databases via testcontainers

- Add tests/global-setup.ts: spins up a Postgres testcontainer,
  creates a migrated template database once per vitest run.
- Rewrite tests/setup/per-fork-env.ts: each test file (forks pool)
  clones the template into its own database via CREATE DATABASE ...
  TEMPLATE, preserving the same per-file isolation granularity.
- Update vitest.config.ts: add globalSetup, pg alias, update comment.
- Fix tests/integration/test-server.ts: remove DB_PATH mkdir, async
  runMigrations, async db operations, remove SQLite WAL checkpoint.
- Fix 21 unit test db/index mocks: add pool and closeDb exports.
- Fix 8 unit test files: add async/await for now-async permission,
  audit, and analytics functions.
- Fix 18 integration test files: convert sync .run()/.all()/.get()
  to async drizzle patterns, add async to callbacks.
- Production change: apps/api/src/routes/teams.ts: cast COUNT(*)
  to ::int so Postgres returns a number instead of bigint string.

* fix(db): seed built-in roles, reject NUL bytes, cast COUNT, serialize job persists

- Seed built-in roles (admin, editor, user) at boot via ensureBuiltinRoles()
  with onConflictDoNothing, restoring data that legacy SQLite migration 0007
  provided via INSERT statements (the pg baseline is DDL-only).
- Reject NUL bytes in login credentials with 401 (postgres rejects \x00 in
  text columns; valid usernames never contain NUL, matching 1.x behavior).
- Cast COUNT(*)::int in user-files, audit-log, and roles listing queries so
  postgres returns a JS number instead of bigint-as-string.
- Serialize fire-and-forget job progress DB writes per jobId so the final
  "completed" status is never overwritten by a late-arriving "processing"
  write (race condition exposed by async postgres round-trips).

* test: fix teams race, seed roles in test server, poll for job status

- Add missing await to resetTeams() in teams PUT beforeEach (the async
  delete raced with the subsequent insert under postgres).
- Call ensureBuiltinRoles() in test server bootstrap so integration tests
  have the same built-in roles as production.
- Replace fixed 100ms flushPersist delay with a polling helper that waits
  for terminal job status, eliminating timing-dependent failures caused by
  postgres network round-trip latency.

* test: make heic temp-file cleanup assertion resilient to concurrent workers

Use a set-based diff instead of raw file count when checking that
decodeHeic cleans up temp files. Other concurrent test workers can
create heic-in-*/heic-out-* files in the shared tmpdir, inflating the
"after" count and causing spurious failures under full-suite load.

* fix(db): align builtin-role seed to post-0010 legacy state; test polish

* feat(docker): three-container compose (app, postgres, redis) with boot wait and migrations

* fix(docker): set TEST_DATABASE_URL so containerized tests skip testcontainers

* chore(docker): test compose project name, clearer 1.x upgrade comment, unref probe timer

* feat(enterprise): enforce D15 license boundary; move s3 storage into packages/enterprise

* fix(enterprise): restore lazy aws-sdk loading; community installs load no s3 code at boot

* fix(enterprise): boundary check catches dynamic imports; document getS3 concurrency

* feat(db)!: SnapOtter 2.0 phase 1 foundation: postgres, migrator, compose stack

BREAKING CHANGE: SQLite is no longer the runtime database. Deployments now
require Postgres (and Redis, used from phase 2). Existing installs migrate
with SQLITE_MIGRATE_PATH or 'pnpm --filter @snapotter/api migrate:sqlite'.

* fix(ci): postgres service + fresh e2e database per run; ignore unfixable torch CVE-2025-3000
2026-06-13 10:15:23 +08:00
SnapOtterandGitHub 4ec39c556f test: testing overhaul -- CI e2e gates, parallel suites, generated matrices, mutation testing (#215)
Closes the "e2e never runs in CI" hole. Adds per-PR e2e smoke gate,
nightly full-suite workflows, parallel vitest forks (per-fork DBs),
Playwright parallel/serial/visual projects against production builds,
metadata-generated test suites (drift guards, hostile inputs, format
matrix, pairwise settings, property-based fuzz), Stryker mutation
testing, Schemathesis API fuzz, coverage ratchet, and fixes for three
session-poisoning bugs that caused 200+ serial-bucket failures.

Bug fix included: favicon/split/bulk-rename could hang clients forever
when ZIP streaming failed after reply.hijack().
2026-06-10 22:01:13 +08:00
SnapOtter 5b3b3c7632 fix(test): mock db/index.js in unit tests that import API modules
Five unit tests crashed because they imported modules that transitively
reached db/index.ts, which eagerly calls `new Database()` at module load
time. Added vi.mock for db/index.js matching the pattern used by 16
other passing API unit tests.

Moved cleanup.test.ts to tests/integration/ since it uses a real SQLite
database, runs migrations, and inserts rows.
2026-06-09 23:34:36 +08:00