Files
Unlucky-Launcher/crates/core/tests/game/manifest_parse.rs
T
AskaEth bc41dfcf7d
CI / fmt (rustfmt --check) (push) Canceled after 0s
CI / clippy (ubuntu-latest) (push) Canceled after 0s
CI / clippy (windows-latest) (push) Canceled after 0s
CI / test (ubuntu-latest) (push) Canceled after 0s
CI / test (windows-latest) (push) Canceled after 0s
CI / build release (ubuntu-latest) (push) Canceled after 0s
CI / build release (windows-latest) (push) Canceled after 0s
Guards / core 零 UI 守门 (push) Canceled after 0s
Guards / 文件行数 ≤400 守门 (push) Canceled after 0s
Guards / 依赖许可与来源(cargo-deny) (push) Canceled after 0s
Unlucky Launcher v0.0.1-alpha
Rust 原生单文件 Minecraft 启动器:CLI 完整可用(install/launch/accounts/java/diagnose/agent),
MCP server 接入外部 Agent 工具,GUI 开发中。
2026-10-31 21:54:10 +08:00

187 lines
6.6 KiB
Rust

//! 版本 JSON 解析:宽松解析、未知字段保真、旧版字段并存。
use unlucky_core::game::arguments::{Argument, ArgumentValue, RuleAction};
use unlucky_core::game::manifest::VersionJson;
use unlucky_core::game::version::ReleaseType;
use crate::helpers::parse;
const VANILLA_1_20_1: &str = r#"{
"id": "1.20.1",
"type": "release",
"time": "2023-06-12T13:25:51+00:00",
"releaseTime": "2023-06-12T13:25:51+00:00",
"complianceLevel": 1,
"minimumLauncherVersion": 21,
"mainClass": "net.minecraft.client.main.Main",
"assets": "5",
"assetIndex": {
"id": "5",
"sha1": "a3b3b1b0e0d8f2a8f0fbe1a4b0a8f6a1c6c6a7a2",
"size": 404297,
"totalSize": 598385820,
"url": "https://piston-meta.mojang.com/v1/packages/sha/5.json"
},
"javaVersion": { "component": "java-runtime-gamma", "majorVersion": 17 },
"downloads": {
"client": { "sha1": "abc", "size": 100, "url": "https://example.invalid/client.jar" },
"client_mappings": { "sha1": "def", "size": 50, "url": "https://example.invalid/mappings.txt" }
},
"arguments": {
"game": [
"--username",
"${auth_player_name}",
{ "rules": [{ "action": "allow", "features": { "has_custom_resolution": true } }],
"value": ["--width", "${resolution_width}"] }
],
"jvm": [
{ "rules": [{ "action": "allow", "os": { "name": "windows", "version": "^10\\." } }],
"value": "-Dos.name=Windows 10" }
]
},
"libraries": [
{ "name": "com.mojang:logging:1.1.1",
"downloads": { "artifact": { "path": "com/mojang/logging/1.1.1/logging-1.1.1.jar",
"sha1": "aaa", "size": 10,
"url": "https://example.invalid/logging.jar" } } }
],
"logging": {
"client": { "argument": "-Dlog4j.configurationFile=${path}",
"file": { "id": "client-1.12.xml", "sha1": "bbb", "size": 20,
"url": "https://example.invalid/log4j.xml" },
"type": "log4j2-xml" }
},
"tlauncherVersion": 1,
"MMC-hint": "local"
}"#;
#[test]
fn parses_full_version_json() {
let version = parse(VANILLA_1_20_1);
assert_eq!(version.id.as_deref(), Some("1.20.1"));
assert_eq!(version.version_type, Some(ReleaseType::Release));
assert_eq!(
version.main_class.as_deref(),
Some("net.minecraft.client.main.Main")
);
assert_eq!(version.compliance_level, Some(1));
assert_eq!(version.minimum_launcher_version, Some(21));
assert_eq!(version.assets_id(), "5");
let asset_index = version.asset_index.as_ref().expect("assetIndex 应解析");
assert_eq!(asset_index.id, "5");
assert_eq!(asset_index.total_size, Some(598_385_820));
let java = version.java_version.as_ref().expect("javaVersion 应解析");
assert_eq!(java.component.as_deref(), Some("java-runtime-gamma"));
assert_eq!(java.major_version, Some(17));
let downloads = version.downloads.as_ref().expect("downloads 应解析");
assert_eq!(downloads.len(), 2, "未知键也要保留在映射里");
assert_eq!(
downloads.client().and_then(|c| c.url.as_deref()),
Some("https://example.invalid/client.jar")
);
assert!(downloads.get("client_mappings").is_some());
}
#[test]
fn keeps_unknown_fields_verbatim() {
let version = parse(VANILLA_1_20_1);
// 第三方启动器私有字段(rawJson 保真,analyse/03 §4.2)。
assert_eq!(
version.extra.get("tlauncherVersion"),
Some(&serde_json::json!(1))
);
assert_eq!(
version.extra.get("MMC-hint"),
Some(&serde_json::json!("local"))
);
let roundtrip = version.to_json_string().expect("应可序列化");
let reparsed = VersionJson::from_json_str(&roundtrip).expect("应可再解析");
assert_eq!(reparsed.extra, version.extra);
}
#[test]
fn parses_arguments_with_rules() {
let version = parse(VANILLA_1_20_1);
let arguments = version.arguments.as_ref().expect("arguments 应解析");
assert_eq!(arguments.game.len(), 3);
assert_eq!(arguments.jvm.len(), 1);
assert!(matches!(&arguments.game[0], Argument::Plain(value) if value == "--username"));
let conditional = match &arguments.game[2] {
Argument::Conditional(cond) => cond,
Argument::Plain(_) => panic!("第三项应是条件参数"),
};
assert_eq!(conditional.rules.len(), 1);
assert_eq!(conditional.rules[0].action, RuleAction::Allow);
assert_eq!(
conditional.rules[0]
.features
.as_ref()
.and_then(|f| f.get("has_custom_resolution")),
Some(&true)
);
assert_eq!(
conditional.value,
ArgumentValue::Many(vec!["--width".into(), "${resolution_width}".into()])
);
// jvm 里的 os 限制带正则版本模式。
let jvm = match &arguments.jvm[0] {
Argument::Conditional(cond) => cond,
Argument::Plain(_) => panic!("jvm 第一项应是条件参数"),
};
let os = jvm.rules[0].os.as_ref().expect("应有 os 限制");
assert_eq!(os.name.as_deref(), Some("windows"));
assert_eq!(os.version.as_deref(), Some("^10\\."));
}
#[test]
fn parses_libraries_and_logging() {
let version = parse(VANILLA_1_20_1);
let libraries = version.libraries.as_ref().expect("libraries 应解析");
assert_eq!(libraries.len(), 1);
let artifact = libraries[0].artifact().expect("应有 artifact");
assert_eq!(
artifact.path.as_deref(),
Some("com/mojang/logging/1.1.1/logging-1.1.1.jar")
);
let logging = version.logging.as_ref().expect("logging 应解析");
let client = logging.client().expect("应有 client 日志配置");
assert_eq!(client.kind.as_deref(), Some("log4j2-xml"));
assert_eq!(client.file.id.as_deref(), Some("client-1.12.xml"));
}
#[test]
fn tolerant_defaults_for_minimal_json() {
let version = parse(r#"{ "id": "b1.7.3" }"#);
assert_eq!(version.id.as_deref(), Some("b1.7.3"));
assert_eq!(version.version_type, None, "未知 type 不报错");
assert_eq!(version.client_jar(), None, "老 JSON 没有 downloads.client");
assert_eq!(version.assets_id(), "legacy", "assets 缺失按 legacy");
assert!(!version.is_resolved());
}
#[test]
fn unknown_type_value_falls_back_to_unknown() {
let version = parse(r#"{ "id": "x", "type": "some_new_kind" }"#);
assert_eq!(version.version_type, Some(ReleaseType::Unknown));
}
#[test]
fn missing_id_is_reported() {
let version = parse(r#"{ "mainClass": "a.B" }"#);
let error = version.require_id().expect_err("缺 id 应报错");
assert_eq!(
error.to_string(),
"版本 JSON 缺少 id",
"错误文案要给用户看得懂"
);
}