rws-server 18.27.1

The server layer of rust-web-server (internal: use the rust-web-server crate).
Documentation
use super::*;
use crate::authz::{Authorizer, Schema};

fn memory_store() -> SqlStore {
    SqlStore::new(run(async { DbPool::memory().await }).unwrap().unwrap()).unwrap()
}

fn file_config(path: &str) -> DbConfig {
    DbConfig {
        host: String::new(),
        port: 0,
        user: String::new(),
        password: String::new(),
        database: format!("{path}?mode=rwc"),
        pool_size: 2,
        backend: Backend::Sqlite,
    }
}

fn tuple(text: &str) -> Tuple {
    Tuple::parse(text).unwrap()
}

#[test]
fn tuples_are_written_read_listed_and_deleted() {
    let store = memory_store();
    assert!(store.write(&tuple("doc:1#owner@user:a")).unwrap());
    assert!(!store.write(&tuple("doc:1#owner@user:a")).unwrap(), "already there");
    assert!(store.write(&tuple("doc:1#viewer@group:eng#member")).unwrap());
    assert!(store.write(&tuple("doc:2#owner@user:a")).unwrap());
    let object = ObjectRef::parse("doc:1").unwrap();
    assert_eq!(store.read(&object, "owner").unwrap(), [Subject::parse("user:a").unwrap()]);
    assert_eq!(store.read(&object, "viewer").unwrap(), [Subject::parse("group:eng#member").unwrap()]);
    let by_a = TupleFilter { subject: Some(Subject::parse("user:a").unwrap()), ..Default::default() };
    assert_eq!(store.list(&by_a, 100).unwrap().iter().map(ToString::to_string).collect::<Vec<_>>(), ["doc:1#owner@user:a", "doc:2#owner@user:a"]);
    assert_eq!(store.list(&TupleFilter::default(), 2).unwrap().len(), 2);
    assert!(store.delete(&tuple("doc:1#owner@user:a")).unwrap());
    assert!(store.read(&object, "owner").unwrap().is_empty(), "a write clears the read cache");
    assert!(!store.delete(&tuple("doc:1#owner@user:a")).unwrap());
}

#[test]
fn checks_run_against_the_database_and_tuples_outlive_the_store() {
    let path = crate::test_env::TempPath::new("authz_sql_tuples.db");
    let schema = || {
        Schema::new()
            .relation("group", "member", "user | group#member").unwrap()
            .relation("doc", "editor", "user | group#member").unwrap()
            .relation("doc", "viewer", "user | editor").unwrap()
    };
    {
        let authz = Authorizer::new(schema(), SqlStore::open(file_config(path.as_str())).unwrap()).unwrap();
        for t in ["group:eng#member@user:gina", "doc:42#editor@group:eng#member"] {
            authz.write_str(t).unwrap();
        }
        assert!(authz.check("user:gina", "viewer", "doc:42").unwrap());
    }
    // Opened again (as after a restart): the table exists, the tuples too.
    let authz = Authorizer::new(schema(), SqlStore::open(file_config(path.as_str())).unwrap()).unwrap();
    assert!(authz.check("user:gina", "viewer", "doc:42").unwrap());
    assert!(!authz.check("user:eve", "viewer", "doc:42").unwrap());
}

#[test]
fn reads_are_cached_briefly() {
    let store = memory_store();
    store.write(&tuple("doc:1#owner@user:a")).unwrap();
    let object = ObjectRef::parse("doc:1").unwrap();
    assert_eq!(store.read(&object, "owner").unwrap().len(), 1);
    // Behind the store's back (another process): seen only after READ_CACHE.
    let pool = store.pool.clone();
    run(async move { pool.execute("DELETE FROM rws_authz_tuples", &[]).await }).unwrap().unwrap();
    assert_eq!(store.read(&object, "owner").unwrap().len(), 1, "cached");
    std::thread::sleep(READ_CACHE + Duration::from_millis(50));
    assert!(store.read(&object, "owner").unwrap().is_empty());
}