from pathlib import Path from backend.app.utils.paths import ( canonical_subdir, 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&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 # --- canonical_subdir (milestone #421) -------------------------------------- def test_canonical_subdir_replaces_the_top_segment(): assert canonical_subdir("Conto/patreon", "conto") == "conto/patreon" assert canonical_subdir("Conto", "conto") == "conto" def test_canonical_subdir_keeps_everything_below_the_artist(): """Only the artist segment is authoritative — post folders below it are the downloader's business and must survive untouched.""" assert canonical_subdir( "Big Bang/patreon/2026-01-02_123_A Post", "big-bang" ) == "big-bang/patreon/2026-01-02_123_A Post" def test_canonical_subdir_passes_through_without_an_artist(): """No resolved artist means nothing authoritative to canonicalise against, so the import folder's own name stands.""" assert canonical_subdir("Conto/patreon", None) == "Conto/patreon" assert canonical_subdir("Conto", "") == "Conto" def test_canonical_subdir_leaves_the_images_root_alone(): """An empty subdir is a file landing at the root — there is no artist folder to correct, and what becomes of those is task #4247's call.""" assert canonical_subdir("", "conto") == "" assert canonical_subdir("", None) == "" def test_canonical_subdir_is_idempotent(): once = canonical_subdir("Conto/patreon", "conto") assert canonical_subdir(once, "conto") == once