lesavka/testing/tests/client_paste_contract.rs

142 lines
5.2 KiB
Rust
Raw Normal View History

//! Integration coverage for the client paste-request contract.
//!
//! Scope: validate key loading, encryption metadata, and truncation behavior
//! through the public paste helper.
//! Targets: `client/src/paste.rs`.
//! Why: these checks are pure policy and crypto framing, so they belong in the
//! centralized `testing/tests` contract suite.
use chacha20poly1305::aead::Aead;
use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce};
use lesavka_client::paste::build_paste_request;
use serial_test::serial;
use temp_env::with_var;
use tempfile::tempdir;
const TEST_KEY_HEX: &str = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
const TEST_KEY_RAW: &str = "0123456789abcdef0123456789abcdef";
#[test]
#[serial]
fn build_paste_request_requires_a_shared_key() {
let dir = tempdir().expect("tempdir");
with_var("LESAVKA_PASTE_KEY", None::<&str>, || {
with_var("LESAVKA_PASTE_KEY_FILE", None::<&str>, || {
with_var("HOME", Some(dir.path().as_os_str()), || {
let err = build_paste_request("hello").expect_err("missing key should fail");
let rendered = format!("{err:#}");
assert!(
rendered.contains("paste key file") || rendered.contains("LESAVKA_PASTE_KEY")
);
});
});
});
}
#[test]
#[serial]
fn build_paste_request_requires_explicit_key_when_home_is_unset() {
with_var("LESAVKA_PASTE_KEY", None::<&str>, || {
with_var("LESAVKA_PASTE_KEY_FILE", None::<&str>, || {
with_var("HOME", None::<&str>, || {
let err = build_paste_request("hello").expect_err("missing key should fail");
let rendered = format!("{err:#}");
assert!(rendered.contains("LESAVKA_PASTE_KEY"));
});
});
});
}
#[test]
#[serial]
fn build_paste_request_sets_encryption_fields() {
with_var("LESAVKA_PASTE_KEY", Some(TEST_KEY_HEX), || {
let req = build_paste_request("hello world").expect("build request");
assert!(req.encrypted);
assert_eq!(req.nonce.len(), 12);
assert!(!req.data.is_empty());
});
}
#[test]
#[serial]
fn build_paste_request_accepts_raw_32_byte_shared_key() {
with_var("LESAVKA_PASTE_KEY", Some(TEST_KEY_RAW), || {
let req = build_paste_request("hello raw key").expect("build request");
assert!(req.encrypted);
assert_eq!(req.nonce.len(), 12);
assert!(!req.data.is_empty());
});
}
#[test]
#[serial]
fn build_paste_request_truncates_plaintext_before_encryption() {
with_var("LESAVKA_PASTE_KEY", Some(TEST_KEY_HEX), || {
with_var("LESAVKA_PASTE_MAX", Some("5"), || {
let req = build_paste_request("hello-from-lesavka").expect("build truncated request");
let key = lesavka_common::paste::decode_shared_key(TEST_KEY_HEX).expect("decode key");
let cipher = ChaCha20Poly1305::new(Key::from_slice(&key));
let nonce = Nonce::from_slice(&req.nonce);
let plaintext = cipher
.decrypt(nonce, req.data.as_ref())
.expect("decrypt request data");
assert_eq!(std::str::from_utf8(&plaintext).expect("utf8"), "hello");
});
});
}
#[test]
#[serial]
fn build_paste_request_loads_shared_key_from_file() {
let dir = tempdir().expect("tempdir");
let path = dir.path().join("paste-key");
std::fs::write(&path, TEST_KEY_HEX).expect("write key");
with_var("LESAVKA_PASTE_KEY", None::<&str>, || {
with_var("LESAVKA_PASTE_KEY_FILE", Some(path.as_os_str()), || {
let req = build_paste_request("hello world").expect("build request");
assert!(req.encrypted);
assert_eq!(req.nonce.len(), 12);
assert!(!req.data.is_empty());
});
});
}
#[test]
#[serial]
fn build_paste_request_uses_default_key_path_under_home() {
let dir = tempdir().expect("tempdir");
let path = dir.path().join(".config/lesavka/paste-key");
std::fs::create_dir_all(path.parent().expect("paste key dir")).expect("create config dir");
std::fs::write(&path, TEST_KEY_HEX).expect("write key");
with_var("LESAVKA_PASTE_KEY", None::<&str>, || {
with_var("LESAVKA_PASTE_KEY_FILE", None::<&str>, || {
with_var("HOME", Some(dir.path().as_os_str()), || {
let req = build_paste_request("hello default path").expect("build request");
assert!(req.encrypted);
assert_eq!(req.nonce.len(), 12);
assert!(!req.data.is_empty());
});
});
});
}
#[test]
#[serial]
fn build_paste_request_rejects_empty_default_key_file() {
let dir = tempdir().expect("tempdir");
let path = dir.path().join(".config/lesavka/paste-key");
std::fs::create_dir_all(path.parent().expect("paste key dir")).expect("create config dir");
std::fs::write(&path, "").expect("write empty key");
with_var("LESAVKA_PASTE_KEY", None::<&str>, || {
with_var("LESAVKA_PASTE_KEY_FILE", None::<&str>, || {
with_var("HOME", Some(dir.path().as_os_str()), || {
let err = build_paste_request("hello").expect_err("empty key file should fail");
assert!(format!("{err:#}").contains("is empty"));
});
});
});
}