mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-22 01:15:14 +08:00
feat(official-sync): 错误码模型铺开到 launcher/metadata 拉取路径
此前统一错误码只覆盖资源下载路径,launcher/metadata 链 (fetch_game_config → manifest URL → remote manifest → resources.assets → GameMainConfig)整条返回 Result<_, String>,经 anyhow::Error::msg 抹成无类型消息,RPC/任务层只能标为 internal。 本次将该链整体改为 Result<_, DownloadError>: - core 新增两个错误码:LAUNCHER_API_REJECTED(300031,官方启动器 API 返回非 200 业务码)与 LAUNCHER_RESPONSE_INVALID(600004,响应无法 解析或缺少必需字段),并纳入唯一性测试; - official_launcher:curl 失败保留准确网络域码(403/404/DNS/超时/TLS/ 代理等,final_error_code()),API 非 200 → LAUNCHER_API_REJECTED, 响应解析/缺字段 → LAUNCHER_RESPONSE_INVALID,非官方 manifest URL → NON_OFFICIAL_URL,manifest 无文件条目 → MANIFEST_PARSE_FAILED, 空版本/路径入参 → INVALID_ARGUMENT; - official_game_main_config:下载失败带网络域码(主/备 CDN 均失败时 以最终一次为准),ZIP 结构 → ZIP_STRUCTURE_INVALID,manifest 大小 不符 → SIZE_MISMATCH,GameMainConfig 解密/解析 → GAME_MAIN_CONFIG_FAILED,manifest 缺 source → LAUNCHER_RESPONSE_INVALID; - official_update 两处调用改 anyhow::Error::msg → anyhow::Error::new, 保留类型,task worker 现有 downcast 直接接住 launcher 路径错误码; - USERGUIDE 错误码表补录 300031 与 600004。 内部工具函数(launcher_package_url / launcher_api_url / launcher_authorization_header)仍返回 String,经 From<String> 归 INTERNAL——这些是硬编码常量上的内部不变量校验,非运行时主要失败面。 验证:新增 4 个单测断言映射(API 403 业务码 → 300031、非法 JSON → 600004、HTTP 404 → 300002、非官方 URL → 300030);全量 fmt / clippy --workspace --all-targets -D warnings / test --workspace 全绿(含 examples 编译)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ pub struct DownloadError {
|
||||
}
|
||||
|
||||
impl DownloadError {
|
||||
fn new(code: bat_core::ErrorCode, message: impl Into<String>) -> Self {
|
||||
pub(crate) fn new(code: bat_core::ErrorCode, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
code,
|
||||
message: message.into(),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! `GameMainConfig`, and does not install or execute the official launcher.
|
||||
|
||||
use crate::curl_transfer::{run_curl_with_retry_with_proxy, CurlProxyConfig};
|
||||
use crate::official_download::DownloadError;
|
||||
use crate::official_launcher::launcher_package_url;
|
||||
use crate::official_launcher::OfficialLauncherBootstrapService;
|
||||
use crate::official_launcher::{
|
||||
@@ -13,6 +14,7 @@ use crate::official_launcher::{
|
||||
use crate::zip_validation::validate_zip_structure;
|
||||
use bat_adapters::official::game_main_config::YostarJpGameMainConfig;
|
||||
use bat_adapters::official::launcher::YostarJpLauncherManifestFile;
|
||||
use bat_core::ErrorCode;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
@@ -88,13 +90,18 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
|
||||
/// Fetches the latest official game ZIP by re-querying the official
|
||||
/// launcher API, extracts `resources.assets`, and decrypts `GameMainConfig`.
|
||||
pub fn fetch_bootstrap(&self) -> Result<OfficialGameMainConfigBootstrap, String> {
|
||||
pub fn fetch_bootstrap(&self) -> Result<OfficialGameMainConfigBootstrap, DownloadError> {
|
||||
let (game_config, manifest_url, manifest) = self.launcher.fetch_latest_remote_manifest()?;
|
||||
let manifest_source = manifest
|
||||
.source
|
||||
.clone()
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| "官方启动器远端 manifest 缺少 source".to_string())?;
|
||||
.ok_or_else(|| {
|
||||
DownloadError::new(
|
||||
ErrorCode::LAUNCHER_RESPONSE_INVALID,
|
||||
"官方启动器远端 manifest 缺少 source",
|
||||
)
|
||||
})?;
|
||||
let cdn_config = self.launcher.fetch_cdn_config()?;
|
||||
let temp_dir = TempDir::new().map_err(|error| format!("创建临时目录失败:{error}"))?;
|
||||
let (game_zip_url, resources_assets) = self.fetch_resources_assets(
|
||||
@@ -104,7 +111,8 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
&cdn_config,
|
||||
temp_dir.path(),
|
||||
)?;
|
||||
let game_main_config = YostarJpGameMainConfig::from_resources_assets(resources_assets)?;
|
||||
let game_main_config = YostarJpGameMainConfig::from_resources_assets(resources_assets)
|
||||
.map_err(|error| DownloadError::new(ErrorCode::GAME_MAIN_CONFIG_FAILED, error))?;
|
||||
|
||||
Ok(OfficialGameMainConfigBootstrap {
|
||||
game_config,
|
||||
@@ -119,12 +127,12 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
|
||||
/// Fetches the latest official game ZIP, extracts `resources.assets`, and
|
||||
/// decrypts `GameMainConfig`.
|
||||
pub fn fetch_game_main_config(&self) -> Result<YostarJpGameMainConfig, String> {
|
||||
pub fn fetch_game_main_config(&self) -> Result<YostarJpGameMainConfig, DownloadError> {
|
||||
self.fetch_bootstrap()
|
||||
.map(|bootstrap| bootstrap.game_main_config)
|
||||
}
|
||||
|
||||
fn download_file(&self, url: &str, destination: &Path) -> Result<(), String> {
|
||||
fn download_file(&self, url: &str, destination: &Path) -> Result<(), DownloadError> {
|
||||
run_curl_with_retry_with_proxy(
|
||||
&self.curl_command,
|
||||
url,
|
||||
@@ -146,7 +154,11 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
},
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|error| format!("curl 拉取失败 {url}:{error}"))
|
||||
.map_err(|error| {
|
||||
// 保留 curl 失败的准确网络域码;进程类失败归 INTERNAL。
|
||||
let code = error.final_error_code().unwrap_or(ErrorCode::INTERNAL);
|
||||
DownloadError::new(code, format!("curl 拉取失败 {url}:{error}"))
|
||||
})
|
||||
}
|
||||
|
||||
fn download_file_with_fallback(
|
||||
@@ -155,14 +167,18 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
backup_cdn_root: &str,
|
||||
relative_path: &str,
|
||||
destination: &Path,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), DownloadError> {
|
||||
match self.download_file(primary_url, destination) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(primary_error) => {
|
||||
let backup_url = launcher_package_url(backup_cdn_root, relative_path)?;
|
||||
self.download_file(&backup_url, destination).map_err(|backup_error| {
|
||||
format!(
|
||||
"下载官方游戏资源失败:主 CDN 失败后已切换官方备用 CDN;主地址失败({primary_error}),备用地址也失败({backup_error})"
|
||||
// 保留备用 CDN 失败的错误码(两次都失败时以最终一次为准)。
|
||||
DownloadError::new(
|
||||
backup_error.code(),
|
||||
format!(
|
||||
"下载官方游戏资源失败:主 CDN 失败后已切换官方备用 CDN;主地址失败({primary_error}),备用地址也失败({backup_error})"
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -176,7 +192,7 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
manifest_source: &str,
|
||||
cdn_config: &YostarJpLauncherCdnConfig,
|
||||
temp_root: &Path,
|
||||
) -> Result<(String, PathBuf), String> {
|
||||
) -> Result<(String, PathBuf), DownloadError> {
|
||||
match select_game_main_config_source(game_config, manifest_source, manifest)? {
|
||||
GameMainConfigSource::Archive(package_path) => {
|
||||
let game_zip_url = launcher_package_url(&cdn_config.primary_cdn, package_path)?;
|
||||
@@ -187,7 +203,8 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
package_path,
|
||||
&archive_path,
|
||||
)?;
|
||||
validate_zip_structure(&archive_path)?;
|
||||
validate_zip_structure(&archive_path)
|
||||
.map_err(|error| DownloadError::new(ErrorCode::ZIP_STRUCTURE_INVALID, error))?;
|
||||
let extract_root = temp_root.join("extract");
|
||||
fs::create_dir_all(&extract_root)
|
||||
.map_err(|error| format!("创建解压目录失败:{error}"))?;
|
||||
@@ -207,7 +224,8 @@ impl OfficialGameMainConfigBootstrapService {
|
||||
&relative_path,
|
||||
&resources_assets,
|
||||
)?;
|
||||
verify_manifest_file_size(&resources_assets, file)?;
|
||||
verify_manifest_file_size(&resources_assets, file)
|
||||
.map_err(|error| DownloadError::new(ErrorCode::SIZE_MISMATCH, error))?;
|
||||
Ok((resources_assets_url, resources_assets))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
use crate::curl_transfer::{
|
||||
run_curl_with_retry_with_proxy as run_curl_command_with_retry, CurlProxyConfig,
|
||||
};
|
||||
use crate::official_download::DownloadError;
|
||||
use bat_adapters::official::launcher::{YostarJpLauncherManifestFile, YOSTAR_JP_GAME_TAG};
|
||||
use bat_core::ErrorCode;
|
||||
use md5::{Digest, Md5};
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
@@ -119,23 +121,35 @@ impl OfficialLauncherBootstrapService {
|
||||
}
|
||||
|
||||
/// Fetches latest official PC game config.
|
||||
pub fn fetch_game_config(&self) -> Result<YostarJpLauncherGameConfig, String> {
|
||||
pub fn fetch_game_config(&self) -> Result<YostarJpLauncherGameConfig, DownloadError> {
|
||||
let envelope = self.fetch_launcher_envelope("/api/launcher/game/config")?;
|
||||
let config: YostarJpLauncherGameConfig = serde_json::from_value(envelope.data)
|
||||
.map_err(|error| format!("解析官方启动器 game config 数据失败:{error}"))?;
|
||||
let config: YostarJpLauncherGameConfig =
|
||||
serde_json::from_value(envelope.data).map_err(|error| {
|
||||
DownloadError::new(
|
||||
ErrorCode::LAUNCHER_RESPONSE_INVALID,
|
||||
format!("解析官方启动器 game config 数据失败:{error}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
if config.game_latest_version.is_empty() || config.game_latest_file_path.is_empty() {
|
||||
return Err("官方启动器 game config 缺少最新版本或基准路径".into());
|
||||
return Err(DownloadError::new(
|
||||
ErrorCode::LAUNCHER_RESPONSE_INVALID,
|
||||
"官方启动器 game config 缺少最新版本或基准路径",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Fetches official CDN config for PC client package downloads.
|
||||
pub fn fetch_cdn_config(&self) -> Result<YostarJpLauncherCdnConfig, String> {
|
||||
pub fn fetch_cdn_config(&self) -> Result<YostarJpLauncherCdnConfig, DownloadError> {
|
||||
let envelope = self.fetch_launcher_envelope("/api/launcher/advanced/game/download/cdn")?;
|
||||
serde_json::from_value(envelope.data)
|
||||
.map_err(|error| format!("解析官方启动器 CDN config 数据失败:{error}"))
|
||||
serde_json::from_value(envelope.data).map_err(|error| {
|
||||
DownloadError::new(
|
||||
ErrorCode::LAUNCHER_RESPONSE_INVALID,
|
||||
format!("解析官方启动器 CDN config 数据失败:{error}"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetches the official remote manifest URL for a PC client version and
|
||||
@@ -144,9 +158,12 @@ impl OfficialLauncherBootstrapService {
|
||||
&self,
|
||||
version: &str,
|
||||
file_path: &str,
|
||||
) -> Result<YostarJpLauncherManifestUrl, String> {
|
||||
) -> Result<YostarJpLauncherManifestUrl, DownloadError> {
|
||||
if version.is_empty() || file_path.is_empty() {
|
||||
return Err("启动器 manifest 版本和文件路径不能为空".into());
|
||||
return Err(DownloadError::new(
|
||||
ErrorCode::INVALID_ARGUMENT,
|
||||
"启动器 manifest 版本和文件路径不能为空",
|
||||
));
|
||||
}
|
||||
|
||||
let path = format!(
|
||||
@@ -156,12 +173,20 @@ impl OfficialLauncherBootstrapService {
|
||||
);
|
||||
let envelope = self.fetch_launcher_envelope(&path)?;
|
||||
let manifest_url: YostarJpLauncherManifestUrl = serde_json::from_value(envelope.data)
|
||||
.map_err(|error| format!("解析官方启动器 manifest URL 数据失败:{error}"))?;
|
||||
.map_err(|error| {
|
||||
DownloadError::new(
|
||||
ErrorCode::LAUNCHER_RESPONSE_INVALID,
|
||||
format!("解析官方启动器 manifest URL 数据失败:{error}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !is_official_launcher_package_url(&manifest_url.url) {
|
||||
return Err(format!(
|
||||
"官方启动器 API 返回了非官方 manifest URL:{}",
|
||||
manifest_url.url
|
||||
return Err(DownloadError::new(
|
||||
ErrorCode::NON_OFFICIAL_URL,
|
||||
format!(
|
||||
"官方启动器 API 返回了非官方 manifest URL:{}",
|
||||
manifest_url.url
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -172,29 +197,38 @@ impl OfficialLauncherBootstrapService {
|
||||
pub fn fetch_remote_manifest(
|
||||
&self,
|
||||
manifest_url: &str,
|
||||
) -> Result<YostarJpLauncherRemoteManifest, String> {
|
||||
) -> Result<YostarJpLauncherRemoteManifest, DownloadError> {
|
||||
if !is_official_launcher_package_url(manifest_url) {
|
||||
return Err(format!("拒绝拉取非官方启动器 manifest URL:{manifest_url}"));
|
||||
return Err(DownloadError::new(
|
||||
ErrorCode::NON_OFFICIAL_URL,
|
||||
format!("拒绝拉取非官方启动器 manifest URL:{manifest_url}"),
|
||||
));
|
||||
}
|
||||
|
||||
let output = self
|
||||
.run_curl_with_retry(manifest_url, || {
|
||||
let mut command = Command::new(&self.curl_command);
|
||||
command
|
||||
.arg("--fail")
|
||||
.arg("--location")
|
||||
.arg("--silent")
|
||||
.arg("--show-error")
|
||||
.arg("--url")
|
||||
.arg(manifest_url);
|
||||
command
|
||||
})
|
||||
.map_err(|error| format!("curl 拉取启动器 manifest 失败 {manifest_url}:{error}"))?;
|
||||
let output = self.run_curl_with_retry(manifest_url, || {
|
||||
let mut command = Command::new(&self.curl_command);
|
||||
command
|
||||
.arg("--fail")
|
||||
.arg("--location")
|
||||
.arg("--silent")
|
||||
.arg("--show-error")
|
||||
.arg("--url")
|
||||
.arg(manifest_url);
|
||||
command
|
||||
})?;
|
||||
|
||||
let manifest: YostarJpLauncherRemoteManifest = serde_json::from_slice(&output.stdout)
|
||||
.map_err(|error| format!("解析官方启动器 manifest 失败:{error}"))?;
|
||||
.map_err(|error| {
|
||||
DownloadError::new(
|
||||
ErrorCode::MANIFEST_PARSE_FAILED,
|
||||
format!("解析官方启动器 manifest 失败:{error}"),
|
||||
)
|
||||
})?;
|
||||
if manifest.files.is_empty() {
|
||||
return Err("官方启动器远端 manifest 没有文件条目".into());
|
||||
return Err(DownloadError::new(
|
||||
ErrorCode::MANIFEST_PARSE_FAILED,
|
||||
"官方启动器远端 manifest 没有文件条目",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(manifest)
|
||||
@@ -209,7 +243,7 @@ impl OfficialLauncherBootstrapService {
|
||||
YostarJpLauncherManifestUrl,
|
||||
YostarJpLauncherRemoteManifest,
|
||||
),
|
||||
String,
|
||||
DownloadError,
|
||||
> {
|
||||
let game_config = self.fetch_game_config()?;
|
||||
let manifest_url = self.fetch_manifest_url(
|
||||
@@ -223,40 +257,46 @@ impl OfficialLauncherBootstrapService {
|
||||
|
||||
/// Fetches a signed official launcher API endpoint and parses the generic
|
||||
/// response envelope.
|
||||
pub fn fetch_launcher_envelope(&self, path: &str) -> Result<LauncherEnvelope, String> {
|
||||
pub fn fetch_launcher_envelope(&self, path: &str) -> Result<LauncherEnvelope, DownloadError> {
|
||||
let url = launcher_api_url(path)?;
|
||||
let authorization = launcher_authorization_header(&self.launcher_version, "", None)?;
|
||||
|
||||
let output = self
|
||||
.run_curl_with_retry(&url, || {
|
||||
let mut command = Command::new(&self.curl_command);
|
||||
command
|
||||
.arg("--fail")
|
||||
.arg("--location")
|
||||
.arg("--silent")
|
||||
.arg("--show-error")
|
||||
.arg("--header")
|
||||
.arg("Content-Type: application/json;charset=UTF-8")
|
||||
.arg("--header")
|
||||
.arg(format!("Authorization: {authorization}"))
|
||||
.arg("--url")
|
||||
.arg(&url);
|
||||
command
|
||||
})
|
||||
.map_err(|error| format!("curl 拉取失败 {url}:{error}"))?;
|
||||
let output = self.run_curl_with_retry(&url, || {
|
||||
let mut command = Command::new(&self.curl_command);
|
||||
command
|
||||
.arg("--fail")
|
||||
.arg("--location")
|
||||
.arg("--silent")
|
||||
.arg("--show-error")
|
||||
.arg("--header")
|
||||
.arg("Content-Type: application/json;charset=UTF-8")
|
||||
.arg("--header")
|
||||
.arg(format!("Authorization: {authorization}"))
|
||||
.arg("--url")
|
||||
.arg(&url);
|
||||
command
|
||||
})?;
|
||||
|
||||
let envelope: LauncherEnvelope = serde_json::from_slice(&output.stdout)
|
||||
.map_err(|error| format!("解析官方启动器 API 响应失败:{error}"))?;
|
||||
let envelope: LauncherEnvelope =
|
||||
serde_json::from_slice(&output.stdout).map_err(|error| {
|
||||
DownloadError::new(
|
||||
ErrorCode::LAUNCHER_RESPONSE_INVALID,
|
||||
format!("解析官方启动器 API 响应失败:{error}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
if envelope.code != 200 {
|
||||
return Err(format!(
|
||||
"官方启动器 API 返回错误码 {}:{}",
|
||||
envelope.code,
|
||||
envelope
|
||||
.message
|
||||
.as_deref()
|
||||
.or(envelope.msg.as_deref())
|
||||
.unwrap_or("<无消息>")
|
||||
return Err(DownloadError::new(
|
||||
ErrorCode::LAUNCHER_API_REJECTED,
|
||||
format!(
|
||||
"官方启动器 API 返回错误码 {}:{}",
|
||||
envelope.code,
|
||||
envelope
|
||||
.message
|
||||
.as_deref()
|
||||
.or(envelope.msg.as_deref())
|
||||
.unwrap_or("<无消息>")
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -267,7 +307,7 @@ impl OfficialLauncherBootstrapService {
|
||||
&self,
|
||||
url: &str,
|
||||
mut build_command: impl FnMut() -> Command,
|
||||
) -> Result<Output, String> {
|
||||
) -> Result<Output, DownloadError> {
|
||||
run_curl_command_with_retry(
|
||||
Path::new(&self.curl_command),
|
||||
url,
|
||||
@@ -276,7 +316,12 @@ impl OfficialLauncherBootstrapService {
|
||||
&self.curl_proxy,
|
||||
&mut build_command,
|
||||
)
|
||||
.map_err(|error| error.to_string())
|
||||
.map_err(|error| {
|
||||
// 保留 curl 失败的准确网络域码(403/404/DNS/连接/超时/TLS/代理等),
|
||||
// 进程类失败归 INTERNAL。
|
||||
let code = error.final_error_code().unwrap_or(ErrorCode::INTERNAL);
|
||||
DownloadError::new(code, format!("curl 拉取失败 {url}:{error}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,6 +625,84 @@ esac
|
||||
assert_eq!(manifest.files[0].path, "/BlueArchive.exe");
|
||||
}
|
||||
|
||||
fn fake_launcher_config_response_script(payload: &str) -> String {
|
||||
format!(
|
||||
r#"#!/bin/sh
|
||||
set -eu
|
||||
url=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--url) url="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
case "$url" in
|
||||
*/api/launcher/game/config)
|
||||
printf '%s' '{payload}'
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "unexpected url: $url" >&2
|
||||
exit 22
|
||||
;;
|
||||
esac
|
||||
"#,
|
||||
payload = payload
|
||||
)
|
||||
}
|
||||
|
||||
fn fake_launcher_http_404_script() -> &'static str {
|
||||
r#"#!/bin/sh
|
||||
set -eu
|
||||
printf 'curl: (22) The requested URL returned error: 404\n' >&2
|
||||
exit 22
|
||||
"#
|
||||
}
|
||||
|
||||
fn launcher_service_with_script(script: &str) -> (TempDir, OfficialLauncherBootstrapService) {
|
||||
let bin_dir = TempDir::new().unwrap();
|
||||
let curl_path = bin_dir.path().join("curl");
|
||||
write_shell_script(&curl_path, script);
|
||||
let service = OfficialLauncherBootstrapService::with_curl_command(
|
||||
"1.7.2",
|
||||
curl_path.to_string_lossy().to_string(),
|
||||
)
|
||||
.with_retry_attempts(1);
|
||||
(bin_dir, service)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launcher_api_non_200_maps_to_launcher_api_rejected() {
|
||||
let (_bin, service) = launcher_service_with_script(&fake_launcher_config_response_script(
|
||||
r#"{"code":403,"message":"version rejected","data":{}}"#,
|
||||
));
|
||||
let error = service.fetch_game_config().unwrap_err();
|
||||
assert_eq!(error.code().id(), ErrorCode::LAUNCHER_API_REJECTED.id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launcher_api_bad_json_maps_to_launcher_response_invalid() {
|
||||
let (_bin, service) =
|
||||
launcher_service_with_script(&fake_launcher_config_response_script("not-json"));
|
||||
let error = service.fetch_game_config().unwrap_err();
|
||||
assert_eq!(error.code().id(), ErrorCode::LAUNCHER_RESPONSE_INVALID.id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn launcher_http_404_preserves_network_error_code() {
|
||||
let (_bin, service) = launcher_service_with_script(fake_launcher_http_404_script());
|
||||
let error = service.fetch_game_config().unwrap_err();
|
||||
assert_eq!(error.code().id(), ErrorCode::HTTP_NOT_FOUND.id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_official_manifest_url_maps_to_non_official_url() {
|
||||
let (_bin, service) = launcher_service_with_script(fake_launcher_http_404_script());
|
||||
let error = service
|
||||
.fetch_remote_manifest("https://launcher-pkg-ba-jp.bluearchive.cafe/prod/manifest.json")
|
||||
.unwrap_err();
|
||||
assert_eq!(error.code().id(), ErrorCode::NON_OFFICIAL_URL.id());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retries_transient_launcher_api_failures() {
|
||||
let bin_dir = TempDir::new().unwrap();
|
||||
|
||||
@@ -2480,7 +2480,7 @@ fn resolve_bootstrap(
|
||||
));
|
||||
let (game_config, manifest_url, manifest) = launcher
|
||||
.fetch_latest_remote_manifest()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
.map_err(anyhow::Error::new)?;
|
||||
check_shutdown_requested(should_cancel)?;
|
||||
let launcher_metadata =
|
||||
launcher_metadata_from_parts(launcher_version, &game_config, &manifest_url, &manifest);
|
||||
@@ -2523,7 +2523,7 @@ fn resolve_bootstrap(
|
||||
tools.unzip_command.to_path_buf(),
|
||||
)
|
||||
.with_proxy_config(tools.curl_proxy.clone());
|
||||
let bootstrap = bootstrapper.fetch_bootstrap().map_err(anyhow::Error::msg)?;
|
||||
let bootstrap = bootstrapper.fetch_bootstrap().map_err(anyhow::Error::new)?;
|
||||
check_shutdown_requested(should_cancel)?;
|
||||
let launcher_metadata = LauncherMetadataSnapshot {
|
||||
launcher_version: launcher_version.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user