PatchBukkit/rust/tests/bot_integration_test.rs
2026-09-18 20:19:58 +02:00

220 lines
7.1 KiB
Rust

mod common;
use std::fs;
use std::sync::Arc;
use std::time::Duration;
use common::bot::BotClient;
use patchbukkit::{
PatchBukkitPlugin, commands::SimpleCommandSender, directories::setup_directories,
java::jvm::commands::JvmCommand,
};
use pumpkin::{PumpkinServer, data::VanillaData, plugin::Context};
use pumpkin_config::{AdvancedConfiguration, BasicConfiguration, TelemetryConfig};
use tokio::sync::oneshot;
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_fake_bot_join_and_interaction() {
let _ = pumpkin::LOGGER_IMPL.set(None);
let _ = tracing_subscriber::fmt().try_init();
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir");
let prev_dir = std::env::current_dir().expect("Failed to get current dir");
std::env::set_current_dir(temp_dir.path()).expect("Failed to set current dir");
let basic_config = BasicConfiguration {
default_level_name: "test_bot_world".to_string(),
..Default::default()
};
let mut advanced_config = AdvancedConfiguration::default();
advanced_config.networking.java.enabled = true;
advanced_config.networking.java.address = "127.0.0.1:0".parse().unwrap();
advanced_config.networking.java.online_mode = false;
advanced_config.networking.java.encryption = false;
advanced_config.networking.bedrock.enabled = false;
advanced_config.networking.rcon.enabled = false;
advanced_config.commands.use_console = false;
let vanilla_data = VanillaData::load();
let telemetry_config = TelemetryConfig::default();
let pumpkin_server = Arc::new(
PumpkinServer::new(
basic_config,
advanced_config,
telemetry_config,
vanilla_data,
)
.await,
);
let server_addr = pumpkin_server
.tcp_listener
.as_ref()
.expect("TCP listener should be bound")
.local_addr()
.expect("Should have local address");
println!("Pumpkin TCP server bound to ephemeral port: {server_addr}");
let metadata = pumpkin::plugin::PluginMetadata {
name: "patchbukkit".to_string(),
version: "0.1.0".to_string(),
authors: vec!["PumpkinMC".to_string()],
description: "PatchBukkit".to_string(),
dependencies: vec![],
permissions: vec![],
};
let handlers = Arc::new(arc_swap::ArcSwap::from_pointee(
std::collections::HashMap::new(),
));
let context = Arc::new(Context::new(
metadata,
pumpkin_server.server.clone(),
handlers,
Arc::clone(&pumpkin_server.server.plugin_manager),
Arc::clone(&pumpkin::LOGGER_IMPL),
));
// Setup directories
let dirs = setup_directories(&context, "patchbukkit-plugins")
.expect("Failed to setup PatchBukkit directories");
// Copy test plugins if present
let test_plugins_dir = if prev_dir.join("java").exists() {
prev_dir
.join("java")
.join("patchbukkit")
.join("build")
.join("test-plugins")
} else if let Some(parent) = prev_dir.parent() {
parent
.join("java")
.join("patchbukkit")
.join("build")
.join("test-plugins")
} else {
std::path::PathBuf::from("java/patchbukkit/build/test-plugins")
};
let protocollib_source = test_plugins_dir.join("ProtocolLib.jar");
if protocollib_source.exists() {
let _ = fs::copy(&protocollib_source, dirs.plugins.join("ProtocolLib.jar"));
}
let plugin = PatchBukkitPlugin::new();
patchbukkit::on_load_inner(&plugin, context.clone())
.await
.expect("Failed to on_load PatchBukkit");
// Wait for JVM initialization
for _ in 0..100 {
tokio::time::sleep(Duration::from_millis(200)).await;
let (tx, rx) = oneshot::channel();
if plugin
.command_tx
.send(JvmCommand::TriggerCommand {
full_command: "version".to_string(),
command_sender: SimpleCommandSender::Console,
respond_to: tx,
})
.await
.is_ok()
&& let Ok(res) = rx.await
&& res.is_ok()
{
break;
}
}
// Wait for ProtocolLib to finish initializing if present
if protocollib_source.exists() {
for _ in 0..150 {
tokio::time::sleep(Duration::from_millis(200)).await;
let (tx, rx) = oneshot::channel();
if plugin
.command_tx
.send(JvmCommand::TriggerCommand {
full_command: "protocol".to_string(),
command_sender: SimpleCommandSender::Console,
respond_to: tx,
})
.await
.is_ok()
&& let Ok(res) = rx.await
&& res.is_ok()
{
println!("ProtocolLib command responded successfully!");
break;
}
}
}
// Start Pumpkin server network accept loop in background
let server_run = pumpkin_server.clone();
let server_task = tokio::spawn(async move {
server_run.start().await;
});
tokio::time::sleep(Duration::from_millis(200)).await;
// Connect fake bot
println!("Connecting fake bot 'TestBot_1' to {server_addr}...");
let bot = BotClient::connect(server_addr, "TestBot_1")
.await
.expect("Failed to connect bot");
// Await transition into Play state (completes Handshake -> Login -> Config -> Play)
println!("Waiting for bot to enter Play state...");
bot.wait_for_play(Duration::from_secs(30))
.await
.expect("Bot should enter Play state successfully");
println!("Bot successfully in Play state!");
// Verify player is registered on server
tokio::time::sleep(Duration::from_millis(200)).await;
let players = pumpkin_server.server.get_all_players();
assert!(
!players.is_empty(),
"Server should have at least 1 player connected"
);
let player = players
.iter()
.find(|p| p.gameprofile.name == "TestBot_1")
.expect("TestBot_1 should be registered on the server");
assert_eq!(player.gameprofile.name, "TestBot_1");
// Send arm swing packet from bot
bot.swing_arm()
.await
.expect("Bot should be able to send SSwingArm");
// Send movement packet from bot
bot.move_to(0.0, 64.0, 0.0)
.await
.expect("Bot should be able to send SPlayerPosition");
// Send command packet from bot
bot.send_command("version")
.await
.expect("Bot should be able to send SChatCommand");
tokio::time::sleep(Duration::from_millis(300)).await;
// Disconnect bot
bot.disconnect().await;
tokio::time::sleep(Duration::from_millis(300)).await;
// Gracefully stop server
pumpkin::stop_server();
let _ = server_task.await;
// Gracefully unload PatchBukkit
let _ = patchbukkit::on_unload_inner(&plugin, context.clone()).await;
// Restore working directory
let _ = std::env::set_current_dir(prev_dir);
tokio::task::spawn_blocking(move || drop(plugin))
.await
.unwrap();
}