Files
opensandbox-group--opensandbox/sdks/code-interpreter/csharp/tests/OpenSandbox.CodeInterpreter.Tests/CodesAdapterTests.cs
T
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

175 lines
6.5 KiB
C#

// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using OpenSandbox.CodeInterpreter.Adapters;
using OpenSandbox.CodeInterpreter.Models;
using OpenSandbox.Core;
using OpenSandbox.Internal;
using OpenSandbox.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
namespace OpenSandbox.CodeInterpreter.Tests;
public class CodesAdapterTests
{
[Fact]
public async Task ListContextsAsync_ThrowsOnEmptyLanguage()
{
var adapter = CreateAdapter(
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))),
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))));
await Assert.ThrowsAsync<InvalidArgumentException>(() => adapter.ListContextsAsync(" "));
}
[Fact]
public async Task ListContextsAsync_SendsLanguageQuery()
{
var httpHandler = new StubHttpMessageHandler((request, _) =>
{
var body = "[{\"id\":\"ctx-1\",\"language\":\"python\"}]";
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(body, Encoding.UTF8, "application/json")
};
return Task.FromResult(response);
});
var adapter = CreateAdapter(
httpHandler,
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))));
var contexts = await adapter.ListContextsAsync("python");
Assert.Single(contexts);
Assert.Equal("python", contexts[0].Language);
Assert.Contains(httpHandler.RequestUris, uri => uri.Contains("/code/contexts?language=python", StringComparison.Ordinal));
}
[Fact]
public async Task RunStreamAsync_ThrowsOnEmptyCode()
{
var adapter = CreateAdapter(
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))),
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))));
var request = new RunCodeRequest
{
Code = " ",
Context = new CodeContext { Language = SupportedLanguage.Python }
};
await Assert.ThrowsAsync<InvalidArgumentException>(() => DrainAsync(adapter.RunStreamAsync(request)));
}
[Fact]
public async Task RunStreamAsync_ParsesSseEvent()
{
var sseHandler = new StubHttpMessageHandler((request, _) =>
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(
"data: {\"type\":\"stdout\",\"text\":\"hello\",\"timestamp\":1}\n\n",
Encoding.UTF8,
"text/event-stream")
};
return Task.FromResult(response);
});
var adapter = CreateAdapter(
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))),
sseHandler);
var request = new RunCodeRequest
{
Code = "print('hello')",
Context = new CodeContext { Language = SupportedLanguage.Python }
};
var events = new List<ServerStreamEvent>();
await foreach (var ev in adapter.RunStreamAsync(request))
{
events.Add(ev);
}
Assert.Single(events);
Assert.Equal(ServerStreamEventTypes.Stdout, events[0].Type);
Assert.Equal("hello", events[0].Text);
Assert.Contains(sseHandler.AcceptHeaders, value => value.Contains("text/event-stream", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public async Task InterruptAsync_SendsExecutionIdAsQueryParameter()
{
var httpHandler = new StubHttpMessageHandler((request, _) =>
Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
var adapter = CreateAdapter(
httpHandler,
new StubHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))));
await adapter.InterruptAsync("exec-123");
Assert.Contains(httpHandler.RequestUris, uri => uri.Contains("/code?id=exec-123", StringComparison.Ordinal));
}
private static async Task DrainAsync<T>(IAsyncEnumerable<T> source)
{
await foreach (var _ in source)
{
}
}
private static CodesAdapter CreateAdapter(HttpMessageHandler httpHandler, HttpMessageHandler sseHandler)
{
var baseUrl = "http://execd.local";
var headers = new Dictionary<string, string> { ["X-Test"] = "true" };
var client = new HttpClientWrapper(new HttpClient(httpHandler), baseUrl, headers);
var sseHttpClient = new HttpClient(sseHandler);
var logger = NullLoggerFactory.Instance.CreateLogger("CodesAdapterTests");
return new CodesAdapter(client, sseHttpClient, baseUrl, headers, logger);
}
private sealed class StubHttpMessageHandler : HttpMessageHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handler;
public StubHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handler)
{
_handler = handler;
}
public List<string> RequestUris { get; } = new();
public List<string> AcceptHeaders { get; } = new();
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
RequestUris.Add(request.RequestUri?.ToString() ?? string.Empty);
AcceptHeaders.Add(string.Join(",", request.Headers.Accept.Select(MediaTypeToString)));
return await _handler(request, cancellationToken).ConfigureAwait(false);
}
private static string MediaTypeToString(MediaTypeWithQualityHeaderValue value)
{
return value.MediaType ?? string.Empty;
}
}
}