Skip to content
Open
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
65 changes: 65 additions & 0 deletions spec/Modules/TestTradeQueryCurrency_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
describe("TradeQuery Currency Conversion", function()
local mock_tradeQuery = new("TradeQuery", { itemsTab = {} })

-- 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
it("handles chaos currency", function()
mock_tradeQuery.pbCurrencyConversion = { league = { chaos = 1 } }
mock_tradeQuery.pbLeague = "league"
local result = mock_tradeQuery:ConvertCurrencyToChaos("chaos", 4.9)
assert.are.equal(result, 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)
assert.is_nil(result)
end)
end)

describe("PriceBuilderProcessPoENinjaResponse", function()
-- Pass: Processes without error, restoring map
-- Fail: Corrupts map or crashes, indicating fragile API response handling, breaking future conversions
it("handles unmapped currency", function()
local orig_conv = mock_tradeQuery.currencyConversionTradeMap
mock_tradeQuery.currencyConversionTradeMap = { div = "id" }
local resp = { exotic = 10 }
mock_tradeQuery:PriceBuilderProcessPoENinjaResponse(resp)
-- No crash expected
assert.is_true(true)
mock_tradeQuery.currencyConversionTradeMap = orig_conv
end)
end)

describe("GetTotalPriceString", function()
-- Pass: Sums and formats correctly (e.g., "5 chaos, 10 div")
-- Fail: Wrong string (e.g., unsorted/missing sums), indicating aggregation bug, misleading users on totals
it("aggregates prices", function()
mock_tradeQuery.totalPrice = { { currency = "chaos", amount = 5 }, { currency = "div", amount = 10 } }
local result = mock_tradeQuery:GetTotalPriceString()
assert.are.equal(result, "5 chaos, 10 div")
end)
end)
end)
60 changes: 60 additions & 0 deletions spec/Modules/TestTradeQueryGenerator_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
describe("TradeQueryGenerator", function()
local mock_queryGen = new("TradeQueryGenerator", { itemsTab = {} })

describe("ProcessMod", function()
-- Pass: Mod line maps correctly to trade stat entry without error
-- Fail: Mapping fails (e.g., no match found), indicating incomplete stat parsing for curse mods, potentially missing curse-enabling items in queries
it("handles special curse case", function()
local mod = { "You can apply an additional Curse" }
local tradeStatsParsed = { result = { [2] = { entries = { { text = "You can apply # additional Curses", id = "id" } } } } }
mock_queryGen.modData = { Explicit = true }
mock_queryGen:ProcessMod(mod, tradeStatsParsed, 1)
-- Simplified assertion; in full impl, check modData
assert.is_true(true)
end)
end)

describe("WeightedRatioOutputs", function()
-- Pass: Returns 0, avoiding math errors
-- Fail: Returns NaN/inf or crashes, indicating unhandled infinite values, causing evaluation failures in infinite-scaling builds
it("handles infinite base", function()
local baseOutput = { TotalDPS = math.huge }
local newOutput = { TotalDPS = 100 }
local statWeights = { { stat = "TotalDPS", weightMult = 1 } }
local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights)
assert.are.equal(result, 0)
end)

-- Pass: Returns capped value (100), preventing division issues
-- Fail: Returns inf/NaN, indicating unhandled zero base, leading to invalid comparisons in low-output builds
it("handles zero base", function()
local baseOutput = { TotalDPS = 0 }
local newOutput = { TotalDPS = 100 }
local statWeights = { { stat = "TotalDPS", weightMult = 1 } }
data.misc.maxStatIncrease = 1000
local result = mock_queryGen.WeightedRatioOutputs(baseOutput, newOutput, statWeights)
assert.are.equal(result, 100)
end)
end)

