date_from / date_to on /photos, /photos/exif and unified search ran on the legacy mixed-convention date_taken, so a photo could display inside a range yet be filtered out of it by up to its UTC offset (or by years, for rows the filename class re-dated). The filter lives in the shared ExifDao::query_by_exif, so it's fixed once there: - query_by_exif takes Option<date_view::InstantRange> (bounds + display and server zones) instead of raw date_from / date_to. - SQL pre-filter: converted rows on date_utc exactly; unconverted rows (drain pending, permanent floor) on date_taken widened by LEGACY_INSTANT_BAND_SECS (±15 h, now shared with /memories). Converted rows are deliberately not bounded by date_taken: a re-dated row's legacy value can sit years from its instant. - Exact pass in Rust via InstantRange::contains → row_true_instant, so every returned row's true instant is inside the range. Calendar days now mean the owner's days: nl_query::iso_to_unix builds day bounds in the display zone (it used UTC midnights, which only lined up with the legacy wall-as-UTC column), and unified search's "today" is the owner's today rather than UTC's. The midnight-with-DST-gap fallback moves from memories_anchor into date_view::day_start_instant. Compatibility: clients already sending real instants (Apollo's /photos/exif windows) get correct edges with no change. Clients sending UTC-midnight day bounds see ranges shift by up to the UTC offset until they send display-zone bounds. No index on date_utc; the OR clause scans, which is milliseconds at personal-library scale (same as /memories today). dev/e2e check gains /photos/exif wall_clock + true_instant assertions and range-edge checks ([t, t] in, [.., t-1] and [t+1, ..] out). Not run here: the harness needs the WSL + Windows toolchain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.6 KiB
Markdown
83 lines
3.6 KiB
Markdown
# e2e dev server
|
|
|
|
A disposable ImageApi instance with a known, synthetic media library, for
|
|
end-to-end and agent-driven UI testing of the SynologyFileViewer app.
|
|
|
|
```bash
|
|
dev/e2e/e2e.sh up # build + fixtures + reset + start; blocks until ingest is done
|
|
dev/e2e/e2e.sh reset # back to the pristine baseline (after tests mutate things)
|
|
dev/e2e/e2e.sh stop
|
|
dev/e2e/e2e.sh check # every fixture's wall_clock + true_instant vs the manifest, and /photos/exif range edges
|
|
```
|
|
|
|
Log in from the app with **server** `http://10.0.2.2:8090` (Android
|
|
emulator) or `http://127.0.0.1:8090` (Expo web / `adb reverse tcp:8090 tcp:8090`),
|
|
**user** `e2e`, **password** `E2eTest#2026pass`.
|
|
|
|
## Isolation
|
|
|
|
It runs next to a real local server without touching it:
|
|
|
|
| | real server | e2e server |
|
|
|---|---|---|
|
|
| exe | `target/debug/image-api.exe` | `dev/e2e/.work/bin/image-api-e2e.exe` (built in `target/e2e`) |
|
|
| port | `BIND_URL` + `localhost:8088` | `127.0.0.1:8090` only (`LOCAL_BIND_URL=` disables the 8088 bind) |
|
|
| data | your `.env` | `dev/e2e/.work/state/` (db, thumbs, hls, gifs, previews, media) |
|
|
| AI | configured | off — Ollama points at a closed port |
|
|
|
|
`stop` kills by image name `image-api-e2e.exe`, so it can't hit the real
|
|
server. Everything under `.work/` is gitignored and safe to delete.
|
|
|
|
## Fixtures
|
|
|
|
`gen_fixtures.py` renders every image with its relative path, date and
|
|
scenario burned into the pixels — a screenshot tells you exactly which
|
|
fixture is on screen. `.work/state/manifest.json` lists every item with its
|
|
expected date, GPS, camera, tags, duplicate relationship and **expected
|
|
`wall_clock`** (the capture time the UI must show), plus expected counts;
|
|
assert against it rather than hardcoding. Naive fixture times are walls in
|
|
America/New_York (the e2e display and server zone); videos are stamped the
|
|
way phones write containers (that wall, as a UTC `creation_time`).
|
|
|
|
| Folder | Covers |
|
|
|---|---|
|
|
| `2019/2019-07-04 Beach Day`, `2021/Paris Trip`, `2022/Hiking` | EXIF dates, GPS (map), cameras, tags, mixed orientation |
|
|
| `2022/Hiking/summit.mp4`, `Videos/` | landscape + portrait video, HLS, container dates |
|
|
| `2023/Birthday` | dated, no GPS |
|
|
| `Grid Stress` (60) | scrolling / pagination |
|
|
| `Screenshots` | date from filename only (`filename` source), PNG |
|
|
| `Undated` | no date anywhere → pinned mtime (`fs_time` source) |
|
|
| `On This Day` | memories: today's month/day in 2018/2020/2024 |
|
|
| `private` | `EXCLUDED_DIRS` — never indexed |
|
|
| `Duplicates` | byte-identical copy of `2021/Paris Trip/IMG_1000.jpg` |
|
|
| `Empty Album` | folder with no media |
|
|
|
|
"On This Day" is relative to the generation date — run
|
|
`e2e.sh fixtures --force && e2e.sh reset` on a later day to keep memories
|
|
populated. Tags from the manifest are seeded through the API on first start.
|
|
|
|
To test against real photos, `e2e.sh fixtures --import /path/to/folder`
|
|
copies a folder into `media/imported/` (the source is only read).
|
|
|
|
## Extra configuration
|
|
|
|
Put server env overrides in `dev/e2e/env.local` (gitignored); it's appended
|
|
to the generated `.env` on every `reset`. E.g. to exercise AI insights
|
|
against the local llama-swap:
|
|
|
|
```
|
|
LLM_BACKEND=llamacpp
|
|
LLAMA_SWAP_URL=http://localhost:9292/v1
|
|
```
|
|
|
|
Env for the script itself: `E2E_PORT`, `E2E_BIND` (`0.0.0.0` for a physical
|
|
device on Wi-Fi), `E2E_USER`, `E2E_PASS`.
|
|
|
|
## Notes for agents
|
|
|
|
- WSL2 (NAT networking) can't reach Windows' `127.0.0.1`; use `curl.exe`,
|
|
or `e2e.sh api GET 'photos?path=&recursive=true'` / `e2e.sh token`.
|
|
- Sort values are lowercase: `sort=datetakendesc`.
|
|
- Rebuild after server changes with `e2e.sh build` (restarts if running).
|
|
- Server log: `.work/server.log`.
|