Skip to content
Merged
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
28 changes: 18 additions & 10 deletions spec/System/TestCompareBuySimilar_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ Can have 3 additional Enchantment Modifiers]])
end)
end)
describe("popup URL controls", function()
local tradeHelpers = require "Classes.TradeHelpers"
local originalEncode, encodedQuery
local originalCopy
local originalOpenURL
local originalFetchLeagues
Expand All @@ -135,6 +137,12 @@ Can have 3 additional Enchantment Modifiers]])

before_each(function()
newBuild()
originalEncode = tradeHelpers.B64GzipEncode
-- SimpleGraphic provides compression; capture the query at that boundary.
tradeHelpers.B64GzipEncode = function(json)
encodedQuery = dkjson.decode(json)
return urlEncode(json)
end
originalCopy = _G.Copy
originalOpenURL = _G.OpenURL
copiedUrl = nil
Expand All @@ -146,6 +154,7 @@ Can have 3 additional Enchantment Modifiers]])
end)

after_each(function()
tradeHelpers.B64GzipEncode = originalEncode
build.itemsTab.tradeQuery.tradeQueryRequests.FetchLeagues = originalFetchLeagues
_G.Copy = originalCopy
_G.OpenURL = originalOpenURL
Expand All @@ -166,8 +175,7 @@ Can have 3 additional Enchantment Modifiers]])

local function getQuery(controls)
controls.search.onClick()
local queryJson = urlDecode(assert(copiedUrl:match("[?&]q=(.+)$")))
return dkjson.decode(queryJson)
return encodedQuery
end

local function getUniqueQuery(name, baseName)
Expand All @@ -185,13 +193,13 @@ Can have 3 additional Enchantment Modifiers]])
controls.leagueDrop:SetSel(isValueInArray(controls.leagueDrop.list, "Standard"))
getQuery(controls)
assert.not_equal(initialUrl, copiedUrl)
assert.is_truthy(copiedUrl:find("/Standard?", 1, true))
assert.is_truthy(copiedUrl:find("/Standard/", 1, true))
local standardUrl = copiedUrl

controls.listedDrop:SetSel(4)
local query = getQuery(controls)
assert.not_equal(standardUrl, copiedUrl)
assert.equal("any", query.query.status.option)
assert.equal("any", query.status.option)
end)

it("preserves apostrophes in unique names", function()
Expand All @@ -202,15 +210,15 @@ Riveted Boots
Implicits: 0]])
local controls = openPopup(item, "Boots")

assert.equal("Ralakesh's Impatience", getQuery(controls).query.name)
assert.equal("Ralakesh's Impatience", getQuery(controls).name)
end)

it("removes a trailing unique ID without removing name punctuation", function()
assert.equal("Uul-Netol's Embrace", getUniqueQuery("Uul-Netol's Embrace 1234", "Vaal Axe").query.name)
assert.equal("Uul-Netol's Embrace", getUniqueQuery("Uul-Netol's Embrace 1234", "Vaal Axe").name)
end)

it("trims whitespace after a unique name", function()
assert.equal("The Hateful Accuser", getUniqueQuery("The Hateful Accuser ", "Ghastly Eye Jewel").query.name)
assert.equal("The Hateful Accuser", getUniqueQuery("The Hateful Accuser ", "Ghastly Eye Jewel").name)
end)

it("persists league choices by name for each realm", function()
Expand Down Expand Up @@ -250,7 +258,7 @@ Corrupted]])
controls.mod1Check.state = true
controls.mod1Check.changeFunc()

local filter = getQuery(controls).query.stats[1].filters[1]
local filter = getQuery(controls).stats[1].filters[1]
assert.equal("explicit.stat_2422708892", filter.id)
assert.equal(22088, filter.value.option)
end)
Expand All @@ -266,7 +274,7 @@ Implicits: 0
controls.mod1Check.state = true
controls.mod1Check.changeFunc()

