Skip to content

Commit b217cef

Browse files
committed
chore: consolidate tests and align CI/docs for M.E.AI
1 parent d2203c0 commit b217cef

21 files changed

+211
-54
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,8 @@ jobs:
4444
- name: Build
4545
run: dotnet build ManagedCode.CodexSharpSDK.slnx -c Release -warnaserror --no-restore
4646

47+
- name: Test (full solution)
48+
run: dotnet test --solution ManagedCode.CodexSharpSDK.slnx -c Release --no-build
49+
4750
- name: Smoke tests (Codex CLI discover + invoke)
48-
run: dotnet test --solution ManagedCode.CodexSharpSDK.slnx -c Release --no-build -- --treenode-filter "/*/*/*/CodexCli_Smoke_*"
51+
run: dotnet test --project CodexSharpSDK.Tests/CodexSharpSDK.Tests.csproj -c Release --no-build -- --treenode-filter "/*/*/*/CodexCli_Smoke_*"

.github/workflows/real-integration.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@ jobs:
4646
- name: Build
4747
run: dotnet build ManagedCode.CodexSharpSDK.slnx -c Release -warnaserror --no-restore
4848

49+
- name: Test (full solution)
50+
run: dotnet test --solution ManagedCode.CodexSharpSDK.slnx -c Release --no-build
51+
4952
- name: Smoke tests (Codex CLI discover + invoke)
50-
run: dotnet test --solution ManagedCode.CodexSharpSDK.slnx -c Release --no-build -- --treenode-filter "/*/*/*/CodexCli_Smoke_*"
53+
run: dotnet test --project CodexSharpSDK.Tests/CodexSharpSDK.Tests.csproj -c Release --no-build -- --treenode-filter "/*/*/*/CodexCli_Smoke_*"

.github/workflows/release.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,11 @@ jobs:
6161
- name: Build
6262
run: dotnet build ManagedCode.CodexSharpSDK.slnx --configuration Release --warnaserror --no-restore
6363

64+
- name: Test (full solution)
65+
run: dotnet test --solution ManagedCode.CodexSharpSDK.slnx --configuration Release --no-build
66+
6467
- name: Codex CLI smoke tests
65-
run: dotnet test --solution ManagedCode.CodexSharpSDK.slnx --configuration Release --no-build -- --treenode-filter "/*/*/*/CodexCli_Smoke_*"
68+
run: dotnet test --project CodexSharpSDK.Tests/CodexSharpSDK.Tests.csproj --configuration Release --no-build -- --treenode-filter "/*/*/*/CodexCli_Smoke_*"
6669

6770
- name: Pack NuGet package
6871
run: dotnet pack CodexSharpSDK/CodexSharpSDK.csproj --configuration Release --no-build -p:IncludeSymbols=false -p:SymbolPackageFormat=snupkg --output ./artifacts

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ If no new rule is detected -> do not update the file.
123123
- For CLI process interaction tests, use the real installed `codex` CLI (no `FakeCodexProcessRunner` test doubles).
124124
- Treat `codex` CLI as a test prerequisite: ensure local/CI test setup installs `codex` before running CLI interaction tests; do not replace this with fakes.
125125
- CI must validate SDK on all Codex-supported desktop/server platforms (macOS, Linux, Windows): run build + tests and include a non-auth smoke check that `codex` is discoverable and invokable.
126+
- CI/release workflow smoke checks are additive gates; they must not replace full `dotnet test --solution ManagedCode.CodexSharpSDK.slnx -c Release` execution.
126127
- Cross-platform non-auth smoke must run `codex` from local installation in CI and verify unauthenticated behavior explicitly (for example `codex login status` in isolated profile returns "Not logged in"), proving binary discovery + process launch on each platform.
127128
- Real Codex integration tests must rely on existing local Codex CLI login/session only; do not read or require `OPENAI_API_KEY` in test setup.
128129
- Do not use nullable `TryGetSettings()` + early `return` skip patterns in real integration tests; resolve required settings directly and fail fast with actionable errors when missing.

CodexSharpSDK.Extensions.AI.Tests/CodexSharpSDK.Extensions.AI.Tests.csproj

Lines changed: 0 additions & 22 deletions
This file was deleted.

CodexSharpSDK.Extensions.AI/CodexChatClientOptions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace ManagedCode.CodexSharpSDK.Extensions.AI;
55

66
public sealed record CodexChatClientOptions
77
{
8-
public CodexOptions? CodexOptions { get; init; }
9-
public string? DefaultModel { get; init; }
10-
public ThreadOptions? DefaultThreadOptions { get; init; }
8+
public CodexOptions? CodexOptions { get; set; }
9+
public string? DefaultModel { get; set; }
10+
public ThreadOptions? DefaultThreadOptions { get; set; }
1111
}

CodexSharpSDK.Extensions.AI/Internal/ChatMessageMapper.cs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
using System.Text;
21
using ManagedCode.CodexSharpSDK.Models;
32
using Microsoft.Extensions.AI;
43

54
namespace ManagedCode.CodexSharpSDK.Extensions.AI.Internal;
65

76
internal static class ChatMessageMapper
87
{
8+
private const string SystemPrefix = "[System] ";
9+
private const string AssistantPrefix = "[Assistant] ";
10+
private const string ParagraphSeparator = "\n\n";
11+
private const string ImageMediaPrefix = "image/";
12+
913
internal static (string Prompt, List<DataContent> ImageContents) ToCodexInput(IEnumerable<ChatMessage> messages)
1014
{
11-
var prompt = new StringBuilder();
12-
var userTextParts = new List<string>();
15+
var promptParts = new List<string>();
1316
var imageContents = new List<DataContent>();
1417

1518
foreach (var message in messages)
@@ -18,38 +21,42 @@ internal static (string Prompt, List<DataContent> ImageContents) ToCodexInput(IE
1821
{
1922
if (message.Text is { } systemText)
2023
{
21-
prompt.Append("[System] ").Append(systemText).Append("\n\n");
24+
promptParts.Add(string.Concat(SystemPrefix, systemText));
2225
}
26+
27+
continue;
2328
}
24-
else if (message.Role == ChatRole.User)
29+
30+
if (message.Role == ChatRole.User)
2531
{
32+
var userTextParts = new List<string>();
2633
foreach (var content in message.Contents)
2734
{
28-
if (content is TextContent tc && tc.Text is not null)
35+
if (content is TextContent textContent && textContent.Text is { } text)
2936
{
30-
userTextParts.Add(tc.Text);
37+
userTextParts.Add(text);
3138
}
32-
else if (content is DataContent dc && dc.MediaType is not null && dc.MediaType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
39+
else if (content is DataContent dataContent && IsImageMediaType(dataContent.MediaType))
3340
{
34-
imageContents.Add(dc);
41+
imageContents.Add(dataContent);
3542
}
3643
}
37-
}
38-
else if (message.Role == ChatRole.Assistant)
39-
{
40-
if (message.Text is { } assistantText)
44+
45+
if (userTextParts.Count > 0)
4146
{
42-
prompt.Append("[Assistant] ").Append(assistantText).Append("\n\n");
47+
promptParts.Add(string.Join(ParagraphSeparator, userTextParts));
4348
}
49+
50+
continue;
4451
}
45-
}
4652

47-
if (userTextParts.Count > 0)
48-
{
49-
prompt.Append(string.Join("\n\n", userTextParts));
53+
if (message.Role == ChatRole.Assistant && message.Text is { } assistantText)
54+
{
55+
promptParts.Add(string.Concat(AssistantPrefix, assistantText));
56+
}
5057
}
5158

52-
return (prompt.ToString(), imageContents);
59+
return (string.Join(ParagraphSeparator, promptParts), imageContents);
5360
}
5461

5562
internal static IReadOnlyList<UserInput> BuildUserInput(string prompt, IReadOnlyList<DataContent> imageContents)
@@ -74,6 +81,9 @@ internal static IReadOnlyList<UserInput> BuildUserInput(string prompt, IReadOnly
7481
return inputs;
7582
}
7683

84+
private static bool IsImageMediaType(string? mediaType) =>
85+
mediaType is not null && mediaType.StartsWith(ImageMediaPrefix, StringComparison.OrdinalIgnoreCase);
86+
7787
private static string GenerateFileName(string? mediaType)
7888
{
7989
var extension = mediaType switch
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
using System.Runtime.CompilerServices;
22

3-
[assembly: InternalsVisibleTo("ManagedCode.CodexSharpSDK.Extensions.AI.Tests")]
3+
[assembly: InternalsVisibleTo("ManagedCode.CodexSharpSDK.Tests")]

CodexSharpSDK.Tests/CodexSharpSDK.Tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
<PrivateAssets>all</PrivateAssets>
1717
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1818
</PackageReference>
19+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
1920
</ItemGroup>
2021

2122
<ItemGroup>
22-
<ProjectReference Include="../CodexSharpSDK/CodexSharpSDK.csproj" />
23+
<ProjectReference Include="..\CodexSharpSDK.Extensions.AI\CodexSharpSDK.Extensions.AI.csproj" />
24+
<ProjectReference Include="..\CodexSharpSDK\CodexSharpSDK.csproj" />
2325
</ItemGroup>
2426
</Project>

CodexSharpSDK.Extensions.AI.Tests/ChatMessageMapperTests.cs renamed to CodexSharpSDK.Tests/MEAI/ChatMessageMapperTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ public async Task ToCodexInput_AssistantMessage_AppendsAssistantPrefix()
4242
await Assert.That(prompt).Contains("Follow up");
4343
}
4444

45+
[Test]
46+
public async Task ToCodexInput_MixedRoles_PreservesMessageChronology()
47+
{
48+
var messages = new[]
49+
{
50+
new ChatMessage(ChatRole.System, "Be concise"),
51+
new ChatMessage(ChatRole.User, "First question"),
52+
new ChatMessage(ChatRole.Assistant, "First answer"),
53+
new ChatMessage(ChatRole.User, "Follow up"),
54+
};
55+
56+
var (prompt, _) = ChatMessageMapper.ToCodexInput(messages);
57+
58+
await Assert.That(prompt).IsEqualTo(
59+
"[System] Be concise\n\nFirst question\n\n[Assistant] First answer\n\nFollow up");
60+
}
61+
4562
[Test]
4663
public async Task ToCodexInput_ImageContent_ExtractedSeparately()
4764
{

0 commit comments

Comments
 (0)