Add city gameplay, snapshot migrations, administration and Compose operations

This commit is contained in:
mrleo1nid committed 2026-10-30 22:17:17 +03:00
1 parent 3b1fcd996a
commit 85df37c26e
24 files changed
+1042 -55

No files matched your search

+73
View File
@@ -31,6 +31,19 @@ async fn read(
}
}
}
async fn http_request(
address: std::net::SocketAddr,
path: &str,
method: &str,
token: &str,
) -> String {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut socket = tokio::net::TcpStream::connect(address).await.unwrap();
socket.write_all(format!("{method} {path} HTTP/1.1\r\nHost: localhost\r\nAuthorization: Bearer {token}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n").as_bytes()).await.unwrap();
let mut response = String::new();
socket.read_to_string(&mut response).await.unwrap();
response
}
#[tokio::test]
async fn thirty_observers_snapshot_delta_vote_and_restart() {
let dir = tempfile::tempdir().unwrap();
@@ -38,6 +51,7 @@ async fn thirty_observers_snapshot_delta_vote_and_restart() {
let app = App::new(Config {
cities: dir.path().join("cities"),
saves: dir.path().join("rooms"),
admin_token: Some("test-only-administrator-token-32-chars".into()),
..Default::default()
})
.unwrap();
@@ -63,6 +77,26 @@ async fn thirty_observers_snapshot_delta_vote_and_restart() {
let value: serde_json::Value =
serde_json::from_str(response.split("\r\n\r\n").nth(1).unwrap()).unwrap();
let id = value["id"].as_str().unwrap().to_string();
assert!(http_request(address, "/api/admin", "GET", "wrong")
.await
.starts_with("HTTP/1.1 401"));
assert!(http_request(
address,
"/api/admin",
"GET",
"test-only-administrator-token-32-chars"
)
.await
.starts_with("HTTP/1.1 200"));
let checkpoint = http_request(
address,
&format!("/api/admin/rooms/{id}/checkpoint"),
"POST",
"test-only-administrator-token-32-chars",
)
.await;
assert!(checkpoint.starts_with("HTTP/1.1 200"), "{checkpoint}");
assert!(citygen_server::storage::load(&dir.path().join("rooms"), &id).is_ok());
let mut sockets = vec![];
for i in 0..30 {
let (mut socket, _) =
@@ -117,6 +151,37 @@ async fn thirty_observers_snapshot_delta_vote_and_restart() {
}
}
// 31st observer must be rejected without affecting existing players.
let command = p::ClientMessage {
body: Some(client_message::Body::Intervene(p::Intervene {
action: "meal".into(),
})),
}
.encode_to_vec();
s.send(tokio_tungstenite::tungstenite::Message::Binary(
command.clone().into(),
))
.await
.unwrap();
s.send(tokio_tungstenite::tungstenite::Message::Binary(
command.into(),
))
.await
.unwrap();
loop {
if let Some(server_message::Body::Error(error)) = read(s).await.body {
assert_eq!(error.code, "COMMAND_LIMIT");
break;
}
}
loop {
if let Some(server_message::Body::Gameplay(state)) = read(s).await.body {
assert!(
state.pending.iter().any(|p| p == "meal")
|| state.events.iter().any(|e| e.kind == "meal")
);
break;
}
}
let (mut extra, _) =
tokio_tungstenite::connect_async(format!("ws://{address}/api/rooms/{id}/ws"))
.await
@@ -232,6 +297,14 @@ async fn thirty_observers_snapshot_delta_vote_and_restart() {
))
.await
.unwrap();
assert!(http_request(
address,
"/api/admin",
"GET",
"test-only-administrator-token-32-chars"
)
.await
.starts_with("HTTP/1.1 404"));
assert!(
matches!(read(&mut socket).await.body,Some(server_message::Body::Welcome(w)) if w.tick>=saved.tick)
);