local value = getQuery(controls).query.stats[1].filters[1].value
local value = getQuery(controls).stats[1].filters[1].value
assert.is_nil(value.min)
assert.equal(-67, value.max)
end)
Expand All @@ -293,7 +301,7 @@ Adds 5 to 15 Fire Damage]])
check.state = true
check.changeFunc()

local countFilter = getQuery(controls).query.stats[2]
local countFilter = getQuery(controls).stats[2]
assert.equal("count", countFilter.type)
assert.equal(1, countFilter.value.min)
assert.equal(#modEntries[ambiguousIndex].tradeIds, #countFilter.filters)
Expand Down
49 changes: 49 additions & 0 deletions spec/System/TestTimelessJewelSettings_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,55 @@ describe("TestTimelessJewelSettings", function()
newBuild()
end)

describe("trade search", function()
local tradeHelpers = require "Classes.TradeHelpers"
local originalEncode, originalCopy, originalOpenURL
local query, copiedUrl, openedUrl

before_each(function()
originalEncode, originalCopy, originalOpenURL = tradeHelpers.B64GzipEncode, _G.Copy, _G.OpenURL
tradeHelpers.B64GzipEncode = function(json)
query = require("dkjson").decode(json)
return "encoded"
end
_G.Copy = function(url) copiedUrl = url end
_G.OpenURL = function(url) openedUrl = url end
end)

after_each(function()
tradeHelpers.B64GzipEncode, _G.Copy, _G.OpenURL = originalEncode, originalCopy, originalOpenURL
main:ClosePopup()
end)

it("includes seed and devotion constraints in a console trade link", function()
build.timelessData.jewelType = { id = 4 }
build.treeTab.tradeLeaguesList = { pc = { "Standard" }, xbox = { "Hardcore Test" } }
build.treeTab:FindTimelessJewel()
local controls = main.popups[1].controls
build.timelessData.sharedResults = {
conqueror = { id = 3 }, devotionVariant1 = { id = 2 }, devotionVariant2 = { id = 7 },
}
build.timelessData.searchResults = { { seed = 5000 }, { seed = 6000 } }
controls.realmSelection:SetSel(3)
controls.tradeTypeSelection:SetSel(2)
controls.searchTradeButton.onClick()

assert.are.equal("https://www.pathofexile.com/trade/search/xbox/Hardcore%20Test/encoded", openedUrl)
assert.are.equal(openedUrl, copiedUrl)
assert.are.equal("available", query.status.option)
assert.same({
type = "count", value = { min = 1 }, filters = {
{ id = data.timelessJewelTradeIDs[4].keystone[2], value = { min = 5000, max = 5000 } },
{ id = data.timelessJewelTradeIDs[4].keystone[2], value = { min = 6000, max = 6000 } },
},
}, query.stats[1])
assert.same({ type = "and", filters = {
{ id = data.timelessJewelTradeIDs[4].devotion[1] },
{ id = data.timelessJewelTradeIDs[4].devotion[6] },
} }, query.stats[2])
end)
end)

-- The finder's "Socket Jewel" toggle lives in timelessData, which only reaches the
-- build XML through the TimelessData element, so cover the save/load round trip.
it("round trips socketAllocate through the build XML", function()
Expand Down
15 changes: 13 additions & 2 deletions spec/System/TestTradeQueryGenerator_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,26 @@ describe("TradeQueryGenerator", function()
}
queryGen.tradeTypeIndex = 1
local query
queryGen.requesterCallback = function(_, queryJson)
query = require("dkjson").decode(queryJson).query
queryGen.requesterCallback = function(_, generatedQuery)
query = generatedQuery
end

queryGen:FinishQuery()

assert.are.equal(31, #query.stats[1].filters)
assert.is_not_nil(query.filters.socket_filters.filters.sockets)
assert.is_not_nil(query.filters.socket_filters.filters.links)

-- Account searches must also work without a maximum price.
queryGen.calcContext.options = {
statWeights = {}, influence1 = 1, influence2 = 1,
includeMirrored = true, account = "ExampleAccount", maxLevel = 80, links = 5,
}
queryGen:FinishQuery()
assert.same({ input = "ExampleAccount" }, query.filters.trade_filters.filters.account)
assert.same({ max = 80 }, query.filters.req_filters.filters.lvl)
assert.same({ min = 5, max = 5 }, query.filters.socket_filters.filters.links)
assert.is_nil(query.filters.socket_filters.filters.sockets)
end)
end)
end)
164 changes: 164 additions & 0 deletions spec/System/TestTradeQueryRequests_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,170 @@ Strict-Transport-Security: max-age=63115200; includeSubDomains; preload]]
end)
end)

