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
|
|
@ -55,7 +55,7 @@ pub async fn put_github_account(
|
|||
.map_err(|e| error_problem(StatusCode::BAD_REQUEST, &e.to_string()))?;
|
||||
let credential = credential(&req).map_err(|e| error_problem(StatusCode::BAD_REQUEST, e))?;
|
||||
|
||||
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())
|
||||
})?;
|
||||
|
|
@ -87,6 +87,9 @@ fn credential(req: &PutGithubAccountRequest) -> Result<github::Credential, &'sta
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::linked_accounts::{Accounts, tests::FakeStore};
|
||||
use super::{PutGithubAccountRequest, credential};
|
||||
|
||||
fn request(token: &str) -> PutGithubAccountRequest {
|
||||
|
|
@ -108,7 +111,7 @@ mod tests {
|
|||
}
|
||||
|
||||
/// Bare-minimum `AppState`, as `forge_account`'s tests build it.
|
||||
fn state() -> super::super::AppState {
|
||||
fn state(store: &Arc<FakeStore>) -> super::super::AppState {
|
||||
super::super::AppState {
|
||||
hives: std::sync::Arc::new(vec![super::super::HiveEntry {
|
||||
name: "pr1ma".to_owned(),
|
||||
|
|
@ -129,58 +132,61 @@ mod tests {
|
|||
auth: None,
|
||||
forge: None,
|
||||
create_gate: std::sync::Arc::default(),
|
||||
accounts: Accounts::Fake(Arc::clone(store)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn put(agent: &str, token: &str) -> problem_details::ProblemDetails {
|
||||
async fn put(
|
||||
store: &Arc<FakeStore>,
|
||||
agent: &str,
|
||||
token: &str,
|
||||
) -> Result<axum::http::StatusCode, problem_details::ProblemDetails> {
|
||||
super::put_github_account(
|
||||
axum::extract::State(state()),
|
||||
axum::extract::State(state(store)),
|
||||
axum::extract::Path(("pr1ma".to_owned(), agent.to_owned())),
|
||||
axum::Json(request(token)),
|
||||
)
|
||||
.await
|
||||
.expect_err("no store is configured in a test")
|
||||
}
|
||||
|
||||
fn assert_store_unset() {
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An agent name or an empty token is refused before the store: with
|
||||
/// `BAO_*` unset a store connect would answer 500.
|
||||
/// An agent name or an empty token is refused before the store.
|
||||
#[tokio::test]
|
||||
async fn a_bad_agent_or_an_empty_token_is_refused_before_the_store() {
|
||||
assert_store_unset();
|
||||
let store = Arc::default();
|
||||
for (agent, token) in [("Atlas", "t0k3n"), ("../x", "t0k3n"), ("atlas", " ")] {
|
||||
let problem = put(agent, token).await;
|
||||
let problem = put(&store, agent, token).await.expect_err("refused");
|
||||
assert_eq!(
|
||||
problem.status,
|
||||
Some(axum::http::StatusCode::BAD_REQUEST),
|
||||
"{agent:?}: {problem:?}"
|
||||
);
|
||||
}
|
||||
assert!(store.written().is_empty());
|
||||
}
|
||||
|
||||
/// The control: a plain agent and a token reach the store connect.
|
||||
/// The control: a plain agent and a token are written.
|
||||
#[tokio::test]
|
||||
async fn a_plain_request_reaches_the_store() {
|
||||
assert_store_unset();
|
||||
let problem = put("atlas", "t0k3n").await;
|
||||
assert_eq!(
|
||||
problem.status,
|
||||
Some(axum::http::StatusCode::INTERNAL_SERVER_ERROR),
|
||||
"{problem:?}"
|
||||
);
|
||||
use super::super::linked_accounts::AccountStore;
|
||||
use swarm_secret_client::github;
|
||||
|
||||
let store = Arc::default();
|
||||
let status = put(&store, "atlas", "t0k3n")
|
||||
.await
|
||||
.expect("nothing is stored");
|
||||
assert_eq!(status, axum::http::StatusCode::NO_CONTENT);
|
||||
assert_eq!(store.written(), ["swarm/agents/atlas/github-token"]);
|
||||
let stored: github::Credential = store
|
||||
.read_optional("swarm/agents/atlas/github-token")
|
||||
.await
|
||||
.expect("store answers")
|
||||
.expect("stored");
|
||||
assert_eq!(stored.value, "t0k3n");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn linking_a_second_token_is_a_409_and_the_first_stays() {
|
||||
use super::super::linked_accounts::{AccountStore, link, tests::FakeStore};
|
||||
use super::super::linked_accounts::{AccountStore, link};
|
||||
use swarm_secret_client::github;
|
||||
|
||||
let store = FakeStore::default();
|
||||
|
|
|
|||
Loading…
Reference in a new issue