Add comprehensive documentation and unit tests

Documentation:
- Add detailed README files for all services (auth, character, database, launcher, packet, utils, world)
- Create API documentation for the database service with detailed endpoint specifications
- Document database schema and relationships
- Add service architecture overviews and configuration instructions

Unit Tests:
- Implement comprehensive test suite for database repositories (user, character, session)
- Add gRPC service tests for database interactions
- Create tests for packet service components (bufferpool, connection, packets)
- Add utility service tests (health check, logging, load balancer, redis cache, service discovery)
- Implement auth service user tests
- Add character service tests

Code Structure:
- Reorganize test files into a more consistent structure
- Create a dedicated tests crate for integration testing
- Add test helpers and mock implementations for easier testing
This commit is contained in:
2025-04-09 13:29:38 -04:00
parent d47d5f44b1
commit a8755bd3de
85 changed files with 4218 additions and 764 deletions

View File

@@ -69,10 +69,7 @@ impl AuthClient {
Ok(response.into_inner())
}
pub async fn logout(
&mut self,
session_id: &str,
) -> Result<Empty, Box<dyn std::error::Error + Send + Sync>> {
pub async fn logout(&mut self, session_id: &str) -> Result<Empty, Box<dyn std::error::Error + Send + Sync>> {
let request = LogoutRequest {
session_id: session_id.to_string(),
};

View File

@@ -1,8 +1,7 @@
use crate::character::character_service_client::CharacterServiceClient;
use crate::character::{
CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest,
DeleteCharacterResponse, GetCharacterListRequest, GetCharacterListResponse,
GetCharacterRequest, GetCharacterResponse,
CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, DeleteCharacterResponse,
GetCharacterListRequest, GetCharacterListResponse, GetCharacterRequest, GetCharacterResponse,
};
use tonic::transport::Channel;
use utils::null_string::NullTerminatedString;

View File

@@ -17,15 +17,12 @@ impl ConnectionService {
pub fn add_connection(&self) -> String {
let connection_id = Uuid::new_v4().to_string();
self.connections
.insert(connection_id.clone(), ConnectionState::new());
self.connections.insert(connection_id.clone(), ConnectionState::new());
connection_id
}
pub fn get_connection(&self, connection_id: &str) -> Option<ConnectionState> {
self.connections
.get(connection_id)
.map(|entry| entry.clone())
self.connections.get(connection_id).map(|entry| entry.clone())
}
pub fn get_connection_mut(

View File

@@ -133,10 +133,7 @@ pub(crate) async fn handle_login_req(
connection_id: String,
addr: SocketAddr,
) -> Result<(), Box<dyn Error + Send + Sync>> {
debug!(
"decoding packet payload of size {}",
packet.payload.as_slice().len()
);
debug!("decoding packet payload of size {}", packet.payload.as_slice().len());
let data = CliLoginTokenReq::decode(packet.payload.as_slice())?;
debug!("{:?}", data);
@@ -175,9 +172,7 @@ pub(crate) async fn handle_login_req(
"name" => {
server_name = value;
}
"tags" => {
}
"tags" => {}
_ => {}
}
}

View File

@@ -7,16 +7,15 @@ use crate::enums::ItemType;
use crate::packet::{send_packet, Packet, PacketPayload};
use crate::packet_type::PacketType;
use crate::packets::*;
use std::collections::hash_map::DefaultHasher;
use std::error::Error;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use tonic::{Code, Status};
use tracing::{debug, error, info, warn};
use utils::null_string::NullTerminatedString;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
fn string_to_u32(s: &str) -> u32 {
let mut hasher = DefaultHasher::new();
@@ -80,20 +79,16 @@ pub(crate) async fn handle_char_list_req(
let request = CliCharListReq::decode(packet.payload.as_slice());
debug!("{:?}", request);
let mut user_id= "".to_string();
let mut user_id = "".to_string();
let session_id;
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state
.session_id
.expect("Missing session id in connection state");
session_id = state.session_id.expect("Missing session id in connection state");
}
// query the character service for the character list for this user
let mut character_client = character_client.lock().await;
let character_list = character_client
.get_character_list(&user_id)
.await?;
let character_list = character_client.get_character_list(&user_id).await?;
let mut characters = vec![];
let mut character_id_list: Vec<u32> = Vec::new();
for character in character_list.characters {
@@ -154,9 +149,7 @@ pub(crate) async fn handle_create_char_req(
let session_id;
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state
.session_id
.expect("Missing session id in connection state");
session_id = state.session_id.expect("Missing session id in connection state");
}
// send the data to the character service to create the character
@@ -181,10 +174,7 @@ pub(crate) async fn handle_create_char_req(
_ => srv_create_char_reply::Result::Failed,
};
let data = SrvCreateCharReply {
result,
platininum: 0,
};
let data = SrvCreateCharReply { result, platininum: 0 };
let response_packet = Packet::new(PacketType::PakccCreateCharReply, &data)?;
send_packet(stream, &response_packet).await?;
@@ -209,9 +199,7 @@ pub(crate) async fn handle_delete_char_req(
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state
.session_id
.expect("Missing session id in connection state");
session_id = state.session_id.expect("Missing session id in connection state");
character_id_list = state.character_list.expect("Missing character id list");
}
@@ -256,10 +244,7 @@ pub(crate) async fn handle_select_char_req(
let mut character_id_list: Vec<u32> = Vec::new();
if let Some(mut state) = connection_service.get_connection_mut(&connection_id) {
user_id = state.user_id.clone().expect("Missing user id in connection state");
character_id_list = state
.character_list
.clone()
.expect("Missing character id list");
character_id_list = state.character_list.clone().expect("Missing character id list");
state.character_id = Some(request.char_id as i8);
}
@@ -274,10 +259,7 @@ pub(crate) async fn handle_select_char_req(
let mut character_client = character_client.lock().await;
let character_data = character_client
.get_character(
&user_id.to_string(),
character_id_list[request.char_id as usize] as u8,
)
.get_character(&user_id.to_string(), character_id_list[request.char_id as usize] as u8)
.await?;
let character = character_data.character.unwrap_or_default();
@@ -332,8 +314,7 @@ pub(crate) async fn handle_select_char_req(
let mut effect_list: [StatusEffect; (MAX_STATUS_EFFECTS as usize)] =
core::array::from_fn(|i| StatusEffect::default());
let mut hotbar_list: [HotbarItem; (MAX_HOTBAR_ITEMS as usize)] =
core::array::from_fn(|i| HotbarItem::default());
let mut hotbar_list: [HotbarItem; (MAX_HOTBAR_ITEMS as usize)] = core::array::from_fn(|i| HotbarItem::default());
let data = SrvSelectCharReply {
race: looks.race as u8,
map: position.map_id as u16,

View File

@@ -1,4 +1,4 @@
use crate::character_client::CharacterClient;
use crate::character_client::CharacterClient;
use crate::connection_service::ConnectionService;
use crate::packet::{send_packet, Packet, PacketPayload};
use crate::packet_type::PacketType;
@@ -32,24 +32,14 @@ pub(crate) async fn handle_change_map_req(
let session_id;
if let Some(mut state) = connection_service.get_connection(&connection_id) {
user_id = state.user_id.expect("Missing user id in connection state");
session_id = state
.session_id
.expect("Missing session id in connection state");
char_id = state
.character_id
.expect("Missing character id in connection state");
character_id_list = state
.character_list
.clone()
.expect("Missing character id list");
session_id = state.session_id.expect("Missing session id in connection state");
char_id = state.character_id.expect("Missing character id in connection state");
character_id_list = state.character_list.clone().expect("Missing character id list");
}
let mut character_client = character_client.lock().await;
let character_data = character_client
.get_character(
&user_id.to_string(),
character_id_list[char_id as usize] as u8,
)
.get_character(&user_id.to_string(), character_id_list[char_id as usize] as u8)
.await?;
let character = character_data.character.unwrap_or_default();
@@ -93,24 +83,14 @@ pub(crate) async fn handle_mouse_cmd_req(
let mut char_id = 0;
let mut character_id_list: Vec<u32> = Vec::new();
if let Some(mut state) = connection_service.get_connection(&connection_id) {
char_id = state
.character_id
.expect("Missing character id in connection state");
character_id_list = state
.character_list
.clone()
.expect("Missing character id list");
char_id = state.character_id.expect("Missing character id in connection state");
character_id_list = state.character_list.clone().expect("Missing character id list");
}
let data = SrvMouseCmd {
id: character_id_list[char_id as usize] as u16,
target_id: request.target_id,
distance: distance(
520000 as f64,
520000 as f64,
request.x as f64,
request.y as f64,
),
distance: distance(520000 as f64, 520000 as f64, request.x as f64, request.y as f64),
x: request.x,
y: request.y,
z: request.z,

View File

@@ -0,0 +1,7 @@
// Re-export only the modules needed for tests
pub mod bufferpool;
pub mod connection_service;
pub mod connection_state;
pub mod metrics;
pub mod packet;
pub mod packet_type;

View File

@@ -21,8 +21,8 @@ use tokio::{select, signal};
use tracing::Level;
use tracing::{debug, error, info, warn};
use tracing_subscriber::EnvFilter;
use utils::service_discovery::get_kube_service_endpoints_by_dns;
use utils::{health_check, logging};
use utils::service_discovery::{get_kube_service_endpoints_by_dns};
use warp::Filter;
mod auth_client;
@@ -66,8 +66,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string());
let port = env::var("SERVICE_PORT").unwrap_or_else(|_| "29000".to_string());
let metrics_port = env::var("PACKET_METRICS_PORT").unwrap_or_else(|_| "4001".to_string());
let auth_url = format!("http://{}",get_kube_service_endpoints_by_dns("auth-service","tcp","auth-service").await?.get(0).unwrap());
let character_url = format!("http://{}",get_kube_service_endpoints_by_dns("character-service","tcp","character-service").await?.get(0).unwrap());
let auth_url = format!(
"http://{}",
get_kube_service_endpoints_by_dns("auth-service", "tcp", "auth-service")
.await?
.get(0)
.unwrap()
);
let character_url = format!(
"http://{}",
get_kube_service_endpoints_by_dns("character-service", "tcp", "character-service")
.await?
.get(0)
.unwrap()
);
// Start health-check endpoint
health_check::start_health_check(addr.as_str()).await?;
@@ -109,9 +121,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
{
error!("Error handling connection: {}", e);
}
packet_router
.connection_service
.remove_connection(&connection_id);
packet_router.connection_service.remove_connection(&connection_id);
});
}
});

View File

@@ -1,7 +1,6 @@
use lazy_static::lazy_static;
use prometheus::{
register_counter, register_gauge, register_histogram, Counter, Encoder, Gauge, Histogram,
TextEncoder,
register_counter, register_gauge, register_histogram, Counter, Encoder, Gauge, Histogram, TextEncoder,
};
lazy_static! {

View File

@@ -52,8 +52,7 @@ impl PacketRouter {
Ok(packet) => {
debug!("Parsed Packet: {:?}", packet);
// Handle the parsed packet (route it, process it, etc.)
self.route_packet(stream, packet, connection_id.clone())
.await?;
self.route_packet(stream, packet, connection_id.clone()).await?;
}
Err(e) => warn!("Failed to parse packet: {}", e),
}