mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
* 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.
113 lines
3.7 KiB
Rust
113 lines
3.7 KiB
Rust
//! 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());
|
|
}
|
|
|