#![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};
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 env_token_reaches_the_wire_and_an_explicit_token_still_wins() {
unsafe {
std::env::set_var("GH_TOKEN", "env-token");
std::env::set_var("GITHUB_TOKEN", "github-token-must-lose");
}
let mut list = github::ReleaseList::configure();
list.repo_owner("o").repo_name("r");
assert!(
!list.has_auth_token(),
"nothing is set before the call: the listing would run anonymously"
);
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"),
"the env-resolved token must reach the wire with github's `token` scheme"
);
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();
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"));
for (order, list) in [
("env then explicit", {
let mut b = github::ReleaseList::configure();
b.repo_owner("o")
.repo_name("r")
.auth_token_from_env()
.auth_token("explicit");
b
}),
("explicit then env", {
let mut b = github::ReleaseList::configure();
b.repo_owner("o")
.repo_name("r")
.auth_token("explicit")
.auth_token_from_env();
b
}),
] {
let mut list = list;
let header = auth_header_of(|client| {
let _ = list.http_client(client).build().unwrap().fetch();
});
assert_eq!(
header.as_deref(),
Some("token explicit"),
"an explicit auth_token(..) must beat the populated environment ({order})"
);
}
for (order, upd) in [
("env then explicit", {
let mut b = github::Update::configure();
b.repo_owner("o")
.repo_name("r")
.bin_name("app")
.current_version("0.1.0")
.auth_token_from_env()
.auth_token("explicit");
b
}),
("explicit then env", {
let mut b = github::Update::configure();
b.repo_owner("o")
.repo_name("r")
.bin_name("app")
.current_version("0.1.0")
.auth_token("explicit")
.auth_token_from_env();
b
}),
] {
let mut upd = upd;
let header = auth_header_of(|client| {
let _ = upd
.http_client(client)
.build()
.unwrap()
.get_latest_release();
});
assert_eq!(
header.as_deref(),
Some("token explicit"),
"an explicit auth_token(..) must beat the populated environment ({order})"
);
}
}