mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
DM pairing + Telegram channel improvements (#17)
* feat: Implement DM pairing for channels - Introduced a new pairing system to manage direct messages from unknown senders. - Added `PairingStore` to handle pending requests and allowlist management. - Implemented CLI commands for listing and approving pairing requests. - Updated Telegram channel to utilize the new pairing logic, including workspace paths for storing pairing data. - Enhanced WASM channel integration to support pairing functionality. This feature enhances security by requiring approval for unknown senders before they can interact with the agent. * Enhance Telegram channel support with media captioning and DM pairing features - Added support for media captions in Telegram messages, allowing for richer content handling. - Updated message processing to utilize either text or caption, improving message flexibility. - Enhanced DM pairing functionality to include approval and listing capabilities for direct messages. - Updated feature parity documentation to reflect new capabilities and improvements in Telegram integration. * Update README and BUILDING_CHANNELS documentation for Telegram channel integration - Enhanced README with instructions for building and running the Telegram channel, including a note on running `./scripts/build-all.sh` for full releases. - Added detailed steps in BUILDING_CHANNELS.md for building and deploying the Telegram channel, emphasizing the need to run `./channels-src/telegram/build.sh` before building the main crate to ensure updated WASM is included. - Updated CLI module to expose a new command for pairing with store functionality. * Implement build script for Telegram channel WASM and enhance pairing error handling - Added a new `build.rs` script to automate the compilation of the Telegram channel's WASM binary from source, ensuring reproducible builds and emphasizing supply chain security by preventing committed binaries. - Updated `BUILDING_CHANNELS.md` to reflect the new build process and the importance of not committing compiled binaries. - Enhanced error handling in the pairing approval process to include rate limiting for failed attempts, improving security and user feedback. * Remove Telegram channel WASM binary file as part of the build process cleanup, ensuring no committed binaries are present in the repository.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
//! Integration tests for the DM pairing flow.
|
||||
//!
|
||||
//! Verifies the full pairing lifecycle: upsert → list → approve → allowFrom → is_sender_allowed.
|
||||
//! Uses temp directory for isolation.
|
||||
|
||||
use ironclaw::cli::{run_pairing_command_with_store, PairingCommand};
|
||||
use ironclaw::pairing::PairingStore;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn test_store() -> (PairingStore, TempDir) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let store = PairingStore::with_base_dir(dir.path().to_path_buf());
|
||||
(store, dir)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pairing_flow_unknown_user_to_approved() {
|
||||
let (store, _) = test_store();
|
||||
let channel = "telegram";
|
||||
|
||||
// 1. Unknown user sends first message -> upsert creates request
|
||||
let r1 = store.upsert_request(channel, "user_12345", Some(serde_json::json!({
|
||||
"chat_id": 999,
|
||||
"username": "alice"
|
||||
}))).unwrap();
|
||||
assert!(r1.created);
|
||||
assert!(!r1.code.is_empty());
|
||||
assert_eq!(r1.code.len(), 8);
|
||||
|
||||
// 2. List pending shows the request
|
||||
let pending = store.list_pending(channel).unwrap();
|
||||
assert_eq!(pending.len(), 1);
|
||||
assert_eq!(pending[0].id, "user_12345");
|
||||
assert_eq!(pending[0].code, r1.code);
|
||||
|
||||
// 3. User is not allowed yet
|
||||
assert!(!store.is_sender_allowed(channel, "user_12345", Some("alice")).unwrap());
|
||||
|
||||
// 4. Approve via code
|
||||
let approved = store.approve(channel, &r1.code).unwrap();
|
||||
assert!(approved.is_some());
|
||||
assert_eq!(approved.unwrap().id, "user_12345");
|
||||
|
||||
// 5. User is now allowed
|
||||
assert!(store.is_sender_allowed(channel, "user_12345", None).unwrap());
|
||||
assert!(store.is_sender_allowed(channel, "user_12345", Some("alice")).unwrap());
|
||||
|
||||
// 6. Pending list is empty
|
||||
let pending_after = store.list_pending(channel).unwrap();
|
||||
assert!(pending_after.is_empty());
|
||||
|
||||
// 7. allowFrom contains the user
|
||||
let allow = store.read_allow_from(channel).unwrap();
|
||||
assert_eq!(allow, vec!["user_12345"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pairing_flow_cli_approve() {
|
||||
let (store, _) = test_store();
|
||||
store.upsert_request("telegram", "user_999", None).unwrap();
|
||||
let pending = store.list_pending("telegram").unwrap();
|
||||
let code = pending[0].code.clone();
|
||||
|
||||
let result = run_pairing_command_with_store(
|
||||
&store,
|
||||
PairingCommand::Approve {
|
||||
channel: "telegram".to_string(),
|
||||
code,
|
||||
},
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
assert!(store.is_sender_allowed("telegram", "user_999", None).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pairing_reject_invalid_code() {
|
||||
let (store, _) = test_store();
|
||||
store.upsert_request("telegram", "user_1", None).unwrap();
|
||||
|
||||
let result = store.approve("telegram", "INVALID1");
|
||||
assert!(result.unwrap().is_none());
|
||||
|
||||
let result = run_pairing_command_with_store(
|
||||
&store,
|
||||
PairingCommand::Approve {
|
||||
channel: "telegram".to_string(),
|
||||
code: "BADCODE1".to_string(),
|
||||
},
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pairing_multiple_channels_isolated() {
|
||||
let (store, _) = test_store();
|
||||
|
||||
let r_telegram = store.upsert_request("telegram", "user_a", None).unwrap();
|
||||
let r_slack = store.upsert_request("slack", "user_b", None).unwrap();
|
||||
|
||||
// Each channel has its own pending
|
||||
assert_eq!(store.list_pending("telegram").unwrap().len(), 1);
|
||||
assert_eq!(store.list_pending("slack").unwrap().len(), 1);
|
||||
|
||||
// Approve in one channel doesn't affect the other
|
||||
store.approve("telegram", &r_telegram.code).unwrap();
|
||||
assert!(store.is_sender_allowed("telegram", "user_a", None).unwrap());
|
||||
assert!(!store.is_sender_allowed("slack", "user_a", None).unwrap());
|
||||
|
||||
store.approve("slack", &r_slack.code).unwrap();
|
||||
assert!(store.is_sender_allowed("slack", "user_b", None).unwrap());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user