Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 6 additions & 8 deletions tests/ModelContextProtocol.TestSseServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ namespace ModelContextProtocol.TestSseServer;

public class Program
{
private static ILoggerFactory CreateLoggerFactory()
private static ILoggerFactory CreateLoggerFactory() => LoggerFactory.Create(ConfigureSerilog);

public static void ConfigureSerilog(ILoggingBuilder loggingBuilder)
{
// Use serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose() // Capture all log levels
.WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"),
Expand All @@ -21,15 +22,12 @@ private static ILoggerFactory CreateLoggerFactory()
.CreateLogger();

var logsPath = Path.Combine(AppContext.BaseDirectory, "testserver.log");
return LoggerFactory.Create(builder =>
{
builder.AddSerilog();
});
loggingBuilder.AddSerilog();
}

public static Task Main(string[] args) => MainAsync(args);

public static async Task MainAsync(string[] args, CancellationToken cancellationToken = default)
public static async Task MainAsync(string[] args, ILoggerFactory? loggerFactory = null, CancellationToken cancellationToken = default)
{
Console.WriteLine("Starting server...");

Expand Down Expand Up @@ -385,7 +383,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st
},
};

using var loggerFactory = CreateLoggerFactory();
loggerFactory ??= CreateLoggerFactory();
server = McpServerFactory.Create(new HttpListenerSseServerTransport("TestServer", 3001, loggerFactory), options, loggerFactory);

Console.WriteLine("Server initialized.");
Expand Down
22 changes: 9 additions & 13 deletions tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

namespace ModelContextProtocol.Tests;

public class ClientIntegrationTestFixture : IDisposable
public class ClientIntegrationTestFixture
{
public ILoggerFactory LoggerFactory { get; }
private ILoggerFactory? _loggerFactory;

public McpClientOptions DefaultOptions { get; }
public McpServerConfig EverythingServerConfig { get; }
public McpServerConfig TestServerConfig { get; }
Expand All @@ -16,10 +17,6 @@ public class ClientIntegrationTestFixture : IDisposable

public ClientIntegrationTestFixture()
{
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
builder.AddConsole()
.SetMinimumLevel(LogLevel.Debug));

DefaultOptions = new()
{
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" },
Expand Down Expand Up @@ -56,17 +53,16 @@ public ClientIntegrationTestFixture()
}
}

public void Initialize(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}

public Task<IMcpClient> CreateClientAsync(string clientId, McpClientOptions? clientOptions = null) =>
McpClientFactory.CreateAsync(clientId switch
{
"everything" => EverythingServerConfig,
"test_server" => TestServerConfig,
_ => throw new ArgumentException($"Unknown client ID: {clientId}")
}, clientOptions ?? DefaultOptions, loggerFactory: LoggerFactory);

public void Dispose()
{
LoggerFactory?.Dispose();
GC.SuppressFinalize(this);
}
}, clientOptions ?? DefaultOptions, loggerFactory: _loggerFactory);
}
9 changes: 6 additions & 3 deletions tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@
using System.Text.Json;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Tests.Utils;
using Xunit.Sdk;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Tests;

