kcode-k1-access-profile-sqlite 0.3.0

Exact SQLite persistence leaf for K1 saved access profiles
Documentation
use super::*;
use kcode_k1_access_profile_values::ProfileOwner;

fn tx(byte: u8) -> TxId {
    TxId::from_bytes([byte; 12])
}

fn profile() -> AuthorizationProfile {
    AuthorizationProfile::new(vec![ProfileOwner::RequestUser], Vec::new()).unwrap()
}

fn root() -> PathBuf {
    std::env::temp_dir().join(format!("profile-sqlite-test-{}", std::process::id()))
}

#[test]
fn color_actions_privacy_and_rebuildable_state() {
    let root = root();
    fs::remove_dir_all(&root).ok();
    let owner = UserId::from_tx_id(tx(1));
    let stranger = UserId::from_tx_id(tx(9));
    let (database, _) = ProfileDatabase::open(&root).unwrap();
    database
        .apply(
            tx(2),
            ProfileAction::Create {
                owner,
                profile: profile(),
            },
        )
        .unwrap();
    let name = ProfileName::new("Named".to_owned()).unwrap();
    database
        .apply(
            tx(3),
            ProfileAction::CreateNamed {
                owner,
                name: name.clone(),
                profile: profile(),
            },
        )
        .unwrap();
    assert!(
        database
            .list_for_user(owner)
            .unwrap()
            .iter()
            .all(|saved| saved.color().is_none())
    );
    let cyan = ProfileColor::new("cyan".to_owned()).unwrap();
    database
        .apply(
            tx(4),
            ProfileAction::CreateNamedColored {
                owner,
                name,
                color: Some(cyan.clone()),
                profile: profile(),
            },
        )
        .unwrap();
    let id = ProfileId::new(tx(4));
    assert_eq!(database.get_for_user(stranger, id).unwrap(), None);
    assert!(database.list_for_user(stranger).unwrap().is_empty());
    let equal = ProfileAction::SetColor {
        profile_id: id,
        actor: owner,
        color: Some(cyan),
    };
    assert_eq!(
        database.apply(tx(5), equal).unwrap(),
        ApplyOutcome::Unchanged(ProfileRevision::new(id, tx(4)))
    );
    let rose = ProfileColor::new("rose".to_owned()).unwrap();
    let denied = ProfileAction::SetColor {
        profile_id: id,
        actor: stranger,
        color: Some(rose.clone()),
    };
    assert_eq!(database.apply(tx(6), denied).unwrap(), unavailable());
    database
        .apply(
            tx(7),
            ProfileAction::SetColor {
                profile_id: id,
                actor: owner,
                color: Some(rose),
            },
        )
        .unwrap();
    let saved = database.get_for_user(owner, id).unwrap().unwrap();
    assert_eq!(
        (saved.color().unwrap().as_str(), saved.revision()),
        ("rose", ProfileRevision::new(id, tx(7)))
    );
    drop(database);
    let raw = Connection::open(root.join(DATABASE_FILE)).unwrap();
    raw.execute("UPDATE profiles SET color='purple'", [])
        .unwrap();
    drop(raw);
    assert!(matches!(
        ProfileDatabase::open(&root),
        Err(OpenError::Rebuildable)
    ));
    fs::remove_dir_all(&root).unwrap();
    fs::create_dir_all(&root).unwrap();
    let raw = Connection::open(root.join(DATABASE_FILE)).unwrap();
    raw.execute_batch("CREATE TABLE metadata(schema_version INTEGER NOT NULL CHECK(schema_version=1), last_applied_txid BLOB CHECK(last_applied_txid IS NULL OR (typeof(last_applied_txid)='blob' AND length(last_applied_txid)=12)));
CREATE TABLE profiles(profile_id BLOB PRIMARY KEY NOT NULL CHECK(typeof(profile_id)='blob' AND length(profile_id)=12), owner BLOB NOT NULL CHECK(typeof(owner)='blob' AND length(owner)=12), revision BLOB NOT NULL CHECK(typeof(revision)='blob' AND length(revision)=12), name TEXT NOT NULL, profile BLOB NOT NULL CHECK(typeof(profile)='blob')) WITHOUT ROWID;
CREATE INDEX profiles_owner ON profiles(owner);
INSERT INTO metadata(schema_version, last_applied_txid) VALUES(1, NULL);").unwrap();
    drop(raw);
    assert!(matches!(
        ProfileDatabase::open(&root),
        Err(OpenError::Rebuildable)
    ));
    fs::remove_dir_all(root).unwrap();
}