Skip to content

Commit eb5ac7f

Browse files
timhughAntoineGS
authored andcommitted
rethink system stubs for nodejs tests
1 parent d68f6b1 commit eb5ac7f

File tree

4 files changed

+173
-85
lines changed

4 files changed

+173
-85
lines changed

lua/copilot/lsp/nodejs.lua

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,7 @@ end
9797

9898
---@return table
9999
function M.get_execute_command()
100-
if type(M.node_command) == "string" then
101-
return {
102-
M.node_command,
103-
M.server_path or M.get_server_path(),
104-
"--stdio",
105-
}
106-
elseif type(M.node_command) == "table" then
107-
return util.append_command(M.node_command, { M.server_path or M.get_server_path(), "--stdio" })
108-
else
109-
error(string.format("failed to build node command from %s (type %s)", M.node_command, type(M.node_command)))
110-
end
100+
return util.append_command(M.node_command, { M.server_path or M.get_server_path(), "--stdio" })
111101
end
112102

113103
---@param node_command? string|string[]

lua/copilot/util.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,9 @@ M.append_command = function(cmd, append)
197197
table.insert(full_cmd, cmd)
198198
elseif type(cmd) == "table" then
199199
for _, part in ipairs(cmd) do
200-
table.insert(full_cmd, part)
200+
if part ~= nil then
201+
table.insert(full_cmd, part)
202+
end
201203
end
202204
end
203205

@@ -206,7 +208,9 @@ M.append_command = function(cmd, append)
206208
table.insert(full_cmd, append)
207209
elseif type(append) == "table" then
208210
for _, part in ipairs(append) do
209-
table.insert(full_cmd, part)
211+
if part ~= nil then
212+
table.insert(full_cmd, part)
213+
end
210214
end
211215
end
212216

tests/test_nodejs.lua

Lines changed: 87 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local eq = MiniTest.expect.equality
2+
local stub = require("tests.test_nodejs_stubs")
23

