feat: scraper spec

This commit is contained in:
Barrett Ruth 2025-09-18 22:58:14 -04:00
parent c4f0937668
commit d83bc6c306

View file

@ -1,55 +1,363 @@
describe('cp.scrape', function()
local scrape
local mock_cache
local mock_system_calls
local temp_files
before_each(function()
scrape = require('cp.scrape')
temp_files = {}
mock_cache = {
load = function() end,
get_contest_data = function() return nil end,
set_contest_data = function() end
}
mock_system_calls = {}
local original_system = vim.system
vim.system = function(cmd, opts)
table.insert(mock_system_calls, { cmd = cmd, opts = opts })
local result = { code = 0, stdout = '{}', stderr = '' }
if cmd[1] == 'ping' then
result = { code = 0 }
elseif cmd[1] == 'uv' and cmd[2] == 'sync' then
result = { code = 0 }
elseif cmd[1] == 'uv' and cmd[2] == 'run' then
if vim.tbl_contains(cmd, 'metadata') then
result.stdout = '{"success": true, "problems": [{"id": "a", "name": "Test Problem"}]}'
elseif vim.tbl_contains(cmd, 'tests') then
result.stdout = '{"success": true, "tests": [{"input": "1 2", "expected": "3"}], "url": "https://example.com"}'
end
end
return {
wait = function()
return result
end
}
end
package.loaded['cp.cache'] = mock_cache
local original_fn = vim.fn
vim.fn = vim.tbl_extend('force', vim.fn, {
executable = function(cmd)
if cmd == 'uv' then return 1 end
return original_fn.executable(cmd)
end,
isdirectory = function(path)
if path:match('%.venv$') then return 1 end
return original_fn.isdirectory(path)
end,
filereadable = function(path)
if temp_files[path] then return 1 end
return 0
end,
readfile = function(path)
return temp_files[path] or {}
end,
writefile = function(lines, path)
temp_files[path] = lines
end,
mkdir = function() end,
fnamemodify = function(path, modifier)
if modifier == ':r' then
return path:gsub('%..*$', '')
end
return original_fn.fnamemodify(path, modifier)
end
})
end)
describe('platform detection', function()
it('detects codeforces contests correctly', function() end)
it('detects atcoder contests correctly', function() end)
it('detects cses problems correctly', function() end)
it('handles invalid contest identifiers', function() end)
end)
describe('metadata scraping', function()
it('retrieves contest metadata from scrapers', function() end)
it('parses problem lists correctly', function() end)
it('handles scraper failures gracefully', function() end)
it('validates scraped data structure', function() end)
end)
describe('test case scraping', function()
it('retrieves test cases for problems', function() end)
it('handles missing test cases', function() end)
it('validates test case format', function() end)
it('processes multiple test cases correctly', function() end)
after_each(function()
package.loaded['cp.cache'] = nil
vim.system = vim.system_original or vim.system
temp_files = {}
end)
describe('cache integration', function()
it('stores scraped data in cache', function() end)
it('returns cached data when available', function()
mock_cache.get_contest_data = function(platform, contest_id)
if platform == 'atcoder' and contest_id == 'abc123' then
return { problems = { { id = 'a', name = 'Cached Problem' } } }
end
return nil
end
it('retrieves cached data when available', function() end)
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
it('respects cache expiry settings', function() end)
assert.is_true(result.success)
assert.equals(1, #result.problems)
assert.equals('Cached Problem', result.problems[1].name)
assert.equals(0, #mock_system_calls)
end)
it('handles cache invalidation correctly', function() end)
it('stores scraped data in cache after successful scrape', function()
local stored_data = nil
mock_cache.set_contest_data = function(platform, contest_id, problems)
stored_data = { platform = platform, contest_id = contest_id, problems = problems }
end
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
assert.is_true(result.success)
assert.is_not_nil(stored_data)
assert.equals('atcoder', stored_data.platform)
assert.equals('abc123', stored_data.contest_id)
assert.equals(1, #stored_data.problems)
end)
end)
describe('error handling', function()
it('handles network connectivity issues', function() end)
describe('system dependency checks', function()
it('handles missing uv executable', function()
vim.fn.executable = function(cmd)
if cmd == 'uv' then return 0 end
return 1
end
it('reports scraper execution errors', function() end)
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
it('provides meaningful error messages', function() end)
assert.is_false(result.success)
assert.is_true(result.error:match('Python environment setup failed'))
end)
it('handles python environment setup failure', function()
vim.system = function(cmd, opts)
if cmd[1] == 'ping' then
return { wait = function() return { code = 0 } end }
elseif cmd[1] == 'uv' and cmd[2] == 'sync' then
return { wait = function() return { code = 1, stderr = 'setup failed' } end }
end
return { wait = function() return { code = 0 } end }
end
vim.fn.isdirectory = function() return 0 end
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
assert.is_false(result.success)
assert.is_true(result.error:match('Python environment setup failed'))
end)
it('handles network connectivity issues', function()
vim.system = function(cmd, opts)
if cmd[1] == 'ping' then
return { wait = function() return { code = 1 } end }
end
return { wait = function() return { code = 0 } end }
end
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
assert.is_false(result.success)
assert.equals('No internet connection available', result.error)
end)
end)
describe('subprocess execution', function()
it('constructs correct command for atcoder metadata', function()
scrape.scrape_contest_metadata('atcoder', 'abc123')
local metadata_call = nil
for _, call in ipairs(mock_system_calls) do
if vim.tbl_contains(call.cmd, 'metadata') then
metadata_call = call
break
end
end
assert.is_not_nil(metadata_call)
assert.equals('uv', metadata_call.cmd[1])
assert.equals('run', metadata_call.cmd[2])
assert.is_true(vim.tbl_contains(metadata_call.cmd, 'metadata'))
assert.is_true(vim.tbl_contains(metadata_call.cmd, 'abc123'))
end)
it('constructs correct command for cses metadata', function()
scrape.scrape_contest_metadata('cses', 'problemset')
local metadata_call = nil
for _, call in ipairs(mock_system_calls) do
if vim.tbl_contains(call.cmd, 'metadata') then
metadata_call = call
break
end
end
assert.is_not_nil(metadata_call)
assert.equals('uv', metadata_call.cmd[1])
assert.is_true(vim.tbl_contains(metadata_call.cmd, 'metadata'))
assert.is_false(vim.tbl_contains(metadata_call.cmd, 'problemset'))
end)
it('handles subprocess execution failure', function()
vim.system = function(cmd, opts)
if cmd[1] == 'ping' then
return { wait = function() return { code = 0 } end }
elseif cmd[1] == 'uv' and vim.tbl_contains(cmd, 'metadata') then
return { wait = function() return { code = 1, stderr = 'execution failed' } end }
end
return { wait = function() return { code = 0 } end }
end
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
assert.is_false(result.success)
assert.is_true(result.error:match('Failed to run metadata scraper'))
assert.is_true(result.error:match('execution failed'))
end)
end)
describe('json parsing', function()
it('handles invalid json output', function()
vim.system = function(cmd, opts)
if cmd[1] == 'ping' then
return { wait = function() return { code = 0 } end }
elseif cmd[1] == 'uv' and vim.tbl_contains(cmd, 'metadata') then
return { wait = function() return { code = 0, stdout = 'invalid json' } end }
end
return { wait = function() return { code = 0 } end }
end
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
assert.is_false(result.success)
assert.is_true(result.error:match('Failed to parse metadata scraper output'))
end)
it('handles scraper-reported failures', function()
vim.system = function(cmd, opts)
if cmd[1] == 'ping' then
return { wait = function() return { code = 0 } end }
elseif cmd[1] == 'uv' and vim.tbl_contains(cmd, 'metadata') then
return { wait = function()
return {
code = 0,
stdout = '{"success": false, "error": "contest not found"}'
}
end }
end
return { wait = function() return { code = 0 } end }
end
local result = scrape.scrape_contest_metadata('atcoder', 'abc123')
assert.is_false(result.success)
assert.equals('contest not found', result.error)
end)
end)
describe('problem scraping', function()
local test_context
before_each(function()
test_context = {
contest = 'atcoder',
contest_id = 'abc123',
problem_id = 'a',
problem_name = 'abc123a',
input_file = 'io/abc123a.cpin',
expected_file = 'io/abc123a.expected'
}
end)
it('uses existing files when available', function()
temp_files['io/abc123a.cpin'] = { '1 2' }
temp_files['io/abc123a.expected'] = { '3' }
temp_files['io/abc123a.1.cpin'] = { '4 5' }
temp_files['io/abc123a.1.cpout'] = { '9' }
local result = scrape.scrape_problem(test_context)
assert.is_true(result.success)
assert.equals('abc123a', result.problem_id)
assert.equals(1, result.test_count)
assert.equals(0, #mock_system_calls)
end)
it('scrapes and writes test case files', function()
local result = scrape.scrape_problem(test_context)
assert.is_true(result.success)
assert.equals('abc123a', result.problem_id)
assert.equals(1, result.test_count)
assert.is_not_nil(temp_files['io/abc123a.1.cpin'])
assert.is_not_nil(temp_files['io/abc123a.1.cpout'])
assert.equals('1 2', table.concat(temp_files['io/abc123a.1.cpin'], '\n'))
assert.equals('3', table.concat(temp_files['io/abc123a.1.cpout'], '\n'))
end)
it('constructs correct command for atcoder problem tests', function()
scrape.scrape_problem(test_context)
local tests_call = nil
for _, call in ipairs(mock_system_calls) do
if vim.tbl_contains(call.cmd, 'tests') then
tests_call = call
break
end
end
assert.is_not_nil(tests_call)
assert.is_true(vim.tbl_contains(tests_call.cmd, 'tests'))
assert.is_true(vim.tbl_contains(tests_call.cmd, 'abc123'))
assert.is_true(vim.tbl_contains(tests_call.cmd, 'a'))
end)
it('constructs correct command for cses problem tests', function()
test_context.contest = 'cses'
test_context.contest_id = '1001'
test_context.problem_id = nil
scrape.scrape_problem(test_context)
local tests_call = nil
for _, call in ipairs(mock_system_calls) do
if vim.tbl_contains(call.cmd, 'tests') then
tests_call = call
break
end
end
assert.is_not_nil(tests_call)
assert.is_true(vim.tbl_contains(tests_call.cmd, 'tests'))
assert.is_true(vim.tbl_contains(tests_call.cmd, '1001'))
assert.is_false(vim.tbl_contains(tests_call.cmd, 'a'))
end)
end)
describe('error scenarios', function()
it('validates input parameters', function()
assert.has_error(function()
scrape.scrape_contest_metadata(nil, 'abc123')
end)
assert.has_error(function()
scrape.scrape_contest_metadata('atcoder', nil)
end)
end)
it('handles file system errors gracefully', function()
vim.fn.mkdir = function()
error('permission denied')
end
local ctx = {
contest = 'atcoder',
contest_id = 'abc123',
problem_id = 'a',
problem_name = 'abc123a',
input_file = 'io/abc123a.cpin',
expected_file = 'io/abc123a.expected'
}
assert.has_error(function()
scrape.scrape_problem(ctx)
end)
end)
end)
end)