Files

484 lines
17 KiB
Rust

use std::{error::Error, path::Path};
use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig};
use ely_domain::{
DomainError, DownloadChecksum, DownloadDestination, DownloadId, DownloadPolicy,
DownloadSecurity, DownloadState, ProfileKind, UrlText,
};
const REPORT_SHA256: &str = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855";
#[test]
fn download_entries_stay_with_active_profile() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let default_profile_id = core.active_tab()?.profile_id().clone();
core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
let default_snapshot = core.snapshot()?;
assert_eq!(default_snapshot.download_entries.len(), 1);
assert_eq!(default_snapshot.download_entries[0].file_name(), "report.pdf");
assert_eq!(default_snapshot.download_entries[0].profile_id(), &default_profile_id);
assert_eq!(default_snapshot.download_entries[0].target_file_path(), None);
core.create_profile("Personal", 0xf54e00, ProfileKind::Standard)?;
let personal_snapshot = core.snapshot()?;
assert!(personal_snapshot.download_entries.is_empty());
core.record_download_started(
UrlText::parse("https://example.com/archive.zip")?,
"archive.zip",
None,
)?;
assert_eq!(core.snapshot()?.download_entries.len(), 1);
core.select_profile(&default_profile_id)?;
let default_snapshot = core.snapshot()?;
assert_eq!(default_snapshot.download_entries.len(), 1);
assert_eq!(default_snapshot.download_entries[0].file_name(), "report.pdf");
Ok(())
}
#[test]
fn download_controls_stay_with_active_profile() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let default_profile_id = core.active_tab()?.profile_id().clone();
let default_download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
core.create_profile("Personal", 0xf54e00, ProfileKind::Standard)?;
let error = match core.cancel_download(&default_download_id) {
Ok(()) => return Err("hidden profile download should stay out of scope".into()),
Err(error) => error,
};
assert_eq!(error, CoreError::DownloadNotFound { id: default_download_id.clone() });
core.select_profile(&default_profile_id)?;
core.cancel_download(&default_download_id)?;
assert_eq!(active_download(&core)?.state(), &DownloadState::Cancelled);
Ok(())
}
#[test]
fn controls_download_lifecycle() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
core.update_download_progress(&download_id, 1024)?;
assert_eq!(active_download(&core)?.received_bytes(), 1024);
core.pause_download(&download_id)?;
assert_eq!(active_download(&core)?.state(), &DownloadState::Paused);
core.resume_download(&download_id)?;
assert_eq!(active_download(&core)?.state(), &DownloadState::InProgress);
core.complete_download(&download_id, 2048)?;
let completed = active_download(&core)?;
assert_eq!(completed.state(), &DownloadState::Completed);
assert_eq!(completed.received_bytes(), 2048);
Ok(())
}
#[test]
fn records_prompted_download_target_path() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let target_path = Path::new("/tmp/ely-prompted-download/report.pdf");
let download_id = core.record_download_started_at_path(
UrlText::parse("https://example.com/report.pdf")?,
target_path,
None,
)?;
let entry = active_download(&core)?;
assert_eq!(entry.id(), &download_id);
assert_eq!(entry.file_name(), "report.pdf");
assert_eq!(entry.destination(), &DownloadDestination::AskEveryTime);
assert_eq!(entry.target_file_path(), Some(target_path));
Ok(())
}
#[test]
fn records_checksum_after_download_completion() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
core.complete_download(&download_id, 2048)?;
core.record_download_checksum(&download_id, DownloadChecksum::sha256_hex(REPORT_SHA256)?)?;
let checksum =
active_download(&core)?.checksum().ok_or("download checksum should exist")?.clone();
assert_eq!(checksum.value(), REPORT_SHA256.to_ascii_lowercase());
Ok(())
}
#[test]
fn rejects_checksum_before_download_completion() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
let error = match core
.record_download_checksum(&download_id, DownloadChecksum::sha256_hex(REPORT_SHA256)?)
{
Ok(()) => return Err("checksum should require completed download".into()),
Err(error) => error,
};
assert_eq!(
error,
CoreError::Domain(DomainError::InvalidDownloadTransition {
action: "record checksum",
state: "in_progress"
})
);
Ok(())
}
#[test]
fn rejects_invalid_sha256_checksum() -> Result<(), Box<dyn Error>> {
let error = match DownloadChecksum::sha256_hex("not-a-sha256") {
Ok(_) => return Err("checksum should require 64 hex characters".into()),
Err(error) => error,
};
assert_eq!(
error,
DomainError::InvalidDownloadChecksum {
algorithm: "sha256",
value: "not-a-sha256".to_string()
}
);
Ok(())
}
#[test]
fn records_active_profile_download_policy_on_started_entry() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let profile_id = core.active_tab()?.profile_id().clone();
let policy = DownloadPolicy::fixed_directory("/tmp/ely-work-downloads")?;
core.set_profile_download_policy(&profile_id, policy.clone())?;
core.record_download_started(
UrlText::parse("https://example.com/installer.dmg")?,
"installer.dmg",
Some(4096),
)?;
let snapshot = core.snapshot()?;
let entry = active_download(&core)?;
assert_eq!(snapshot.active_download_policy, policy);
assert_eq!(entry.destination(), policy.destination());
assert_eq!(entry.target_file_path(), Some(Path::new("/tmp/ely-work-downloads/installer.dmg")));
assert_eq!(entry.security(), &DownloadSecurity::DangerousExtension);
assert!(entry.requires_security_confirmation());
assert!(!entry.security_prompt_confirmed());
Ok(())
}
#[test]
fn active_profile_download_policy_updates_started_entries() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let policy = DownloadPolicy::fixed_directory("/tmp/ely-active-downloads")?;
core.set_active_profile_download_policy(policy.clone())?;
core.record_download_started(
UrlText::parse("https://example.com/manual.pdf")?,
"manual.pdf",
Some(1024),
)?;
let snapshot = core.snapshot()?;
let entry = active_download(&core)?;
assert_eq!(snapshot.active_download_policy, policy);
assert_eq!(entry.destination(), policy.destination());
assert_eq!(entry.target_file_path(), Some(Path::new("/tmp/ely-active-downloads/manual.pdf")));
Ok(())
}
#[test]
fn confirms_dangerous_download_security_prompt() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/installer.dmg")?,
"installer.dmg",
Some(4096),
)?;
core.complete_download(&download_id, 4096)?;
assert!(core.download_requires_security_confirmation(&download_id)?);
core.confirm_download_security_prompt(&download_id)?;
assert!(!core.download_requires_security_confirmation(&download_id)?);
assert!(active_download(&core)?.security_prompt_confirmed());
Ok(())
}
#[test]
fn rejects_security_prompt_confirmation_before_completion() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/installer.dmg")?,
"installer.dmg",
Some(4096),
)?;
let error = match core.confirm_download_security_prompt(&download_id) {
Ok(()) => return Err("security prompt confirmation should require completion".into()),
Err(error) => error,
};
assert_eq!(
error,
CoreError::Domain(DomainError::InvalidDownloadTransition {
action: "confirm security prompt",
state: "in_progress"
})
);
Ok(())
}
#[test]
fn standard_download_skips_security_confirmation() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
core.complete_download(&download_id, 2048)?;
assert!(!core.download_requires_security_confirmation(&download_id)?);
Ok(())
}
#[test]
fn returns_visible_download_target_file_path() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let profile_id = core.active_tab()?.profile_id().clone();
core.set_profile_download_policy(
&profile_id,
DownloadPolicy::fixed_directory("/tmp/ely-work-downloads")?,
)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
assert_eq!(
core.download_target_file_path(&download_id)?,
Path::new("/tmp/ely-work-downloads/report.pdf")
);
Ok(())
}
#[test]
fn clears_downloads_for_active_profile() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let default_profile_id = core.active_tab()?.profile_id().clone();
core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
core.create_profile("Personal", 0xf54e00, ProfileKind::Standard)?;
core.record_download_started(
UrlText::parse("https://example.com/archive.zip")?,
"archive.zip",
Some(4096),
)?;
assert_eq!(core.clear_downloads_for_active_profile(), 1);
assert!(core.snapshot()?.download_entries.is_empty());
core.select_profile(&default_profile_id)?;
let default_snapshot = core.snapshot()?;
assert_eq!(default_snapshot.download_entries.len(), 1);
assert_eq!(default_snapshot.download_entries[0].file_name(), "report.pdf");
assert_eq!(core.clear_downloads_for_active_profile(), 1);
assert!(core.snapshot()?.download_entries.is_empty());
assert_eq!(core.clear_downloads_for_active_profile(), 0);
Ok(())
}
#[test]
fn rejects_target_path_for_ask_every_time_download() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
let error = match core.download_target_file_path(&download_id) {
Ok(_) => return Err("download target path should require a fixed destination".into()),
Err(error) => error,
};
assert_eq!(error, CoreError::DownloadTargetPathUnavailable { id: download_id });
Ok(())
}
#[test]
fn download_policies_stay_with_profile() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let default_profile_id = core.active_tab()?.profile_id().clone();
let default_policy = DownloadPolicy::fixed_directory("/tmp/ely-default-downloads")?;
core.set_profile_download_policy(&default_profile_id, default_policy.clone())?;
let personal_profile_id = core.create_profile("Personal", 0xf54e00, ProfileKind::Standard)?;
assert_eq!(
core.snapshot()?.active_download_policy.destination(),
&DownloadDestination::AskEveryTime
);
let personal_policy = DownloadPolicy::fixed_directory("/tmp/ely-personal-downloads")?;
core.set_profile_download_policy(&personal_profile_id, personal_policy.clone())?;
assert_eq!(core.snapshot()?.active_download_policy, personal_policy);
core.select_profile(&default_profile_id)?;
assert_eq!(core.snapshot()?.active_download_policy, default_policy);
Ok(())
}
#[test]
fn rejects_relative_download_directory() -> Result<(), Box<dyn Error>> {
let error = match DownloadPolicy::fixed_directory("downloads") {
Ok(_) => return Err("relative download directory should be rejected".into()),
Err(error) => error,
};
assert_eq!(error, DomainError::InvalidDownloadDirectory { path: "downloads".to_string() });
Ok(())
}
#[test]
fn rejects_path_like_download_file_name() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let error = match core.record_download_started(
UrlText::parse("https://example.com/evil.sh")?,
"../evil.sh",
None,
) {
Ok(_) => return Err("path-like download file name should be rejected".into()),
Err(error) => error,
};
assert_eq!(
error,
CoreError::Domain(DomainError::InvalidFileName { value: "../evil.sh".to_string() })
);
Ok(())
}
#[test]
fn retries_cancelled_download_from_zero_bytes() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/archive.zip")?,
"archive.zip",
Some(4096),
)?;
core.update_download_progress(&download_id, 1024)?;
core.cancel_download(&download_id)?;
core.retry_download(&download_id)?;
let retried = active_download(&core)?;
assert_eq!(retried.state(), &DownloadState::InProgress);
assert_eq!(retried.received_bytes(), 0);
Ok(())
}
#[test]
fn rejects_invalid_download_transition() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
core.complete_download(&download_id, 2048)?;
let error = match core.pause_download(&download_id) {
Ok(()) => return Err("completed download should reject pause".into()),
Err(error) => error,
};
assert_eq!(
error,
CoreError::Domain(DomainError::InvalidDownloadTransition {
action: "pause",
state: "completed"
})
);
Ok(())
}
#[test]
fn rejects_progress_above_total_bytes() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = core.record_download_started(
UrlText::parse("https://example.com/report.pdf")?,
"report.pdf",
Some(2048),
)?;
let error = match core.update_download_progress(&download_id, 4096) {
Ok(()) => return Err("progress above total should be rejected".into()),
Err(error) => error,
};
assert_eq!(
error,
CoreError::Domain(DomainError::InvalidDownloadProgress {
received_bytes: 4096,
total_bytes: 2048,
})
);
Ok(())
}
#[test]
fn rejects_unknown_download_id() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let download_id = DownloadId::new();
let error = match core.cancel_download(&download_id) {
Ok(()) => return Err("unknown download should be rejected".into()),
Err(error) => error,
};
assert_eq!(error, CoreError::DownloadNotFound { id: download_id });
Ok(())
}
fn active_download(core: &BrowserCore) -> Result<ely_domain::DownloadEntry, Box<dyn Error>> {
core.snapshot()?
.download_entries
.into_iter()
.next()
.ok_or_else(|| "download entry should exist".into())
}