furt/tests/test_http.lua

274 lines
7.4 KiB
Lua
Raw Normal View History

feat(furt): implement complete Lua HTTP-Server for digital sovereignty (#63) - Add furt-lua/ directory with pure Lua implementation - Replace planned Go implementation with Corporate-free technology - Complete Week 1 Challenge: HTTP-Server to production-ready in 48min - HTTP-Server in pure Lua (185 lines, lua-socket based) - JSON API endpoints with request/response parsing - Modular architecture: each file < 200 lines - Error handling for 404, 400, validation scenarios - GET /health - Service health check with timestamp - POST /test - Development testing with request echo - POST /v1/mail/send - Mail service foundation with validation - Comprehensive error responses with structured JSON - Smart startup script with dependency auto-detection - Automated test suite with lua-socket HTTP client - Manual curl test suite for development workflow - Complete documentation and installation guide - FROM: Go (Google-controlled) → TO: Lua (PUC-Rio University) - Corporate-free dependency chain: lua-socket + lua-cjson + lua-ssl - Performance superior: < 1ms response time, minimal memory usage - Foundation for planned C+Lua hybrid architecture - furt-lua/src/main.lua - HTTP-Server implementation - furt-lua/config/server.lua - Lua-based configuration - furt-lua/scripts/start.sh - Startup with dependency checks - furt-lua/scripts/test_curl.sh - Manual testing suite - furt-lua/tests/test_http.lua - Automated test framework - furt-lua/README.md - Implementation documentation - README.md - Document Go→Lua migration strategy - .gitignore - Add Lua artifacts, luarocks, issue-scripts All endpoints tested and working: ✓ Health check returns proper JSON status ✓ Test endpoint processes POST requests with JSON ✓ Mail endpoint validates required fields (name, email, message) ✓ Error handling returns appropriate HTTP status codes Ready for Week 2: SMTP integration with mail.dragons-at-work.de Completes #63 Related #62
2025-06-17 20:40:40 +02:00
-- furt-lua/tests/test_http.lua
-- Basic HTTP tests for Furt Lua HTTP-Server
local socket = require("socket")
local cjson = require("cjson")
-- Test configuration
local TEST_HOST = "127.0.0.1"
local TEST_PORT = 8080
local TEST_TIMEOUT = 5
-- Test results
local tests_run = 0
local tests_passed = 0
local tests_failed = 0
-- ANSI colors
local GREEN = "\27[32m"
local RED = "\27[31m"
local YELLOW = "\27[33m"
local RESET = "\27[0m"
-- Test helper functions
local function log(level, message)
local prefix = {
INFO = YELLOW .. "[INFO]" .. RESET,
PASS = GREEN .. "[PASS]" .. RESET,
FAIL = RED .. "[FAIL]" .. RESET
}
print(prefix[level] .. " " .. message)
end
local function http_request(method, path, body, headers)
local client = socket.connect(TEST_HOST, TEST_PORT)
if not client then
return nil, "Connection failed"
end
client:settimeout(TEST_TIMEOUT)
-- Build request
headers = headers or {}
local request_lines = {method .. " " .. path .. " HTTP/1.1"}
-- Add headers
table.insert(request_lines, "Host: " .. TEST_HOST .. ":" .. TEST_PORT)
if body then
table.insert(request_lines, "Content-Length: " .. #body)
table.insert(request_lines, "Content-Type: application/json")
end
for key, value in pairs(headers) do
table.insert(request_lines, key .. ": " .. value)
end
table.insert(request_lines, "") -- Empty line
local request = table.concat(request_lines, "\r\n")
if body then
request = request .. body
end
-- Send request
local success, err = client:send(request)
if not success then
client:close()
return nil, "Send failed: " .. (err or "unknown")
end
-- Read response
local response_line = client:receive()
if not response_line then
client:close()
return nil, "No response received"
end
-- Parse status
local status = response_line:match("HTTP/1%.1 (%d+)")
status = tonumber(status)
-- Read headers
local response_headers = {}
local content_length = 0
while true do
local line = client:receive()
if not line or line == "" then
break
end
local key, value = line:match("([^:]+): (.+)")
if key and value then
response_headers[key:lower()] = value
if key:lower() == "content-length" then
content_length = tonumber(value) or 0
end
end
end
-- Read body
local response_body = ""
if content_length > 0 then
response_body = client:receive(content_length) or ""
end
client:close()
return {
status = status,
headers = response_headers,
body = response_body
}
end
local function assert_equal(actual, expected, message)
tests_run = tests_run + 1
if actual == expected then
tests_passed = tests_passed + 1
log("PASS", message)
return true
else
tests_failed = tests_failed + 1
log("FAIL", message .. " (expected: " .. tostring(expected) .. ", got: " .. tostring(actual) .. ")")
return false
end
end
local function assert_status(response, expected_status, test_name)
return assert_equal(response and response.status, expected_status,
test_name .. " - Status Code")
end
-- Test functions
local function test_health_check()
log("INFO", "Testing health check endpoint...")
local response = http_request("GET", "/health")
if not response then
log("FAIL", "Health check - No response")
tests_run = tests_run + 1
tests_failed = tests_failed + 1
return
end
assert_status(response, 200, "Health check")
if response.body then
local success, data = pcall(cjson.decode, response.body)
if success then
assert_equal(data.status, "healthy", "Health check - Status field")
assert_equal(data.service, "furt-lua", "Health check - Service field")
else
log("FAIL", "Health check - Invalid JSON response")
tests_run = tests_run + 1
tests_failed = tests_failed + 1
end
end
end
local function test_basic_post()
log("INFO", "Testing basic POST endpoint...")
local test_data = {test = "data", number = 42}
local response = http_request("POST", "/test", cjson.encode(test_data))
if not response then
log("FAIL", "Basic POST - No response")
tests_run = tests_run + 1
tests_failed = tests_failed + 1
return
end
assert_status(response, 200, "Basic POST")
if response.body then
local success, data = pcall(cjson.decode, response.body)
if success then
assert_equal(data.message, "Test endpoint working", "Basic POST - Message field")
else
log("FAIL", "Basic POST - Invalid JSON response")
tests_run = tests_run + 1
tests_failed = tests_failed + 1
end
end
end
local function test_mail_endpoint()
log("INFO", "Testing mail endpoint...")
-- Test with valid data
local mail_data = {
name = "Test User",
email = "test@example.com",
message = "This is a test message"
}
local response = http_request("POST", "/v1/mail/send", cjson.encode(mail_data))
if not response then
log("FAIL", "Mail endpoint - No response")
tests_run = tests_run + 1
tests_failed = tests_failed + 1
return
end
assert_status(response, 200, "Mail endpoint - Valid data")
-- Test with invalid data (missing fields)
local invalid_data = {name = "Test"}
local response2 = http_request("POST", "/v1/mail/send", cjson.encode(invalid_data))
assert_status(response2, 400, "Mail endpoint - Invalid data")
-- Test with no body
local response3 = http_request("POST", "/v1/mail/send")
assert_status(response3, 400, "Mail endpoint - No body")
end
local function test_404_handling()
log("INFO", "Testing 404 handling...")
local response = http_request("GET", "/nonexistent")
assert_status(response, 404, "404 handling")
end
-- Main test runner
local function run_tests()
log("INFO", "Starting Furt HTTP-Server tests...")
log("INFO", "Target: http://" .. TEST_HOST .. ":" .. TEST_PORT)
print("")
-- Check if server is running
local test_response = http_request("GET", "/health")
if not test_response then
log("FAIL", "Server is not running on " .. TEST_HOST .. ":" .. TEST_PORT)
log("INFO", "Start server with: ./scripts/start.sh")
return false
end
-- Run tests
test_health_check()
test_basic_post()
test_mail_endpoint()
test_404_handling()
-- Print results
print("")
log("INFO", "Test Results:")
log("INFO", "Tests run: " .. tests_run)
log("INFO", "Passed: " .. tests_passed)
log("INFO", "Failed: " .. tests_failed)
if tests_failed == 0 then
log("PASS", "All tests passed! 🎉")
return true
else
log("FAIL", tests_failed .. " test(s) failed")
return false
end
end
-- Run tests if executed directly
if arg and arg[0] and arg[0]:match("test_http%.lua$") then
local success = run_tests()
os.exit(success and 0 or 1)
end
-- Export for use as module
return {
run_tests = run_tests,
http_request = http_request
}