Task 7 completes the outbound relay: restructured the crate as a proper lib+bin, wired the full relay loop in main.rs, and added wiremock integration tests. 23 tests pass, 1 ignored, clippy clean. Commit: 8f0d776.

Cargo.toml lib/bin Split

Cargo.toml now declares both a [lib] and a [[bin]] target so integration tests can import via the crate name:

[lib]
name = "telegram_connector"
path = "src/lib.rs"
 
[[bin]]
name = "telegram_connector"
path = "src/main.rs"

Without this split, tests/ cannot do use telegram_connector::… — rustc only exposes crate items to integration tests through the lib target.

src/lib.rs

Re-exports all 6 modules as pub mod:

  • config
  • consumer
  • outbound
  • relay
  • telegram
  • telemetry

For Agents

relay.rs uses crate:: paths internally. These resolve correctly in lib crate context. Integration tests import via telegram_connector::relay::handle_one etc. — the lib target makes this transparent.

src/main.rs Rewrite

main.rs is now a thin orchestrator:

  1. Dormant exit — if TELEGRAM_BOT_TOKEN or NATS_URL is unset, log and exit cleanly (no panic). Mirrors cohort_algo’s dormant guard convention.
  2. NATS bind — connects the JetStream pull consumer.
  3. Relay loop — runs inside tokio::select! alongside a shutdown future.
  4. Graceful shutdown — listens for Ctrl-C and (on unix) SIGTERM; exits cleanly on either signal.

Shutdown Signal: cfg(unix) Pattern

async fn shutdown_signal() {
    let ctrl_c = async { signal::ctrl_c().await.expect("...") };
 
    #[cfg(unix)]
    {
        let terminate = async {
            signal::unix::signal(signal::unix::SignalKind::terminate())
                .expect("...")
                .recv()
                .await;
        };
        tokio::select! {
            _ = ctrl_c => {},
            _ = terminate => {},
        }
        return;
    }
 
    ctrl_c.await;
}

Key Insight

The #[cfg(unix)] block ends with an explicit return;. This compiles cleanly on both unix and non-unix without lint noise. Without the return;, the compiler sees ctrl_c.await as unreachable on unix but reachable on non-unix — the explicit return inside the cfg block resolves the ambiguity for all targets.

Integration Tests

tests/outbound_relay.rs (3 tests, all passing)

Uses wiremock to mock the Telegram Bot API.

TestScenarioExpected disposition
happy_pathTelegram returns 200 OKAck
rate_limitedTelegram returns 429 with retry_afterNak(retry_after duration)
bad_requestTelegram returns 400Term (no retry)

These tests import telegram_connector::relay::handle_one directly from the lib target.

tests/nats_smoke.rs (1 test, ignored)

Skeleton for a full NATS round-trip smoke test. Marked #[ignore] pending a real NATS server fixture. Exists to define the shape and reserve the file.

Build Status After Task 7

MetricValue
Tests passing23
Tests ignored1 (nats_smoke)
Clippyclean
Commit8f0d776
Outbound relaycomplete
Inbound commandsdesign-gated (no change)