#![cfg(feature = "github")]
use std::sync::{Arc, Mutex};
use std::time::Duration;
use self_update::backends::github;
use self_update::http_client::{HeaderMap, HttpClient, HttpResponse};
const ENV_TOKEN: &str = "env-token-blank-explicit";
struct CannedResponse;
impl HttpResponse for CannedResponse {
fn headers(&self) -> &HeaderMap {
static EMPTY: std::sync::OnceLock<HeaderMap> = std::sync::OnceLock::new();
EMPTY.get_or_init(HeaderMap::new)
}
fn body(self: Box<Self>) -> Box<dyn std::io::Read> {
Box::new(std::io::Cursor::new(b"[]".to_vec()))
}
}
struct AuthRecorder(Arc<Mutex<Vec<Option<String>>>>);
impl HttpClient for AuthRecorder {
fn get(
&self,
_url: &str,
headers: &HeaderMap,
_timeout: Option<Duration>,
) -> self_update::Result<Box<dyn HttpResponse>> {
self.0.lock().unwrap().push(
headers
.get(self_update::http::header::AUTHORIZATION)
.map(|v| v.to_str().expect("a header value is ASCII").to_string()),
);
Ok(Box::new(CannedResponse))
}
}
fn auth_header_of(f: impl FnOnce(Arc<dyn HttpClient>)) -> Option<String> {
let seen = Arc::new(Mutex::new(Vec::new()));
f(Arc::new(AuthRecorder(seen.clone())));
let seen = seen.lock().unwrap();
assert_eq!(
seen.len(),
1,
"exactly one request must have gone through the transport, got {seen:?}"
);
seen[0].clone()
}
#[test]
fn a_blank_explicit_token_behaves_exactly_like_an_unset_one() {
unsafe {
std::env::set_var("GH_TOKEN", ENV_TOKEN);
}
let mut upd = github::Update::configure();
upd.repo_owner("o")
.repo_name("r")
.bin_name("app")
.current_version("0.1.0")
.auth_token("");
assert!(
!upd.has_auth_token(),
"an empty explicit token must not count as configured"
);
upd.auth_token_from_env();
assert!(
upd.has_auth_token(),
"the env fallback must not be blocked by the blank value"
);
let header = auth_header_of(|client| {
let _ = upd
.http_client(client)
.build()
.unwrap()
.get_latest_release();
});
assert_eq!(
header.as_deref(),
Some("token env-token-blank-explicit"),
"the env-resolved token must reach the wire over the blank explicit one"
);
let mut list = github::ReleaseList::configure();
list.repo_owner("o").repo_name("r").auth_token(" \t ");
assert!(!list.has_auth_token());
list.auth_token_from_env();
assert!(list.has_auth_token());
let header = auth_header_of(|client| {
let _ = list.http_client(client).build().unwrap().fetch();
});
assert_eq!(
header.as_deref(),
Some("token env-token-blank-explicit"),
"an all-whitespace explicit token must not block the fallback either"
);
let mut upd = github::Update::configure();
upd.repo_owner("o")
.repo_name("r")
.bin_name("app")
.current_version("0.1.0")
.auth_token(" ");
assert!(!upd.has_auth_token());
let header = auth_header_of(|client| {
let _ = upd
.http_client(client)
.build()
.unwrap()
.get_latest_release();
});
assert_eq!(
header, None,
"a blank token must produce no Authorization header at all"
);
let mut upd = github::Update::configure();
upd.repo_owner("o")
.repo_name("r")
.bin_name("app")
.current_version("0.1.0")
.auth_token_from_env();
assert!(upd.has_auth_token(), "the env token was picked up first");
upd.auth_token("");
assert!(
!upd.has_auth_token(),
"a blank explicit token replaces the env-sourced one and leaves nothing configured"
);
let header = auth_header_of(|client| {
let _ = upd
.http_client(client)
.build()
.unwrap()
.get_latest_release();
});
assert_eq!(
header, None,
"auth_token(\"\") after auth_token_from_env() must leave the request anonymous"
);
let mut upd = github::Update::configure();
upd.repo_owner("o")
.repo_name("r")
.bin_name("app")
.current_version("0.1.0")
.auth_token_from_env()
.auth_token("explicit-token");
let header = auth_header_of(|client| {
let _ = upd
.http_client(client)
.build()
.unwrap()
.get_latest_release();
});
assert_eq!(
header.as_deref(),
Some("token explicit-token"),
"a real explicit token must still win over the environment in either order"
);
}