Commit Graph

132 Commits

Author SHA1 Message Date
Georges-Antoine Assi
f077484b55 fix tests 2026-06-08 11:22:14 -04:00
Georges-Antoine Assi
cd101758fd cleanup for simplicity 2026-06-08 11:13:15 -04:00
Georges-Antoine Assi
036cf108f4 sinmplify siblings 2026-06-08 10:25:27 -04:00
nendo
cfdeab589a Derive sibling_ids from expanded siblings 2026-06-08 11:26:15 +09:00
nendo
37f0feab8c Add opt-in files/siblings expansion to GET /api/roms 2026-06-07 16:12:21 +09:00
copilot-swe-agent[bot]
a2775ca2b8 fix: handle malformed authorization header in hybrid auth backend
Co-authored-by: zurdi15 <34356590+zurdi15@users.noreply.github.com>
2026-06-06 22:22:41 +00:00
nendo
287c487308 feat(saves): expose per-device sync attribution and origin device
saves responses now include one device_syncs entry per device that has
synced a save, not just the caller's, so clients can tell which devices
hold a save. is_current is computed per entry and the caller's own entry
is ordered first for backward compatibility.

add a saves.origin_device_id column (migration 0081) recording the
device that created a save, set on initial upload only, surfaced as
origin_device_id on the save schema.
2026-06-05 20:25:01 +09:00
Georges-Antoine Assi
5cf67cd87c Merge pull request #3473 from Spinnich/fix/rom-content-404-on-stale-file-ids
fix(roms): return 404 when content file_ids match no files
2026-06-03 15:52:28 -04:00
Georges-Antoine Assi
1a1effd315 Merge pull request #3472 from Spinnich/test/update-rom-region-tag-reparse
test(roms): cover region-tag re-parse on rename (#3471)
2026-06-03 15:15:36 -04:00
Georges-Antoine Assi
357e5e0d51 Merge branch 'master' into fix/rom-content-404-on-stale-file-ids 2026-06-03 14:57:24 -04:00
Spinnich
00b894733d fix(roms): return 404 when content file_ids match no files
Renaming a ROM gives its file a new internal id, but the EmulatorJS
player keeps a remembered file id ("disc") in localStorage and reuses
it on the next launch. After a rename that id is stale, so the content
download endpoint matched zero files and fell through to its multi-file
ZIP path, producing a download whose only entry was an empty .m3u
playlist. nginx's mod_zip decode step rejects the blank value (HTTP
400) and aborts the response, sending 0 bytes — which EmulatorJS
surfaces as a generic "network error" (issue #3470).

The frontend half (validating the remembered disc against the ROM's
current files) already landed on master in d1696cd04. This is the
backend half: when no files match the request, raise a clean 404
instead of building a broken empty-.m3u ZIP. This also covers a ROM
with zero files.

Add endpoint tests (auth, single-file, valid file id, stale file id
-> 404, missing rom -> 404) plus a `rom_file` fixture.

Written primarily by Claude Code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:14:25 +00:00
Spinnich
c57c7b986b test(roms): cover region-tag re-parse on rename (#3471)
The update_rom path already re-parses filename tags when fs_name
changes (master commit d7a896b5da), but the headline scenario from
issue #3471 — an untagged ROM renamed to add (Europe) so the region
flag appears — was never asserted; existing coverage only exercised
the tag-removal direction.

Add a test that renames the untagged rom fixture to "test_rom
(Europe).zip" and asserts regions == ["Europe"], locking down the
add-region direction described in the issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:45:38 +00:00
Claude
3da3765ea3 test(feeds): give pkgj feed tests RomFile rows so they exercise output
The five pkgj feed tests created a ROM but no RomFile, so the per-file
feeds emitted only a header and the "in response.text" assertions never
actually verified output (pre-existing failures, also red on master).

Add a top-level `.pkg` GAME file (games feeds) or a DLC-category file
(dlc feeds), mirroring the pkgi_ps3 test, so the feeds produce rows.
This also gives real coverage of the new `include_files=True` path that
these feeds rely on.

https://claude.ai/code/session_01PSXKmejPRzdxLFMN6P2QQ4
2026-06-02 18:40:45 +00:00
Claude
824ce185fe test(roms): add fixture-driven multi-file ROM download test
Add a shared `multi_file_rom` fixture (a game folder with multiple
RomFile rows) and an endpoint-level test that downloads it via
`GET /api/roms/{id}/content/{file_name}`. This exercises the multi-file
download path end-to-end, which builds each mod_zip manifest entry from
`file.rom.full_path` after the handler session has closed — the exact
path that 500'd with `DetachedInstanceError` before the backref fix.

The download endpoint had no test coverage for multi-file ROMs (the
`rom` fixture has no RomFile rows), which is why the regression slipped
through. Reuse the new fixture in the handler-level regression test too.

https://claude.ai/code/session_01PSXKmejPRzdxLFMN6P2QQ4
2026-06-02 09:13:34 +00:00
nendo
f1d44c3321 SaveSync: pair negotiate on (rom_id, slot), not tagged file_name
The server datetime-tags every slot upload's filename (archival spec), so a
slot accrues many rows and the stored file_name never equals the client's
untagged canonical name. Keying negotiate's server-save map on file_name meant
every client save missed -> perpetual "upload", and every tagged server row
went unmatched -> perpetual "download", with save rows growing unbounded.

Pair on (rom_id, slot), collapsing each slot to its newest row, so
compare_save_state actually runs and content hashes decide the action.

Tests: real upload->negotiate round-trip (lets _apply_datetime_tag run, client
reports the untagged name) and a 3-device convergence test; both fail against
the old file_name keying.
2026-05-30 07:39:51 +09:00
nendo
edb5d15420 Fix save-sync hash drift, archival save leak, and dedupe scoping
Cleanup pass on save-sync addressing three independent failure modes
that interact in production data: content_hash drift between client
and server, null-slot archival saves leaking into sync flows, and
content-hash dedupe collapsing legitimately-distinct slots.

Bug fixes
- compute_content_hash dispatched on zipfile.is_zipfile(relative_path),
  which silently returned False whenever the process's CWD wasn't
  ASSETS_BASE_PATH. Every zip save fell through to the raw-MD5 branch,
  persisting hashes that disagreed with clients computing the intended
  per-entry zip-hash. Resolve to a full path before the dispatch.
- _build_negotiate_plan, sync_push_pull_task, and sync_watcher all
  treated null-slot saves as sync-eligible. Null-slot saves represent
  web-UI / archival uploads; including them in negotiate plans matched
  them against device pushes by filename and overwrote archival data.
  Filter null-slot saves at all three call sites.
- get_save_by_content_hash matched on (rom_id, user_id, content_hash)
  only, so identical bytes uploaded to different slots collapsed into
  one record. Scope the lookup by slot when provided so clone-save-
  to-new-slot creates a distinct row per slot.
- get_save_by_filename matched on (rom_id, user_id, file_name) only.
  When two uploads to different slots happened in the same wall-clock
  second (the datetime tag is per-second), the second upload UPDATED
  the first record's slot instead of creating a distinct row. Scope
  the filename lookup by slot too.

One-shot recovery
- New recompute_save_content_hashes manual task walks every Save row,
  recomputes via the fixed dispatch, and updates rows whose values
  differ. Idempotent; safe to re-run.
- Backend startup runs a COUNT(content_hash IS NULL) query and, if
  any rows exist, enqueues the recompute task on the low-priority
  RQ queue. The API process moves on; the worker handles the
  recompute out-of-band. Subsequent restarts find zero NULL hashes
  and skip. Admins can also trigger the task manually.

Test infrastructure
- Added tests/_zipfile_shim.reload_zipfile() mirroring the pattern
  from utils/zip_cache.py for the same zipfile-inflate64 + CPython
  3.13.5 incompatibility. Test fixtures that build ZIPs call it
  immediately before opening the archive.
2026-05-29 17:00:01 +09:00
Georges-Antoine Assi
90945685e4 Stuff 2026-05-17 12:43:33 -04:00
Georges-Antoine Assi
783d9a257e test(backend): cover artwork upload validation for roms and collections
Adds rejection + acceptance tests for update_rom, add_collection, and
update_collection artwork uploads, mirroring the existing avatar tests:
non-image content returns 400, and a real PNG uploaded under a misleading
filename like payload.html is stored with the trusted .png extension.

Also fixes two `return HTTPException(...)` → `raise` in raw.py so the 404
path actually surfaces instead of silently returning the exception object.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:37:44 -04:00
Georges-Antoine Assi
53f14f5710 fix(backend): validate uploaded images with libmagic before storing
Avatar, ROM artwork, and collection artwork uploads now sniff the file
header with libmagic and reject anything that isn't PNG/JPEG/WebP/GIF,
saving the file with an extension derived from the detected MIME rather
than the user-supplied filename. Pairs with the raw asset endpoint,
which decides inline vs attachment from the on-disk extension.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 09:18:02 -04:00
copilot-swe-agent[bot]
ee1faf363c fix: update test mocks to use job.id attribute instead of get_id()
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/f053983a-ae39-44b3-a59d-083d65f2055d

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-29 19:57:57 +00:00
Georges-Antoine Assi
c2d6b08a6b Merge pull request #3293 from rommapp/copilot/fix-update-tags-metadata
feat: open refresh metadata dialog with scan type and metadata source selection
2026-04-20 21:57:29 -04:00
Georges-Antoine Assi
ac45d1a32f changes from bot review 2026-04-20 21:51:30 -04:00
Georges-Antoine Assi
98f1d65a7c fixes 2026-04-20 21:36:59 -04:00
zurdi
d7a896b5da fix: re-parse tags from filename when renaming a rom
Renaming a rom via PUT /roms/{id} only updated fs_name and its
derivatives, leaving regions/languages/tags/revision/version stale
against the new filename. Re-parse them whenever fs_name changes so
edits like "patapon (Fr En)" -> "Patapon (Fr, En)" are reflected in
the database.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:23:33 +00:00
Spinnich
14342b5726 fix(tests): use client.request(\"DELETE\") to send JSON body in tests
Starlette's TestClient (httpx-based) does not expose body kwargs on the
delete() convenience method; client.request(\"DELETE\", ..., json=...) is
the correct approach. Also switch datetime.utcnow() to
datetime.now(timezone.utc) to silence Python 3.13 deprecation warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:02:26 -04:00
Spinnich
9aab88ad8b fix(tests): fix remaining DELETE body and timestamp comparison issues
- Switch content= to data= for DELETE requests (Starlette TestClient is
  requests-based and does not accept content= keyword argument)
- Fix test_bumps_updated_at to record time before the API call and use >=
  comparison, avoiding false failures when MariaDB truncates DATETIME to
  whole seconds and creation/update land in the same second

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 20:53:57 -04:00
Spinnich
fae6d869f1 fix(tests): correct test failures in collection endpoint tests
- Replace client.delete(json=...) with content=json.dumps()+Content-Type header
  (Starlette TestClient does not forward json= on DELETE requests)
- Adjust duplicate-name test to expect HTTP 500 matching CollectionAlreadyExistsException
- Add description="" to collections created without it to satisfy Pydantic schema
- Strip tzinfo before comparing updated_at to avoid offset-naive/aware TypeError

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 20:43:12 -04:00
Spinnich
1c9f010918 Fix isort import ordering in test_collection.py 2026-04-14 20:33:45 -04:00
Spinnich
4cc0c868b7 Fix linting and formatting issues from trunk CI 2026-04-14 20:27:45 -04:00
Spinnich
2ecefa3d3f Fix race condition in collection and favorite rom membership updates
Replace full rom_ids list replacement with atomic POST/DELETE endpoints
that add or remove individual ROMs from a collection. This prevents
concurrent rapid clicks from overwriting each other (last-write-wins).

Also fix missing session.flush() in add_rom_user() and add collection
endpoint tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 15:08:53 -04:00
Georges-Antoine Assi
686c609b3a Merge pull request #3155 from tmgast/feature/play-session-ingest
Add play session ingest for game time tracking
2026-04-11 11:23:02 -04:00
Georges-Antoine Assi
ccf5f656b7 Add if_not_exists/if_exists guards to all alembic add_column/drop_column ops 2026-04-09 22:50:07 -04:00
Georges-Antoine Assi
ae03b0b322 ingest playtime with sync sessions 2026-04-06 21:32:13 -04:00
Georges-Antoine Assi
e5cb29ae80 Merge branch 'master' into feature/play-session-ingest 2026-04-06 12:42:15 -04:00
Georges-Antoine Assi
21eee327b0 Merge branch 'master' into save-sync 2026-04-06 09:09:53 -04:00
Georges-Antoine Assi
af69630481 more self review 2026-04-05 23:17:57 -04:00
Georges-Antoine Assi
fafb804bc6 mega cleanup 2026-04-05 22:35:37 -04:00
Georges-Antoine Assi
a61ff81e22 Merge branch 'master' into gamelist-customize 2026-04-05 22:11:02 -04:00
Georges-Antoine Assi
f2e8e337b2 Merge branch 'master' into save-sync 2026-04-05 21:47:53 -04:00
Georges-Antoine Assi
ef35ecaea9 props rom updte endpoint 2026-04-04 14:16:00 -04:00
Georges-Antoine Assi
494af9e4c8 Merge pull request #3217 from rommapp/copilot/fix-get-api-tasks-status-error
Fix GET /api/tasks/status 500 on expired job TTL (NoSuchJobError)
2026-04-03 14:37:21 -04:00
Georges-Antoine Assi
dbb3b9e469 run trunk fmt 2026-04-03 14:23:45 -04:00
copilot-swe-agent[bot]
674061c1d3 fix: narrow OSError catch and clean up test mock
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/303f2c27-6b65-41a9-b201-c055142b1edb

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-03 17:31:14 +00:00
copilot-swe-agent[bot]
394799d7c3 fix: wrap empty parent dir cleanup in try-except and add test coverage for it
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/303f2c27-6b65-41a9-b201-c055142b1edb

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-03 17:28:50 +00:00
copilot-swe-agent[bot]
e540d7c1a2 fix: handle nested ROM directories in delete endpoint and reorder FS/DB operations
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/303f2c27-6b65-41a9-b201-c055142b1edb

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-03 17:26:22 +00:00
copilot-swe-agent[bot]
789f8d4e31 fix: handle NoSuchJobError in get_tasks_status when job TTL expires
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/bc0915c3-2d5f-4ac1-ab89-24942eeac825

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-03 17:22:58 +00:00
Georges-Antoine Assi
ceddf2f9e9 changes from bot review 2026-04-03 11:24:02 -04:00
Georges-Antoine Assi
ec8583016b mega ton of fixes for 4.8 2026-04-03 10:54:31 -04:00
Vargash
2b17da0c6c fix: manage the new export.gamelist.media configs via _update_config_file 2026-03-31 15:29:23 +02:00
Georges-Antoine Assi
4c97eddfc3 fix trunk check 2026-03-22 16:30:14 -04:00