Quests: /myquests parsing and the 30-second quest stream
The backend's quest panel shows the stipend, blank aug gem and insatiable jaw timers from the `quest` frame, so the port reads /myquests the way the original did (a three-second window of chat lines matched by its regex) and streams the three priority stamps every 30 s with the same five-field frame and the same countdown strings. The name table is copied as it was, so the duplicate insatiableeaterjaw entry still resolves to the later name. Timers run on the tick scheduler instead of thread-pool timers. The session tests clear the login-time /myquests before counting the commands the backend ran. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5dbb294a96
commit
0b5da797e0
6 changed files with 857 additions and 2 deletions
232
tests/OpenAC.MosswartMassacre.Tests/Quests/QuestTests.cs
Normal file
232
tests/OpenAC.MosswartMassacre.Tests/Quests/QuestTests.cs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
using OpenAC.MosswartMassacre.Quests;
|
||||
|
||||
namespace OpenAC.MosswartMassacre.Tests.Quests;
|
||||
|
||||
public sealed class QuestTests
|
||||
{
|
||||
// Real answer lines in the shape the server writes them (ACE's /myquests:
|
||||
// "stamp - N solves (last)\"message\" max delta"). The first is the example
|
||||
// in the original's parser comment, the second the one UtilityBelt quotes.
|
||||
private const string CommentSample = "SomeQuest - 5 solves (1640995200)\"Quest description here\" 10 3600";
|
||||
private const string UbSample =
|
||||
"regaliamaskuber - 1 solves (1522807913)\"Player has collected the mask from a Virinidi Profatrix.\" -1 72000";
|
||||
|
||||
private static long UnixNow(PluginHarness harness) =>
|
||||
new DateTimeOffset(harness.Clock.UtcNow).ToUnixTimeSeconds();
|
||||
|
||||
private static string Line(string stamp, int solves, long lastSolved, int max, long delta) =>
|
||||
$"{stamp} - {solves} solves ({lastSolved})\"A message\" {max} {delta}";
|
||||
|
||||
[Fact]
|
||||
public void Login_asks_the_server_and_reads_the_answer_for_three_seconds()
|
||||
{
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
QuestManager quests = harness.Feature<QuestManager>();
|
||||
|
||||
Assert.Equal(["/myquests"], harness.Host.Automation.Chat.Submitted);
|
||||
Assert.True(quests.IsRefreshing);
|
||||
|
||||
harness.Host.Automation.Chat.Deliver(CommentSample);
|
||||
harness.Host.Automation.Chat.Deliver(UbSample);
|
||||
harness.Host.Automation.Chat.Deliver("You say, \"hello - 3 solves\"");
|
||||
harness.Run(3.1);
|
||||
harness.Host.Automation.Chat.Deliver("late - 1 solves (1)\"x\" 1 1");
|
||||
|
||||
Assert.False(quests.IsRefreshing);
|
||||
Assert.Equal(2, quests.QuestList.Count);
|
||||
|
||||
Quest first = quests.QuestList[0];
|
||||
Assert.Equal("SomeQuest", first.Id);
|
||||
Assert.Equal(5, first.Solves);
|
||||
Assert.Equal(1640995200, first.Timestamp);
|
||||
Assert.Equal("Quest description here", first.Description);
|
||||
Assert.Equal(10, first.MaxSolves);
|
||||
Assert.Equal(3600, first.Delta);
|
||||
Assert.Equal(1640995200 + 3600, first.ExpireTime);
|
||||
|
||||
Quest second = quests.QuestList[1];
|
||||
Assert.Equal("regaliamaskuber", second.Id);
|
||||
Assert.Equal(-1, second.MaxSolves);
|
||||
Assert.Equal(72000, second.Delta);
|
||||
Assert.Equal("Player has collected the mask from a Virinidi Profatrix.", second.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_refresh_while_one_is_reading_is_ignored_and_the_answer_stays_in_chat()
|
||||
{
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
|
||||
harness.Context.Commands.Dispatch("refreshquests");
|
||||
|
||||
Assert.Equal(["/myquests"], harness.Host.Automation.Chat.Submitted);
|
||||
Assert.Contains("[Mosswart Massacre] [QUEST] Refreshing quest data...", harness.ChatLines);
|
||||
|
||||
harness.Host.Automation.Chat.Deliver(UbSample);
|
||||
// The original never ate the answer lines.
|
||||
Assert.Contains(harness.Host.Automation.Chat.Delivered, message => message.Text == UbSample);
|
||||
|
||||
harness.Run(3.1);
|
||||
harness.Context.Commands.Dispatch("refreshquests");
|
||||
Assert.Equal(["/myquests", "/myquests"], harness.Host.Automation.Chat.Submitted);
|
||||
Assert.Empty(harness.Feature<QuestManager>().QuestList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_line_whose_number_overflows_is_reported_like_the_original()
|
||||
{
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
|
||||
harness.Host.Automation.Chat.Deliver("big - 1 solves (1)\"x\" 1 99999999999");
|
||||
|
||||
Assert.Empty(harness.Feature<QuestManager>().QuestList);
|
||||
Assert.Contains(harness.ChatLines, line => line.StartsWith(
|
||||
"[Mosswart Massacre] Error parsing quest line 'big - 1 solves (1)\"x\" 1 99999999999': ",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_quest_frame_is_exactly_the_original_one()
|
||||
{
|
||||
// The original: WebSocket.SendQuestDataAsync serialized
|
||||
// new { type = "quest", timestamp = DateTime.UtcNow.ToString("o"),
|
||||
// character_name = CharacterFilter.Name, quest_name, countdown }
|
||||
// with JsonConvert.SerializeObject, and QuestStreamingService passed
|
||||
// QuestManager.GetFriendlyQuestName(quest.Id) and FormatCountdown.
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
long now = UnixNow(harness);
|
||||
harness.Host.Automation.Chat.Deliver(Line("stipendtimer_0812", 3, now - 100, -1, 100 + 93600));
|
||||
|
||||
harness.Feature<QuestManager>().StreamPriorityQuests();
|
||||
|
||||
Assert.Equal(
|
||||
["{\"type\":\"quest\",\"timestamp\":\"2026-09-25T08:30:15.1230000Z\",\"character_name\":\"Mossy\",\"quest_name\":\"Stipend Collection Timer\",\"countdown\":\"1d 02h\"}"],
|
||||
harness.Frames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Only_the_three_priority_quests_stream_once_each_in_answer_order()
|
||||
{
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
long now = UnixNow(harness);
|
||||
harness.Host.Automation.Chat.Deliver(Line("insatiableeaterjaw", 1, now - 10, -1, 5));
|
||||
harness.Host.Automation.Chat.Deliver(Line("somethingelse", 1, now, -1, 500));
|
||||
harness.Host.Automation.Chat.Deliver(Line("augmentationblankgemacquired", 1, now, -1, 3 * 3600 + 5 * 60 + 30));
|
||||
harness.Host.Automation.Chat.Deliver(Line("insatiableeaterjaw", 9, now, -1, 99999));
|
||||
|
||||
harness.Feature<QuestManager>().StreamPriorityQuests();
|
||||
|
||||
// "insatiableeaterjaw" is in the name table twice; the later entry,
|
||||
// "Insatiable Eater Jaw", is the one the original sent.
|
||||
Assert.Equal(
|
||||
[
|
||||
"{\"type\":\"quest\",\"timestamp\":\"2026-09-25T08:30:15.1230000Z\",\"character_name\":\"Mossy\",\"quest_name\":\"Insatiable Eater Jaw\",\"countdown\":\"READY\"}",
|
||||
"{\"type\":\"quest\",\"timestamp\":\"2026-09-25T08:30:15.1230000Z\",\"character_name\":\"Mossy\",\"quest_name\":\"Blank Augmentation Gem Pickup Timer\",\"countdown\":\"3h 05m\"}",
|
||||
],
|
||||
harness.Frames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_stream_runs_every_thirty_seconds_after_login()
|
||||
{
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
long now = UnixNow(harness);
|
||||
harness.Host.Automation.Chat.Deliver(Line("stipendtimer_0812", 1, now, -1, 3600 + 30));
|
||||
|
||||
harness.Run(29.5);
|
||||
Assert.Empty(harness.FramesOfType("quest"));
|
||||
|
||||
harness.Run(1);
|
||||
Assert.Single(harness.FramesOfType("quest"));
|
||||
Assert.Equal("1h 00m", (string?)harness.FramesOfType("quest").Single()["countdown"]);
|
||||
|
||||
harness.Run(30);
|
||||
Assert.Equal(2, harness.FramesOfType("quest").Count());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Nothing_streams_while_streaming_is_off_or_after_logoff()
|
||||
{
|
||||
using var off = new PluginHarness(webSocketEnabled: false);
|
||||
off.Login();
|
||||
off.Host.Automation.Chat.Deliver(Line("stipendtimer_0812", 1, UnixNow(off), -1, 60));
|
||||
off.Feature<QuestManager>().StreamPriorityQuests();
|
||||
Assert.Empty(off.Frames);
|
||||
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
harness.Host.Automation.Chat.Deliver(Line("stipendtimer_0812", 1, UnixNow(harness), -1, 60));
|
||||
harness.Plugin.OnLogoff();
|
||||
harness.Run(31);
|
||||
Assert.Empty(harness.Frames);
|
||||
Assert.Equal(0, harness.Host.Automation.Chat.ReceivedSubscriberCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Quest_status_lists_the_priority_quests()
|
||||
{
|
||||
using var harness = new PluginHarness();
|
||||
harness.Login();
|
||||
harness.Host.Automation.Chat.Deliver(Line("stipendtimer_0812", 1, UnixNow(harness), -1, 60));
|
||||
|
||||
harness.Context.Commands.Dispatch("queststatus");
|
||||
|
||||
Assert.Contains("[Mosswart Massacre] Timer Active: True", harness.ChatLines);
|
||||
Assert.Contains("[Mosswart Massacre] Quest Count: 1", harness.ChatLines);
|
||||
Assert.Contains("[Mosswart Massacre] Priority Quests Found: 1", harness.ChatLines);
|
||||
Assert.Contains("[Mosswart Massacre] - Stipend Collection Timer (stipendtimer_0812)", harness.ChatLines);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-5L, "READY")]
|
||||
[InlineData(0L, "READY")]
|
||||
[InlineData(1L, "1s")]
|
||||
[InlineData(59L, "59s")]
|
||||
[InlineData(60L, "1m 00s")]
|
||||
[InlineData(69L, "1m 09s")]
|
||||
[InlineData(3599L, "59m 59s")]
|
||||
[InlineData(3600L, "1h 00m")]
|
||||
[InlineData(3660L, "1h 01m")]
|
||||
[InlineData(86399L, "23h 59m")]
|
||||
[InlineData(86400L, "1d 00h")]
|
||||
[InlineData(93600L, "1d 02h")]
|
||||
[InlineData(10L * 86400 + 23 * 3600 + 3599, "10d 23h")]
|
||||
public void Countdown_formats_like_the_original(long seconds, string expected)
|
||||
{
|
||||
Assert.Equal(expected, QuestStreamingService.FormatCountdown(seconds));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, "0s")]
|
||||
[InlineData(-1, "0s")]
|
||||
[InlineData(59, "59s")]
|
||||
[InlineData(60, "1m")]
|
||||
[InlineData(3600, "1h")]
|
||||
[InlineData(72000, "20h")]
|
||||
[InlineData(90061, "1d 1h 1m 1s")]
|
||||
[InlineData(1209600, "14d")]
|
||||
public void Cooldown_formats_like_the_original(int seconds, string expected)
|
||||
{
|
||||
Assert.Equal(expected, QuestManager.FormatSeconds(seconds));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Last_solved_is_month_first_in_utc_whatever_the_culture()
|
||||
{
|
||||
System.Globalization.CultureInfo saved = System.Globalization.CultureInfo.CurrentCulture;
|
||||
try
|
||||
{
|
||||
System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo("sv-SE");
|
||||
Assert.Equal("04/04/2018 05:31:53", QuestManager.FormatTimeStamp(1522819913));
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.Globalization.CultureInfo.CurrentCulture = saved;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue