113 lines
4.9 KiB
Rust
113 lines
4.9 KiB
Rust
//! Bounded download and index fetch of the add-on registry, against a fake transport.
|
|
|
|
mod addons_support;
|
|
|
|
use addons_support::{entry, index_json, url};
|
|
use notescraft_engine::addons::{FakeTransport, File, INDEX_URL, IndexCache, Response, Transport, Ureq, download_file, fetch_index, split_code};
|
|
use notescraft_syntax::validate::sha256_hex;
|
|
|
|
const BODY: &[u8] = b"hello grammar";
|
|
const CAP: u64 = 512 * 1024;
|
|
|
|
fn file(link: &str) -> File {
|
|
File { path: "a.txt".into(), size: BODY.len() as u64, sha256: sha256_hex(BODY), url: link.into() }
|
|
}
|
|
|
|
fn good() -> String {
|
|
url("grammar", "x", "1.0.0", "a.txt")
|
|
}
|
|
|
|
fn refused(link: &str) -> String {
|
|
let transport = FakeTransport::new().with(link, BODY);
|
|
download_file(&transport, &file(link), CAP).expect_err("refused").to_string()
|
|
}
|
|
|
|
#[test]
|
|
fn a_listed_file_downloads() {
|
|
let transport = FakeTransport::new().with(&good(), BODY);
|
|
assert_eq!(download_file(&transport, &file(&good()), CAP).unwrap(), BODY);
|
|
}
|
|
|
|
#[test]
|
|
fn hostile_links_never_reach_the_transport() {
|
|
for link in [
|
|
"https://evil.example.com/a.txt".to_owned(),
|
|
good().replacen("https", "http", 1),
|
|
good().replacen("https://", "https://user:pw@", 1),
|
|
good().replacen("git.marcospaulo.dev.br", "127.0.0.1", 1),
|
|
format!("{}?x=1", good()),
|
|
] {
|
|
let transport = FakeTransport::new().with(&link, BODY);
|
|
assert!(download_file(&transport, &file(&link), CAP).is_err(), "{link}");
|
|
assert!(transport.asked().is_empty(), "{link} was fetched");
|
|
}
|
|
assert!(refused("https://evil.example.com/a.txt").contains("registry"));
|
|
}
|
|
|
|
#[test]
|
|
fn a_redirect_status_is_a_failure() {
|
|
let transport = FakeTransport::new().with_response(&good(), Ok(Response { status: 302, etag: None, bytes: Vec::new() }));
|
|
let error = download_file(&transport, &file(&good()), CAP).expect_err("redirect").to_string();
|
|
assert!(error.contains("download_failed"), "{error}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_body_one_byte_too_long_or_short_is_refused() {
|
|
let mut longer = BODY.to_vec();
|
|
longer.push(b'!');
|
|
let transport = FakeTransport::new().with(&good(), &longer);
|
|
assert!(download_file(&transport, &file(&good()), CAP).is_err());
|
|
let transport = FakeTransport::new().with(&good(), &BODY[..BODY.len() - 1]);
|
|
assert!(download_file(&transport, &file(&good()), CAP).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn a_wrong_hash_or_a_size_over_the_cap_is_refused() {
|
|
let transport = FakeTransport::new().with(&good(), b"HELLO grammar");
|
|
assert!(download_file(&transport, &file(&good()), CAP).unwrap_err().to_string().contains("sha256"));
|
|
let transport = FakeTransport::new().with(&good(), BODY);
|
|
assert!(download_file(&transport, &file(&good()), 3).unwrap_err().to_string().contains("cap"));
|
|
}
|
|
|
|
#[test]
|
|
fn no_network_maps_to_the_offline_code() {
|
|
let error = download_file(&FakeTransport::new(), &file(&good()), CAP).unwrap_err().to_string();
|
|
let message = error.strip_prefix("add-on: ").expect("an add-on error");
|
|
assert_eq!(split_code(message).0, "offline");
|
|
assert_eq!(split_code("a gate failed").0, "registry_refused");
|
|
}
|
|
|
|
#[test]
|
|
fn the_real_transport_refuses_plain_http_without_a_request() {
|
|
let error = Ureq::new().get("http://git.marcospaulo.dev.br/x", 10, None).expect_err("http").to_string();
|
|
assert!(error.contains("https"), "{error}");
|
|
}
|
|
|
|
#[test]
|
|
fn a_304_keeps_the_cache_and_a_failed_fetch_changes_nothing() {
|
|
let doc = index_json(&[entry("grammar", "x", "1.0.0", &[("a.txt", BODY)], &[])], &[]);
|
|
let first = FakeTransport::new().with_response(INDEX_URL, Ok(Response { status: 200, etag: Some("\"v1\"".into()), bytes: doc.clone() }));
|
|
let cache = fetch_index(&first, None, 100).unwrap();
|
|
assert_eq!((cache.fetched_at, cache.etag.as_deref()), (100, Some("\"v1\"")));
|
|
let again = FakeTransport::new().with_response(INDEX_URL, Ok(Response { status: 304, etag: None, bytes: Vec::new() }));
|
|
let kept = fetch_index(&again, Some(&cache), 200).unwrap();
|
|
assert_eq!((kept.fetched_at, kept.body.as_str(), kept.etag.as_deref()), (200, cache.body.as_str(), Some("\"v1\"")));
|
|
let down = fetch_index(&FakeTransport::new(), Some(&cache), 300).expect_err("offline").to_string();
|
|
assert!(down.contains("offline"), "{down}");
|
|
let junk = FakeTransport::new().with(INDEX_URL, b"not json");
|
|
assert!(fetch_index(&junk, Some(&cache), 300).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn the_cache_round_trips_on_disk() {
|
|
let dir = std::env::temp_dir().join(format!("notescraft-addons-net-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
assert_eq!(IndexCache::load(&dir).unwrap(), None);
|
|
let cache = IndexCache { fetched_at: 5, etag: None, body: String::from_utf8(index_json(&[], &[])).unwrap() };
|
|
cache.save(&dir).unwrap();
|
|
assert_eq!(IndexCache::load(&dir).unwrap(), Some(cache));
|
|
std::fs::write(dir.join("index-cache.json"), b"{").unwrap();
|
|
assert!(IndexCache::load(&dir).is_err());
|
|
std::fs::remove_dir_all(&dir).unwrap();
|
|
}
|