mirror of
https://github.com/Evil0ctal/Douyin_TikTok_Download_API.git
synced 2026-10-17 04:40:38 +08:00
fix(tiktok): the handle lookup gave up whenever its answer was cached
The 5.0.2 fix worked when I tested it and stopped working once I had used it, which is the shape of a bug that is better at hiding than at happening. `FetchService.fetch` answers a cache hit straight from storage and never calls the `parse` callback. The lookup collected its author through that callback, so the very first resolution of a handle succeeded - cold cache, real fetch, callback runs - and every one inside the next fifteen minutes found an empty list and gave up. Giving up is silent by design there: it leaves the original refusal standing rather than replacing it with an error about a call the caller never made. So the failure was a return to exactly the message it was supposed to remove, with nothing in the log and no upstream request to notice. It parses `result.payload` now, which is populated either way. Two log lines were missing for the same reason I could not see this from the outside: the no-id branch said nothing at all, and the resolved line now carries `cached` so the two paths are distinguishable in a log. The regression test is the cache-hit shape specifically - payload set, callback never invoked - because a test that only exercises a real fetch is the test that passed while this shipped.
This commit is contained in:
+16
-11
@@ -626,21 +626,14 @@ class TaskWorker:
|
||||
handle = value.lstrip("@")
|
||||
lookup = registry.resolve(profile_endpoint, {registry.UNIQUE_ID: handle}, self._config())
|
||||
|
||||
found: list[Any] = []
|
||||
|
||||
def parse(payload: dict[str, Any]) -> Any:
|
||||
model = lookup.parse(payload)
|
||||
found.append(model)
|
||||
return model
|
||||
|
||||
try:
|
||||
async with self._session_factory() as session:
|
||||
await self._fetch.fetch(
|
||||
result = await self._fetch.fetch(
|
||||
session,
|
||||
lookup.platform,
|
||||
lookup.endpoint,
|
||||
lookup.params,
|
||||
parse=parse,
|
||||
parse=lookup.parse,
|
||||
cache_ttl=lookup.cache_ttl,
|
||||
ctx=FetchContext(
|
||||
task_id=run.id,
|
||||
@@ -652,6 +645,12 @@ class TaskWorker:
|
||||
is_demo=run.is_demo,
|
||||
),
|
||||
)
|
||||
# Parsed from what came back, not collected from the `parse`
|
||||
# callback. A cache hit returns the stored payload and never calls
|
||||
# it, so a callback-collected author was present on the first
|
||||
# lookup of a handle and absent on every one after - which is a
|
||||
# failure that gets better when you stop looking at it.
|
||||
author = lookup.parse(result.payload)
|
||||
except DtkError as exc:
|
||||
log.info(
|
||||
"worker.author_handle.lookup_failed",
|
||||
@@ -661,11 +660,17 @@ class TaskWorker:
|
||||
)
|
||||
return params
|
||||
|
||||
sec_uid = getattr(found[0], "sec_uid", None) if found else None
|
||||
sec_uid = getattr(author, "sec_uid", None)
|
||||
if not isinstance(sec_uid, str) or not sec_uid:
|
||||
log.info("worker.author_handle.no_id", endpoint=endpoint, handle=handle)
|
||||
return params
|
||||
|
||||
log.info("worker.author_handle.resolved", endpoint=endpoint, handle=handle)
|
||||
log.info(
|
||||
"worker.author_handle.resolved",
|
||||
endpoint=endpoint,
|
||||
handle=handle,
|
||||
cached=result.cached,
|
||||
)
|
||||
resolved = dict(params)
|
||||
resolved.pop("sec_user_id", None)
|
||||
resolved[registry.AUTHOR_ID] = sec_uid
|
||||
|
||||
@@ -1768,7 +1768,10 @@ async def test_a_tiktok_handle_is_resolved_before_the_call_that_cannot_use_it()
|
||||
first - advice that led nowhere, because the profile it would have returned
|
||||
carried no usable id either. Now the worker does the lookup itself.
|
||||
"""
|
||||
fetch = FakeFetch(parse_payload=_tiktok_profile_payload())
|
||||
# Both set: a fresh fetch returns the payload *and* runs the callback. The
|
||||
# cache-hit case, which only returns the payload, is the test below.
|
||||
payload = _tiktok_profile_payload()
|
||||
fetch = FakeFetch(payload=payload, parse_payload=payload)
|
||||
worker, _ = make_worker(FakeStore(), fetch)
|
||||
run = a_run("tiktok.author_posts", sec_user_id="owlcitymusic")
|
||||
|
||||
@@ -1781,6 +1784,29 @@ async def test_a_tiktok_handle_is_resolved_before_the_call_that_cannot_use_it()
|
||||
assert "sec_user_id" not in resolved
|
||||
|
||||
|
||||
async def test_a_cached_profile_still_yields_the_id() -> None:
|
||||
"""The lookup must read what came back, not what the callback collected.
|
||||
|
||||
`FetchService.fetch` returns a cache hit straight from storage and never
|
||||
calls `parse`. Collecting the author through that callback therefore worked
|
||||
on the first lookup of a handle and failed on every one afterwards - a bug
|
||||
that got better when you stopped looking at it, and the reason the fix
|
||||
shipped in 5.0.2 appeared to work when it was tested and not when it was
|
||||
used.
|
||||
"""
|
||||
# payload set, parse_payload left None: FakeFetch answers without ever
|
||||
# invoking the callback, which is exactly what a cache hit does.
|
||||
fetch = FakeFetch(payload=_tiktok_profile_payload())
|
||||
worker, _ = make_worker(FakeStore(), fetch)
|
||||
run = a_run("tiktok.author_posts", sec_user_id="owlcitymusic")
|
||||
|
||||
resolved = await worker._resolve_author_handle("tiktok.author_posts", dict(run.params), run)
|
||||
|
||||
assert len(fetch.calls) == 1
|
||||
assert resolved["author_id"].startswith("MS4wLjAB")
|
||||
assert "sec_user_id" not in resolved
|
||||
|
||||
|
||||
async def test_an_id_that_is_already_a_sec_uid_costs_no_lookup() -> None:
|
||||
"""The common case must not pay for a request it does not need."""
|
||||
fetch = FakeFetch()
|
||||
|
||||
Reference in New Issue
Block a user