Skip to content

Commit f7c7b85

Browse files
committed
feat(tests): improve test coverage and add LuaDoc annotations
Change-Id: I9dd7e5e6ba33d0228a6c10d259110d426b5fefe2 Signed-off-by: Thomas Kosiewski <[email protected]>
1 parent 7376790 commit f7c7b85

File tree

8 files changed

+212
-105
lines changed

8 files changed

+212
-105
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,22 @@ jobs:
4343
luarocks install luacov-reporter-lcov
4444
4545
- name: Run Luacheck
46-
run: luacheck lua/ tests/
46+
run: luacheck lua/ tests/ --no-unused-args --no-max-line-length
4747

4848
- name: Run tests
4949
run: |
50-
cd tests
51-
busted --coverage
50+
chmod +x ./run_tests.sh
51+
./run_tests.sh
5252
5353
- name: Generate coverage report
5454
run: |
55-
cd tests
5655
luacov-console
5756
luacov-console -r lcov > lcov.info
5857
5958
- name: Upload coverage report
6059
uses: codecov/codecov-action@c16abc29c95fcf9174b58eb7e1abf4c866893bc8 # v4
6160
with:
62-
files: ./tests/lcov.info
61+
files: ./lcov.info
6362
fail_ci_if_error: false
6463

6564
integration-tests:

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ luac.out
4343
/luacov.*
4444
/tests/luacov.*
4545
/tests/lcov.info
46+
/lcov.info
47+
luacov.report.out
48+
luacov.stats.out
4649

4750
# Temporary files
4851
.DS_Store

flake.nix

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
treefmt = treefmt-nix.lib.evalModule pkgs {
1818
projectRootFile = "flake.nix";
1919
programs = {
20-
stylua.enable = true; # Lua formatter
21-
nixpkgs-fmt.enable = true; # Nix formatter
22-
prettier.enable = true; # Markdown/YAML/JSON formatter
23-
shfmt.enable = true; # Shell formatter
24-
actionlint.enable = true; # GitHub Actions linter
25-
zizmor.enable = true; # GitHub Actions security analyzer
26-
shellcheck.enable = true; # Shell script analyzer
20+
stylua.enable = true;
21+
nixpkgs-fmt.enable = true;
22+
prettier.enable = true;
23+
shfmt.enable = true;
24+
actionlint.enable = true;
25+
zizmor.enable = true;
26+
shellcheck.enable = true;
2727
};
2828
settings.formatter.shellcheck.options = [ "--exclude=SC1091,SC2016" ];
2929
};
@@ -37,24 +37,21 @@
3737

3838
devShells.default = pkgs.mkShell {
3939
buildInputs = with pkgs; [
40-
# Lua and development tools
4140
lua5_1
4241
luajitPackages.luacheck
43-
luajitPackages.busted # Testing framework
42+
luajitPackages.busted
43+
luajitPackages.luacov
4444

45-
# WebSocket implementation
4645
luajitPackages.luasocket
4746
luajitPackages.lua-cjson
4847

49-
# Development utilities
5048
ast-grep
51-
neovim # For testing the plugin
52-
luarocks # Lua package manager
53-
gnumake # For running the Makefile
54-
websocat # WebSocket testing utility
55-
jq # JSON processor for parsing responses
49+
neovim
50+
luarocks
51+
gnumake
52+
websocat
53+
jq
5654

57-
# Formatting tools (via treefmt-nix)
5855
treefmt.config.build.wrapper
5956
];
6057
};

lua/claudecode/server/init.lua

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,51 @@
1-
-- WebSocket server for Claude Code Neovim integration
1+
---@brief WebSocket server for Claude Code Neovim integration
22
local M = {}
3+
math.randomseed(os.time()) -- Seed for random port selection
34

4-
-- Server state
5+
---@class ServerState
6+
---@field server table|nil The server instance
7+
---@field port number|nil The port server is running on
8+
---@field clients table A list of connected clients
9+
---@field handlers table Message handlers by method name
510
M.state = {
611
server = nil,
712
port = nil,
813
clients = {},
914
handlers = {},
1015
}
1116