describe("SearchWithURL", function()
local dkjson = require "dkjson"
local tradeHelpers = require "Classes.TradeHelpers"
local originalDecode, urlRequests, decodedQuery
local url = "https://www.pathofexile.com/trade/search/Standard/encoded"

before_each(function()
urlRequests = new("TradeQueryRequests"):TradeQueryRequests(mock_limiter)
originalDecode = tradeHelpers.B64GzipDecode
-- Compression is provided by SimpleGraphic, which the headless wrapper stubs.
tradeHelpers.B64GzipDecode = function() return decodedQuery end
end)

after_each(function()
tradeHelpers.B64GzipDecode = originalDecode
end)

for _, realmPath in ipairs({ "", "sony/", "xbox/" }) do
it("preserves the realm and league in " .. realmPath .. "Hardcore%20Test", function()
tradeHelpers.B64GzipDecode = function(encoded)
assert.are.equal("encoded", encoded)
return [[{"stats":[{"type":"and","filters":[]}]}]]
end
urlRequests:SearchWithURL("https://pathofexile.com/trade/search/" .. realmPath .. "Hardcore%20Test/encoded", function() end)
assert.are.equal("https://www.pathofexile.com/api/trade/search/" .. realmPath .. "Hardcore%20Test",
urlRequests.requestQueue.search[1].url)
end)
end

it("fetches ordinary searches by price and returns their query", function()
decodedQuery = [[{"stats":[{"type":"and","filters":[{"id":"explicit.stat_3299347043","value":{"min":1}}]}]}]]
local expectedQuery = dkjson.decode(decodedQuery)
local fetchedItems = { { id = "item1" } }
urlRequests.FetchResults = function(_, ids, queryId, callback)
assert.same({ "item1" }, ids)
assert.are.equal("searchId", queryId)
callback(fetchedItems)
end
local calls = 0
urlRequests:SearchWithURL(url, function(items, err, query)
calls = calls + 1
assert.is_nil(err)
assert.same(fetchedItems, items)
assert.same({ query = expectedQuery, sort = { price = "asc" }, engine = "new" }, dkjson.decode(query))
end)
local search = table.remove(urlRequests.requestQueue.search, 1)
assert.are.equal("https://www.pathofexile.com/api/trade/search/Standard", search.url)
search.callback([[{"total":1,"result":["item1"],"id":"searchId"}]])
assert.are.equal(1, calls)
assert.are.equal(0, #urlRequests.requestQueue.search)
end)

it("relays ordinary search errors to the caller", function()
decodedQuery = [[{"stats":[{"type":"and","filters":[]}]}]]
local calls = 0
urlRequests:SearchWithURL(url, function(items, err, query)
calls = calls + 1
assert.is_nil(items)
assert.are.equal("Response code: 403", err)
assert.is_table(dkjson.decode(query).query)
end)
table.remove(urlRequests.requestQueue.search, 1).callback(nil, "Response code: 403")
assert.are.equal(1, calls)
end)

it("moves a later weighted group first while retaining the other filters", function()
decodedQuery = [[{"stats":[{"type":"count","value":{"min":1},"filters":[]},{"type":"weight","value":{"min":10},"filters":[]}]}]]
urlRequests:SearchWithURL(url, function() end)
local query = dkjson.decode(urlRequests.requestQueue.search[1].body)
assert.same({ ["statgroup.0"] = "desc" }, query.sort)
assert.are.equal("weight", query.query.stats[1].type)
assert.are.equal(10, query.query.stats[1].value.min)
assert.are.equal("count", query.query.stats[2].type)
assert.are.equal(1, query.query.stats[2].value.min)
end)

for _, case in ipairs({
{ name = "an explicit minimum with only five matches", bounds = { min = 2000 }, weight = 2100, total = 5 },
{ name = "negative weights with a maximum-only bound", bounds = { max = -50 }, weight = -60, total = 10000 },
{ name = "negative weights without bounds", weight = -60, total = 10000 },
{ name = "both bounds with exactly the fetch limit", bounds = { min = 100, max = 200 }, weight = 150, total = 10 },
}) do
it("fetches pasted searches once, preserving " .. case.name, function()
local filters = { { id = "explicit.stat_3299347043", value = { weight = case.weight < 0 and -1 or 1 } } }
local suppliedQuery = { stats = { { type = "weight", filters = filters, value = case.bounds } } }
decodedQuery = dkjson.encode(suppliedQuery)
local fetchedItems = { { id = "item1", weight = tostring(case.weight) } }
local fetchCalls, callbackCalls = 0, 0
urlRequests.FetchResults = function(_, ids, queryId, callback)
fetchCalls = fetchCalls + 1
assert.same({ "item1" }, ids)
assert.are.equal("searchId", queryId)
callback(fetchedItems)
end
urlRequests:SearchWithURL(url, function(items, err, query)
callbackCalls = callbackCalls + 1
assert.is_nil(err)
assert.same(fetchedItems, items)
assert.same({ query = suppliedQuery, sort = { ["statgroup.0"] = "desc" }, engine = "new" }, dkjson.decode(query))
end)
assert.are.equal(1, #urlRequests.requestQueue.search)
local search = table.remove(urlRequests.requestQueue.search, 1)
assert.same(suppliedQuery, dkjson.decode(search.body).query)
search.callback(dkjson.encode({ total = case.total, result = { "item1" }, id = "searchId" }))
assert.are.equal(1, fetchCalls)
assert.are.equal(1, callbackCalls)
assert.are.equal(0, #urlRequests.requestQueue.search)
end)
end

it("reports no matches without relaxing a pasted minimum", function()
decodedQuery = [[{"stats":[{"type":"weight","value":{"min":2000},"filters":[]}]}]]
local calls = 0
urlRequests:SearchWithURL(url, function(items, err, query)
calls = calls + 1
assert.is_nil(items)
assert.are.equal("No Matching Results Found", err)
assert.are.equal(2000, dkjson.decode(query).query.stats[1].value.min)
end)
table.remove(urlRequests.requestQueue.search, 1).callback([[{"total":0,"result":[],"id":"emptySearch"}]])
assert.are.equal(1, calls)
assert.are.equal(0, #urlRequests.requestQueue.search)
end)

for _, malformed in ipairs({
[[{"stats":[]}]],
[[{"stats":[{"type":"and"},false]}]],
[[{"stats":[{}]}]],
[[42]],
[[not JSON]],
}) do
it("rejects malformed query " .. malformed .. " through the callback", function()
decodedQuery = malformed
local calls = 0
urlRequests:SearchWithURL(url, function(items, err)
calls = calls + 1
assert.is_nil(items)
assert.are.equal("URL is malformed", err)
end)
assert.are.equal(1, calls)
assert.are.equal(0, #urlRequests.requestQueue.search)
end)
end

it("rejects failed decompression through the callback", function()
decodedQuery = nil
local calls = 0
urlRequests:SearchWithURL(url, function(_, err)
calls = calls + 1
assert.are.equal("URL is malformed", err)
end)
assert.are.equal(1, calls)
end)

it("rejects an unrelated URL through the callback", function()
local calls = 0
urlRequests:SearchWithURL("https://example.com/", function(_, err)
calls = calls + 1
assert.are.equal("Invalid URL", err)
end)
assert.are.equal(1, calls)
end)
end)

describe("FetchResults", function()
-- Pass: Fetches exactly 10 from 11, in 1 block
-- Fail: Fetches wrong count/blocks, indicating batch limit violation, triggering rate limits
Expand Down
Loading
Loading