diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-26 12:42:08 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-26 12:42:08 -0300 |
| commit | 7fd555fcaf4304cb0dff3e8f5661565c3618299e (patch) | |
| tree | 61199793c3046d73b97e9822895ede2a33034d41 /test/server.lua | |
| download | lua-7fd555fcaf4304cb0dff3e8f5661565c3618299e.tar.gz | |
Initial importv0.1.0
Diffstat (limited to 'test/server.lua')
| -rw-r--r-- | test/server.lua | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/test/server.lua b/test/server.lua new file mode 100644 index 0000000..a51b8bf --- /dev/null +++ b/test/server.lua @@ -0,0 +1,182 @@ +--- Programmable HTTP server used by the suite, run as a separate process. +-- +-- Lua has no threads, and the client blocks on its socket, so the server +-- cannot share this interpreter with the code under test. It is spawned once +-- per run, binds a free loopback port and writes it to the file named in +-- `arg[1]`, then answers until it is told to stop or nothing has connected for +-- a while. +-- +-- Three control routes drive it from the test process: +-- +-- POST /__program replace the routes and clear the call log +-- GET /__calls the request targets seen so far, as a JSON array +-- GET /__stop shut down +-- +-- A route maps a path (query string excluded) to `{ responses = { ... } }`. +-- Each request consumes the next response; the last one repeats once the list +-- runs out, which is how retrying is tested. A route may instead set +-- `csv_from_query`, and the server then generates one CSV row per CEP in the +-- query — that keeps the 501-CEP chunking test from shipping a body through +-- the control channel. + +local socket = require("socket") +local json = require("dkjson") + +local IDLE_TIMEOUT = 60 + +local REASON = { + [200] = "OK", + [400] = "Bad Request", + [404] = "Not Found", + [429] = "Too Many Requests", + [500] = "Internal Server Error", + [502] = "Bad Gateway", + [503] = "Service Unavailable", +} + +local CSV_HEADER = + "cep,logradouro,complemento,unidade,bairro,localidade,uf,estado,regiao,ibge,gia,ddd,siafi" + +local routes = {} +local cursor = {} +local calls = {} +local running = true + +local function unescape(value) + return (value:gsub("%%(%x%x)", function(hex) + return string.char(tonumber(hex, 16)) + end)) +end + +local function read_request(client) + local line = client:receive("*l") + if not line then + return nil + end + + local method, target = line:match("^(%u+)%s+(%S+)") + if not method then + return nil + end + + local headers = {} + while true do + local header = client:receive("*l") + if not header or header == "" then + break + end + local name, value = header:match("^([^:]+):%s*(.*)$") + if name then + headers[name:lower()] = value + end + end + + local body = "" + local length = tonumber(headers["content-length"] or "0") or 0 + if length > 0 then + body = client:receive(length) or "" + end + + return method, target, body +end + +local function send(client, status, body, headers) + local out = { + string.format("HTTP/1.1 %d %s\r\n", status, REASON[status] or "Status"), + } + for name, value in pairs(headers or {}) do + out[#out + 1] = name .. ": " .. tostring(value) .. "\r\n" + end + out[#out + 1] = "Content-Length: " .. #body .. "\r\n" + out[#out + 1] = "Connection: close\r\n\r\n" + out[#out + 1] = body + client:send(table.concat(out)) +end + +local function csv_for(target) + local raw = target:match("[?&]ceps=([^&]*)") or "" + local rows = { CSV_HEADER } + for cep in unescape(raw):gmatch("[^,]+") do + rows[#rows + 1] = cep .. ",,,,,X,SP,São Paulo,Sudeste,1,,11,1" + end + return table.concat(rows, "\n") .. "\n" +end + +local function next_response(path) + local route = routes[path] + if not route then + return nil + end + + local responses = route.responses + if not responses or #responses == 0 then + return route + end + + local index = cursor[path] or 1 + if index < #responses then + cursor[path] = index + 1 + end + return responses[index] +end + +local function serve(client, target, body) + local path = target:match("^([^?]*)") + + if path == "/__program" then + routes = json.decode(body) or {} + cursor = {} + calls = {} + send(client, 200, "{}") + return + end + + if path == "/__calls" then + send(client, 200, json.encode(calls)) + return + end + + if path == "/__stop" then + running = false + send(client, 200, "{}") + return + end + + calls[#calls + 1] = target + + local response = next_response(path) + if not response then + send(client, 404, '{"erro":"cep_nao_encontrado","mensagem":"CEP nao existe"}') + return + end + + if response.csv_from_query then + send(client, response.status or 200, csv_for(target), response.headers) + return + end + + send(client, response.status or 200, response.body or "", response.headers) +end + +local server = assert(socket.bind("127.0.0.1", 0)) +local _, port = server:getsockname() +server:settimeout(IDLE_TIMEOUT) + +local handle = assert(io.open(arg[1], "w")) +handle:write(tostring(port)) +handle:close() + +while running do + local client = server:accept() + if not client then + break + end + client:settimeout(10) + local _, target, body = read_request(client) + if target then + serve(client, target, body) + end + client:close() +end + +server:close() |