Files
SashegDev 874b6f20c2 Клиент: начальный импорт кодовой базы
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
2026-10-29 22:04:28 +03:00

90 lines
3.5 KiB
Rust

//! The owner gate from the outside: with no account that owns Minecraft: Java Edition in the
//! launcher, neither `horizon-launcher play` nor the MCP server's play starts the game.
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
const REFUSED: &str = "Sign in with a Microsoft account that owns Minecraft: Java Edition to play.";
/// Fresh homes with one installed (not prepared) version and an offline name, as a player who
/// played offline before the gate has.
fn home(name: &str) -> PathBuf {
let root =
std::env::temp_dir().join(format!("horizon-owner-gate-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let version = root.join("launcher/versions/v2026.10.27.01");
std::fs::create_dir_all(&version).unwrap();
std::fs::write(version.join(".installed"), "").unwrap();
std::fs::write(
root.join("launcher/accounts.json"),
r#"{"accounts":[],"selected":null,"offline":["Builder_1"],"offline_selected":"Builder_1"}"#,
)
.unwrap();
root
}
fn launcher(root: &Path, args: &[&str]) -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_horizon-launcher"));
cmd.args(args)
.env("HORIZON_HOME", root.join("game"))
.env("HORIZON_LAUNCHER_HOME", root.join("launcher"))
.env("XDG_DATA_HOME", root.join("data"))
.env("XDG_CONFIG_HOME", root.join("config"))
.env("HORIZON_LAUNCHER_NO_SELF_UPDATE", "1")
.env("HORIZON_NO_AUDIO", "1")
.env("HORIZON_TELEMETRY_URL", "off")
// Memory only: the test never reads the player's keychain.
.env("HORIZON_NO_KEYCHAIN", "1")
.env("HORIZON_MSA_CLIENT_ID", "test-client");
cmd
}
fn text(out: &Output) -> String {
String::from_utf8_lossy(&out.stdout).into_owned() + &String::from_utf8_lossy(&out.stderr)
}
#[test]
fn the_cli_refuses_to_play_without_an_owner() {
let root = home("cli");
let out = launcher(&root, &["play"]).output().unwrap();
let said = text(&out);
assert!(!out.status.success(), "{said}");
assert!(said.contains(REFUSED), "{said}");
assert!(said.contains("horizon-launcher account sign-in"), "{said}");
// Nothing of the game ran, and the offline name is still there.
assert!(!root.join("game/sessions").exists());
let accounts = std::fs::read_to_string(root.join("launcher/accounts.json")).unwrap();
assert!(accounts.contains("Builder_1"), "{accounts}");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn the_mcp_server_refuses_to_play_without_an_owner() {
let root = home("mcp");
let mut child = launcher(&root, &["mcp"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let requests = [
r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#,
r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"play","arguments":{}}}"#,
];
let mut stdin = child.stdin.take().unwrap();
for r in requests {
writeln!(stdin, "{r}").unwrap();
}
drop(stdin);
let out = child.wait_with_output().unwrap();
let reply = String::from_utf8_lossy(&out.stdout)
.lines()
.map(|l| serde_json::from_str::<serde_json::Value>(l).unwrap())
.find(|v| v["id"] == 2)
.expect("a reply to play");
assert_eq!(reply["result"]["isError"], true, "{reply}");
let said = reply["result"]["content"][0]["text"].as_str().unwrap();
assert!(said.contains(REFUSED), "{said}");
let _ = std::fs::remove_dir_all(&root);
}