Files
VolcanicAgenticCore/tests/Volcanic.AgenticCore.Tests/Features/SessionTimeZoneTests.cs
EugeneTes 76bda6cf55 fix(processes): address review findings
- Fill TimeZoneId only for a configured session/host zone with a resolvable IANA id
  (IExtensionHost.TimeZoneId); otherwise children inherit the host's TZ instead of TZ=Local/UTC.
- Host hardening logs a warning instead of throwing when libc/prctl can't be resolved.
- A hook or process_spawn handler returning a null or incomplete context fails the spawn with
  InvalidOperationException, so bash reports "process environment hook failed".
- Move tool-result redaction into the Agent slice with a behavioural no-change check.
- ProcessSpawnContext.ApplyTo(psi) sets cwd and replaces the environment; used by bash and ExecAsync.
- Split the Telegram process environment types into their own files; move the Extensions.Tests
  NoHostHardening under Support/; check the Claude Code assembly never references the spawn policy.
- Spec and docs: TZ inheritance, incomplete contexts, ApplyTo, async spawn handlers, root skip.
2026-09-26 19:32:47 +02:00

80 lines
3.2 KiB
C#

using Volcanic.AgenticCore.Abstractions.Extensions;
using Volcanic.AgenticCore.Abstractions.Tools;
using Volcanic.AgenticCore.Features.Extensions;
using Volcanic.AgenticCore.Hosting;
using Volcanic.AgenticCore.Testing;
using Volcanic.AgenticCore.Tests.Support;
namespace Volcanic.AgenticCore.Tests.Features;
public class SessionTimeZoneTests
{
private static readonly TimeZoneInfo Tokyo = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
private static readonly TimeZoneInfo Berlin = TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin");
[Fact]
public async Task Session_option_wins_over_the_host_default()
{
await using var host = new TestHost(o => o.DefaultTimeZone = Tokyo);
await using var session = await host.CreateSessionAsync(b => b.TimeZone = Berlin);
Assert.Equal("Europe/Berlin", TimeZoneIds.ToIana(session.TimeZone));
}
[Fact]
public async Task Host_default_applies_when_the_session_sets_none()
{
await using var host = new TestHost(o => o.DefaultTimeZone = Tokyo);
await using var session = await host.CreateSessionAsync();
Assert.Equal("Asia/Tokyo", TimeZoneIds.ToIana(session.TimeZone));
}
[Fact]
public async Task Local_zone_is_the_fallback()
{
await using var host = new TestHost();
await using var session = await host.CreateSessionAsync();
Assert.Equal(TimeZoneInfo.Local.Id, session.TimeZone.Id);
}
public sealed record ProbeArgs(string Value);
[Fact]
public async Task Extension_context_reports_the_session_zone()
{
TimeZoneInfo? seen = null;
var probe = new DelegateTool<ProbeArgs>("probe", "p", (_, inv, _) => { seen = inv.Context.TimeZone; return Task.FromResult(ToolResult.Text("ok")); });
await using var host = new TestHost(null, new LambdaExtension("tools", api => { api.RegisterTool(probe); return ValueTask.CompletedTask; }));
host.Faux.Enqueue(FauxTurn.Tool("probe", new { value = "x" }), FauxTurn.Say("done"));
await using var session = await host.CreateSessionAsync(b => b.TimeZone = Tokyo);
await session.PromptAsync("go");
Assert.NotNull(seen);
Assert.Equal("Asia/Tokyo", TimeZoneIds.ToIana(seen!));
}
[Fact]
public async Task Session_reports_a_TZ_id_only_for_a_configured_zone()
{
await using var host = new TestHost();
await using (var plain = await host.CreateSessionAsync())
Assert.Null(((IExtensionHost)plain).TimeZoneId);
await using var configured = await host.CreateSessionAsync(b => b.TimeZone = Tokyo);
Assert.Equal("Asia/Tokyo", ((IExtensionHost)configured).TimeZoneId);
}
[Fact]
public void ToIana_returns_null_for_an_unnamed_or_unknown_zone()
{
Assert.Null(TimeZoneIds.ToIana(TimeZoneInfo.CreateCustomTimeZone("Local", TimeSpan.FromHours(1), "Local", "Local")));
Assert.Null(TimeZoneIds.ToIana(TimeZoneInfo.CreateCustomTimeZone("Not/AZone", TimeSpan.FromHours(1), "x", "x")));
}
[Fact]
public void ToIana_keeps_iana_ids_and_converts_windows_ids()
{
Assert.Equal("Europe/Berlin", TimeZoneIds.ToIana(Berlin));
Assert.Equal("Europe/Berlin", TimeZoneIds.ToIana(TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time")));
}
}