Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ai-dev-net.AppHost/ai-dev-net.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Aspire.AppHost.Sdk/13.2.4">
<Project Sdk="Aspire.AppHost.Sdk/13.3.0">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand Down
5 changes: 2 additions & 3 deletions ai-dev-net.tests.integration/LmStudioExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,10 @@ private static async Task<string> SkipIfUnavailableAsync()
var models = new List<string>();
if (doc.RootElement.TryGetProperty("data", out var data))
{
models = data.EnumerateArray()
models = [.. data.EnumerateArray()
.Select(m => m.TryGetProperty("id", out var idProp) ? idProp.GetString() : null)
.Where(n => n is { Length: > 0 })
.Select(n => n!)
.ToList();
.Select(n => n!)];
}

if (models.Count == 0)
Expand Down
173 changes: 91 additions & 82 deletions ai-dev-net.tests.unit/AgentInfoTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,136 +2,145 @@ namespace AiDevNet.Tests.Unit;

public class AgentInfoTests
{
private static AgentInfo CreateInfo() => new(
slug: new AgentSlug("my-agent"),
name: "My Agent",
role: "Assistant",
description: "Handles agent workflows");

// -------------------------------------------------------------------------
// Defaults
// -------------------------------------------------------------------------
private static AgentInfoIdle CreateIdle(DateTime? previousRunAt = null) => new()
{
Slug = new AgentSlug("my-agent"),
Name = "My Agent",
Role = "Assistant",
Description = "Handles agent workflows",
Model = "sonnet",
Executor = AgentExecutorName.Default,
Skills = [],
ThinkingLevel = default,
InboxCount = 0,
PreviousRunAt = previousRunAt,
};

// ── AgentInfoIdle ────────────────────────────────────────────────────────

[Fact]
public void Defaults_AreCorrect()
public void AgentInfoIdle_DefaultProperties_AreCorrect()
{
var info = CreateInfo();
var info = CreateIdle();

info.Name.ShouldBe("My Agent");
info.Role.ShouldBe("Assistant");
info.Model.ShouldBe("sonnet");
info.Status.ShouldBe(AgentStatus.Idle);
info.Description.ShouldBe("Handles agent workflows");
info.LastRunAt.ShouldBeNull();
info.InboxCount.ShouldBe(0);
info.Executor.ShouldBe(AgentExecutorName.Default);
info.Executor.ShouldBe(AgentExecutorName.Claude);
info.PreviousRunAt.ShouldBeNull();
}

// -------------------------------------------------------------------------
// Status
// -------------------------------------------------------------------------

[Fact]
public void Status_CanBeSetToRunning()
public void AgentInfoIdle_Status_IsIdle()
{
var info = CreateInfo();
info.MarkRunning(DateTime.UtcNow);
info.Status.IsRunning.ShouldBeTrue();
var info = CreateIdle();
info.Status.ShouldBe(AgentStatus.Idle);
}

[Fact]
public void Status_CanBeSetToError()
public void AgentInfoIdle_LastRunAt_ReturnsPreviousRunAt()
{
var info = CreateInfo();
info.MarkError();
info.Status.IsError.ShouldBeTrue();
var now = DateTime.UtcNow;
var info = CreateIdle(previousRunAt: now);
info.LastRunAt.ShouldBe(now);
}

// -------------------------------------------------------------------------
// LastRunAt
// -------------------------------------------------------------------------

[Fact]
public void LastRunAt_WhenSet_ReturnsCorrectDateTime()
public void AgentInfoIdle_LastError_IsNull()
{
var now = DateTime.UtcNow;
var info = CreateInfo();
info.MarkRunning(now);
info.LastRunAt.ShouldBe(now);
var info = CreateIdle();
info.LastError.ShouldBeNull();
info.LastErrorAt.ShouldBeNull();
}

// -------------------------------------------------------------------------
// InboxCount
// -------------------------------------------------------------------------
// ── AgentInfoRunning ─────────────────────────────────────────────────────

[Fact]
public void InboxCount_ReflectsAssignedValue()
public void AgentInfoRunning_Status_IsRunning()
{
var info = CreateInfo();
info.SetInboxCount(5);
info.InboxCount.ShouldBe(5);
var info = CreateIdle() with { } as AgentInfo;
var running = new AgentInfoRunning
{
Slug = new AgentSlug("my-agent"), Name = "My Agent", Role = "Assistant",
Description = "Handles agent workflows", Model = "sonnet",
Executor = AgentExecutorName.Default, Skills = [], ThinkingLevel = default,
InboxCount = 0, StartedAt = DateTime.UtcNow,
};

running.Status.IsRunning.ShouldBeTrue();
}

// -------------------------------------------------------------------------
// Executor default
// -------------------------------------------------------------------------

[Fact]
public void Executor_DefaultMatchesIAgentExecutorDefault()
public void AgentInfoRunning_LastRunAt_ReturnsStartedAt()
{
var info = CreateInfo();
info.Executor.ShouldBe(AgentExecutorName.Claude);
var now = DateTime.UtcNow;
var running = new AgentInfoRunning
{
Slug = new AgentSlug("my-agent"), Name = "My Agent", Role = "Assistant",
Description = "", Model = "sonnet", Executor = AgentExecutorName.Default,
Skills = [], ThinkingLevel = default, InboxCount = 0, StartedAt = now,
};

running.LastRunAt.ShouldBe(now);
running.StartedAt.ShouldBe(now);
}

// ── AgentInfoFailed ──────────────────────────────────────────────────────

[Fact]
public void UpdateMetadata_WhenExecutorProvided_StoresSupportedExecutor()
public void AgentInfoFailed_Status_IsError()
{
var info = CreateInfo();

info.UpdateMetadata("My Agent", "Assistant", "Handles agent workflows", "sonnet", AgentExecutorName.Anthropic, []);

info.Executor.ShouldBe(AgentExecutorName.Anthropic);
var failed = new AgentInfoFailed
{
Slug = new AgentSlug("my-agent"), Name = "My Agent", Role = "Assistant",
Description = "", Model = "sonnet", Executor = AgentExecutorName.Default,
Skills = [], ThinkingLevel = default, InboxCount = 0,
Failure = new AgentFailure("Something broke", DateTime.UtcNow),
};

failed.Status.IsError.ShouldBeTrue();
}

[Fact]
public void SetLastError_WhenProvided_StoresMessageAndTimestamp()
public void AgentInfoFailed_FailureAlwaysPresent()
{
var occurredAt = DateTime.UtcNow;
var info = CreateInfo();

info.SetLastError("Unsupported Ollama tools", occurredAt);

info.LastError.ShouldBe("Unsupported Ollama tools");
info.LastErrorAt.ShouldBe(occurredAt);
var failed = new AgentInfoFailed
{
Slug = new AgentSlug("my-agent"), Name = "My Agent", Role = "Assistant",
Description = "", Model = "sonnet", Executor = AgentExecutorName.Default,
Skills = [], ThinkingLevel = default, InboxCount = 0,
Failure = new AgentFailure("Unsupported Ollama tools", occurredAt),
};

failed.Failure.Error.ShouldBe("Unsupported Ollama tools");
failed.Failure.OccurredAt.ShouldBe(occurredAt);
failed.LastError.ShouldBe("Unsupported Ollama tools");
failed.LastErrorAt.ShouldBe(occurredAt);
}

[Fact]
public void SetLastError_WhenCleared_RemovesTimestamp()
{
var info = CreateInfo();
info.SetLastError("Unsupported Ollama tools", DateTime.UtcNow);

info.SetLastError(null, DateTime.UtcNow);

info.LastError.ShouldBeNull();
info.LastErrorAt.ShouldBeNull();
}
// ── with expressions ─────────────────────────────────────────────────────

[Fact]
public void Constructor_WhenNameMissing_ThrowsArgumentException()
public void WithExpression_CanUpdateInboxCount()
{
Should.Throw<ArgumentException>(() => new AgentInfo(
slug: new AgentSlug("my-agent"),
name: " ",
role: "Assistant",
description: "Handles agent workflows"));
var info = CreateIdle();
var updated = info with { InboxCount = 5 };

updated.InboxCount.ShouldBe(5);
info.InboxCount.ShouldBe(0); // original unchanged
}

[Fact]
public void SetInboxCount_WhenNegative_ThrowsArgumentOutOfRangeException()
public void WithExpression_CanSetFailover()
{
var info = CreateInfo();
var info = CreateIdle();
var failover = new AgentFailover(AgentExecutorName.Anthropic, DateTime.UtcNow);
var updated = info with { Failover = failover };

Should.Throw<ArgumentOutOfRangeException>(() => info.SetInboxCount(-1));
updated.Failover.ShouldBe(failover);
info.Failover.ShouldBeNull(); // original unchanged
}
}
7 changes: 3 additions & 4 deletions ai-dev-net.tests.unit/AgentServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,9 @@ public void LoadAgent_WhenJsonContainsLastError_LoadsPersistedFailureState()

var agent = service.LoadAgent(projectSlug, agentSlug);

agent.ShouldNotBeNull();
agent!.Status.IsError.ShouldBeTrue();
agent.LastError!.ShouldContain("does not support workspace tools");
agent.LastErrorAt.ShouldBe(errorAt);
var failed = agent.ShouldBeOfType<AgentInfoFailed>();
failed.Failure.Error.ShouldContain("does not support workspace tools");
failed.Failure.OccurredAt.ShouldBe(errorAt);
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion ai-dev-net.tests.unit/BoardServiceCompleteTaskTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private static BoardService CreateService()

private static SessionResult MakeResult(string taskId, IReadOnlyList<string>? tags = null) =>
new(TaskId: taskId,
Status: "completed",
SessionStatus: SessionStatus.Completed,
Summary: "Done",
PullRequestUrl: null,
FilesChanged: [],
Expand Down
Loading
Loading