223 lines
9.0 KiB
Rust
223 lines
9.0 KiB
Rust
mod tests {
|
|
use super::*;
|
|
#[tokio::test]
|
|
async fn persists_and_rejects_stale_writes() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let file = dir.path().join("test.sqlite3");
|
|
let store = Store::open(&file, &Config::default()).await.unwrap();
|
|
assert_eq!(store.load().await.unwrap().revision, 0);
|
|
let mut config = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
|
config.cache.memory_preset = Some(crate::cache::MemoryPreset::Recommended);
|
|
config.cache.cold_preset = Some(crate::cache::ColdPreset::Recommended);
|
|
config.transform.preset = Some(crate::transform::Preset::Performance);
|
|
config.transform.memory_budget_bytes = 512 * 1024 * 1024;
|
|
config.transform.max_inflight = 64;
|
|
assert!(store.replace(0, &config).await.unwrap());
|
|
assert!(!store.replace(0, &Config::default()).await.unwrap());
|
|
assert_eq!(store.recent_audit().await.unwrap().len(), 1);
|
|
store.close().await;
|
|
let reopened = Store::open(&file, &Config::default()).await.unwrap();
|
|
let data = reopened.load().await.unwrap();
|
|
assert_eq!(data.revision, 1);
|
|
assert_eq!(data.config.vhosts[0].backends[0].id, "demo");
|
|
assert_eq!(
|
|
data.config.cache.memory_preset,
|
|
Some(crate::cache::MemoryPreset::Recommended)
|
|
);
|
|
assert_eq!(
|
|
data.config.cache.cold_preset,
|
|
Some(crate::cache::ColdPreset::Recommended)
|
|
);
|
|
assert_eq!(
|
|
data.config.transform.preset,
|
|
Some(crate::transform::Preset::Performance)
|
|
);
|
|
assert_eq!(data.config.transform.memory_budget_bytes, 512 * 1024 * 1024);
|
|
assert_eq!(data.config.transform.max_inflight, 64);
|
|
reopened.close().await;
|
|
}
|
|
#[tokio::test]
|
|
async fn configuration_history_keeps_revision_documents_and_actor() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let file = dir.path().join("history.sqlite3");
|
|
let store = Store::open(&file, &Config::default()).await.unwrap();
|
|
let mut first = Config::default();
|
|
first.server_header.value = "first".into();
|
|
assert!(store.replace_as(0, &first, "alice").await.unwrap());
|
|
let mut second = first.clone();
|
|
second.server_header.value = "second".into();
|
|
assert!(store.replace_as(1, &second, "bob").await.unwrap());
|
|
let history = store.configuration_history(10).await.unwrap();
|
|
assert_eq!(history[0].revision, 2);
|
|
assert_eq!(history[0].actor, "bob");
|
|
assert_eq!(history[1].revision, 1);
|
|
let detail = store.configuration_revision(2).await.unwrap().unwrap();
|
|
assert_eq!(detail.config.server_header.value, "second");
|
|
assert_eq!(detail.previous_revision, Some(1));
|
|
assert_eq!(detail.previous_config.unwrap().server_header.value, "first");
|
|
store.close().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn certificate_renewal_observations_survive_reopen_in_schema_v3() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let file = dir.path().join("renewals.sqlite3");
|
|
let store = Store::open(&file, &Config::default()).await.unwrap();
|
|
let mut values = std::collections::BTreeMap::new();
|
|
values.insert(
|
|
"example-cert".to_string(),
|
|
crate::acme::RenewalState {
|
|
last_checked: 100,
|
|
next_check: 200,
|
|
last_renewed: Some(50),
|
|
not_after: Some(500),
|
|
failures: 2,
|
|
status: "retry".to_string(),
|
|
message: "temporary CA failure".to_string(),
|
|
},
|
|
);
|
|
store.replace_certificate_renewals(&values).await.unwrap();
|
|
let version: i64 = sqlx::query_scalar("PRAGMA user_version")
|
|
.fetch_one(&store.pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(version, SCHEMA_VERSION);
|
|
store.close().await;
|
|
|
|
let reopened = Store::open(&file, &Config::default()).await.unwrap();
|
|
let saved = reopened.load_certificate_renewals().await.unwrap();
|
|
let state = saved.get("example-cert").unwrap();
|
|
assert_eq!(state.last_checked, 100);
|
|
assert_eq!(state.next_check, 200);
|
|
assert_eq!(state.last_renewed, Some(50));
|
|
assert_eq!(state.not_after, Some(500));
|
|
assert_eq!(state.failures, 2);
|
|
assert_eq!(state.status, "retry");
|
|
assert_eq!(state.message, "temporary CA failure");
|
|
reopened.close().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn certificate_api_credentials_persist_hash_comments_and_individual_usage() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let file = dir.path().join("certificate-api.sqlite3");
|
|
let store = Store::open(&file, &Config::default()).await.unwrap();
|
|
let token = "b".repeat(64);
|
|
let hash = crate::auth::secret_hash(&token);
|
|
let saved = store
|
|
.upsert_certificate_api_credential(
|
|
"certbot-main",
|
|
&hash,
|
|
"production certbot",
|
|
"owner",
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(saved.key_id, "certbot-main");
|
|
assert_eq!(saved.comment, "production certbot");
|
|
assert!(saved.last_used_at.is_none());
|
|
|
|
let raw: String = sqlx::query_scalar(
|
|
"SELECT token_hash FROM certificate_api_credentials WHERE key_id = ?",
|
|
)
|
|
.bind("certbot-main")
|
|
.fetch_one(&store.pool)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(raw, hash);
|
|
assert_ne!(raw, token);
|
|
|
|
let second_hash = crate::auth::secret_hash(&"d".repeat(64));
|
|
store
|
|
.upsert_certificate_api_credential(
|
|
"deploy-hook",
|
|
&second_hash,
|
|
"secondary deploy hook",
|
|
"owner",
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(store.certificate_api_credentials().await.unwrap().len(), 2);
|
|
|
|
assert!(store
|
|
.verify_certificate_api_credential("certbot-main", &hash)
|
|
.await
|
|
.unwrap());
|
|
assert!(!store
|
|
.verify_certificate_api_credential(
|
|
"certbot-main",
|
|
&crate::auth::secret_hash(&"c".repeat(64)),
|
|
)
|
|
.await
|
|
.unwrap());
|
|
let credentials = store.certificate_api_credentials().await.unwrap();
|
|
assert!(credentials
|
|
.iter()
|
|
.find(|item| item.key_id == "certbot-main")
|
|
.unwrap()
|
|
.last_used_at
|
|
.is_some());
|
|
assert!(credentials
|
|
.iter()
|
|
.find(|item| item.key_id == "deploy-hook")
|
|
.unwrap()
|
|
.last_used_at
|
|
.is_none());
|
|
|
|
assert!(store
|
|
.revoke_certificate_api_credential("certbot-main", "owner")
|
|
.await
|
|
.unwrap());
|
|
let remaining = store.certificate_api_credentials().await.unwrap();
|
|
assert_eq!(remaining.len(), 1);
|
|
assert_eq!(remaining[0].key_id, "deploy-hook");
|
|
assert_eq!(store.revoke_all_certificate_api_credentials("owner").await.unwrap(), 1);
|
|
assert!(store.certificate_api_credentials().await.unwrap().is_empty());
|
|
store.close().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn existing_empty_config_is_not_reseeded() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let file = dir.path().join("test.sqlite3");
|
|
let store = Store::open(&file, &Config::default()).await.unwrap();
|
|
store.close().await;
|
|
let demo = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
|
let store = Store::open(&file, &demo).await.unwrap();
|
|
assert!(store.load().await.unwrap().config.vhosts.is_empty());
|
|
store.close().await;
|
|
}
|
|
#[tokio::test]
|
|
async fn migration_keeps_v1_config_and_revision() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = dir.path().join("old.sqlite3");
|
|
let options = SqliteConnectOptions::new()
|
|
.filename(&path)
|
|
.create_if_missing(true);
|
|
let pool = SqlitePoolOptions::new()
|
|
.max_connections(1)
|
|
.connect_with(options)
|
|
.await
|
|
.unwrap();
|
|
sqlx::query("CREATE TABLE configuration(id INTEGER PRIMARY KEY, revision INTEGER NOT NULL, document TEXT NOT NULL)")
|
|
.execute(&pool).await.unwrap();
|
|
let demo = crate::config::demo_config("127.0.0.1:9000".parse().unwrap());
|
|
sqlx::query("INSERT INTO configuration VALUES(1, 42, ?)")
|
|
.bind(serde_json::to_string(&demo).unwrap())
|
|
.execute(&pool)
|
|
.await
|
|
.unwrap();
|
|
sqlx::query("PRAGMA user_version = 1")
|
|
.execute(&pool)
|
|
.await
|
|
.unwrap();
|
|
pool.close().await;
|
|
let store = Store::open(&path, &Config::default()).await.unwrap();
|
|
let saved = store.load().await.unwrap();
|
|
assert_eq!(saved.revision, 42);
|
|
assert_eq!(saved.config.vhosts[0].backends[0].id, "demo");
|
|
assert!(store.admin_record().await.unwrap().is_none());
|
|
store.close().await;
|
|
}
|
|
}
|