Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,675 changes: 5,675 additions & 0 deletions runtime/lua/sha2.lua

Large diffs are not rendered by default.

149 changes: 149 additions & 0 deletions runtime/lua/socket.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
-----------------------------------------------------------------------------
-- LuaSocket helper module
-- Author: Diego Nehab
-----------------------------------------------------------------------------

-----------------------------------------------------------------------------
-- Declare module and import dependencies
-----------------------------------------------------------------------------
local base = _G
local string = require("string")
local math = require("math")
local socket = require("socket.core")

local _M = socket

-----------------------------------------------------------------------------
-- Exported auxiliar functions
-----------------------------------------------------------------------------
function _M.connect4(address, port, laddress, lport)
return socket.connect(address, port, laddress, lport, "inet")
end

function _M.connect6(address, port, laddress, lport)
return socket.connect(address, port, laddress, lport, "inet6")
end

function _M.bind(host, port, backlog)
if host == "*" then host = "0.0.0.0" end
local addrinfo, err = socket.dns.getaddrinfo(host);
if not addrinfo then return nil, err end
local sock, res
err = "no info on address"
for i, alt in base.ipairs(addrinfo) do
if alt.family == "inet" then
sock, err = socket.tcp4()
else
sock, err = socket.tcp6()
end
if not sock then return nil, err end
sock:setoption("reuseaddr", true)
res, err = sock:bind(alt.addr, port)
if not res then
sock:close()
else
res, err = sock:listen(backlog)
if not res then
sock:close()
else
return sock
end
end
end
return nil, err
end

_M.try = _M.newtry()

function _M.choose(table)
return function(name, opt1, opt2)
if base.type(name) ~= "string" then
name, opt1, opt2 = "default", name, opt1
end
local f = table[name or "nil"]
if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
else return f(opt1, opt2) end
end
end

-----------------------------------------------------------------------------
-- Socket sources and sinks, conforming to LTN12
-----------------------------------------------------------------------------
-- create namespaces inside LuaSocket namespace
local sourcet, sinkt = {}, {}
_M.sourcet = sourcet
_M.sinkt = sinkt

_M.BLOCKSIZE = 2048

sinkt["close-when-done"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if not chunk then
sock:close()
return 1
else return sock:send(chunk) end
end
})
end

sinkt["keep-open"] = function(sock)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function(self, chunk, err)
if chunk then return sock:send(chunk)
else return 1 end
end
})
end

sinkt["default"] = sinkt["keep-open"]

_M.sink = _M.choose(sinkt)

sourcet["by-length"] = function(sock, length)
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if length <= 0 then return nil end
local size = math.min(socket.BLOCKSIZE, length)
local chunk, err = sock:receive(size)
if err then return nil, err end
length = length - string.len(chunk)
return chunk
end
})
end

sourcet["until-closed"] = function(sock)
local done
return base.setmetatable({
getfd = function() return sock:getfd() end,
dirty = function() return sock:dirty() end
}, {
__call = function()
if done then return nil end
local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
if not err then return chunk
elseif err == "closed" then
sock:close()
done = 1
return partial
else return nil, err end
end
})
end


sourcet["default"] = sourcet["until-closed"]

_M.source = _M.choose(sourcet)

return _M
58 changes: 27 additions & 31 deletions spec/System/TestTradeQueryCurrency_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,56 @@ describe("TradeQuery Currency Conversion", function()
mock_tradeQuery = new("TradeQuery", { itemsTab = {} })
end)

-- test case for commit: "Skip callback on errors to prevent incomplete conversions"
describe("FetchCurrencyConversionTable", function()
-- Pass: Callback not called on error
-- Fail: Callback called, indicating partial data risk
it("skips callback on error", function()
local orig_launch = launch
local spy = { called = false }
launch = {
DownloadPage = function(url, callback, opts)
callback(nil, "test error")
end
}
mock_tradeQuery:FetchCurrencyConversionTable(function()
spy.called = true
end)
launch = orig_launch
assert.is_false(spy.called)
end)
end)

