"""A Facebook download's title when the signed-in read gives only a placeholder. Measured on prod (2026-11-01): every Facebook download since 2026-10-28 is titled "Facebook" — the signed-in read (the owner's cookie) now gives that placeholder and NO description, so the adapter had no post text to fall back on, and the download name copied the placeholder too. A signed-out read of the same reel (yt-dlp 2026.08.19, as on prod) still carries the post's text in its description. """ from sqlalchemy import select, update from app.config import settings from app.db import SessionLocal from app.downloads import adapters, backfill, pipeline from app.downloads.models import DownloadJob, MediaAsset _URL = "https://www.facebook.com/reel/1860682468422402/" # The page yt-dlp says it read for it (`webpage_url`); a group post's link resolves the same way. _PAGE = "https://www.facebook.com/watch/?v=1860682468422402" # The measured signed-out read of that reel. _PUBLIC = { "title": "163K reactions · 14K shares | sorry not helping #workhumor #officelife | Work dot exe", "description": "sorry not helping\n\n#workhumor #officelife", "uploader": "Work dot exe", } def _fake_ydl(monkeypatch, outcome): """yt-dlp faked at its `YoutubeDL` boundary: records each read (its options and its `url`), answers `outcome`.""" seen: list[dict] = [] class FakeYDL: def __init__(self, opts): self.opts = opts def __enter__(self): return self def __exit__(self, *exc): return False def extract_info(self, url, download): seen.append({**self.opts, "url": url}) return outcome monkeypatch.setattr(backfill.yt_dlp, "YoutubeDL", FakeYDL) return seen def test_a_placeholder_with_nothing_behind_it_takes_the_signed_out_pages_text(monkeypatch): seen = _fake_ydl(monkeypatch, _PUBLIC) fb = adapters.FacebookAdapter() info = backfill.public_title_info({"title": "Facebook", "description": None}, _URL, fb, 7) assert fb.title(info) == "sorry not helping" # One metadata-only read, and signed out: no cookie goes with it. assert len(seen) == 1 assert seen[0]["skip_download"] is True assert not seen[0].get("cookiefile") def test_a_real_title_reads_nothing_more(monkeypatch): seen = _fake_ydl(monkeypatch, _PUBLIC) info = {"title": "Funny Dark Humor Dad Joke!", "description": None} assert backfill.public_title_info(info, _URL, adapters.FacebookAdapter(), 7) is info assert seen == [] def test_a_placeholder_with_the_posts_text_behind_it_reads_nothing_more(monkeypatch): seen = _fake_ydl(monkeypatch, _PUBLIC) info = {"title": "Video", "description": "Office jokes, part two"} assert backfill.public_title_info(info, _URL, adapters.FacebookAdapter(), 7) is info assert seen == [] def test_a_placeholder_the_signed_out_page_cannot_better_stays(monkeypatch): _fake_ydl(monkeypatch, {"title": "Facebook", "description": None}) fb = adapters.FacebookAdapter() info = backfill.public_title_info({"title": "Facebook", "description": None}, _URL, fb, 7) assert fb.title(info) == "Facebook" def test_another_source_is_never_read_again(monkeypatch): seen = _fake_ydl(monkeypatch, _PUBLIC) info = {"title": "Video", "description": None} assert backfill.public_title_info(info, _URL, adapters.SourceAdapter(), 7) is info assert seen == [] def test_the_backfill_retitles_a_placeholder_download_and_the_names_that_repeated_it( db, make_user, monkeypatch ): seen = _fake_ydl(monkeypatch, _PUBLIC) owner, other = make_user(), make_user() asset = MediaAsset( source_kind="url", source_ref=_URL, format_sig="fb", status="ready", rel_path="facebook/x.mp4", poster_path="facebook/x.jpg", uploader_url="https://www.facebook.com/1", title="Facebook", ref_count=2, ) db.add(asset) db.flush() copied = DownloadJob( user_id=owner.id, asset_id=asset.id, source_kind="url", source_ref=_URL, status="done", queue_pos=0, profile_snapshot={}, display_name="Facebook", ) renamed = DownloadJob( user_id=other.id, asset_id=asset.id, source_kind="url", source_ref=_URL, status="done", queue_pos=0, profile_snapshot={}, display_name="Office jokes", ) db.add_all([copied, renamed]) db.commit() assert backfill.run()["retitled"] == 1 assert len(seen) == 1 db.expire_all() assert db.get(MediaAsset, asset.id).title == "sorry not helping" assert db.get(DownloadJob, copied.id).display_name == "sorry not helping" assert db.get(DownloadJob, renamed.id).display_name == "Office jokes" # Run again: nothing left to retitle, and no page read for it. assert backfill.run()["retitled"] == 0 assert len(seen) == 1 def test_a_name_given_while_the_backfill_runs_stays(db, make_user, monkeypatch): # The owner renames the download while the backfill reads the page: the owner's name wins, it is # not overwritten by what was read before it. _fake_ydl(monkeypatch, _PUBLIC) owner = make_user() asset = MediaAsset( source_kind="url", source_ref=_URL, format_sig="fb", status="ready", rel_path="facebook/x.mp4", uploader_url="https://www.facebook.com/1", title="Facebook", ref_count=1, ) db.add(asset) db.flush() job = DownloadJob( user_id=owner.id, asset_id=asset.id, source_kind="url", source_ref=_URL, status="done", queue_pos=0, profile_snapshot={}, display_name="Facebook", ) db.add(job) db.commit() read_page = backfill.public_title_info def renamed_while_reading(*args): with SessionLocal() as other: other.execute( update(DownloadJob) .where(DownloadJob.id == job.id) .values(display_name="Office jokes") ) other.commit() return read_page(*args) monkeypatch.setattr(backfill, "public_title_info", renamed_while_reading) monkeypatch.setattr(backfill.editmod, "ensure_poster", lambda a, root: None) assert backfill.run()["retitled"] == 1 db.expire_all() assert db.get(DownloadJob, job.id).display_name == "Office jokes" def test_the_poster_step_holds_no_row_lock(db, make_user, monkeypatch): # It can cut a frame for 30 s. A delete locks the asset and then the job: with the job held here # and the asset still to write, that is a deadlock, and a rename waits out the whole cut. _fake_ydl(monkeypatch, _PUBLIC) owner = make_user() asset = MediaAsset( source_kind="url", source_ref=_URL, format_sig="fb", status="ready", rel_path="facebook/x.mp4", uploader_url="https://www.facebook.com/1", title="Facebook", ref_count=1, ) db.add(asset) db.flush() job = DownloadJob( user_id=owner.id, asset_id=asset.id, source_kind="url", source_ref=_URL, status="done", queue_pos=0, profile_snapshot={}, display_name="Facebook", ) db.add(job) db.commit() asset_id, job_id = asset.id, job.id def poster_finds_rows_free(a, root): with SessionLocal() as other: other.execute( select(MediaAsset).where(MediaAsset.id == asset_id).with_for_update(nowait=True) ) other.execute( select(DownloadJob).where(DownloadJob.id == job_id).with_for_update(nowait=True) ) other.rollback() return None monkeypatch.setattr(backfill.editmod, "ensure_poster", poster_finds_rows_free) assert backfill.run()["retitled"] == 1 def test_a_download_titled_facebook_is_named_from_the_signed_out_page( db, make_user, monkeypatch, tmp_path ): # The whole download path with yt-dlp and the media tools faked: the signed-in read gives only # the placeholder, and the file and the download's name get the post's text. seen = _fake_ydl(monkeypatch, _PUBLIC) monkeypatch.setattr(settings, "files_root", str(tmp_path)) monkeypatch.setattr(pipeline.urlguard, "check_url", lambda url: None) monkeypatch.setattr(pipeline, "_ensure_phone_safe", lambda _j, produced, *_a: produced) monkeypatch.setattr( pipeline.probe, "resolve_media_facts", lambda produced, info: ("h264", "aac", 720, 1280, 9) ) monkeypatch.setattr(pipeline.editmod, "thumbnail_to_jpg", lambda *a: None) monkeypatch.setattr(pipeline.editmod, "ensure_poster", lambda *a: None) owner = make_user() asset = MediaAsset( source_kind="url", source_ref=_URL, format_sig="fb", status="downloading", ref_count=1 ) db.add(asset) db.flush() job = DownloadJob( user_id=owner.id, asset_id=asset.id, source_kind="url", source_ref=_URL, status="running", queue_pos=0, profile_snapshot={}, ) db.add(job) db.commit() def signed_in_read(job_id, asset_id, url, spec, staging, adapter, **kw): f = staging / "1860682468422402.mp4" f.write_bytes(b"v" * 100) return { "id": "1860682468422402", "title": "Facebook", "uploader": "Work dot exe", "webpage_url": _PAGE, "requested_downloads": [{"filepath": str(f)}], } monkeypatch.setattr(pipeline, "_extract_with_fallback", signed_in_read) pipeline._download(job.id, asset.id, "url", _URL, {}, owner.id) # The signed-out read goes to the page yt-dlp read, not the link queued: a group post's link # resolves to its video, and signed out the post itself is the login wall. assert [r["url"] for r in seen] == [_PAGE] db.expire_all() assert db.get(DownloadJob, job.id).status == "done" assert db.get(MediaAsset, asset.id).title == "sorry not helping" assert db.get(DownloadJob, job.id).display_name == "sorry not helping"