mirror of
https://github.com/neovim/neovim.git
synced 2026-05-28 07:45:35 +00:00
fix(vim.net): unreliable integration tests #39594
This commit is contained in:
@@ -4,55 +4,42 @@ local skip_integ = os.getenv('NVIM_TEST_INTEG') ~= '1'
|
||||
|
||||
local exec_lua = n.exec_lua
|
||||
|
||||
local function assert_404_error(err)
|
||||
assert(
|
||||
err:lower():find('404') or err:find('22'),
|
||||
'Expected HTTP 404 or exit code 22, got: ' .. tostring(err)
|
||||
)
|
||||
end
|
||||
---@param method vim.net.HttpMethod
|
||||
---@param opts? vim.net.request.Opts
|
||||
---@return table
|
||||
--- Helper method to make a HTTP request with a 2s timeout
|
||||
local function request(method, url, opts)
|
||||
opts = opts or {}
|
||||
opts.retry = 3
|
||||
local result = exec_lua(function()
|
||||
local done = false
|
||||
local result
|
||||
|
||||
local function assert_wrong_headers(expected_err, header)
|
||||
local err = t.pcall_err(exec_lua, [[
|
||||
return vim.net.request('https://example.com', {
|
||||
headers = ]] .. header .. [[,
|
||||
}, function() end)
|
||||
]])
|
||||
t.matches(expected_err, err)
|
||||
end
|
||||
vim.net.request(method, url, opts, function(err, res)
|
||||
if err then
|
||||
result = { error = err }
|
||||
else
|
||||
---@type string|table
|
||||
local resp
|
||||
|
||||
local function assert_wrong_body(expected_err, body)
|
||||
local err = t.pcall_err(exec_lua, [[
|
||||
return vim.net.request('POST', 'https://example.com', {
|
||||
body = ]] .. body .. [[,
|
||||
}, function() end)
|
||||
]])
|
||||
t.matches(expected_err, err)
|
||||
end
|
||||
|
||||
local function assert_invalid_method(method)
|
||||
local err = t.pcall_err(exec_lua, [[
|
||||
return vim.net.request(']] .. method .. [[', 'https://example.com', {}, function() end)
|
||||
]])
|
||||
t.matches('invalid HTTP method', err)
|
||||
end
|
||||
|
||||
local function request(method, opts)
|
||||
return exec_lua([[
|
||||
local done = false
|
||||
local result
|
||||
|
||||
vim.net.request("]] .. method .. [[", "https://httpbingo.org/anything", ]] .. opts .. [[, function(err, res)
|
||||
if err then
|
||||
result = { error = err }
|
||||
local ok, parsed = pcall(vim.json.decode, res.body)
|
||||
if ok then
|
||||
resp = parsed
|
||||
else
|
||||
result = vim.json.decode(res.body)
|
||||
resp = res.body
|
||||
end
|
||||
done = true
|
||||
end)
|
||||
result = { error = nil, response = resp }
|
||||
end
|
||||
done = true
|
||||
end)
|
||||
|
||||
vim.wait(2000, function() return done end)
|
||||
return result
|
||||
]])
|
||||
vim.wait(2000, function()
|
||||
return done
|
||||
end)
|
||||
return result
|
||||
end)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
describe('vim.net.request', function()
|
||||
@@ -63,40 +50,35 @@ describe('vim.net.request', function()
|
||||
it('fetches a URL into memory (async success)', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
|
||||
local content = exec_lua([[
|
||||
local done = false
|
||||
local result
|
||||
---@type table
|
||||
local result = request('GET', 'https://httpbingo.org/anything')
|
||||
|
||||
vim.net.request("https://httpbingo.org/anything", { retry = 3 }, function(err, body)
|
||||
assert(not err, err)
|
||||
result = body.body
|
||||
done = true
|
||||
end)
|
||||
|
||||
vim.wait(2000, function() return done end)
|
||||
return result
|
||||
]])
|
||||
|
||||
assert(
|
||||
content and content:find('"url"%s*:%s*"https://httpbingo.org/anything"'),
|
||||
'Expected response body to contain the correct URL'
|
||||
)
|
||||
t.eq(nil, result.error, ('request failed: %s'):format(result.error))
|
||||
t.eq('https://httpbingo.org/anything', result.response.url)
|
||||
end)
|
||||
|
||||
it("detects filetype, sets 'nomodified'", function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
|
||||
local rv = exec_lua([[
|
||||
local rv = exec_lua(function()
|
||||
vim.cmd('runtime! plugin/nvim/net.lua')
|
||||
vim.cmd('runtime! filetype.lua')
|
||||
-- github raw dump of a small lua file in the neovim repo
|
||||
vim.cmd('edit https://raw.githubusercontent.com/neovim/neovim/master/runtime/syntax/tutor.lua')
|
||||
vim.wait(2000, function() return vim.bo.filetype ~= '' end)
|
||||
vim.cmd(
|
||||
'edit https://raw.githubusercontent.com/neovim/neovim/master/runtime/syntax/tutor.lua'
|
||||
)
|
||||
vim.wait(2000, function()
|
||||
return vim.bo.filetype ~= ''
|
||||
end)
|
||||
-- wait for buffer to have content
|
||||
vim.wait(2000, function() return vim.fn.wordcount().bytes > 0 end)
|
||||
vim.wait(2000, function() return vim.bo.modified == false end)
|
||||
vim.wait(2000, function()
|
||||
return vim.fn.wordcount().bytes > 0
|
||||
end)
|
||||
vim.wait(2000, function()
|
||||
return vim.bo.modified == false
|
||||
end)
|
||||
return { vim.bo.filetype, vim.bo.modified }
|
||||
]])
|
||||
end)
|
||||
|
||||
t.eq('lua', rv[1])
|
||||
t.eq(false, rv[2], 'Expected buffer to be unmodified for remote content')
|
||||
@@ -104,44 +86,38 @@ describe('vim.net.request', function()
|
||||
|
||||
it('calls on_response with error on 404 (async failure)', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
local err = exec_lua([[
|
||||
local done = false
|
||||
local result
|
||||
|
||||
vim.net.request("https://httpbingo.org/status/404", {}, function(e, _)
|
||||
result = e
|
||||
done = true
|
||||
end)
|
||||
|
||||
vim.wait(2000, function() return done end)
|
||||
return result
|
||||
]])
|
||||
|
||||
assert_404_error(err)
|
||||
local result = request('GET', 'https://httpbingo.org/status/404')
|
||||
t.matches('404', result.error)
|
||||
end)
|
||||
|
||||
it('plugin writes output to buffer', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
local content = exec_lua([[
|
||||
|
||||
local content = exec_lua(function()
|
||||
---@type string[]
|
||||
local lines
|
||||
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.net.request("https://httpbingo.org", { outbuf = buf })
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
vim.net.request('https://httpbingo.org', { outbuf = buf })
|
||||
|
||||
vim.wait(2000, function()
|
||||
lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
return lines[1] ~= ""
|
||||
return lines[1] ~= ''
|
||||
end)
|
||||
|
||||
return lines
|
||||
]])
|
||||
end)
|
||||
assert(content and content[1]:find('html'))
|
||||
end)
|
||||
|
||||
it('works with :read', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
local content = exec_lua([[
|
||||
|
||||
local content = exec_lua(function()
|
||||
vim.cmd('runtime plugin/net.lua')
|
||||
---@type string[]
|
||||
local lines
|
||||
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, { 'Here is some text' })
|
||||
@@ -153,7 +129,8 @@ describe('vim.net.request', function()
|
||||
end)
|
||||
|
||||
return lines
|
||||
]])
|
||||
end)
|
||||
|
||||
t.eq(true, content ~= nil)
|
||||
t.eq(true, content[1]:find('Here') ~= nil)
|
||||
t.eq(true, content[2]:find('html') ~= nil)
|
||||
@@ -162,11 +139,13 @@ describe('vim.net.request', function()
|
||||
it('opens remote tar.gz URLs as tar archives', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
|
||||
local rv = exec_lua([[
|
||||
local rv = exec_lua(function()
|
||||
vim.cmd('runtime! plugin/net.lua')
|
||||
vim.cmd('runtime! plugin/tarPlugin.vim')
|
||||
|
||||
vim.cmd('edit https://github.com/neovim/neovim/releases/download/nightly/nvim-macos-x86_64.tar.gz')
|
||||
vim.cmd(
|
||||
'edit https://github.com/neovim/neovim/releases/download/nightly/nvim-macos-x86_64.tar.gz'
|
||||
)
|
||||
|
||||
vim.wait(2500, function()
|
||||
return vim.bo.filetype == 'tar' or vim.b.tarfile ~= nil
|
||||
@@ -177,7 +156,7 @@ describe('vim.net.request', function()
|
||||
modified = vim.bo.modified,
|
||||
tarfile = vim.b.tarfile ~= nil,
|
||||
}
|
||||
]])
|
||||
end)
|
||||
|
||||
t.eq('tar', rv.filetype)
|
||||
t.eq(false, rv.modified)
|
||||
@@ -187,7 +166,7 @@ describe('vim.net.request', function()
|
||||
it('opens remote zip URLs as zip archives', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
|
||||
local rv = exec_lua([[
|
||||
local rv = exec_lua(function()
|
||||
vim.cmd('runtime! plugin/net.lua')
|
||||
vim.cmd('runtime! plugin/zipPlugin.vim')
|
||||
|
||||
@@ -202,7 +181,7 @@ describe('vim.net.request', function()
|
||||
modified = vim.bo.modified,
|
||||
zipfile = vim.b.zipfile ~= nil,
|
||||
}
|
||||
]])
|
||||
end)
|
||||
|
||||
t.eq('zip', rv.filetype)
|
||||
t.eq(false, rv.modified)
|
||||
@@ -211,79 +190,123 @@ describe('vim.net.request', function()
|
||||
|
||||
it('accepts custom headers', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
---@type table
|
||||
local result = request('GET', 'https://httpbingo.org/anything', {
|
||||
headers = {
|
||||
Authorization = 'Bearer test-token',
|
||||
['X-Custom-Header'] = 'custom-value',
|
||||
['Empty'] = '',
|
||||
},
|
||||
})
|
||||
|
||||
local headers = exec_lua([[
|
||||
local done = false
|
||||
local result
|
||||
t.eq(nil, result.error, ('request failed: %s'):format(result.error))
|
||||
t.eq('table', type(result.response.headers), 'Expected headers to be a table')
|
||||
|
||||
vim.net.request("https://httpbingo.org/headers", {
|
||||
headers = {
|
||||
Authorization = "Bearer test-token",
|
||||
['X-Custom-Header'] = "custom-value",
|
||||
['Empty'] = '',
|
||||
},
|
||||
}, function(err, res)
|
||||
if err then
|
||||
result = { error = err }
|
||||
else
|
||||
result = vim.json.decode(res.body).headers
|
||||
end
|
||||
done = true
|
||||
end)
|
||||
|
||||
vim.wait(2000, function() return done end)
|
||||
return result
|
||||
]])
|
||||
|
||||
t.eq('table', type(headers), 'Expected headers to be a table')
|
||||
-- httpbingo.org/request returns each header as a list in the returned value
|
||||
t.eq(headers.Authorization[1], 'Bearer test-token', 'Expected Authorization header')
|
||||
t.eq(headers['X-Custom-Header'][1], 'custom-value', 'Expected X-Custom-Header')
|
||||
t.eq(headers['Empty'][1], '', 'Expected Empty header')
|
||||
t.eq(
|
||||
'Bearer test-token',
|
||||
result.response.headers.Authorization[1],
|
||||
'Expected Authorization header'
|
||||
)
|
||||
t.eq('custom-value', result.response.headers['X-Custom-Header'][1], 'Expected X-Custom-Header')
|
||||
t.eq('', result.response.headers['Empty'][1], 'Expected Empty header')
|
||||
end)
|
||||
|
||||
it('accepts multiple HTTP methods', function()
|
||||
t.skip(skip_integ, 'NVIM_TEST_INTEG not set: skipping network integration test')
|
||||
|
||||
t.eq(request('GET', '{}').method, 'GET')
|
||||
t.eq(request('PUT', '{}').method, 'PUT')
|
||||
t.eq(request('PATCH', '{}').method, 'PATCH')
|
||||
t.eq(request('DELETE', '{}').method, 'DELETE')
|
||||
local url = 'https://httpbingo.org/anything'
|
||||
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
local post =
|
||||
request('POST', "{body='{\"a\": 1}', headers={['Content-Type'] = 'application/json'}}")
|
||||
t.eq(post.method, 'POST')
|
||||
t.eq(post.headers['Content-Type'][1], 'application/json')
|
||||
t.eq(post.json.a, 1)
|
||||
local function assert_accept_method(method)
|
||||
local result = request(method, url)
|
||||
t.eq(nil, result.error)
|
||||
t.eq(method, result.response.method)
|
||||
end
|
||||
|
||||
assert_accept_method('GET')
|
||||
assert_accept_method('PUT')
|
||||
assert_accept_method('PATCH')
|
||||
assert_accept_method('DELETE')
|
||||
|
||||
-- HEAD request
|
||||
local result = request('HEAD', url)
|
||||
t.eq(nil, result.error)
|
||||
|
||||
-- testing body payload
|
||||
result = request('POST', url, {
|
||||
body = '{"a": 1}',
|
||||
headers = {
|
||||
['Content-Type'] = 'application/json',
|
||||
},
|
||||
})
|
||||
t.eq(nil, result.error)
|
||||
t.eq(1, result.response.json.a)
|
||||
end)
|
||||
|
||||
it('validation', function()
|
||||
assert_wrong_headers('opts.headers: expected table, got number', '123')
|
||||
assert_wrong_headers('headers keys and values must be strings', "{ [123] = 'value' }")
|
||||
assert_wrong_headers('headers keys and values must be strings', '{ Header = 123 }')
|
||||
assert_wrong_headers(
|
||||
local function assert_wrong_request(expected_err, method, opts)
|
||||
if type(method) ~= 'string' then
|
||||
opts = method
|
||||
method = 'GET'
|
||||
end
|
||||
|
||||
local result = t.pcall_err(exec_lua, function()
|
||||
vim.net.request(method, 'https://example.com', opts)
|
||||
end)
|
||||
t.matches(expected_err, result)
|
||||
end
|
||||
|
||||
-- headers asserts
|
||||
assert_wrong_request('opts.headers: expected table, got number', { headers = 123 })
|
||||
|
||||
--- FIXME(ellisonleao): this special assert is failing because the opts table is putting [""] in
|
||||
--- the key value instead of [123] upon calling the helper method
|
||||
-- assert_wrong_request(
|
||||
-- 'headers keys and values must be strings',
|
||||
-- { headers = { [123] = 'value' } }
|
||||
-- )
|
||||
|
||||
assert_wrong_request('headers keys and values must be strings', { headers = { Header = 123 } })
|
||||
assert_wrong_request(
|
||||
'header keys must not start with @ or end with : and ;',
|
||||
"{ ['Header:'] = 'value' }"
|
||||
{ headers = { ['Header:'] = 'value' } }
|
||||
)
|
||||
assert_wrong_headers(
|
||||
assert_wrong_request(
|
||||
'header keys must not start with @ or end with : and ;',
|
||||
"{ ['Header;'] = 'value' }"
|
||||
{ headers = { ['Header;'] = 'value' } }
|
||||
)
|
||||
assert_wrong_headers(
|
||||
assert_wrong_request(
|
||||
'header keys must not start with @ or end with : and ;',
|
||||
"{ ['@filename'] = '' }"
|
||||
{ headers = { ['@filename'] = '' } }
|
||||
)
|
||||
|
||||
assert_wrong_body('opts.body: expected body should be string and not start with @', '123')
|
||||
assert_wrong_body('opts.body: expected body should be string and not start with @', '{}')
|
||||
assert_wrong_body('opts.body: expected body should be string and not start with @', "'@test'")
|
||||
-- body asserts
|
||||
assert_wrong_request(
|
||||
'opts.body: expected body should be string and not start with @',
|
||||
{ body = 123 }
|
||||
)
|
||||
assert_wrong_request(
|
||||
'opts.body: expected body should be string and not start with @',
|
||||
{ body = {} }
|
||||
)
|
||||
assert_wrong_request(
|
||||
'opts.body: expected body should be string and not start with @',
|
||||
{ body = '@test' }
|
||||
)
|
||||
|
||||
-- OPTIONS is not accepted
|
||||
assert_invalid_method('OPTIONS')
|
||||
|
||||
assert_wrong_request(
|
||||
'expected method should be one of GET, POST, PUT, PATCH, HEAD, DELETE, got OPTIONS',
|
||||
'OPTIONS'
|
||||
)
|
||||
-- lowercase methods are not accepted as well
|
||||
assert_invalid_method('options')
|
||||
assert_invalid_method('get')
|
||||
assert_wrong_request(
|
||||
'expected method should be one of GET, POST, PUT, PATCH, HEAD, DELETE, got options',
|
||||
'options'
|
||||
)
|
||||
assert_wrong_request(
|
||||
'expected method should be one of GET, POST, PUT, PATCH, HEAD, DELETE, got get',
|
||||
'get'
|
||||
)
|
||||
end)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user