mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-23 23:32:05 +00:00
fix: let a successful send return a degraded entry to the fast path
Degrading a link-cache entry (previous commit) closed the "user retries right after a failure" case, but left a new one open: the entry could never regain file ids. `cache_sent_task` skipped *every* cached send — its rule was "the entry already holds the ids the next repeat wants" — which is true for a healthy entry and false for a degraded one. So a degraded entry (pixiv's hotlink-protected media, say) kept sending by URL forever, and every repeat paid a download and an upload through the fallback that the file ids would have avoided. Worse than the re-fetch it replaced. The rule is now the one it always meant: a send whose cache snapshot still carries file ids leaves the entry alone, and a send served from a degraded entry writes back the ids it produced (the URLs in the rewritten entry come from that send's own items, so they stay correct). Verified by a test that drives `cache_sent_task` directly with both task shapes: the degraded one updates the entry to the fresh id, the healthy one leaves it untouched. 125 bot tests + 91 x-media tests, live suite (14) pass. `cargo fmt --check`, `cargo clippy --workspace --all-targets --locked -- -D warnings` and `cargo test --workspace --locked` clean.
This commit is contained in:
@@ -787,7 +787,7 @@ pub async fn forward_messages(ctx: &AppContext<'_>, task: &Task) -> Result<(), S
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::post_send::build_edit_markup;
|
||||
use super::post_send::{build_edit_markup, cache_sent_task};
|
||||
use super::upload::sniff_ext;
|
||||
use super::*;
|
||||
use crate::ctx::test_support::{TestStores, cached_photo};
|
||||
@@ -1645,6 +1645,67 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_degraded_entry_regains_the_file_ids_a_send_produced() {
|
||||
let sender = MockSender::scripted(vec![], media_fetch_error);
|
||||
let stores = TestStores::new();
|
||||
let ctx = stores.ctx(&sender);
|
||||
// A degraded entry: no file ids, URL only (what `invalidate_cache`
|
||||
// leaves behind).
|
||||
let mut degraded = cached_photo();
|
||||
degraded.media[0].file_id.clear();
|
||||
stores.link_cache().put("twitter:1", °raded).await;
|
||||
let mut task = cached_sequence_task();
|
||||
if let Task::SendMediaSequence { cache_data, .. } = &mut task {
|
||||
*cache_data = Some(degraded.clone());
|
||||
}
|
||||
|
||||
cache_sent_task(
|
||||
&ctx,
|
||||
&task,
|
||||
vec![CachedMedia {
|
||||
kind: CachedMediaKind::Photo,
|
||||
file_id: "fresh-id".into(),
|
||||
url: "https://pbs.twimg.com/media/photo.jpg".into(),
|
||||
}],
|
||||
)
|
||||
.await;
|
||||
|
||||
let entry = stores
|
||||
.link_cache()
|
||||
.get("twitter:1", Duration::from_secs(3600))
|
||||
.await
|
||||
.expect("the entry must still be there");
|
||||
assert_eq!(
|
||||
entry.media[0].file_id, "fresh-id",
|
||||
"a degraded entry must take the ids its send produced"
|
||||
);
|
||||
|
||||
// A send served from a healthy entry must not rewrite it: the ids it
|
||||
// already holds are exactly what the next repeat wants. Which of the
|
||||
// two a send was is the *task's* cache snapshot — a healthy one carries
|
||||
// file ids.
|
||||
cache_sent_task(
|
||||
&ctx,
|
||||
&cached_sequence_task(),
|
||||
vec![CachedMedia {
|
||||
kind: CachedMediaKind::Photo,
|
||||
file_id: "other-id".into(),
|
||||
url: String::new(),
|
||||
}],
|
||||
)
|
||||
.await;
|
||||
let entry = stores
|
||||
.link_cache()
|
||||
.get("twitter:1", Duration::from_secs(3600))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
entry.media[0].file_id, "fresh-id",
|
||||
"a healthy entry is left alone"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn settled_sent_keeps_the_cache_entry() {
|
||||
let sender = MockSender::scripted(vec![], media_fetch_error);
|
||||
|
||||
@@ -15,13 +15,17 @@ use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use teloxide::types::{ChatId, InlineKeyboardButton, InlineKeyboardMarkup, Message, MessageId};
|
||||
|
||||
/// Persists a successful send under the post's cache key. Only runs for a
|
||||
/// fresh (non-resumed) task that carried raw cache data with no file ids yet.
|
||||
/// Persists a successful send under the post's cache key. Skips a send that was
|
||||
/// served from the cache — its entry already holds the file ids the next repeat
|
||||
/// wants — *unless* the entry was degraded (no file ids left, see
|
||||
/// `invalidate_cache`): then the ids this send just produced are written back,
|
||||
/// which is what returns a degraded entry to the fast path instead of leaving
|
||||
/// it to re-upload the media on every repeat.
|
||||
pub(super) async fn cache_sent_task(ctx: &AppContext<'_>, task: &Task, media: Vec<CachedMedia>) {
|
||||
let Some(cache_data) = task.cache_data() else {
|
||||
return;
|
||||
};
|
||||
if !cache_data.media.is_empty() || media.is_empty() {
|
||||
if cache_data.media.iter().any(|m| !m.file_id.is_empty()) || media.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut post = cache_data.clone();
|
||||
|
||||
Reference in New Issue
Block a user