12-
-- Find an available port in the given range
13-
function M.find_available_port(min, _) -- '_' for unused max param
17+
---@brief Find an available port in the given range
18+
---@param min number The minimum port number
19+
---@param max number The maximum port number
20+
---@return number port The selected port
21+
function M.find_available_port(min, max)
1422
-- TODO: Implement port scanning logic
15-
-- For now, return a mock value
16-
return min
23+
if min > max then
24+
-- Defaulting to min in this edge case to avoid math.random error.
25+
-- Consider logging a warning or error here in a real scenario.
26+
return min
27+
end
28+
return math.random(min, max)
1729
end
1830

19-
-- Initialize the WebSocket server
31+
---@brief Initialize the WebSocket server
32+
---@param config table Configuration options
33+
---@return boolean success Whether server started successfully
34+
---@return number|string port_or_error Port number or error message
2035
function M.start(config)
2136
if M.state.server then
22-
-- Already running
2337
return false, "Server already running"
2438
end
2539

2640
-- TODO: Implement actual WebSocket server
2741
-- This is a placeholder that would be replaced with real implementation
2842

29-
-- Find an available port
3043
local port = M.find_available_port(config.port_range.min, config.port_range.max)
3144

3245
if not port then
3346
return false, "No available ports found"
3447
end
3548

36-
-- Store the port in state
3749
M.state.port = port
3850

3951
-- Mock server object for now
@@ -42,91 +54,78 @@ function M.start(config)
4254
clients = {},
4355
}
4456

45-
-- Register message handlers
4657
M.register_handlers()
4758

4859
return true, port
4960
end
5061

51-
-- Stop the WebSocket server
62+
---@brief Stop the WebSocket server
63+
---@return boolean success Whether server stopped successfully
64+
---@return string|nil error_message Error message if any
5265
function M.stop()
5366
if not M.state.server then
54-
-- Not running
5567
return false, "Server not running"
5668
end
5769

5870
-- TODO: Implement actual WebSocket server shutdown
59-
-- This is a placeholder
6071

61-
-- Reset state
6272
M.state.server = nil
6373
M.state.port = nil
6474
M.state.clients = {}
6575

6676
return true
6777
end
6878

69-
-- Register message handlers
79+
---@brief Register message handlers for the server
7080
function M.register_handlers()
7181
-- TODO: Implement message handler registration
72-
-- These would be functions that handle specific message types
7382

74-
-- Example handlers:
7583
M.state.handlers = {
7684
["mcp.connect"] = function(_, _) -- '_' for unused args
77-
-- Handle connection handshake
7885
-- TODO: Implement
7986
end,
8087

8188
["mcp.tool.invoke"] = function(_, _) -- '_' for unused args
82-
-- Handle tool invocation
8389
-- TODO: Implement by dispatching to tool implementations
8490
end,
8591
}
8692
end
8793

88-
-- Send a message to a client
94+
---@brief Send a message to a client
95+
---@param _client table The client to send to
96+
---@param _method string The method name
97+
---@param _params table|nil The parameters to send
98+
---@return boolean success Whether message was sent successfully
8999
function M.send(_client, _method, _params) -- Prefix unused params with underscore
90100
-- TODO: Implement sending WebSocket message
91-
-- This is a placeholder
92-
93-
-- Structure what would be sent (commented out to avoid unused var warning)
94-
-- local message = {
95-
-- jsonrpc = "2.0",
96-
-- method = _method,
97-
-- params = _params,
98-
-- }
99-
100-
-- Mock sending logic
101-
-- In real implementation, this would send the JSON-encoded message
102-
-- to the WebSocket client
103101

104102
return true
105103
end
106104

