Files
bvandeusen 96c29c370b
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 38s
CI / integration (push) Successful in 3m14s
feat(ingest): localize inline post-body images to local copies (Phase 2)
Render a post body faithfully by serving our stored copies of inline
images instead of hotlinking the public CDN. The join key is the CDN
filehash (32-hex MD5) shared between a body <img src> and the media URL
we downloaded (the same identity extract_media dedups by):

- utils.paths.filehash_from_url — one source of truth for the extractor;
  patreon_client._filehash now delegates so capture- and render-time
  hashing cannot drift.
- ImageRecord gains source_url (provenance) + source_filehash (indexed
  match key); migration 0051.
- the per-media sidecar carries the file's source_url; the importer
  persists it (NULL-only) on the ImageRecord via _apply_sidecar.
- post_feed_service.get_post remaps body <img src> -> /images/<path> for
  every inline image whose filehash maps to a stored image of THIS
  artist; unmatched / pre-Phase-2 images keep hotlinking.

Pre-existing on-disk images have no filehash yet, so they fall back to
hotlinking until re-downloaded; localization is forward-looking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:39:58 -04:00

62 lines
1.9 KiB
Python

from pathlib import Path
from backend.app.utils.paths import (
derive_subdir,
derive_top_level_artist,
filehash_from_url,
hash_suffixed_name,
)
IMPORT = Path("/import")
def test_filehash_from_url_extracts_lowercased_md5():
url = "https://c10.patreonusercontent.com/4/patreon-media/p/post/1/AbC123DeF456789012345678901234EF/1/img.png?token=x"
assert filehash_from_url(url) == "abc123def456789012345678901234ef"
def test_filehash_from_url_none_and_no_hash():
assert filehash_from_url(None) is None
assert filehash_from_url("") is None
assert filehash_from_url("https://example.test/a.png") is None
def test_filehash_from_url_query_unescaping_irrelevant():
# The hash sits in the path before the query, so entity-escaped ampersands
# in the query never affect the match (render-time matches escaped srcs).
base = "https://cdn.test/p/0123456789abcdef0123456789ABCDEF/x.jpg"
assert filehash_from_url(base + "?a=1&amp;b=2") == "0123456789abcdef0123456789abcdef"
def test_derive_subdir_nested():
assert derive_subdir(Path("/import/Alice/sub/x.png"), IMPORT) == "Alice/sub"
def test_derive_subdir_one_level():
assert derive_subdir(Path("/import/Alice/x.png"), IMPORT) == "Alice"
def test_derive_subdir_root():
assert derive_subdir(Path("/import/x.png"), IMPORT) == ""
def test_derive_subdir_outside_root():
assert derive_subdir(Path("/elsewhere/x.png"), IMPORT) == ""
def test_hash_suffixed_name():
h = "abcdef1234567890" + "0" * 48
assert hash_suffixed_name("photo", h, ".png") == "photo__abcdef1234.png"
def test_derive_top_level_artist_present():
assert derive_top_level_artist(Path("/import/Alice/x.png"), IMPORT) == "Alice"
def test_derive_top_level_artist_nested():
assert derive_top_level_artist(Path("/import/Alice/sub/x.png"), IMPORT) == "Alice"
def test_derive_top_level_artist_root():
assert derive_top_level_artist(Path("/import/x.png"), IMPORT) is None