From d8dd4fa91ef15acd14058067a7fb428267e234d4 Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Fri, 18 Sep 2026 01:14:09 +0800 Subject: [PATCH] test(cache): pin that pre-split entries still parse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A payload written before the title/content split has no `content` field; `#[serde(default)]` is what keeps it readable, and the cache deletes any payload it cannot parse — so dropping that default would silently evict entries rather than degrade them. The test inserts the literal pre-split JSON and asserts it comes back with its text left in `title` (no migration: the entry lives one TTL and moving the text would only reshuffle `/set_format` placeholders until it expires) and its stored caption untouched. --- crates/xmedia-bot/src/link_cache.rs | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/crates/xmedia-bot/src/link_cache.rs b/crates/xmedia-bot/src/link_cache.rs index b3c15cb..a52186b 100644 --- a/crates/xmedia-bot/src/link_cache.rs +++ b/crates/xmedia-bot/src/link_cache.rs @@ -198,6 +198,49 @@ mod tests { } } + /// A payload written before the title/content split has no `content` + /// field. It must still read back — the cache deletes what it cannot + /// parse — with its text left where it was stored (`title`) and the + /// caption it replays untouched. No migration: a self-hosted cache entry + /// lives one TTL, and moving the text would only reshuffle `/set_format` + /// placeholders until it expires. + #[tokio::test] + async fn pre_split_entry_still_parses() { + let dir = tempfile::tempdir().unwrap(); + let cache = LinkCache::new( + crate::db::open_store(dir.path().join("c.db").to_str().unwrap()).unwrap(), + ); + let legacy = serde_json::json!({ + "url": "https://x.com/u/status/1", + "caption": "https://x.com/u/status/1\na: old text", + "title": "old text", + "author": "a", + "author_url": "au", + "tags": "", + "sensitive": false, + "media": [{"kind": "photo", "file_id": "AgAC..."}] + }); + { + let conn = rusqlite::Connection::open(dir.path().join("c.db")).unwrap(); + conn.execute( + "INSERT INTO link_cache (url, payload, created_at) VALUES (?1, ?2, ?3)", + params!["twitter:1", legacy.to_string(), now_f64()], + ) + .unwrap(); + } + + let got = cache + .get("twitter:1", Duration::from_secs(3600)) + .await + .expect("a pre-split payload must not be dropped"); + assert_eq!(got.title, "old text"); + assert_eq!(got.content, ""); + assert_eq!( + got.caption, + "https://x.com/u/status/1\na: old text" + ); + } + #[tokio::test] async fn put_get_roundtrip() { let dir = tempfile::tempdir().unwrap();