use std::collections::HashSet;
use crate::collection::Collection;
use crate::environment::{Environment, SECRET_MASK};
pub struct CaptureRow {
pub key: String,
pub value: String,
pub shadows_env: bool,
}
fn generated_names(col: &Collection) -> HashSet<&str> {
col.entries
.iter()
.flat_map(|e| e.generators.iter().map(|(name, _)| name.as_str()))
.collect()
}
pub fn capture_rows(col: &Collection, env: Option<&Environment>) -> Vec<CaptureRow> {
let computed = generated_names(col);
let mut rows: Vec<CaptureRow> = col
.captures
.iter()
.filter(|(k, _)| !computed.contains(k.as_str()))
.map(|(k, v)| CaptureRow {
key: k.clone(),
value: v.clone(),
shadows_env: env.is_some_and(|e| e.vars.iter().any(|var| var.key == *k)),
})
.collect();
rows.sort_by(|a, b| a.key.cmp(&b.key));
rows
}
#[cfg_attr(not(feature = "gui"), allow(dead_code))]
pub fn env_var_overridden(col: &Collection, key: &str) -> bool {
col.captures.contains_key(key) && !generated_names(col).contains(key)
}
pub fn is_superseded(col: &Collection, key: &str, value: &str) -> bool {
col.captures.get(key).is_some_and(|live| live != value)
}
pub fn shown_value(value: &str, revealed: bool) -> String {
if revealed {
value.to_string()
} else {
SECRET_MASK.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::environment::{EnvVar, ValueSource};
fn var(key: &str) -> EnvVar {
EnvVar {
key: key.into(),
value: "from-env".into(),
source: ValueSource::Literal,
resolved: true,
loading: false,
original_value: "from-env".into(),
modified: false,
user_added: false,
raw: String::new(),
}
}
fn env_with(keys: &[&str]) -> Environment {
Environment {
id: 1,
name: "e".into(),
vars: keys.iter().map(|k| var(k)).collect(),
path: None,
git_origin: None,
}
}
fn col_with(captures: &[(&str, &str)]) -> Collection {
let mut col = Collection::new("c".to_string(), Vec::new());
for (k, v) in captures {
col.captures.insert((*k).to_string(), (*v).to_string());
}
col
}
#[test]
fn capture_rows_come_back_sorted_by_name() {
let col = col_with(&[("zeta", "1"), ("alpha", "2"), ("mid", "3")]);
let keys: Vec<String> = capture_rows(&col, None)
.into_iter()
.map(|r| r.key)
.collect();
assert_eq!(
keys,
["alpha", "mid", "zeta"],
"a HashMap reshuffles between iterations; the view must not"
);
}
#[test]
fn a_capture_over_an_env_var_is_flagged_on_both_sides() {
let col = col_with(&[("access_token", "from-capture")]);
let env = env_with(&["access_token", "base_url"]);
let rows = capture_rows(&col, Some(&env));
assert_eq!(rows.len(), 1);
assert!(rows[0].shadows_env);
assert!(env_var_overridden(&col, "access_token"));
assert!(!env_var_overridden(&col, "base_url"));
}
#[test]
fn a_capture_with_no_env_var_of_that_name_shadows_nothing() {
let col = col_with(&[("session", "abc")]);
let env = env_with(&["base_url"]);
assert!(!capture_rows(&col, Some(&env))[0].shadows_env);
assert!(!capture_rows(&col, None)[0].shadows_env);
}
#[test]
fn a_generated_value_never_reaches_a_variables_view() {
let mut col = col_with(&[("nonce", "deadbeef"), ("token", "t")]);
col.entries = vec![crate::hurl::HurlEntry {
generators: vec![("nonce".to_string(), "uuid".to_string())],
..Default::default()
}];
let keys: Vec<String> = capture_rows(&col, None)
.into_iter()
.map(|r| r.key)
.collect();
assert_eq!(keys, ["token"], "the computed name must not be listed");
assert!(!env_var_overridden(&col, "nonce"));
assert!(env_var_overridden(&col, "token"));
}
#[test]
fn a_response_capture_is_superseded_only_once_the_pool_moves_on() {
let col = col_with(&[("token", "second")]);
assert!(
is_superseded(&col, "token", "first"),
"the response is showing a value another run has replaced"
);
assert!(!is_superseded(&col, "token", "second"));
assert!(
!is_superseded(&col, "gone", "first"),
"a name no longer in the pool has nothing newer to be superseded by"
);
}
#[test]
fn values_are_masked_until_revealed() {
assert_eq!(shown_value("secret-token", false), SECRET_MASK);
assert_eq!(shown_value("secret-token", true), "secret-token");
}
}