Skip to content

Update project dependencies and E2E coverage#82

Merged
nothingnesses merged 100 commits into
mainfrom
update-project-dependencies
Jun 4, 2026
Merged

Update project dependencies and E2E coverage#82
nothingnesses merged 100 commits into
mainfrom
update-project-dependencies

Conversation

@nothingnesses
Copy link
Copy Markdown
Owner

Summary

  • update project dependency and RustFS migration follow-up work
  • add headless Playwright E2E runner and authenticated workflow coverage
  • wire the E2E suite into CI with failure artifact upload

Verification

  • just e2e
  • just verify
  • nix develop ./devenv/ --command env SKIP_DIRENV=1 just e2e

Remove the VS Code workspace file from repository tracking while leaving local workspace files ignored.
Simplify the just test recipe to run cargo test directly.

Remove the obsolete test-output cache ignore entry and update contributor docs.
The _FOR_USER variants supersede it; the constant had no remaining references.
The column in object_storage_deletions (V8) and email_outbox (V12) was written
in the claim and cleared in mark-failed but never read; reclaim is gated solely
on next_attempt_at. Remove it from both migrations (edited in place, pre-merge)
and from the two CLAIM and two MARK_FAILED queries.
The presign-batch cap was defined twice (backend usize, frontend i64) and must
agree. Move it to the shared crate (alongside ALLOWED_MIME_TYPES) as the single
source of truth; backend imports it, frontend casts to i64 at the use site.
Add worker::MaintenanceTask (run_once as RPITIT returning impl Future + Send, so
the tokio::spawn driver can require Send) and worker::spawn, which owns the tick
loop and missed-tick-delay behaviour. ObjectLifecycleWorker and EmailWorker now
implement the trait instead of each carrying duplicate spawn/run_forever loops;
worker_interval drops unsigned_abs for a plain cast. main.rs calls worker::spawn.
…nce interval

The three outboxes share lease/retry validation but have genuinely different
defaults (batch_size 1000 for storage deletion to match S3's multi-delete cap,
100 for email), so a shared serde struct cannot express them. Add an
OutboxRetryConfig runtime view (not deserialized) built by per-owner accessors
(storage_deletion/upload_session_cleanup/retry), plus an ensure_positive! macro;
the configs keep their flat fields and validate through the view, collapsing the
duplicated > 0 checks. Rename storage_deletion_worker_interval_seconds to
maintenance_interval_seconds (it drives the whole maintenance pass), including
the env key in .env.example, docs, and README.
Config::from_env becomes Config::load: when MEMORY_MAP_CONFIG is set it reads
that TOML file (required) layered under the environment source, so env still
wins and secrets can stay in a gitignored config.toml; when unset, loading is
pure-environment and byte-for-byte identical to before. Add config.example.toml
(flat outbox keys, blank secrets), an opt-in 'just config' recipe, .gitignore
entries, the layered model in docs/deployment.md, and a test asserting the
example deserializes into Config so it cannot drift from the structs.
Extract OutboxQueue/OutboxProcessor traits, DrainOutcome, and a generic
drain_outbox loop into outbox.rs, replacing the two near-identical
drain_storage_deletion_outbox/drain_email_outbox functions. Storage and email
each get a newtype queue wrapper and a processor (storage deletes a whole claim
in one S3 request and is all-or-nothing; email sends per message and isolates
failures), reusing the existing SQL consts unchanged. EmailSender keeps its test
seam but moves to RPITIT + Send so the generic processor future stays Send;
StorageDeletionSink is dropped (the storage path is integration-covered). The
per-queue drain unit tests collapse into one generic pair plus an email
processor test.
…Config view

claim_expired_upload_sessions and mark_upload_session_cleanup_failed now read
their lease/retry knobs from config.upload_session_cleanup() instead of the flat
fields, matching how the storage and email outboxes read theirs. The reconcile
stays bespoke (not drain_outbox): it reconciles a primary record and fans out
into the deletion queue, and its claim joins objects and filters storage_state.
A comment records that rationale.
The 9-column projection that S3Object::try_from decodes by name was copy-pasted
into five transitional-state mutation queries (INSERT_OBJECT, UPDATE_OBJECT, and
the three delete-pending marks). Extract an object_returning_columns! macro and
compose each const via concat!, so a column or alias change touches one place
and cannot silently drift from the struct's by-name reads.
…accessor

Add ContextWrapper::object_lifecycle_service, which borrows the request's storage
client and lifecycle config over a caller-provided db client. The six
object-lifecycle mutations now build the service through it, dropping the
repeated storage/state lookups and config clone from each resolver.
Share the part-count and part-length arithmetic behind pure part_count/part_length
helpers (the two callers keep their own intentionally different part-count caps);
inline the s3_object_from_row pass-through into S3Object::try_from; and log the
maintenance success counts at info rather than warn (the error branches stay
warn).
Both storage methods now have no production callers (the presigned multipart
upload path replaced direct upload, and metadata is read via head_object). Add a
doc comment on each recording that they are exercised only by the integration
tests and kept as public storage primitives / test seams; gating them behind
cfg(test) would break the integration tests, which compile against the crate
built without cfg(test).
Force Content-Disposition: attachment on presigned GET URLs for
script-capable content types (currently image/svg+xml) so a direct
top-level navigation downloads the file instead of executing embedded
script in the storage origin. In-app img/video embedding ignores the
header, so display is unaffected; the guard is per content type to keep
safe media viewable on navigation. Thread an optional content disposition
through presigned_get_url and cover the round-trip in the storage
integration test.
- Split object_lifecycle.rs into config/service/worker/deletion submodules
  using the project's parent-file convention, preserving the public API via
  re-exports.
- Single-source the upload-session column projection with a macro and trim
  the four queries to the columns actually decoded.
- Redact object name, timestamp, and coordinates from the update-metadata
  debug log, keeping the id and a has_location flag.
- Surface a parked-backlog warning each maintenance pass for cleanups and
  deletions that exhausted their retry budget, backed by two count queries.
- Document the password-reset outbox plaintext-token trade-off, and add the
  incomplete-multipart lifecycle and storage-origin-isolation notes to the
  deployment guide.
The workflow granted id-token: write for the FlakeHub cache, which was then
disabled in favor of the GitHub Actions cache (authenticated by the runtime
token, not OIDC). Remove the unused permission and keep contents: read.
Rename the previous service-skipping verify recipe to verify-fast and make
verify run it plus the service-backed storage, backend-integration, and e2e
suites serially. CI keeps invoking the recipes as parallel jobs. Add
verify-fast to the filtered allowlist and refresh the contributor docs,
including the RustFS rename and the project-structure listing.
Add a counterpart to the config.example.toml deserialization test that feeds
the .env.example MEMORY_MAP__ pairs through the same environment source
Config::load uses, so a renamed or removed key fails CI instead of drifting
silently.
@nothingnesses nothingnesses merged commit 787ffa9 into main Jun 4, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unit tests for public functions

1 participant