34
local T = MiniTest.new_set({
45
hooks = {
@@ -12,156 +13,170 @@ local T = MiniTest.new_set({
1213

1314
T["get_node_version()"] = MiniTest.new_set()
1415

15-
local function stub_process(stdout, code, fail, callback)
16-
local captured_args = nil
17-
local original_vim_system = vim.system
18-
vim.system = function(cmd, opts)
19-
captured_args = cmd
20-
if fail then
21-
error("Command failed")
22-
end
23-
return {
24-
wait = function()
25-
return {
26-
stdout = stdout .. "\n",
27-
code = code
28-
}
29-
end
30-
}
31-
end
32-
callback()
33-
vim.system = original_vim_system
34-
return captured_args
35-
end
36-
3716
T["get_node_version()"]["default node command"] = function()
38-
captured_args = stub_process("v20.10.0", 0, false, function()
17+
local captured_args = stub.valid_node(function()
3918
local nodejs = require("copilot.lsp.nodejs")
4019
nodejs.setup()
4120

4221
local version, error = nodejs.get_node_version()
4322

44-
eq(version, "20.10.0")
23+
eq(version, stub.valid_node_version)
4524
eq(error, nil)
4625
end)
4726
eq(captured_args, { "node", "--version" })
4827
end
4928

5029
T["get_node_version()"]["custom node command as string"] = function()
51-
local captured_args = stub_process("v20.10.0", 0, false, function()
30+
local captured_args = stub.valid_node(function()
5231
local nodejs = require("copilot.lsp.nodejs")
5332
nodejs.setup("/usr/local/bin/node")
5433

5534
local version, error = nodejs.get_node_version()
5635

57-
eq(version, "20.10.0")
36+
eq(version, stub.valid_node_version)
5837
eq(error, nil)
5938
end)
6039
eq(captured_args, { "/usr/local/bin/node", "--version" })
6140
end
6241

6342
T["get_node_version()"]["custom node command as string with spaces"] = function()
64-
local captured_args = stub_process("v20.10.0", 0, false, function()
43+
local captured_args = stub.valid_node(function()
6544
local nodejs = require("copilot.lsp.nodejs")
6645
nodejs.setup("/path to/node")
6746

6847
local version, error = nodejs.get_node_version()
6948

70-
eq(version, "20.10.0")
49+
eq(version, stub.valid_node_version)
7150
eq(error, nil)
7251
end)
7352
eq(captured_args, { "/path to/node", "--version" })
7453
end
7554

7655
T["get_node_version()"]["custom node command as table"] = function()
77-
local captured_args = stub_process("v20.10.0", 0, false, function()
56+
local captured_args = stub.valid_node(function()
7857
local nodejs = require("copilot.lsp.nodejs")
7958
nodejs.setup({ "mise", "x", "node@lts", "--", "node" })
8059

8160
local version, error = nodejs.get_node_version()
8261

83-
eq(version, "20.10.0")
62+
eq(version, stub.valid_node_version)
8463
eq(error, nil)
8564
end)
8665
eq(captured_args, { "mise", "x", "node@lts", "--", "node", "--version" })
8766
end
8867

8968
T["get_node_version()"]["handles vim.system failure"] = function()
90-
local captured_args = stub_process("", -1, true, function()
69+
local captured_args = stub.process("", -1, true, function()
9170
local nodejs = require("copilot.lsp.nodejs")
9271
nodejs.setup("node")
9372

94-
local version, error = nodejs.get_node_version()
73+
local _, error = nodejs.get_node_version()
9574

96-
eq(version, "")
97-
-- Error should contain failure information
98-
local expected_error_pattern = "Could not determine Node%.js version"
99-
eq(type(error), "string")
100-
eq(error:find(expected_error_pattern) ~= nil, true)
75+
eq(error:find("Could not determine Node.js version") ~= nil, true)
10176
end)
77+
eq(captured_args, { "node", "--version" })
10278
end
10379

10480
T["get_node_version()"]["handles process with non-zero exit code"] = function()
105-
local captured_args = stub_process("", 127, false, function()
81+
local captured_args = stub.process("", 127, false, function()
10682
local nodejs = require("copilot.lsp.nodejs")
10783
nodejs.setup("nonexistent-node")
10884

109-
local version, error = nodejs.get_node_version()
85+
local _, error = nodejs.get_node_version()
11086

111-
eq(version, "")
112-
-- Error should contain failure information with exit code
113-
local expected_error_pattern = "Could not determine Node%.js version"
114-
eq(type(error), "string")
115-
eq(error:find(expected_error_pattern) ~= nil, true)
116-
eq(error:find("127") ~= nil, true)
87+
eq(error:find("Could not determine Node.js version") ~= nil, true)
11788
end)
11889
eq(captured_args, { "nonexistent-node", "--version" })
11990
end
12091

12192
T["get_node_version()"]["validates node version requirement"] = function()
122-
local captured_args = stub_process("v18.17.0", 0, false, function()
93+
local captured_args = stub.invalid_node(function()
12394
local nodejs = require("copilot.lsp.nodejs")
12495
nodejs.setup("node")
12596

126-
local version, error = nodejs.get_node_version()
97+
local _, error = nodejs.get_node_version()
12798

128-
eq(version, "18.17.0")
129-
-- Error should indicate version requirement not met
130-
eq(type(error), "string")
131-
eq(error:find("Node%.js version 20 or newer required") ~= nil, true)
132-
eq(error:find("18%.17%.0") ~= nil, true)
99+
eq(error:find("Node.js version 20 or newer required") ~= nil, true)
133100
end)
134101
eq(captured_args, { "node", "--version" })
135102
end
136103

137104
T["get_execute_command()"] = MiniTest.new_set()
138105

139-
T["get_execute_command()"]["default node command"] = function()
140-
local nodejs = require("copilot.lsp.nodejs")
141-
nodejs.setup()
142-
local cmd = nodejs.get_execute_command()
143-
eq(cmd, { "node", nodejs.server_path, "--stdio" })
106+
T["get_execute_command()"]["default node command, default server path"] = function()
107+
local captured_path = stub.get_runtime_server_path(function()
108+
local nodejs = require("copilot.lsp.nodejs")
109+
eq(nodejs.setup(), true)
110+
local cmd = nodejs.get_execute_command()
111+
eq(cmd, { "node", stub.default_server_path, "--stdio" })
112+
end)
113+
eq(captured_path, stub.default_server_path)
114+
end
115+
116+
T["get_execute_command()"]["default node command, custom server path"] = function()
117+
stub.get_runtime_server_path(function()
118+
local nodejs = require("copilot.lsp.nodejs")
119+
eq(nodejs.setup(nil, stub.custom_server_path), true)
120+
local cmd = nodejs.get_execute_command()
121+
eq(cmd, { "node", stub.custom_server_path, "--stdio" })
122+
end)
123+
end
124+
125+
T["get_execute_command()"]["custom node command as string, default server path"] = function()
126+
local captured_path = stub.get_runtime_server_path(function()
127+
local nodejs = require("copilot.lsp.nodejs")
128+
eq(nodejs.setup("/usr/local/bin/node"), true)
129+
local cmd = nodejs.get_execute_command()
130+
eq(cmd, { "/usr/local/bin/node", stub.default_server_path, "--stdio" })
131+
end)
132+
eq(captured_path, stub.default_server_path)
133+
end
134+
135+
T["get_execute_command()"]["custom node command as string, custom server path"] = function()
136+
stub.get_runtime_server_path(function()
137+
local nodejs = require("copilot.lsp.nodejs")
138+
nodejs.setup("/usr/local/bin/node", stub.custom_server_path)
139+
local cmd = nodejs.get_execute_command()
140+
eq(cmd, { "/usr/local/bin/node", stub.custom_server_path, "--stdio" })
141+
end)
142+
end
143+
144+
T["get_execute_command()"]["custom node command as string with spaces, default server path"] = function()
145+
local captured_path = stub.get_runtime_server_path(function()
146+
local nodejs = require("copilot.lsp.nodejs")
147+
nodejs.setup("/path to/node")
148+
local cmd = nodejs.get_execute_command()
149+
eq(cmd, { "/path to/node", stub.default_server_path, "--stdio" })
150+
end)
151+
eq(captured_path, stub.default_server_path)
144152
end
145153

146-
T["get_execute_command()"]["custom node command as string"] = function()
147-
local nodejs = require("copilot.lsp.nodejs")
148-
nodejs.setup("/usr/local/bin/node")
149-
local cmd = nodejs.get_execute_command()
150-
eq(cmd, { "/usr/local/bin/node", nodejs.server_path, "--stdio" })
154+
T["get_execute_command()"]["custom node command as string with spaces, custom server path"] = function()
155+
stub.get_runtime_server_path(function()
156+
local nodejs = require("copilot.lsp.nodejs")
157+
nodejs.setup("/path to/node", stub.custom_server_path)
158+
local cmd = nodejs.get_execute_command()
159+
eq(cmd, { "/path to/node", stub.custom_server_path, "--stdio" })
160+
end)
151161
end
152162

153-
T["get_execute_command()"]["custom node command as string with spaces"] = function()
154-
local nodejs = require("copilot.lsp.nodejs")
155-
nodejs.setup("/path to/node")
156-
local cmd = nodejs.get_execute_command()
157-
eq(cmd, { "/path to/node", nodejs.server_path, "--stdio" })
163+
T["get_execute_command()"]["custom node command as table, default server path"] = function()
164+
local captured_path = stub.get_runtime_server_path(function()
165+
local nodejs = require("copilot.lsp.nodejs")
166+
nodejs.setup({ "mise", "x", "node@lts", "--", "node" })
167+
local cmd = nodejs.get_execute_command()
168+
eq(cmd, { "mise", "x", "node@lts", "--", "node", stub.default_server_path, "--stdio" })
169+
end)
170+
eq(captured_path, stub.default_server_path)
158171
end
159172

160-
T["get_execute_command()"]["custom node command as table"] = function()
161-
local nodejs = require("copilot.lsp.nodejs")
162-
nodejs.setup({ "mise", "x", "node@lts", "--", "node" })
163-
local cmd = nodejs.get_execute_command()
164-
eq(cmd, { "mise", "x", "node@lts", "--", "node", nodejs.server_path, "--stdio" })
173+
T["get_execute_command()"]["custom node command as table, custom server path"] = function()
174+
stub.get_runtime_server_path(function()
175+
local nodejs = require("copilot.lsp.nodejs")
176+
nodejs.setup({ "mise", "x", "node@lts", "--", "node" }, stub.custom_server_path)
177+
local cmd = nodejs.get_execute_command()
178+
eq(cmd, { "mise", "x", "node@lts", "--", "node", stub.custom_server_path, "--stdio" })
179+
end)
165180
end
166181

167182
return T

tests/test_nodejs_stubs.lua

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
-- these are helper functions for testing the nodejs LSP integration
2+
-- they stub out system interactions for verifying access to node and the LSP script
3+
4+
Stub = {}
5+
6+
Stub.default_server_path = "copilot/js/language-server.js"
7+
Stub.custom_server_path = "custom/path/to/language-server.js"
8+
9+
---@param stdout string the stdout that will be returned by the stubbed vim.system
10+
---@param code integer the exit code that will be returned by the stubbed vim.system
11+
---@param fail boolean if true, vim.system will error when called
12+
---@param callback function the function to call while vim.system is stubbed
13+
---@return table|nil captured_args -- the arguments vim.system was called with
14+
Stub.process = function(stdout, code, fail, callback)
15+
local captured_args = nil
16+
local original_vim_system = vim.system
17+
vim.system = function(cmd)
18+
captured_args = cmd
19+
if fail then
20+
error("Command failed")
21+
end
22+
return {
23+
wait = function()
24+
return {
25+
stdout = stdout .. "\n",
26+
code = code
27+
}
28+
end
29+
}
30+
end
31+
-- wrap callback in pcall to ensure vim.system is restored if callback errors
32+
local ok, err = pcall(callback)
33+
vim.system = original_vim_system
34+
if not ok then error(err) end
35+
return captured_args
36+
end
37+
38+
Stub.valid_node_version = "20.0.0"
39+
Stub.invalid_node_version = "10.0.0"
40+
41+
---Convenience wrapper for Stub.process for a valid Node.js version (>= 20)
42+
Stub.valid_node = function(callback)
43+
return Stub.process("v"..Stub.valid_node_version, 0, false, callback)
44+
end
45+
46+
---Convenience wrapper for Stub.process for an invalid Node.js version (< 20)
47+
Stub.invalid_node = function(callback)
48+
return Stub.process("v"..Stub.invalid_node_version, 0, false, callback)
49+
end
50+
51+
---@param callback function the function to call while vim.api.nvim_get_runtime_file is stubbed
52+
---@return string|nil captured_path -- the path vim.api.nvim_get_runtime_file was called with
53+
Stub.get_runtime_server_path = function(callback)
54+
local captured_path = nil
55+
56+
local original_get_file = vim.api.nvim_get_runtime_file
57+
vim.api.nvim_get_runtime_file = function(path)
58+
captured_path = path
59+
return { vim.fn.expand(Stub.default_server_path) }
60+
end
61+
62+
local original_filereadable = vim.fn.filereadable
63+
vim.fn.filereadable = function()
64+
return 1
65+
end
66+
67+
-- stub valid node version for callback so setup() succeeds
68+
Stub.valid_node(function()
69+
-- wrap callback in pcall to ensure vim.api.nvim_get_runtime_file is restored if callback errors
70+
local ok, err = pcall(callback)
71+
vim.api.nvim_get_runtime_file = original_get_file
72+
vim.fn.filereadable = original_filereadable
73+
if not ok then error(err) end
74+
end)
75+
76+
return captured_path
77+
end
78+
79+
return Stub

0 commit comments

Comments
 (0)