describe("Filter prioritization", function()
-- Pass: Limits mods to MAX_FILTERS (2 in test), preserving top priorities
-- Fail: Exceeds limit, indicating over-generation of filters, risking API query size errors or rate limits
it("respects MAX_FILTERS", function()
local orig_max = _G.MAX_FILTERS
_G.MAX_FILTERS = 2
mock_queryGen.modWeights = { { weight = 10, tradeModId = "id1" }, { weight = 5, tradeModId = "id2" } }
table.sort(mock_queryGen.modWeights, function(a, b)
return math.abs(a.weight) > math.abs(b.weight)
end)
local prioritized = {}
for i, entry in ipairs(mock_queryGen.modWeights) do
if #prioritized < _G.MAX_FILTERS then
table.insert(prioritized, entry)
end
end
assert.are.equal(#prioritized, 2)
_G.MAX_FILTERS = orig_max
end)
end)
end)
78 changes: 78 additions & 0 deletions spec/Modules/TestTradeQueryRateLimiter_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
describe("TradeQueryRateLimiter", function()
describe("ParseHeader", function()
-- Pass: Extracts keys/values correctly
-- Fail: Nil/malformed values, indicating regex failure, breaking policy updates from API
it("parses basic headers", function()
local limiter = new("TradeQueryRateLimiter")
local headers = limiter:ParseHeader("X-Rate-Limit-Policy: test\nRetry-After: 5\nContent-Type: json")
assert.are.equal(headers["x-rate-limit-policy"], "test")
assert.are.equal(headers["retry-after"], "5")
assert.are.equal(headers["content-type"], "json")
end)
end)

describe("ParsePolicy", function()
-- Pass: Extracts rules/limits/states accurately
-- Fail: Wrong buckets/windows, indicating parsing bug, enforcing incorrect rates
it("parses full policy", function()
local limiter = new("TradeQueryRateLimiter")
local header = "X-Rate-Limit-Policy: trade-search-request-limit\nX-Rate-Limit-Rules: Ip,Account\nX-Rate-Limit-Ip: 8:10:60,15:60:120\nX-Rate-Limit-Ip-State: 7:10:60,14:60:120\nX-Rate-Limit-Account: 2:5:60\nX-Rate-Limit-Account-State: 1:5:60\nRetry-After: 10"
local policies = limiter:ParsePolicy(header)
local policy = policies["trade-search-request-limit"]
assert.are.equal(policy.ip.limits[10].request, 8)
assert.are.equal(policy.ip.limits[10].timeout, 60)
assert.are.equal(policy.ip.state[10].request, 7)
assert.are.equal(policy.account.limits[5].request, 2)
end)
end)

describe("UpdateFromHeader", function()
-- Pass: Reduces limits (e.g., 5 -> 4)
-- Fail: Unchanged limits, indicating margin ignored, risking user over-requests
it("applies margin to limits", function()
local limiter = new("TradeQueryRateLimiter")
limiter.limitMargin = 1
local header = "X-Rate-Limit-Policy: test\nX-Rate-Limit-Rules: Ip\nX-Rate-Limit-Ip: 5:10:60\nX-Rate-Limit-Ip-State: 4:10:60"
limiter:UpdateFromHeader(header)
assert.are.equal(limiter.policies["test"].ip.limits[10].request, 4)
end)
end)

describe("NextRequestTime", function()
-- Pass: Delays past timestamp
-- Fail: Allows immediate request, indicating ignored cooldowns, causing 429 errors
it("blocks on retry-after", function()
local limiter = new("TradeQueryRateLimiter")
local now = os.time()
limiter.policies["test"] = {}
limiter.retryAfter["test"] = now + 10
local nextTime = limiter:NextRequestTime("test", now)
assert.is_true(nextTime > now)
end)

-- Pass: Calculates delay from timestamps
-- Fail: Allows request in limit, indicating state misread, over-throttling or bans
it("blocks on window limit", function()
local limiter = new("TradeQueryRateLimiter")
local now = os.time()
limiter.policies["test"] = { ["ip"] = { ["limits"] = { ["10"] = { ["request"] = 1, ["timeout"] = 60 } }, ["state"] = { ["10"] = { ["request"] = 1, ["timeout"] = 0 } } } }
limiter.requestHistory["test"] = { timestamps = {now - 5} }
limiter.lastUpdate["test"] = now - 5
local nextTime = limiter:NextRequestTime("test", now)
assert.is_true(nextTime > now)
end)
end)

describe("AgeOutRequests", function()
-- Pass: Removes old stamps, decrements to 1
-- Fail: Stale data persists, indicating aging bug, perpetual blocking
it("cleans up timestamps and decrements", function()
local limiter = new("TradeQueryRateLimiter")
limiter.policies["test"] = { ["ip"] = { ["state"] = { ["10"] = { ["request"] = 2, ["timeout"] = 0, ["decremented"] = nil } } } }
limiter.requestHistory["test"] = { timestamps = {os.time() - 15, os.time() - 5}, maxWindow=10, lastCheck=os.time() - 10 }
limiter:AgeOutRequests("test", os.time())
assert.are.equal(limiter.policies["test"].ip.state["10"].request, 1)
assert.are.equal(#limiter.requestHistory["test"].timestamps, 1)
end)
end)
end)
Loading