describe("ConvertCurrencyToChaos", function()
-- Pass: Ceils amount to integer (e.g., 4.9 -> 5)
-- Fail: Wrong value or nil, indicating broken rounding/baseline logic, causing inaccurate chaos totals
describe("ConvertCurrencyToDivs", function()
-- Pass: Calculates price in divs
-- Fail: Wrong value or nil, indicating broken rounding/baseline logic
it("handles chaos currency", function()
mock_tradeQuery.pbCurrencyConversion = { league = { chaos = 1 } }
mock_tradeQuery.pbCurrencyConversion = { league = { chaos = 0.1 } }
mock_tradeQuery.pbLeague = "league"
local result = mock_tradeQuery:ConvertCurrencyToChaos("chaos", 4.9)
assert.are.equal(result, 5)
local result = mock_tradeQuery:ConvertCurrencyToDivs("chaos", 5)
assert.are.equal(result, 0.5)
end)

-- Pass: Returns nil without crash
-- Fail: Crashes or wrong value, indicating unhandled currencies, corrupting price conversions
it("returns nil for unmapped", function()
local result = mock_tradeQuery:ConvertCurrencyToChaos("exotic", 10)
local result = mock_tradeQuery:ConvertCurrencyToDivs("exotic", 10)
assert.is_nil(result)
end)
end)

describe("PriceBuilderProcessPoENinjaResponse", function()
-- Pass: Processes without error, restoring map while adding a notice
-- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions
it("handles unmapped currency", function()
it("handles empty response", function()
local orig_conv = mock_tradeQuery.currencyConversionTradeMap
mock_tradeQuery.currencyConversionTradeMap = { div = "id" }
mock_tradeQuery.pbLeague = "league"
mock_tradeQuery.pbCurrencyConversion = { league = {} }
mock_tradeQuery.controls.pbNotice = { label = ""}
local resp = { exotic = 10 }
mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp)
mock_tradeQuery.controls.pbNotice = { label = "" }
local resp = { lines = { }}
mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp.lines)
-- No crash expected
assert.is_true(true)
assert.is_true(mock_tradeQuery.controls.pbNotice.label == "No currencies received from PoE Ninja")
mock_tradeQuery.currencyConversionTradeMap = orig_conv
end)

-- Pass: Processes without error, restoring map while adding a notice
-- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions
it("handles empty response", function()
local orig_conv = mock_tradeQuery.currencyConversionTradeMap
mock_tradeQuery.currencyConversionTradeMap = { div = "id" }
mock_tradeQuery.pbLeague = "league"
mock_tradeQuery.pbCurrencyConversion = { league = {} }
mock_tradeQuery.controls.pbNotice = { label = "" }
local resp = { lines = { { malformedLine = "lol"} }}
mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp.lines)
-- No crash expected
assert.is_true(true)
assert.is_true(mock_tradeQuery.controls.pbNotice.label == "Currencies not updated: malformed PoE Ninja response")
mock_tradeQuery.currencyConversionTradeMap = orig_conv
end)
end)

describe("GetTotalPriceString", function()
Expand Down
38 changes: 37 additions & 1 deletion spec/System/TestTradeQueryRequests_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,42 @@ describe("TradeQueryRequests", function()
launch = orig_launch
end)

-- Pass: Does not crash on 401, and passes error message
-- Fail: Crash, or returned error is wrong
it("does not crash on 401", function()
local json = '"{"error":"invalid_token","error_description":"The access token provided is invalid or has expired"}"'
local header = [[HTTP/1.1 401 Unauthorized
Date: Fri, 24 Apr 2026 07:30:38 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Server: cloudflare
WWW-Authenticate: Bearer realm="pathofexile:production", error="invalid_token", error_description="The access token provided is invalid or has expired"
Cache-Control: no-store
Strict-Transport-Security: max-age=63115200; includeSubDomains; preload]]
local orig_launch = launch
launch = {
DownloadPage = function(url, onComplete, opts)
onComplete({ body = json, header = header }, nil)
end
}
table.insert(requests.requestQueue.search, {
url = "test",
callback = function(body, msg)
assert.are.equal(body, json)
assert.are.equal(msg, "Response code: 401\nAuthorization is invalid. Please Re-Log and reset")
end,
retryTime = nil
})
local function mock_next_time(self, policy, time)
return time - 1
end
mock_limiter.NextRequestTime = mock_next_time
requests:ProcessQueue()
assert.are.equal(#requests.requestQueue.search, 0)
launch = orig_launch
end)

-- Pass: Retries with increasing backoff up to cap, preventing infinite loops
-- Fail: No backoff or uncapped, indicating retry bug, risking API bans
it("retries on 429 with exponential backoff", function()
Expand Down Expand Up @@ -192,4 +228,4 @@ describe("TradeQueryRequests", function()
requests.FetchResultBlock = orig_fetchBlock
end)
end)
end)
end)
Loading
Loading