forked from admin/VolcanicAgenticCore
ChatSettingsStore keeps each chat's IANA zone in <AgentDir>/telegram/chat-settings.json; ChatTimeZoneService is the one path for a change (store, live session, scheduler re-evaluation, reply text). ChatRegistry.TimeZoneFor now reads the store and new sessions start in the chat's zone. /timezone, the set_timezone tool (ChatSettingsExtension), /status zone line, DEFAULT_TIMEZONE. The store updates its cache under the write gate so the last file written is the newest state.
136 lines
5.8 KiB
C#
136 lines
5.8 KiB
C#
using Microsoft.Extensions.Time.Testing;
|
|
using Volcanic.AgenticCore.Abstractions.Messages;
|
|
using Volcanic.AgenticCore.Telegram.Tests.Support;
|
|
using Volcanic.AgenticCore.Testing;
|
|
|
|
namespace Volcanic.AgenticCore.Telegram.Tests;
|
|
|
|
/// <summary>Per-chat time zones through <c>/timezone</c>, the <c>set_timezone</c> tool, a restart and <c>/status</c>.</summary>
|
|
public sealed class TimeZoneChatTests
|
|
{
|
|
private static readonly TimeZoneInfo Berlin = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
|
private static readonly DateTimeOffset Now = new(2026, 9, 26, 12, 3, 0, TimeSpan.Zero); // 14:03 Berlin, 21:03 Tokyo
|
|
private const long OtherUser = 2002;
|
|
|
|
private static ChatHost Host(string? root = null) =>
|
|
new(core: o => o.DefaultTimeZone = Berlin, time: new FakeTimeProvider(Now), root: root);
|
|
|
|
[Fact]
|
|
public async Task Timezone_sets_the_zone_the_session_and_the_next_system_prompt()
|
|
{
|
|
await using var host = Host();
|
|
await host.SendAsync("/timezone Asia/Tokyo");
|
|
|
|
Assert.Equal("Time zone set to Asia/Tokyo (UTC+09:00). Now 26 Sep 21:03.", host.LastReply());
|
|
Assert.Equal("Asia/Tokyo", host.Settings.TimeZoneFor(Updates.AllowedUser).Id);
|
|
var chat = await host.ChatAsync();
|
|
Assert.Equal("Asia/Tokyo", chat.Session.TimeZone.Id);
|
|
|
|
host.Faux.Enqueue(FauxTurn.Say("ok"));
|
|
await chat.Session.PromptAsync("hi"); // straight to the session: the inbox window runs on the fake clock
|
|
Assert.Contains("Time zone: Asia/Tokyo (UTC+09:00)", host.Faux.Requests.Last().SystemPrompt);
|
|
Assert.Contains("Current date: 2026-09-26 (Saturday)", host.Faux.Requests.Last().SystemPrompt);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unknown_zone_replies_with_the_error_and_stores_nothing()
|
|
{
|
|
await using var host = Host();
|
|
await host.SendAsync("/timezone Mars/Olympus");
|
|
Assert.Equal("Unknown time zone \"Mars/Olympus\". Use an IANA name such as Europe/Berlin or America/New_York.", host.LastReply());
|
|
Assert.False(host.Settings.HasOwnZone(Updates.AllowedUser));
|
|
Assert.False(File.Exists(Volcanic.AgenticCore.Telegram.Chats.ChatSettingsStore.DefaultPath(Path.Combine(host.Root, "agent"))));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Reset_returns_to_the_default()
|
|
{
|
|
await using var host = Host();
|
|
await host.SendAsync("/timezone Asia/Tokyo");
|
|
await host.SendAsync("/timezone reset");
|
|
|
|
Assert.Equal("Time zone reset to the default, Europe/Berlin (UTC+02:00). Now 26 Sep 14:03.", host.LastReply());
|
|
Assert.False(host.Settings.HasOwnZone(Updates.AllowedUser));
|
|
Assert.Equal("Europe/Berlin", (await host.ChatAsync()).Session.TimeZone.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Timezone_alone_shows_the_zone_and_whether_it_is_the_default()
|
|
{
|
|
await using var host = Host();
|
|
await host.SendAsync("/timezone");
|
|
Assert.Equal("Time zone: Europe/Berlin (UTC+02:00, default). Change it with /timezone <IANA name>, e.g. /timezone America/New_York.", host.LastReply());
|
|
await host.SendAsync("/timezone America/New_York");
|
|
await host.SendAsync("/timezone");
|
|
Assert.StartsWith("Time zone: America/New_York (UTC-04:00). Change it", host.LastReply());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_restart_keeps_the_zone()
|
|
{
|
|
var root = Directory.CreateTempSubdirectory("tz-restart-").FullName;
|
|
try
|
|
{
|
|
await using (var first = Host(root)) await first.SendAsync("/timezone Asia/Tokyo");
|
|
await using var second = Host(root);
|
|
Assert.Equal("Asia/Tokyo", (await second.ChatAsync()).Session.TimeZone.Id);
|
|
}
|
|
finally
|
|
{
|
|
try { Directory.Delete(root, recursive: true); } catch (IOException) { }
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_scripted_set_timezone_call_does_the_same_as_the_command()
|
|
{
|
|
await using var host = Host();
|
|
host.Faux.Enqueue(FauxTurn.Tool("set_timezone", new { zone = "Asia/Tokyo" }), FauxTurn.Say("Done: Tokyo time."));
|
|
var chat = await host.ChatAsync();
|
|
|
|
await chat.Session.PromptAsync("I moved to Tokyo");
|
|
|
|
Assert.Equal("Asia/Tokyo", host.Settings.TimeZoneFor(Updates.AllowedUser).Id);
|
|
Assert.Equal("Asia/Tokyo", chat.Session.TimeZone.Id);
|
|
var result = chat.Session.Messages.OfType<ToolResultMessage>().Single();
|
|
Assert.False(result.IsError);
|
|
Assert.StartsWith("Time zone set to Asia/Tokyo (UTC+09:00).", result.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_scripted_set_timezone_with_an_unknown_zone_is_an_error_result()
|
|
{
|
|
await using var host = Host();
|
|
host.Faux.Enqueue(FauxTurn.Tool("set_timezone", new { zone = "Mars/Olympus" }), FauxTurn.Say("Sorry."));
|
|
var chat = await host.ChatAsync();
|
|
|
|
await chat.Session.PromptAsync("I live on Mars");
|
|
|
|
var result = chat.Session.Messages.OfType<ToolResultMessage>().Single();
|
|
Assert.True(result.IsError);
|
|
Assert.StartsWith("Unknown time zone \"Mars/Olympus\"", result.Text);
|
|
Assert.False(host.Settings.HasOwnZone(Updates.AllowedUser));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Another_chats_zone_is_untouched()
|
|
{
|
|
await using var host = Host();
|
|
host.Options.AllowedUserIds.Add(OtherUser);
|
|
await host.SendAsync("/timezone Asia/Tokyo");
|
|
await host.SendAsync("/timezone", userId: OtherUser);
|
|
|
|
Assert.Equal("Europe/Berlin", host.Settings.TimeZoneFor(OtherUser).Id);
|
|
Assert.StartsWith("Time zone: Europe/Berlin (UTC+02:00, default).", host.LastReply(OtherUser));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Status_shows_the_time_zone()
|
|
{
|
|
await using var host = Host();
|
|
await host.SendAsync("/timezone Asia/Tokyo");
|
|
await host.SendAsync("/status");
|
|
Assert.Contains("Time zone: Asia/Tokyo (UTC+09:00)", host.LastReply());
|
|
}
|
|
}
|