From 82b5aa86dd5355bf6d7e18e45b7ed8f082785aa4 Mon Sep 17 00:00:00 2001 From: Xurkon Date: Sat, 6 Jun 2026 06:38:47 -0500 Subject: [PATCH] fix: harden learner quest accept resolution --- Modules/QuestieLearner.lua | 73 +++++++++----- Tests/QuestieLearnerDataSourceMode_spec.lua | 103 ++++++++++++++++++++ 2 files changed, 150 insertions(+), 26 deletions(-) diff --git a/Modules/QuestieLearner.lua b/Modules/QuestieLearner.lua index 2362a1a..6a3e37f 100644 --- a/Modules/QuestieLearner.lua +++ b/Modules/QuestieLearner.lua @@ -3072,35 +3072,56 @@ function QuestieLearner:GetNPCIdByName(npcName) return index.base[lowerName] end +local function ResolveAcceptedQuestId(firstArg, secondArg) + local maxLog = GetNumQuestLogEntries and GetNumQuestLogEntries() or 25 + + local function resolveFromLogIndex(logIndex) + if not logIndex or type(logIndex) ~= "number" or logIndex <= 0 or logIndex > maxLog then + return nil + end + + local resolvedId = QuestieCompat.GetQuestIDFromLogIndex and QuestieCompat.GetQuestIDFromLogIndex(logIndex) + if resolvedId and resolvedId > 0 then + return resolvedId + end + end + + local function resolveFromQuestId(questId) + if not questId or type(questId) ~= "number" or questId <= 0 then + return nil + end + + if QuestieCompat.GetQuestLogIndexByID and QuestieCompat.GetQuestLogIndexByID(questId) then + return questId + end + end + + if secondArg and type(secondArg) == "number" and secondArg > 0 then + local resolvedId = resolveFromLogIndex(secondArg) or resolveFromQuestId(secondArg) + if resolvedId then + return resolvedId + end + end + + if firstArg and type(firstArg) == "number" and firstArg > 0 then + local resolvedId = resolveFromLogIndex(firstArg) or resolveFromQuestId(firstArg) + if resolvedId then + return resolvedId + end + end + + local selectedIndex = QuestieCompat.GetQuestLogSelection and QuestieCompat.GetQuestLogSelection() + if selectedIndex and selectedIndex > 0 then + return resolveFromLogIndex(selectedIndex) + end + + return nil +end + -- Fires when a quest is accepted. function QuestieLearner:OnQuestAccepted(firstArg, secondArg) Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] OnQuestAccepted raw args: first=" .. tostring(firstArg) .. " second=" .. tostring(secondArg)) - local questId - - -- Try secondArg first (WotLK standard: logIndex, questID) - if secondArg and type(secondArg) == "number" and secondArg > 0 then - questId = secondArg - end - - -- If secondArg was nil/0, firstArg might already be the questID (some 3.3.5 servers), - -- or it's the log index — try resolving it from the log. - if not questId or questId <= 0 then - local maxLog = GetNumQuestLogEntries and GetNumQuestLogEntries() or 25 - if firstArg and type(firstArg) == "number" and firstArg > 0 then - -- If firstArg looks like a log index (small number), look it up - if firstArg <= maxLog then - local resolvedId = QuestieCompat.GetQuestIDFromLogIndex(firstArg) - Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] OnQuestAccepted resolved from log index", firstArg, "->", tostring(resolvedId)) - if resolvedId and resolvedId > 0 then - questId = resolvedId - end - end - -- Still no questId: scan entire log for recently added quests - if not questId or questId <= 0 then - questId = firstArg -- last resort, may be wrong - end - end - end + local questId = ResolveAcceptedQuestId(firstArg, secondArg) Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] OnQuestAccepted id=" .. tostring(questId)) if not questId or questId <= 0 then return end diff --git a/Tests/QuestieLearnerDataSourceMode_spec.lua b/Tests/QuestieLearnerDataSourceMode_spec.lua index 813f777..28c31d3 100644 --- a/Tests/QuestieLearnerDataSourceMode_spec.lua +++ b/Tests/QuestieLearnerDataSourceMode_spec.lua @@ -109,6 +109,109 @@ describe("QuestieLearner learner mode activation", function() end) end) +describe("QuestieLearner quest accept resolution", function() + local QuestieLearner + local originalGetNumQuestLogEntries + local originalGetQuestLogSelection + local originalGetQuestLogTitle + local originalGetQuestIDFromLogIndex + local originalGetQuestLogIndexByID + local originalLearnQuest + local originalUnitGUID + + before_each(function() + dofile("Tests/wow_api_mock.lua") + Questie.dbLearner.global.settings.enabled = true + Questie.dbLearner.global.settings.dataSourceMode = "learner" + + originalGetNumQuestLogEntries = _G.GetNumQuestLogEntries + originalGetQuestLogSelection = QuestieCompat.GetQuestLogSelection + originalGetQuestLogTitle = QuestieCompat.GetQuestLogTitle + originalGetQuestIDFromLogIndex = QuestieCompat.GetQuestIDFromLogIndex + originalGetQuestLogIndexByID = QuestieCompat.GetQuestLogIndexByID + originalUnitGUID = _G.UnitGUID + + _G.GetNumQuestLogEntries = function() + return 1 + end + _G.UnitGUID = function() + return nil + end + + QuestieCompat.GetQuestLogSelection = function() + return 1 + end + + QuestieCompat.GetQuestLogTitle = function(index) + if index == 1 then + return "Real Quest", 10, nil, false, nil, nil, nil, 4321 + end + return nil + end + + QuestieCompat.GetQuestIDFromLogIndex = function(index) + if index == 1 then + return 4321 + end + return nil + end + + QuestieCompat.GetQuestLogIndexByID = function(questId) + if questId == 4321 then + return 1 + end + return nil + end + + QuestieLearner = dofile("Modules/QuestieLearner.lua") + originalLearnQuest = QuestieLearner.LearnQuest + end) + + after_each(function() + QuestieLearner.LearnQuest = originalLearnQuest + _G.GetNumQuestLogEntries = originalGetNumQuestLogEntries + _G.UnitGUID = originalUnitGUID + QuestieCompat.GetQuestLogSelection = originalGetQuestLogSelection + QuestieCompat.GetQuestLogTitle = originalGetQuestLogTitle + QuestieCompat.GetQuestIDFromLogIndex = originalGetQuestIDFromLogIndex + QuestieCompat.GetQuestLogIndexByID = originalGetQuestLogIndexByID + end) + + it("uses a real quest log entry instead of raw accepted event args", function() + local capturedQuestId = nil + QuestieLearner.LearnQuest = function(self, questId, data) + capturedQuestId = questId + end + + QuestieLearner:OnQuestAccepted(615514513, nil) + + assert.equals(4321, capturedQuestId) + end) + + it("refuses impossible quest ids when they do not resolve to the quest log", function() + local capturedQuestId = nil + QuestieCompat.GetQuestLogSelection = function() + return nil + end + QuestieCompat.GetQuestLogTitle = function() + return nil + end + QuestieCompat.GetQuestIDFromLogIndex = function() + return nil + end + QuestieCompat.GetQuestLogIndexByID = function() + return nil + end + QuestieLearner.LearnQuest = function(self, questId, data) + capturedQuestId = questId + end + + QuestieLearner:OnQuestAccepted(615514513, nil) + + assert.is_nil(capturedQuestId) + end) +end) + describe("QuestieDB learner source fallback", function() before_each(function() dofile("Tests/wow_api_mock.lua")