Files
ClaudeDo/tests/ClaudeDo.Worker.Tests/Usage/TokenTracker/TokenTrackerAggregatorTests.cs
T

107 lines
3.5 KiB
C#

using ClaudeDo.Worker.Usage.TokenTracker;
namespace ClaudeDo.Worker.Tests.Usage.TokenTracker;
public sealed class TokenTrackerAggregatorTests
{
private static readonly DateOnly RangeStart = new(2026, 8, 20);
private static readonly DateOnly RangeEnd = new(2026, 8, 21);
private static IReadOnlyList<TokenTrackerSession> Sessions() =>
TokenTrackerExportParser.Parse(
TokenTrackerFixtures.SessionsJson,
new DateTime(2026, 8, 24, 9, 0, 0, DateTimeKind.Utc))!.Sessions;
private static HashSet<string> ClaudeDoHashes() =>
[
SessionHash.ForClaudeSession(TokenTrackerFixtures.ClaudeDoSessionIdA)!,
SessionHash.ForClaudeSession(TokenTrackerFixtures.ClaudeDoSessionIdB)!,
];
[Fact]
public void ByModel_ExcludesSessionsOutsideTheRange()
{
var rows = TokenTrackerAggregator.ByModel(Sessions(), RangeStart, RangeEnd, ClaudeDoHashes());
Assert.All(rows, r => Assert.InRange(r.Date, RangeStart, RangeEnd));
Assert.DoesNotContain(rows, r => r.CostUsd == 99.0);
}
[Fact]
public void ByModel_SplitsClaudeDoFromOther()
{
var rows = TokenTrackerAggregator.ByModel(Sessions(), RangeStart, RangeEnd, ClaudeDoHashes());
var opus = rows.Single(r => r.Model == "claude-opus-5" && r.Scope == "claudedo");
Assert.Equal(110, opus.InputTokens);
Assert.Equal(440, opus.OutputTokens);
Assert.Equal(990, opus.CacheReadTokens);
Assert.Equal(55, opus.CacheCreationTokens);
Assert.Equal(2, opus.Sessions);
Assert.Equal(3.5, opus.CostUsd, 6);
var sonnet = rows.Single(r => r.Model == "claude-sonnet-5");
Assert.Equal("other", sonnet.Scope);
Assert.Equal(0.25, sonnet.CostUsd, 6);
}
[Fact]
public void ByModel_UnknownHash_CountsAsOther()
{
var rows = TokenTrackerAggregator.ByModel(Sessions(), RangeStart, RangeEnd, new HashSet<string>());
Assert.All(rows, r => Assert.Equal("other", r.Scope));
}
[Fact]
public void ByModel_EmptyInput_ReturnsEmpty()
{
Assert.Empty(TokenTrackerAggregator.ByModel([], RangeStart, RangeEnd, new HashSet<string>()));
}
[Fact]
public void ForSessions_SumsCostAndRetriesOfMatchingRows()
{
var extras = TokenTrackerAggregator.ForSessions(
Sessions(),
[TokenTrackerFixtures.ClaudeDoSessionIdA, TokenTrackerFixtures.ClaudeDoSessionIdB]);
Assert.NotNull(extras);
Assert.Equal(3.5, extras!.CostUsd, 6);
Assert.Equal(3, extras.Retries);
Assert.True(extras.Productive); // A is productive
Assert.False(extras.OneShot); // B is not one-shot
}
[Fact]
public void ForSessions_SingleOneShotSession_ReportsOneShot()
{
var extras = TokenTrackerAggregator.ForSessions(
Sessions(), [TokenTrackerFixtures.ClaudeDoSessionIdA]);
Assert.True(extras!.OneShot);
Assert.Equal(2.5, extras.CostUsd, 6);
}
[Fact]
public void ForSessions_NoMatch_ReturnsNull()
{
Assert.Null(TokenTrackerAggregator.ForSessions(Sessions(), ["11111111-2222-3333-4444-555555555555"]));
}
[Fact]
public void ForSessions_BlankIds_ReturnsNull()
{
Assert.Null(TokenTrackerAggregator.ForSessions(Sessions(), [null, "", " "]));
}
[Fact]
public void Totals_SumsTokensAndCostInRange()
{
var totals = TokenTrackerAggregator.Totals(Sessions(), RangeStart, RangeEnd);
Assert.Equal(1615, totals.TotalTokens); // 1450 + 145 + 20
Assert.Equal(3.75, totals.CostUsd, 6);
}
}