AkurAI Build
Menu

Public repository / 705e1ce035ea4b9fb4c413dd38f550650185b571

AkurAI-Notes / crates/notes-api/tests/api_test.rs

Latest change 21fd83f4a6eb769a0ee34aa30f721b5cdc6881af - Fix IDP OAuth: split public issuer vs internal loopback URL for token exchange by Claude Code

use akurai_json::Value;
use notes_api::auth::idp_client::IdpConfig;
use notes_api::NotesState;
use std::path::PathBuf;

fn setup_db(name: &str) -> (NotesState, String) {
    let db_path = format!("_test_{}.db", name);
    let _ = std::fs::remove_file(&db_path);
    let frontend = PathBuf::from("site/frontend");
    let state = NotesState::new(&db_path, frontend, IdpConfig::from_env()).unwrap();
    (state, db_path)
}

fn cleanup(db_path: &str) {
    let _ = std::fs::remove_file(db_path);
}

#[test]
fn test_list_notes_returns_only_owned_notes() {
    let (state, db_path) = setup_db("list_owned");
    let mut db = state.db.lock().unwrap();

    fn make_key(coll: &str, id: u64) -> Vec<u8> {
        let mut k = format!("{}:", coll).into_bytes();
        k.extend_from_slice(&id.to_be_bytes());
        k
    }

    let now = NotesState::now();

    // Insert a note owned by olibuijr@olibuijr.com
    let note1 = Value::Object(vec![
        ("id".into(), Value::Int(1)),
        ("title".into(), Value::Str("My note".into())),
        ("body".into(), Value::Str("Body 1".into())),
        (
            "ownerSub".into(),
            Value::Str("olibuijr@olibuijr.com".into()),
        ),
        ("createdAt".into(), Value::Int(now)),
        ("updatedAt".into(), Value::Int(now)),
    ]);
    db.insert(&make_key("notes", 1), note1.to_json().as_bytes())
        .unwrap();

    // Insert a note owned by other@example.com
    let note2 = Value::Object(vec![
        ("id".into(), Value::Int(2)),
        ("title".into(), Value::Str("Other's note".into())),
        ("body".into(), Value::Str("Body 2".into())),
        ("ownerSub".into(), Value::Str("other@example.com".into())),
        ("createdAt".into(), Value::Int(now)),
        ("updatedAt".into(), Value::Int(now)),
    ]);
    db.insert(&make_key("notes", 2), note2.to_json().as_bytes())
        .unwrap();
    db.commit().unwrap();
    drop(db);

    // Query through the state (simulated owner filter)
    let mut db = state.db.lock().unwrap();
    let prefix = b"notes:";
    let mut end = prefix.to_vec();
    end.push(0xff);

    let mut owned = Vec::new();
    if let Ok(entries) = db.range(prefix, &end) {
        for (_key, val) in entries {
            if let Ok(json) = akurai_json::parse(std::str::from_utf8(&val).unwrap_or("")) {
                if json.get("ownerSub").and_then(|v| v.as_str()) == Some("olibuijr@olibuijr.com") {
                    owned.push(json);
                }
            }
        }
    }

    assert_eq!(owned.len(), 1, "should only find the owned note");
    assert_eq!(
        owned[0].get("title").and_then(|v| v.as_str()),
        Some("My note")
    );

    cleanup(&db_path);
}

#[test]
fn test_create_note_inserts_with_owner() {
    let (state, db_path) = setup_db("create_owner");
    let mut db = state.db.lock().unwrap();

    fn make_key(coll: &str, id: u64) -> Vec<u8> {
        let mut k = format!("{}:", coll).into_bytes();
        k.extend_from_slice(&id.to_be_bytes());
        k
    }

    let now = NotesState::now();

    let record = Value::Object(vec![
        ("id".into(), Value::Int(1)),
        ("title".into(), Value::Str("Test note".into())),
        ("body".into(), Value::Str("Test body".into())),
        (
            "ownerSub".into(),
            Value::Str("olibuijr@olibuijr.com".into()),
        ),
        ("format".into(), Value::Str("markdown".into())),
        ("notebookId".into(), Value::Int(1)),
        ("tagIds".into(), Value::Str("1,2".into())),
        ("createdAt".into(), Value::Int(now)),
        ("updatedAt".into(), Value::Int(now)),
    ]);

    db.insert(&make_key("notes", 1), record.to_json().as_bytes())
        .unwrap();
    db.commit().unwrap();

    let got = db.get(&make_key("notes", 1)).unwrap().unwrap();
    let got_str = String::from_utf8_lossy(&got);
    assert!(got_str.contains("Test note"));
    assert!(got_str.contains("olibuijr@olibuijr.com"));
    assert!(got_str.contains("1,2"));

    cleanup(&db_path);
}