107-
-- Send a response to a client
108-
function M.send_response(_client, id, result, error_data) -- Normal params since they're now used
105+
---@brief Send a response to a client
106+
---@param _client table The client to send to
107+
---@param id number|string The request ID to respond to
108+
---@param result any|nil The result data if successful
109+
---@param error_data table|nil The error data if failed
110+
---@return boolean success Whether response was sent successfully
111+
function M.send_response(_client, id, result, error_data)
109112
-- TODO: Implement sending WebSocket response
110-
-- This is a placeholder
111113

112-
-- Structure what would be sent (this is a comment, we'll make a real variable below)
113-
-- In actual implementation, we would send the message via WebSocket
114114
if error_data then
115-
-- Create error response
116115
local _ = { jsonrpc = "2.0", id = id, error = error_data } -- luacheck: ignore
117116
else
118-
-- Create result response
119117
local _ = { jsonrpc = "2.0", id = id, result = result } -- luacheck: ignore
120118
end
121119

122-
-- Mock sending logic
123120
return true
124121
end
125122

126-
-- Broadcast a message to all connected clients
123+
---@brief Broadcast a message to all connected clients
124+
---@param method string The method name
125+
---@param params table|nil The parameters to send
126+
---@return boolean success Whether broadcast was successful
127127
function M.broadcast(method, params)
128128
-- TODO: Implement broadcasting to all clients
129-
-- This is a placeholder
130129

131130
for _, client in pairs(M.state.clients) do
132131
M.send(client, method, params)

run_tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ echo "Found test files:"
1111
echo "$TEST_FILES"
1212

1313
if [ -n "$TEST_FILES" ]; then
14-
# Pass test files to busted - quotes needed but shellcheck disabled as we need word splitting
14+
# Pass test files to busted with coverage flag - quotes needed but shellcheck disabled as we need word splitting
1515
# shellcheck disable=SC2086
16-
busted -v $TEST_FILES
16+
busted --coverage -v $TEST_FILES
1717
else
1818
echo "No test files found"
1919
fi

tests/mocks/vim.lua

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,67 @@
11
-- Mock implementation of the Neovim API for tests
22

3+
-- Spy functionality for testing
4+
if _G.spy == nil then
5+
_G.spy = {
6+
on = function(table, method_name)
7+
-- Save original function
8+
local original = table[method_name]
9+
-- Keep track of calls
10+
local calls = {}
11+
12+
-- Replace with spied function
13+
table[method_name] = function(...)
14+
table.insert(calls, { vals = { ... } })
15+
if original then
16+
return original(...)
17+
end
18+
end
19+
20+
-- Add spy methods to the function
21+
table[method_name].calls = calls
22+
table[method_name].spy = function()
23+
return {
24+
was_called = function(n)
25+
assert(#calls == n, "Expected " .. n .. " calls, got " .. #calls)
26+
return true
27+
end,
28+
was_not_called = function()
29+
assert(#calls == 0, "Expected 0 calls, got " .. #calls)
30+
return true
31+
end,
32+
was_called_with = function(...)
33+
local expected = { ... }
34+
assert(#calls > 0, "Function was never called")
35+
36+
-- Compare args
37+
local last_call = calls[#calls].vals
38+
for i, v in ipairs(expected) do
39+
if type(v) == "table" and v._type == "match" then
40+
-- Use custom matcher (simplified)
41+
if v._match == "is_table" and type(last_call[i]) ~= "table" then
42+
assert(false, "Expected table at arg " .. i)
43+
end
44+
else
45+
assert(last_call[i] == v, "Argument mismatch at position " .. i)
46+
end
47+
end
48+
return true
49+
end,
50+
}
51+
end
52+
53+
return table[method_name]
54+
end,
55+
}
56+
57+
-- Simple table matcher for spy assertions
58+
_G.match = {
59+
is_table = function()
60+
return { _type = "match", _match = "is_table" }
61+
end,
62+
}
63+
end
64+
365
local vim = {
466
_buffers = {},
567
_windows = {},

0 commit comments

Comments
 (0)