//! v15 is a fence for the `types::v15::RepoState` shape. old rows decode with no resolve time
//! through the serde default, but an older build can't read a stamped row, so it has to refuse
//! the database at open. it also notes when the ttl epoch started. don't drop it as a no-op
use fjall::OwnedWriteBatch;
use miette::Result;
use crate::db::Db;
use crate::db::counts::{IDENTITY_TTL_EPOCH, set_ks_count};
pub(super) fn repo_state_identity_resolved_at(
db: &Db,
batch: &mut OwnedWriteBatch,
) -> Result<bool> {
set_ks_count(
batch,
db,
IDENTITY_TTL_EPOCH,
chrono::Utc::now().timestamp() as u64,
);
Ok(true)
}
#[cfg(test)]
mod tests {
use crate::config::Config;
use crate::db::keys::{self, VERSIONING_KEY};
use crate::db::migration::{LATEST_VERSION, rewind_version_for_test};
use crate::db::{Db, deser_repo_state};
use crate::types::v7;
use jacquard_common::types::did::Did;
use miette::{IntoDiagnostic, Result};
use tempfile::tempdir;
fn version(db: &Db) -> Result<u64> {
let bytes = db
.counts
.get(VERSIONING_KEY)
.into_diagnostic()?
.expect("schema version");
Ok(u64::from_be_bytes(
bytes.as_ref().try_into().into_diagnostic()?,
))
}
fn cfg(path: &std::path::Path) -> Config {
Config {
database_path: path.to_path_buf(),
..Default::default()
}
}
/// relay-only builds never apply v8, so without the fence their version would stay at 7,
/// and a build from before v15 would open the db and fail on every stamped row
#[test]
fn every_build_fences_older_ones_out() -> Result<()> {
let tmp = tempdir().into_diagnostic()?;
let did = Did::new_static("did:plc:ewvi7nxzyoun6zhxrhs64oiz").into_diagnostic()?;
let old_row = rmp_serde::to_vec(&v7::RepoState {
active: true,
status: v7::RepoStatus::Synced,
root: None,
last_message_time: Some(1),
last_identity_time: None,
last_account_time: None,
last_updated_at: 2,
signing_key: None,
pds: Some("https://pds.example/".into()),
handle: None,
})
.into_diagnostic()?;
{
let db = Db::open(&cfg(tmp.path()))?;
db.repos
.insert(keys::repo_key(&did), &old_row)
.into_diagnostic()?;
rewind_version_for_test(&db, 7)?;
db.persist()?;
}
let db = Db::open(&cfg(tmp.path()))?;
assert_eq!(version(&db)?, LATEST_VERSION);
let epoch = crate::db::counts::identity_ttl_epoch(&db)?;
assert!((chrono::Utc::now().timestamp() - epoch).abs() < 60);
let row = db
.repos
.get(keys::repo_key(&did))
.into_diagnostic()?
.expect("repo row");
assert_eq!(
row.as_ref(),
old_row.as_slice(),
"the fence rewrites nothing"
);
assert_eq!(deser_repo_state(&row)?.identity_resolved_at, None);
drop(db);
// and opening it again doesn't move anything, nor trip over what this build skipped
let db = Db::open(&cfg(tmp.path()))?;
assert_eq!(version(&db)?, LATEST_VERSION);
assert_eq!(crate::db::counts::identity_ttl_epoch(&db)?, epoch);
Ok(())
}
}