swarm-controller: inject the account store so link-route tests use the fake
The seven linked-account handlers (matrix, forge and github link and delete, and the list) read their store from `AppState::accounts` instead of calling `store::connect()` themselves. In production that is `Accounts::Live`, which still logs in to the swarm's secret store on every request: a `SecretStore` holds a token it never renews, and a store that is down at startup must fail only the requests made while it is down. Tests set `Accounts::Fake` over the in-memory `FakeStore`. The eight handler tests that proved validation passed by expecting the store connect to fail with 500 now assert that the write or delete landed in the fake, and lose their `BAO_*`-must-be-unset guards: with `BAO_*` set they no longer reach a real store.
This commit is contained in:
parent
c531d39537
commit
dceb803bca
5 changed files with 184 additions and 98 deletions
|
|
@ -166,7 +166,7 @@ pub async fn put_matrix_account(
|
|||
));
|
||||
}
|
||||
|
||||
let store = crate::store::connect().await.map_err(|e| {
|
||||
let store = state.accounts.open().await.map_err(|e| {
|
||||
tracing::warn!(error = %e, "connecting to the swarm secret store failed");
|
||||
error_problem(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
|
||||
})?;
|
||||
|
|
@ -425,6 +425,9 @@ pub(crate) async fn matrix_logout(homeserver: &str, token: &str) -> Result<(), S
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::linked_accounts::{Accounts, tests::FakeStore};
|
||||
use super::{
|
||||
PutMatrixAccountRequest, homeserver_or_configured_default, is_reserved_account,
|
||||
matrix_logout, password_fields, resolve_credential, token_credential,
|
||||
|
|
@ -547,9 +550,9 @@ mod tests {
|
|||
}
|
||||
|
||||
/// Bare-minimum `AppState` for a handler test: one hive, nothing
|
||||
/// queue-backed, and an empty in-memory job graph the endpoint under test
|
||||
/// never touches.
|
||||
fn state() -> super::super::AppState {
|
||||
/// queue-backed, an empty in-memory job graph the endpoint under test
|
||||
/// never touches, and `store` as the account store.
|
||||
fn state(store: &Arc<FakeStore>) -> super::super::AppState {
|
||||
super::super::AppState {
|
||||
hives: std::sync::Arc::new(vec![super::super::HiveEntry {
|
||||
name: "pr1ma".to_owned(),
|
||||
|
|
@ -570,21 +573,16 @@ mod tests {
|
|||
auth: None,
|
||||
forge: None,
|
||||
create_gate: std::sync::Arc::default(),
|
||||
accounts: Accounts::Fake(Arc::clone(store)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Refused before the store is reached: with `BAO_*` unset a store
|
||||
/// connect would answer 500, so a 400 is the reserved-name check.
|
||||
/// Refused before the store is reached.
|
||||
#[tokio::test]
|
||||
async fn a_reserved_account_name_is_refused_before_the_store() {
|
||||
for var in ["BAO_ADDR", "BAO_CLIENT_CERT", "BAO_CLIENT_KEY"] {
|
||||
assert!(
|
||||
std::env::var(var).is_err(),
|
||||
"{var} must be unset for this test to prove anything"
|
||||
);
|
||||
}
|
||||
let store = Arc::default();
|
||||
let result = super::put_matrix_account(
|
||||
axum::extract::State(state()),
|
||||
axum::extract::State(state(&store)),
|
||||
axum::extract::Path(("pr1ma".to_owned(), "atlas".to_owned(), "main".to_owned())),
|
||||
axum::Json(PutMatrixAccountRequest {
|
||||
mode: "token".to_owned(),
|
||||
|
|
@ -602,15 +600,19 @@ mod tests {
|
|||
Some(axum::http::StatusCode::BAD_REQUEST),
|
||||
"{problem:?}"
|
||||
);
|
||||
assert!(store.written().is_empty());
|
||||
}
|
||||
|
||||
/// The control for the test above: an ordinary name gets past every check
|
||||
/// and fails at the store connect, so the 400 above is not what every call
|
||||
/// answers.
|
||||
/// and is written, so the 400 above is not what every call answers.
|
||||
#[tokio::test]
|
||||
async fn an_ordinary_account_name_reaches_the_store() {
|
||||
use super::super::linked_accounts::AccountStore;
|
||||
use swarm_secret_client::matrix;
|
||||
|
||||
let store = Arc::default();
|
||||
let result = super::put_matrix_account(
|
||||
axum::extract::State(state()),
|
||||
axum::extract::State(state(&store)),
|
||||
axum::extract::Path((
|
||||
"pr1ma".to_owned(),
|
||||
"atlas".to_owned(),
|
||||
|
|
@ -626,19 +628,23 @@ mod tests {
|
|||
)
|
||||
.await;
|
||||
|
||||
let problem = result.expect_err("no store is configured in a test");
|
||||
assert_eq!(
|
||||
problem.status,
|
||||
Some(axum::http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||
"{problem:?}"
|
||||
);
|
||||
let response = result.expect("nothing is stored");
|
||||
assert_eq!(response.user_id, None);
|
||||
let path = "swarm/agents/atlas/matrix/workaccount";
|
||||
assert_eq!(store.written(), [path]);
|
||||
let stored: matrix::Credential = store
|
||||
.read_optional(path)
|
||||
.await
|
||||
.expect("store answers")
|
||||
.expect("stored");
|
||||
assert_eq!(stored.value, "t0k3n");
|
||||
}
|
||||
|
||||
/// The second link is password mode against a port nothing listens on:
|
||||
/// a login attempt would answer 400, so the 409 is the check running first.
|
||||
#[tokio::test]
|
||||
async fn linking_a_name_twice_is_a_409_and_the_first_account_stays() {
|
||||
use super::super::linked_accounts::{AccountStore, tests::FakeStore};
|
||||
use super::super::linked_accounts::AccountStore;
|
||||
use swarm_secret_client::matrix;
|
||||
|
||||
let store = FakeStore::default();
|
||||
|
|
|
|||
Loading…
Reference in a new issue