public class ClientIntegrationTests : IClassFixture<ClientIntegrationTestFixture>
public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegrationTestFixture>
{
private static readonly string? s_openAIKey = Environment.GetEnvironmentVariable("AI:OpenAI:ApiKey")!;

private readonly ClientIntegrationTestFixture _fixture;

public ClientIntegrationTests(ClientIntegrationTestFixture fixture)
public ClientIntegrationTests(ClientIntegrationTestFixture fixture, ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
_fixture = fixture;
_fixture.Initialize(LoggerFactory);
}

public static IEnumerable<object[]> GetClients() =>
Expand Down Expand Up @@ -474,7 +477,7 @@ public async Task CallTool_Stdio_MemoryServer()
await using var client = await McpClientFactory.CreateAsync(
serverConfig,
clientOptions,
loggerFactory: _fixture.LoggerFactory,
loggerFactory: LoggerFactory,
cancellationToken: TestContext.Current.CancellationToken);

// act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@
using ModelContextProtocol.Client;
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Test.Utils;
using ModelContextProtocol.TestSseServer;

namespace ModelContextProtocol.Tests;

public class SseServerIntegrationTestFixture : IAsyncDisposable
{
private readonly CancellationTokenSource _stopCts = new();
private readonly Task _serverTask;
private readonly CancellationTokenSource _stopCts = new();
private DelegatingTestOutputHelper _delegatingTestOutputHelper = new();

private ILoggerFactory _redirectingLoggerFactory;

public ILoggerFactory LoggerFactory { get; }
public McpClientOptions DefaultOptions { get; }
public McpServerConfig DefaultConfig { get; }

public SseServerIntegrationTestFixture()
{
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
builder.AddConsole()
.SetMinimumLevel(LogLevel.Debug));
_redirectingLoggerFactory = LoggerFactory.Create(builder =>
{
Program.ConfigureSerilog(builder);
builder.AddProvider(new XunitLoggerProvider(_delegatingTestOutputHelper));
});

DefaultOptions = new()
{
Expand All @@ -34,12 +40,17 @@ public SseServerIntegrationTestFixture()
Location = "http://localhost:3001/sse"
};

_serverTask = TestSseServer.Program.MainAsync([], _stopCts.Token);
_serverTask = Program.MainAsync([], _redirectingLoggerFactory, _stopCts.Token);
}

public void Initialize(ITestOutputHelper output)
{
_delegatingTestOutputHelper.CurrentTestOutputHelper = output;
}

public async ValueTask DisposeAsync()
{
LoggerFactory.Dispose();
_redirectingLoggerFactory.Dispose();
_stopCts.Cancel();
try
{
Expand All @@ -50,4 +61,16 @@ public async ValueTask DisposeAsync()
}
_stopCts.Dispose();
}

private class DelegatingTestOutputHelper() : ITestOutputHelper
{
public ITestOutputHelper? CurrentTestOutputHelper { get; set; }

public string Output => CurrentTestOutputHelper?.Output ?? string.Empty;

public void Write(string message) => CurrentTestOutputHelper?.Write(message);
public void Write(string format, params object[] args) => CurrentTestOutputHelper?.Write(format, args);
public void WriteLine(string message) => CurrentTestOutputHelper?.WriteLine(message);
public void WriteLine(string format, params object[] args) => CurrentTestOutputHelper?.WriteLine(format, args);
}
}
9 changes: 6 additions & 3 deletions tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Tests.Utils;

namespace ModelContextProtocol.Tests;

public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestFixture>
public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerIntegrationTestFixture>
{
private readonly SseServerIntegrationTestFixture _fixture;

public SseServerIntegrationTests(SseServerIntegrationTestFixture fixture)
public SseServerIntegrationTests(SseServerIntegrationTestFixture fixture, ITestOutputHelper output)
: base(output)
{
_fixture = fixture;
_fixture.Initialize(output);
}

private Task<IMcpClient> GetClientAsync(McpClientOptions? options = null)
{
return McpClientFactory.CreateAsync(
_fixture.DefaultConfig,
options ?? _fixture.DefaultOptions,
loggerFactory: _fixture.LoggerFactory);
loggerFactory: LoggerFactory);
}

[Fact]
Expand Down
18 changes: 18 additions & 0 deletions tests/ModelContextProtocol.Tests/Utils/LoggedTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Test.Utils;

namespace ModelContextProtocol.Tests.Utils;

public class LoggedTest(ITestOutputHelper testOutputHelper)
{
public ITestOutputHelper TestOutputHelper { get; } = testOutputHelper;
public ILoggerFactory LoggerFactory { get; } = CreateLoggerFactory(testOutputHelper);

private static ILoggerFactory CreateLoggerFactory(ITestOutputHelper testOutputHelper)
{
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
builder.AddProvider(new XunitLoggerProvider(testOutputHelper));
});
}
}
52 changes: 52 additions & 0 deletions tests/ModelContextProtocol.Tests/Utils/XunitLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Globalization;
using System.Text;
using Microsoft.Extensions.Logging;

namespace ModelContextProtocol.Test.Utils;

public class XunitLoggerProvider(ITestOutputHelper output) : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new XunitLogger(output, categoryName);
}

public void Dispose()
{
}

private class XunitLogger(ITestOutputHelper output, string category) : ILogger
{
public void Log<TState>(
LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
var sb = new StringBuilder();

var timestamp = DateTimeOffset.UtcNow.ToString("s", CultureInfo.InvariantCulture);
var prefix = $"| [{timestamp}] {category} {logLevel}: ";
var lines = formatter(state, exception);
sb.Append(prefix);
sb.Append(lines);

if (exception is not null)
{
sb.AppendLine();
sb.Append(exception.ToString());
}

output.WriteLine(sb.ToString());
}

public bool IsEnabled(LogLevel logLevel) => true;

public IDisposable BeginScope<TState>(TState state) where TState : notnull
=> new NoopDisposable();

private sealed class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
}
Loading