use std::time::{Duration, SystemTime, UNIX_EPOCH};
use axum::http::StatusCode;
use tokio_postgres::Config;
use tower::util::ServiceExt as _;
use super::harness::{
CapturingSink, FakeProvider, PROVIDER_MATERIAL, ROTATED_MATERIAL, Replica, chat_request, first,
live_material, owner, state_pinning, sweep,
};
use crate::backends::control_plane::postgres::{ControlPlaneSettings, PostgresControlPlane};
use crate::backends::control_plane::{ControlPlaneError, ControlPlaneStore};
use crate::desired_state::{
DesiredState, ExpectedRevision, ResourceVersionNumber, RevisionId, SecretLifecycle, fixtures,
};
use crate::routes::router;
use crate::usage::ObservedRecord;
use crate::usage::journal::{PostgresJournal, PostgresJournalSettings, UsageEvent, UsageJournal};
async fn journal() -> Option<(PostgresControlPlane, String)> {
let dsn = crate::test_services::postgres_dsn()?;
let schema = format!(
"secret_redaction_{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("a monotonic wall clock")
.as_nanos()
);
let mut config: Config = dsn.parse().expect("a parseable test DSN");
config.connect_timeout(Duration::from_secs(5));
let (client, connection) = config
.connect(crate::usage::tls_connector())
.await
.expect("a connection to create the test schema");
tokio::spawn(async move {
let _ = connection.await;
});
client
.batch_execute(&format!("CREATE SCHEMA {schema}"))
.await
.expect("a fresh test schema");
let settings = ControlPlaneSettings {
schema: Some(schema.clone()),
operation_timeout: Duration::from_secs(10),
connect_timeout: Duration::from_secs(5),
..ControlPlaneSettings::default()
};
let store = PostgresControlPlane::connect(&dsn, settings)
.await
.expect("a migrated journal");
Some((store, schema))
}
async fn dump(schema: &str) -> String {
let dsn = crate::test_services::postgres_dsn().expect("a configured DSN");
let mut config: Config = dsn.parse().expect("a parseable test DSN");
config.connect_timeout(Duration::from_secs(5));
let (client, connection) = config
.connect(crate::usage::tls_connector())
.await
.expect("a connection to read the schema back");
tokio::spawn(async move {
let _ = connection.await;
});
let tables = client
.query(
"SELECT tablename FROM pg_tables WHERE schemaname = $1 ORDER BY tablename",
&[&schema.to_owned()],
)
.await
.expect("the schema's tables");
assert!(
!tables.is_empty(),
"the journal's schema has no tables, so the sweep would be vacuous"
);
let mut dumped = String::new();
for table in tables {
let name: String = table.get(0);
dumped.push_str(&format!("-- {schema}.{name}\n"));
let rows = client
.query(
&format!(r#"SELECT t::text FROM "{schema}"."{name}" t"#),
&[],
)
.await
.expect("a table's rows as text");
for row in rows {
let rendered: Option<String> = row.get(0);
dumped.push_str(rendered.as_deref().unwrap_or("(null)"));
dumped.push('\n');
}
}
dumped
}
async fn publish(
store: &PostgresControlPlane,
key: &str,
expected: ExpectedRevision,
state: DesiredState,
) -> Result<RevisionId, ControlPlaneError> {
store
.publish_revision(fixtures::candidate(expected, key, state))
.await
.map(|manifest| manifest.id)
}
#[tokio::test]
async fn no_durable_row_or_read_carries_secret_material() {
let Some((store, schema)) = journal().await else {
return;
};
let sweep = sweep();
let rotated = first().rotated();
let resolved =
live_material(&[(first(), PROVIDER_MATERIAL), (rotated, ROTATED_MATERIAL)]).await;
for (label, plaintext) in [("provider", &resolved[0]), ("rotated", &resolved[1])] {
sweep.assert_present("the material resolved out of the store", label, plaintext);
}
let first_revision = publish(
&store,
"publish-first",
ExpectedRevision::Empty,
state_pinning(first(), ResourceVersionNumber::FIRST),
)
.await
.expect("the first revision publishes");
let rotation = publish(
&store,
"publish-rotation",
ExpectedRevision::Exactly(first_revision),
state_pinning(rotated, ResourceVersionNumber::FIRST.next()),
)
.await
.expect("the rotation publishes");
let replayed = publish(
&store,
"publish-rotation",
ExpectedRevision::Exactly(first_revision),
state_pinning(rotated, ResourceVersionNumber::FIRST.next()),
)
.await
.expect("a replay returns the original outcome");
assert_eq!(replayed, rotation);
let conflict = publish(
&store,
"publish-rotation",
ExpectedRevision::Exactly(first_revision),
state_pinning(first(), ResourceVersionNumber::FIRST.next()),
)
.await
.expect_err("a reused key carrying different state is refused");
assert!(
matches!(conflict, ControlPlaneError::IdempotencyKeyReused { .. }),
"{conflict:?}"
);
sweep.assert_absent("a refused replay", &conflict.to_string());
sweep.assert_absent("a refused replay's Debug", &format!("{conflict:?}"));
for id in [first_revision, rotation] {
let manifest = store.load_manifest(id).await.expect("a retained manifest");
sweep.assert_absent("a revision manifest", &format!("{manifest:?}"));
let loaded = store.load_revision(id).await.expect("a hydrated revision");
sweep.assert_absent("a hydrated revision", &format!("{loaded:?}"));
sweep.assert_absent(
"a hydrated revision's state",
&format!("{:?}", loaded.state()),
);
for event in store.audit_trail(id).await.expect("an audit trail") {
sweep.assert_absent("an audit event", &format!("{event:?}"));
}
}
let desired = store
.load_desired_revision()
.await
.expect("the head hydrates")
.expect("a published head");
sweep.assert_absent("the desired revision", &format!("{desired:?}"));
sweep.assert_absent("the journal's durable rows", &dump(&schema).await);
let rows = dump(&schema).await;
let identifier = rotated.secret.to_string();
let hexed: String = identifier
.bytes()
.map(|byte| format!("{byte:02x}"))
.collect();
assert!(
rows.contains(&identifier) || rows.contains(&hexed),
"the credential's secret reference must be durable, or the sweep proves nothing"
);
drop(resolved);
}
async fn outbox() -> Option<(PostgresJournal, String)> {
let dsn = crate::test_services::postgres_dsn()?;
let schema = format!(
"secret_redaction_outbox_{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("a monotonic wall clock")
.as_nanos()
);
let mut config: Config = dsn.parse().expect("a parseable test DSN");
config.connect_timeout(Duration::from_secs(5));
let (client, connection) = config
.connect(crate::usage::tls_connector())
.await
.expect("a connection to create the test schema");
tokio::spawn(async move {
let _ = connection.await;
});
client
.batch_execute(&format!("CREATE SCHEMA {schema}"))
.await
.expect("a fresh test schema");
let journal = PostgresJournal::connect(
&dsn,
PostgresJournalSettings {
schema: Some(schema.clone()),
create_schema: true,
..PostgresJournalSettings::default()
},
)
.await
.expect("an outbox on the test schema");
Some((journal, schema))
}
#[tokio::test]
async fn no_usage_outbox_row_carries_secret_material() {
let Some((outbox, schema)) = outbox().await else {
return;
};
let sweep = sweep();
let provider = FakeProvider::serving().await;
let usage = CapturingSink::default();
let replica = Replica::with_sinks(&provider, vec![Box::new(usage.clone())]);
replica
.secrets
.seed(owner(), first(), PROVIDER_MATERIAL, SecretLifecycle::Active);
replica
.publish(
"first",
state_pinning(first(), ResourceVersionNumber::FIRST),
)
.await;
replica.converge().await;
let response = router(replica.state.clone())
.oneshot(chat_request())
.await
.expect("a response");
assert_eq!(response.status(), StatusCode::OK);
sweep.assert_present(
"the fake provider",
"provider",
provider.presented().last().expect("a served request"),
);
let records = usage.records();
assert_eq!(records.len(), 1, "{records:?}");
let request_id = records[0].request_id.clone();
for record in records {
let event = UsageEvent::new(ObservedRecord::now(record)).expect("a usage event");
outbox.append(&event).await.expect("a durable append");
}
let rows = dump(&schema).await;
sweep.assert_absent("the usage outbox's durable rows", &rows);
assert!(
rows.contains(&request_id),
"the event must actually be in the outbox, or the sweep proves nothing"
);
}