test: share the handler/cache fixtures from ctx::test_support

The same fixtures were rebuilt in five test modules: a `CachedPost`
literal in `link_cache.rs`, `handlers/urls.rs` and twice in
`send/mod.rs`, the edit-before-forward prompt in `handlers/mod.rs` and
`handlers/callback.rs`, and a scripted API error in both handler
modules. They now live in `ctx::test_support` next to `TestStores`:

- `cached_photo()` — the canonical cached post (photo + file id at
  `https://x.com/u/status/1`, key `twitter:1`); tests mutate the fields
  they care about, as the caption-quote test already did.
- `seed_prompt(template, created_at)` + `PROMPT_ID`/`FORWARDED_ID` —
  the prompt record, the chat template and the bound forward channel.
  The two former copies differed only in which knob the caller set (the
  callback tests backdate it for the expiry cases, the reply tests pick
  the template), so the union is one helper.
- `api_error(message)` — construction only; each test module keeps its
  own message constant, because the wording is what that module's path
  answers with (`chat not found` vs `message not found`).

`send/mod.rs`'s `cached_sequence_cache_data()` (which re-extracted the
post out of the task it had just built) is gone: the two settle tests
seed the cache from the same builder the task uses.

No behaviour change: the values are the ones the tests used except
`file_id` (`AgAC-file-id` everywhere, asserted in the link-cache
round-trip) and `sensitive` (the unasserted `true` in the link-cache
fixture), and every test still passes unchanged.

Verified: `cargo fmt`, `cargo clippy --workspace --all-targets --locked
-- -D warnings` and `cargo test --workspace --locked` (180 passed, 14
ignored).
This commit is contained in:
2026-09-21 00:25:01 +08:00
parent 39dbd0f3a2
commit 3828d5b483
7 changed files with 102 additions and 171 deletions
+11 -37
View File
@@ -224,45 +224,19 @@ fn is_group(kind: &ChatKind) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use crate::ctx::test_support::TestStores;
use crate::ctx::test_support::{PROMPT_ID, TestStores, api_error, seed_prompt};
use crate::media_sender::test_support::{MockSender, Outcome};
use crate::state::EditMessage;
use teloxide::ApiError;
const PROMPT_ID: i64 = 7;
const FORWARDED_ID: i64 = 9;
fn api_error() -> RequestError {
RequestError::Api(ApiError::Unknown("Bad Request: message not found".into()))
}
/// Seeds a prompt record; `template` names the chat template used for it
/// (empty = none, the caption gets the bare link).
async fn seed_prompt(ctx: &AppContext<'_>, template: &str) {
ctx.chat_store
.update(1, |data| {
data.template
.insert("tpl".to_string(), "<b>[]</b>".to_string());
data.edit_message.insert(
PROMPT_ID,
EditMessage {
url: "https://x.com/u/status/1".into(),
chat_id: 1,
forward_message_ids: vec![FORWARDED_ID],
template: template.to_string(),
created_at: crate::db::unix_now(),
},
);
})
.await;
}
/// The Telegram wording the mocks answer with: a message the bot cannot
/// edit (the prompt was deleted).
const API_ERROR: &str = "Bad Request: message not found";
#[tokio::test]
async fn reply_to_a_prompt_swaps_the_caption_through_its_template() {
let sender = MockSender::scripted(vec![Outcome::EditOk], api_error);
let sender = MockSender::scripted(vec![Outcome::EditOk], || api_error(API_ERROR));
let stores = TestStores::new();
let ctx = stores.ctx(&sender);
seed_prompt(&ctx, "tpl").await;
seed_prompt(&ctx, "tpl", crate::db::unix_now()).await;
let consumed = edit_message_handler(&ctx, 1, PROMPT_ID, "new caption").await;
@@ -275,10 +249,10 @@ mod tests {
#[tokio::test]
async fn reply_text_and_url_are_escaped_into_the_caption() {
let sender = MockSender::scripted(vec![Outcome::EditOk], api_error);
let sender = MockSender::scripted(vec![Outcome::EditOk], || api_error(API_ERROR));
let stores = TestStores::new();
let ctx = stores.ctx(&sender);
seed_prompt(&ctx, "").await;
seed_prompt(&ctx, "", crate::db::unix_now()).await;
edit_message_handler(&ctx, 1, PROMPT_ID, "<script>alert(1)</script>").await;
@@ -291,10 +265,10 @@ mod tests {
#[tokio::test]
async fn a_failed_caption_swap_still_consumes_the_reply() {
let sender = MockSender::scripted(vec![Outcome::EditErr], api_error);
let sender = MockSender::scripted(vec![Outcome::EditErr], || api_error(API_ERROR));
let stores = TestStores::new();
let ctx = stores.ctx(&sender);
seed_prompt(&ctx, "tpl").await;
seed_prompt(&ctx, "tpl", crate::db::unix_now()).await;
// The edit failed (message deleted etc.); the reply must still be
// swallowed instead of being treated as a link to fetch.
@@ -304,7 +278,7 @@ mod tests {
#[tokio::test]
async fn reply_to_an_unrelated_message_is_not_consumed() {
let sender = MockSender::scripted(vec![], api_error);
let sender = MockSender::scripted(vec![], || api_error(API_ERROR));
let stores = TestStores::new();
let ctx = stores.ctx(&sender);