Skip to content

test(channels): add injectable test transport to TelegramChannel for unit testing without Telegram API #2121

@bug-ops

Description

@bug-ops

Problem

TelegramChannel::start() creates the internal mpsc::Sender<IncomingMessage> in-place and never exposes it. This makes it impossible to write integration tests for the channel's core logic — recv(), send_chunk(), flush_chunks(), confirm() timeout behavior, allowed_users enforcement — without a real Telegram bot token and live API access.

Currently the only unit tests cover trivial pure functions (is_command, should_send_update, state accumulation). The critical behavioral paths are untested.

Proposed Solution

Add a #[cfg(test)] constructor that injects the mpsc::Sender<IncomingMessage> from outside:

#[cfg(test)]
pub fn new_test(allowed_users: Vec<String>) -> (Self, mpsc::Sender<IncomingMessage>) {
    let (tx, rx) = mpsc::channel(64);
    let channel = Self {
        bot: Bot::new("test_token"),
        chat_id: Some(ChatId(1)),
        rx,
        allowed_users,
        accumulated: String::new(),
        last_edit: None,
        message_id: None,
    };
    (channel, tx)
}

The Bot::new("test_token") instance must never make real HTTP calls in tests — all send_message / edit_message_text paths must be gated behind self.chat_id.is_some() checks (already true for send_or_edit) or mocked at the HTTP layer (e.g., wiremock).

Test Cases to Add

  • recv() returns ChannelMessage when IncomingMessage is injected via tx
  • /reset and /skills commands are routed correctly via recv()
  • /start is consumed internally without returning to caller
  • allowed_users filter: messages from non-listed usernames are dropped (requires exposing username in IncomingMessage or rethinking the guard location)
  • send_chunk() accumulates and respects 10s throttle (should_send_update)
  • flush_chunks() sends final edit when message_id is set, then clears state
  • confirm() returns false on 30s timeout (use tokio::time::pause())
  • confirm() returns true when yes injected within timeout
  • confirm() returns false on channel close

Acceptance Criteria

  • new_test() constructor added under #[cfg(test)]
  • All behavioral scenarios above covered by #[tokio::test] tests in telegram.rs
  • No real HTTP calls in tests (either mock HTTP or avoid paths that call bot API)
  • cargo nextest run -p zeph-channels passes with all new tests
  • No changes to production code paths (only additive #[cfg(test)] additions)

Priority

Medium — the channel works in production but test coverage is a blind spot that will cause regressions to go undetected as the channel evolves.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    channelszeph-channels crate (Telegram)enhancementNew feature or requesttestingTests and quality

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions