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.
86 lines
3.1 KiB
C#
86 lines
3.1 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using Volcanic.AgenticCore.Telegram.Chats;
|
|
|
|
namespace Volcanic.AgenticCore.Telegram.Tests;
|
|
|
|
public sealed class ChatSettingsStoreTests : IDisposable
|
|
{
|
|
private static readonly TimeZoneInfo Berlin = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
|
|
private readonly string _root = Directory.CreateTempSubdirectory("chat-settings-").FullName;
|
|
private string FilePath => ChatSettingsStore.DefaultPath(_root);
|
|
|
|
private ChatSettingsStore Store() =>
|
|
new(FilePath, Berlin, NullLogger<ChatSettingsStore>.Instance, new FakeTimeProvider(new DateTimeOffset(2026, 9, 26, 14, 3, 0, TimeSpan.Zero)));
|
|
|
|
public void Dispose()
|
|
{
|
|
try { Directory.Delete(_root, recursive: true); } catch (IOException) { }
|
|
}
|
|
|
|
[Fact]
|
|
public void Missing_file_uses_the_default()
|
|
{
|
|
var store = Store();
|
|
Assert.Same(Berlin, store.TimeZoneFor(1001));
|
|
Assert.False(store.HasOwnZone(1001));
|
|
Assert.EndsWith(Path.Combine("telegram", "chat-settings.json"), FilePath);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Set_persists_and_a_new_store_reads_it()
|
|
{
|
|
await Store().SetTimeZoneAsync(1001, "Asia/Tokyo", CancellationToken.None);
|
|
|
|
var reloaded = Store();
|
|
Assert.Equal("Asia/Tokyo", reloaded.TimeZoneFor(1001).Id);
|
|
Assert.True(reloaded.HasOwnZone(1001));
|
|
Assert.Same(Berlin, reloaded.TimeZoneFor(2002));
|
|
Assert.Equal(["chat-settings.json"], Directory.GetFiles(Path.GetDirectoryName(FilePath)!).Select(Path.GetFileName)); // no temp file left
|
|
}
|
|
|
|
[Fact]
|
|
public async Task File_format_is_versioned_camel_case()
|
|
{
|
|
await Store().SetTimeZoneAsync(1001, "Asia/Tokyo", CancellationToken.None);
|
|
var json = await File.ReadAllTextAsync(FilePath);
|
|
Assert.Contains("\"version\": 1", json);
|
|
Assert.Contains("\"1001\": {", json);
|
|
Assert.Contains("\"timeZone\": \"Asia/Tokyo\"", json);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Null_removes_the_entry()
|
|
{
|
|
var store = Store();
|
|
await store.SetTimeZoneAsync(1001, "Asia/Tokyo", CancellationToken.None);
|
|
await store.SetTimeZoneAsync(1001, null, CancellationToken.None);
|
|
Assert.False(store.HasOwnZone(1001));
|
|
Assert.False(Store().HasOwnZone(1001));
|
|
}
|
|
|
|
[Fact]
|
|
public void Corrupt_file_is_set_aside_and_loading_starts_empty()
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(FilePath)!);
|
|
File.WriteAllText(FilePath, "{ nope");
|
|
|
|
var store = Store();
|
|
|
|
Assert.False(store.HasOwnZone(1001));
|
|
Assert.True(File.Exists(FilePath + ".corrupt-20260926140300"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Unknown_stored_id_is_ignored()
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(FilePath)!);
|
|
File.WriteAllText(FilePath, """{ "version": 1, "chats": { "1001": { "timeZone": "Mars/Olympus" }, "2002": { "timeZone": "Asia/Tokyo" } } }""");
|
|
|
|
var store = Store();
|
|
|
|
Assert.Same(Berlin, store.TimeZoneFor(1001));
|
|
Assert.Equal("Asia/Tokyo", store.TimeZoneFor(2002).Id);
|
|
}
|
|
}
|