diff --git a/.gitea/workflows/smoke-tests.yml b/.gitea/workflows/smoke-tests.yml index 4244575..e048653 100644 --- a/.gitea/workflows/smoke-tests.yml +++ b/.gitea/workflows/smoke-tests.yml @@ -148,3 +148,25 @@ jobs: [[ $r -eq 0 || $r -eq 255 ]] ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true ./target/debug/mudtool -d "$TEST_DB" players delete smoketest + + - name: Smoke - JSON-RPC list_commands + run: | + set -euo pipefail + RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & + MUD_PID=$! + trap 'kill $MUD_PID 2>/dev/null || true' EXIT + bash scripts/ci/wait-for-tcp.sh 127.0.0.1 2222 + set +e + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 rpctest@localhost <<'EOF' + 1 + 1 + quit + EOF + r=$? + set -e + [[ $r -eq 0 || $r -eq 255 ]] + echo '{"_jsonrpc": "2.0", "method": "login", "params": {"username": "rpctest"}, "id": 1}' | nc -w 2 localhost 2223 > rpc_resp.json + echo '{"_jsonrpc": "2.0", "method": "list_commands", "params": {}, "id": 2}' | nc -w 2 localhost 2223 >> rpc_resp.json + grep -q '"shop"' rpc_resp.json + rm rpc_resp.json + ./target/debug/mudtool -d "$TEST_DB" players delete rpctest diff --git a/TESTING.md b/TESTING.md index 39e51b0..643117b 100644 --- a/TESTING.md +++ b/TESTING.md @@ -238,6 +238,13 @@ Run through the checks below before every commit to ensure consistent feature co - [ ] ←→ switches player on Attitudes tab - [ ] 'q' exits TUI +## JSON-RPC Interface +- [ ] `list_commands` returns the currently handleable command list +- [ ] New commands added in `commands.rs` are automatically discovered +- [ ] `login` accepts an existing player name (requires character to be created first) +- [ ] Command output is stripped of ANSI color codes for API consumption +- [ ] Verify manually with: `echo '{"_jsonrpc": "2.0", "method": "list_commands", "params": {}, "id": 1}' | nc localhost 2223` + ## Quick Smoke Test Script -**CI:** each scenario is a separate step in [`.gitea/workflows/smoke-tests.yml`](.gitea/workflows/smoke-tests.yml) (each SSH step starts `mudserver`, runs the block, stops it; the same `TEST_DB` file carries state between steps). **Local:** **`./run-tests.sh`** runs the full sequence in one process. When you add or change coverage, update the workflow steps and `run-tests.sh` together, and keep the checklist sections above aligned. +**CI:** each scenario is a separate step in [`.gitea/workflows/smoke-tests.yml`](.gitea/workflows/smoke-tests.yml) (each SSH step starts `mudserver`, runs the block, stops it; the same `TEST_DB` file carries state between steps). The last step exercises JSON-RPC `login` / `list_commands` on port 2223 (expects `shop` in the command list). **Local:** **`./run-tests.sh`** runs the full sequence in one process. When you add or change coverage, update the workflow steps and `run-tests.sh` together, and keep the checklist sections above aligned. diff --git a/run-tests.sh b/run-tests.sh index 5f9bce3..0832e86 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -83,5 +83,22 @@ EOF echo "quit" ) | ssh_mud smoketest@localhost +ssh_mud rpctest@localhost <<'EOF' +1 +1 +quit +EOF + +echo '{"_jsonrpc": "2.0", "method": "login", "params": {"username": "rpctest"}, "id": 1}' | nc -w 2 localhost 2223 > rpc_resp.json +echo '{"_jsonrpc": "2.0", "method": "list_commands", "params": {}, "id": 2}' | nc -w 2 localhost 2223 >> rpc_resp.json + +if ! grep -q '"shop"' rpc_resp.json; then + echo "Error: 'shop' command missing from JSON-RPC list_commands" + cat rpc_resp.json + exit 1 +fi +rm rpc_resp.json + ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true ./target/debug/mudtool -d "$TEST_DB" players delete smoketest +./target/debug/mudtool -d "$TEST_DB" players delete rpctest diff --git a/src/commands.rs b/src/commands.rs index 9fc2f91..e5c3b7c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -157,6 +157,15 @@ pub async fn execute( } } +pub fn get_command_list() -> Vec<&'static str> { + vec![ + "look", "go", "north", "south", "east", "west", "up", "down", + "say", "who", "take", "drop", "inventory", "equip", "use", + "examine", "talk", "attack", "defend", "flee", "cast", + "spells", "skills", "guild", "stats", "help", "shop", + ] +} + fn send(session: &mut Session, channel: ChannelId, text: &str) -> Result<(), russh::Error> { session.data(channel, CryptoVec::from(text.as_bytes()))?; diff --git a/src/jsonrpc.rs b/src/jsonrpc.rs index 61eae88..eab2cf4 100644 --- a/src/jsonrpc.rs +++ b/src/jsonrpc.rs @@ -126,12 +126,7 @@ async fn handle_request( } }, "list_commands" => { - json!([ - "look", "go", "north", "south", "east", "west", "up", "down", - "say", "who", "take", "drop", "inventory", "equip", "use", - "examine", "talk", "attack", "defend", "flee", "cast", - "spells", "skills", "guild", "stats", "help" - ]) + json!(commands::get_command_list()) }, "execute" => { if let Some(pid) = *current_player_id {