rose-squared-sdk 0.1.0

Privacy-preserving encrypted search SDK implementing the SWiSSSE protocol with forward/backward security and volume-hiding, compilable to WebAssembly
Documentation
// tests/integration.rs
//
// End-to-end integration tests for the full RO(SE)² protocol.
// Run with:  cargo test

use rose_squared_sdk::{PrivacyVault, MockStore, VolumeConfig};
use uuid::Uuid;

fn test_salt() -> [u8; 16] { [0xAB; 16] }
fn test_password() -> &'static str { "test-password-do-not-use-in-prod" }

fn new_vault() -> PrivacyVault {
    PrivacyVault::new(test_password(), &test_salt(), VolumeConfig { n_max: 8 }).unwrap()
}

// ── Basic add + search ────────────────────────────────────────────────────────

#[tokio::test]
async fn test_add_and_search_returns_doc() {
    let mut vault = new_vault();
    let store = MockStore::new();
    let doc_id = Uuid::new_v4();

    vault.add_document(&["invoice"], doc_id, &store).await.unwrap();

    let results = vault.search("invoice", &store).await.unwrap();
    assert_eq!(results.len(), 1);
    assert_eq!(results[0], doc_id);
}

#[tokio::test]
async fn test_unknown_keyword_returns_empty() {
    let vault = new_vault();
    let store = MockStore::new();

    let results = vault.search("ghost", &store).await.unwrap();
    assert!(results.is_empty());
}

// ── Forward security ──────────────────────────────────────────────────────────
// A search token generated BEFORE an add should not see the new doc.
// We verify this structurally: the token captures live_indices at generation time.

#[tokio::test]
async fn test_forward_security_new_doc_invisible_to_old_token() {
    use rose_squared_sdk::protocol::search::SearchProtocol;

    let mut vault = new_vault();
    let store     = MockStore::new();

    let doc_a = Uuid::new_v4();
    vault.add_document(&["report"], doc_a, &store).await.unwrap();

    // Capture a token BEFORE adding doc_b.
    let proto   = SearchProtocol::new(&vault.keys, &vault.state);
    let token_a = proto.prepare_search("report").unwrap().unwrap();
    let count_a = token_a.pairs.len();  // should be 1

    // Add a second doc.
    let doc_b = Uuid::new_v4();
    vault.add_document(&["report"], doc_b, &store).await.unwrap();

    // Old token still has count_a pairs — doc_b's index is not in it.
    assert_eq!(count_a, 1, "old token must not see new doc (forward security)");
}

// ── Backward security ─────────────────────────────────────────────────────────

#[tokio::test]
async fn test_backward_security_delete_removes_doc() {
    let mut vault = new_vault();
    let store     = MockStore::new();

    let doc_a = Uuid::new_v4();
    let doc_b = Uuid::new_v4();

    vault.add_document(&["contract"], doc_a, &store).await.unwrap();
    vault.add_document(&["contract"], doc_b, &store).await.unwrap();

    // Delete doc_a from "contract".
    vault.delete_document("contract", doc_a, &store).await.unwrap();

    let results = vault.search("contract", &store).await.unwrap();
    assert_eq!(results.len(), 1, "only doc_b should remain");
    assert_eq!(results[0], doc_b);
    assert!(!results.contains(&doc_a), "deleted doc must not appear");
}

#[tokio::test]
async fn test_delete_all_leaves_empty_results() {
    let mut vault = new_vault();
    let store     = MockStore::new();
    let doc       = Uuid::new_v4();

    vault.add_document(&["memo"], doc, &store).await.unwrap();
    vault.delete_document("memo", doc, &store).await.unwrap();

    let results = vault.search("memo", &store).await.unwrap();
    assert!(results.is_empty());
}

// ── State export / import ─────────────────────────────────────────────────────

#[tokio::test]
async fn test_state_round_trips_correctly() {
    let mut vault = new_vault();
    let store     = MockStore::new();
    let doc       = Uuid::new_v4();

    vault.add_document(&["budget"], doc, &store).await.unwrap();

    // Export state, re-import into a fresh vault.
    let blob       = vault.export_state().unwrap();
    let vault2     = PrivacyVault::from_exported(
        test_password(), &test_salt(), &blob, VolumeConfig { n_max: 8 }
    ).unwrap();

    let results = vault2.search("budget", &store).await.unwrap();
    assert_eq!(results, vec![doc], "restored vault must find the same doc");
}

// ── Multi-keyword indexing ─────────────────────────────────────────────────────

#[tokio::test]
async fn test_multi_keyword_indexes_all() {
    let mut vault = new_vault();
    let store     = MockStore::new();
    let doc       = Uuid::new_v4();

    vault.add_document(&["alpha", "beta", "gamma"], doc, &store).await.unwrap();

    for kw in &["alpha", "beta", "gamma"] {
        let r = vault.search(kw, &store).await.unwrap();
        assert_eq!(r, vec![doc], "keyword '{kw}' should find the doc");
    }
}