qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
use std::time::{Duration, Instant};

use clap::Parser;
use qq_cli::cli::args::{Cli, Command, MessageCommand};
use qq_cli::messaging::content;
use serde_json::json;

#[test]
fn explicit_sources_preserve_literal_text_and_bound_utf8_input() {
    assert_eq!(
        content::text_from_reader(&b"[CQ:at,qq=all]"[..]).unwrap(),
        "[CQ:at,qq=all]"
    );
    for bytes in [vec![], vec![255], vec![b'x'; 65537]] {
        assert!(content::text_from_reader(bytes.as_slice()).is_err());
    }
    assert_eq!(
        content::text_from_reader(vec![b'x'; 65536].as_slice())
            .unwrap()
            .len(),
        65536
    );
    let temp = tempfile::tempdir().unwrap();
    let file = temp.path().join("input.txt");
    std::fs::write(&file, "synthetic file text").unwrap();
    let cli = Cli::try_parse_from([
        "qq-cli",
        "message",
        "send",
        "--conversation",
        "group:20001",
        "--text-file",
        file.to_str().unwrap(),
        "--mention",
        "10002",
        "--dry-run",
    ])
    .unwrap();
    let Command::Message {
        command: MessageCommand::Send(args),
    } = cli.command
    else {
        panic!()
    };
    let segments = content::prepare(
        &args.content,
        &args.conversation,
        Instant::now() + Duration::from_secs(1),
    )
    .unwrap();
    assert_eq!(
        segments,
        vec![
            json!({"type":"text","data":{"text":"synthetic file text"}}),
            json!({"type":"at","data":{"qq":"10002"}})
        ]
    );
}

#[test]
fn text_input_errors_and_private_mentions_fail_during_content_preparation() {
    for args in [
        vec![
            "qq-cli",
            "message",
            "send",
            "--conversation",
            "group:20001",
            "--image",
            "missing-image.png",
            "--text-file",
            "missing-input.txt",
            "--dry-run",
        ],
        vec![
            "qq-cli",
            "message",
            "send",
            "--conversation",
            "private:10002",
            "--mention",
            "10002",
            "--text-file",
            "missing-input.txt",
            "--dry-run",
        ],
    ] {
        let cli = Cli::try_parse_from(args).unwrap();
        let Command::Message {
            command: MessageCommand::Send(args),
        } = cli.command
        else {
            panic!()
        };
        let error = content::prepare(
            &args.content,
            &args.conversation,
            Instant::now() + Duration::from_secs(1),
        )
        .unwrap_err();
        assert!(matches!(
            error.code,
            "INPUT_INVALID" | "MENTION_UNSUPPORTED"
        ));
    }
}