use super::*;
#[rstest::rstest]
#[case("openjdk version \"25.0.3\" 2026-04-21", Some(25))]
#[case("openjdk version \"21.0.11\" 2026-04-21", Some(21))]
#[case("java version \"1.8.0_402\"", Some(8))]
#[case("garbage", None)]
fn parse_java_major_version_handles_common_outputs(
#[case] output: &str,
#[case] expected: Option<u32>,
) {
assert_eq!(parse_java_major_version(output), expected);
}
#[test]
fn quick_play_world_must_be_a_direct_save_directory() {
let temp = tempfile::tempdir().unwrap();
let minecraft = temp.path().join("minecraft");
std::fs::create_dir_all(minecraft.join("saves/World")).unwrap();
assert!(validate_quick_play_world(&minecraft, "World").is_ok());
assert!(validate_quick_play_world(&minecraft, "../World").is_err());
assert!(validate_quick_play_world(&minecraft, "Missing").is_err());
}
#[test]
fn build_game_args_renders_upstream_arguments() {
use crate::launch_profile::model::{Argument, Arguments, LaunchProfile};
use crate::launch_profile::rules::{FeatureSet, RuleContext};
use TemplateContext;
use std::path::PathBuf;
let lib = PathBuf::from("/m/libraries");
let nat = PathBuf::from("/m/natives");
let game_dir = PathBuf::from("/i/.minecraft");
let assets = PathBuf::from("/m/assets");
let template_ctx = TemplateContext {
library_directory: &lib,
classpath_separator: ":",
version_name: "1.20.1",
natives_directory: &nat,
classpath: "a.jar:b.jar",
game_directory: &game_dir,
assets_root: &assets,
assets_index_name: "5",
auth_player_name: "Player",
auth_uuid: "00000000-0000-0000-0000-000000000000",
auth_access_token: "token",
auth_xuid: "0",
user_type: "msa",
user_properties: "{}",
launcher_name: "rmcl",
launcher_version: "test",
clientid: "0",
quick_play_singleplayer: None,
version_type: "release",
};
let features = FeatureSet::default();
let rule_ctx = RuleContext {
os_name: "linux",
os_version: "6.0",
arch: "x86_64",
features: &features,
};
let profile = LaunchProfile {
id: "1.20.1".into(),
inherits_from: None,
main_class: Some("net.minecraft.client.main.Main".into()),
libraries: Vec::new(),
arguments: Some(Arguments {
game: vec![
Argument::Literal("--username".into()),
Argument::Literal("${auth_player_name}".into()),
],
jvm: vec![Argument::Literal(
"-Djava.library.path=${natives_directory}".into(),
)],
}),
..Default::default()
};
let (jvm, game_args) = build_game_args(&profile, &rule_ctx, &template_ctx).unwrap();
assert_eq!(jvm, vec!["-Djava.library.path=/m/natives"]);
assert_eq!(game_args, vec!["--username", "Player"]);
}
#[rstest::rstest]
#[case::modern_arguments(true, false)]
#[case::legacy_minecraft_arguments(false, true)]
#[tokio::test]
async fn migrate_legacy_meta_skips_when_arguments_present(
#[case] modern: bool,
#[case] legacy: bool,
) {
use crate::launch_profile::model::{Arguments, LaunchProfile};
use tempfile::TempDir;
let tmp = TempDir::new().unwrap();
let meta_path = tmp.path().join("meta.json");
std::fs::write(&meta_path, b"{}").unwrap();
let profile = LaunchProfile {
id: "1.20.1".into(),
main_class: Some("net.test.Main".into()),
arguments: modern.then(Arguments::default),
minecraft_arguments: legacy.then(|| "--username Player".into()),
..Default::default()
};
let result = migrate_legacy_meta_if_needed(&meta_path, &profile, "1.20.1").await;
assert!(
matches!(result, Ok(None)),
"expected Ok(None) for non-legacy profile, got {result:?}"
);
}
#[rstest::rstest]
#[case::forge(ModLoader::Forge, "1.20.1", "47.2.0", Some("1.20.1-forge-47.2.0"))]
#[case::neoforge(ModLoader::NeoForge, "1.21.1", "21.1.0", Some("neoforge-21.1.0"))]
#[case::vanilla(ModLoader::Vanilla, "1.20.1", "v", None)]
#[case::fabric(ModLoader::Fabric, "1.20.1", "0.14.21", None)]
#[case::quilt(ModLoader::Quilt, "1.20.1", "0.20.0", None)]
fn installer_version_dir_name_per_loader(
#[case] loader: ModLoader,
#[case] game_version: &str,
#[case] loader_version: &str,
#[case] expected: Option<&str>,
) {
assert_eq!(
installer_version_dir_name(loader, game_version, loader_version),
expected.map(str::to_owned)
);
}
#[tokio::test]
async fn migrate_legacy_loader_profile_skips_modern_with_inherits_from() {
use LaunchProfile;
use chrono::Utc;
use tempfile::TempDir;
let tmp = TempDir::new().unwrap();
let instance_dir = tmp.path().join("instance");
std::fs::create_dir_all(&instance_dir).unwrap();
let profile_path = tmp.path().join("forge-1.20.1-47.2.0.json");
std::fs::write(&profile_path, b"{}").unwrap();
let modern = LaunchProfile {
id: "1.20.1-forge-47.2.0".into(),
inherits_from: Some("1.20.1".into()),
main_class: Some("cpw.mods.bootstraplauncher.BootstrapLauncher".into()),
..Default::default()
};
let config = InstanceConfig {
name: "test".into(),
game_version: "1.20.1".into(),
loader: ModLoader::Forge,
loader_version: Some("47.2.0".into()),
created: Utc::now(),
last_played: None,
java_path: None,
memory_max: None,
memory_min: None,
jvm_args: Vec::new(),
resolution: None,
config_sync_profile: None,
modpack_source: None,
};
let result =
migrate_legacy_loader_profile_if_needed(&profile_path, &modern, &config, &instance_dir)
.await;
assert!(
matches!(result, Ok(None)),
"expected Ok(None), got {result:?}"
);
}
#[tokio::test]
async fn migrate_legacy_loader_profile_skips_fabric() {
use LaunchProfile;
use chrono::Utc;
use tempfile::TempDir;
let tmp = TempDir::new().unwrap();
let instance_dir = tmp.path().join("instance");
std::fs::create_dir_all(&instance_dir).unwrap();
let profile_path = tmp.path().join("fabric-1.20.1-0.14.21.json");
std::fs::write(&profile_path, b"{}").unwrap();
let upstream_fabric_shape = LaunchProfile {
id: "fabric-loader-0.14.21-1.20.1".into(),
inherits_from: None,
main_class: Some("net.fabricmc.loader.impl.launch.knot.KnotClient".into()),
libraries: Vec::new(),
..Default::default()
};
let config = InstanceConfig {
name: "test".into(),
game_version: "1.20.1".into(),
loader: ModLoader::Fabric,
loader_version: Some("0.14.21".into()),
created: Utc::now(),
last_played: None,
java_path: None,
memory_max: None,
memory_min: None,
jvm_args: Vec::new(),
resolution: None,
config_sync_profile: None,
modpack_source: None,
};
let result = migrate_legacy_loader_profile_if_needed(
&profile_path,
&upstream_fabric_shape,
&config,
&instance_dir,
)
.await;
assert!(
matches!(result, Ok(None)),
"expected Ok(None), got {result:?}"
);
}