arminius_lib_nats/
test_utils.rs1#![expect(
7 clippy::indexing_slicing,
8 clippy::expect_used,
9 reason = "This is test code, so this is fine"
10)]
11
12mod tests;
13
14use std::{env, error};
15
16use async_nats::{Client, ConnectError as NatsError};
17use color_eyre::eyre::Context as _;
18use nats_io_jwt::{KeyPair, OperatorLimits};
19use rand::distr::{Alphanumeric, SampleString as _};
20
21use crate::{
22 NatsConfiguration, NatsCredentials, NatsJwtCredentials, SharedNatsClient,
23 auth_management::{create_account, create_user},
24};
25
26impl NatsConfiguration {
27 pub async fn for_test() -> NatsConfiguration {
29 let credentials =
30 create_nats_account_for_test(&Self::nats_url_from_env().expect("missing env var")[0])
31 .await
32 .expect("error creating NATS account for testing");
33 NatsConfiguration::with_credentials(credentials)
34 .expect("error creating NATS configuration for test")
35 }
36
37 pub async fn connect_for_test(&self) -> SharedNatsClient {
40 self.connect("test".into(), "1.0.0".into(), "description".into())
41 .await
42 .expect("error connecting to NATS for test")
43 }
44}
45
46pub async fn create_nats_account_for_test(
61 url: &str,
62) -> Result<NatsCredentials, Box<dyn error::Error>> {
63 let system_credentials =
64 NatsSystemCredentials::from_env().context("NATS system credentials not configured")?;
65
66 let (account_name, user_name) = {
67 let name = Alphanumeric.sample_string(&mut rand::rng(), 16);
68 (name.to_ascii_uppercase(), name.to_ascii_lowercase())
69 };
70
71 let (test_account_key, test_account_token) = create_account(
72 &system_credentials.operator_account_seed,
73 Some(OperatorLimits::builder().conn(8).try_into()?),
75 &account_name,
76 )?;
77
78 let nc = authenticate_as_sys(url, &system_credentials).await.unwrap_or_else(|err| {
80 panic!("failed to authenticating to NATS server using system credentials: {err:?}")
81 });
82
83 let response = nc.request("$SYS.REQ.CLAIMS.UPDATE", test_account_token.into()).await?;
85
86 tracing::trace!("register account response: {response:?}");
87
88 let (test_user_key, test_user_token) = create_user(&test_account_key, &user_name)?;
90
91 let nats_credentials = NatsJwtCredentials { key: test_user_key, token: test_user_token };
92
93 Ok(nats_credentials.into())
96}
97
98async fn authenticate_as_sys(
102 nats_url: &str,
103 credentials: &NatsSystemCredentials,
104) -> Result<Client, NatsError> {
105 authenticate_via_jwt(nats_url, &credentials.system_user_seed, &credentials.system_user_jwt)
106 .await
107}
108
109async fn authenticate_via_jwt(
111 nats_url: &str,
112 user_key: &KeyPair,
113 jwt: impl Into<String>,
114) -> Result<Client, NatsError> {
115 let user_key: KeyPair = user_key.to_owned();
116 let options = async_nats::ConnectOptions::with_jwt(jwt.into(), move |nonce| {
117 tracing::trace!("signing server nonce: {nonce:?}");
118 let user_key = user_key.clone();
119 async move { user_key.sign(&nonce).map_err(async_nats::AuthError::new) }
120 });
121 async_nats::connect_with_options(nats_url, options).await
122}
123
124struct NatsSystemCredentials {
131 system_user_seed: KeyPair,
134 system_user_jwt: String,
136 operator_account_seed: KeyPair,
138}
139
140impl NatsSystemCredentials {
141 fn from_env() -> color_eyre::eyre::Result<NatsSystemCredentials> {
143 fn var(name: &str) -> color_eyre::eyre::Result<String> {
144 env::var(name).context(format!("missing env var: {name}"))
145 }
146
147 let operator_account_seed = KeyPair::from_seed(&var("NATS_OPERATOR_ACCOUNT_SEED")?)
149 .context("invalid NATS operator account seed")?;
150 let system_user_seed = KeyPair::from_seed(&var("NATS_SYSTEM_USER_SEED")?)
152 .context("invalid NATS system user seed")?;
153 let system_user_jwt = var("NATS_SYSTEM_USER_JWT")?;
155
156 Ok(NatsSystemCredentials { operator_account_seed, system_user_seed, system_user_jwt })
157 }
158}