summaryrefslogtreecommitdiff
path: root/tests/Agent.Core.Tests/SharedFakeOllamaServer.cs
blob: 3264c77b54d680623e5dfecbcccb366bce8d82aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Agent.Core.Tests;

internal sealed class SharedFakeOllamaServer : IAsyncDisposable
{
    private readonly TcpListener _listener = new(IPAddress.Loopback, 0);
    private readonly CancellationTokenSource _stop = new();
    private readonly Task _loop;

    public SharedFakeOllamaServer()
    {
        _listener.Start();
        Endpoint = new Uri($"http://127.0.0.1:{((IPEndPoint)_listener.LocalEndpoint).Port}/");
        _loop = RunAsync();
    }

    public Uri Endpoint { get; }

    private async Task RunAsync()
    {
        try
        {
            while (!_stop.IsCancellationRequested)
            {
                using TcpClient client = await _listener.AcceptTcpClientAsync(_stop.Token).ConfigureAwait(false);
                NetworkStream stream = client.GetStream();
                using var reader = new StreamReader(stream, Encoding.ASCII, false, 1024, true);
                int contentLength = 0;
                while (await reader.ReadLineAsync(_stop.Token).ConfigureAwait(false) is { } line && line.Length > 0)
                    if (line.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase)) contentLength = int.Parse(line[15..].Trim(), System.Globalization.CultureInfo.InvariantCulture);
                if (contentLength > 0)
                {
                    char[] body = new char[contentLength];
                    await reader.ReadBlockAsync(body.AsMemory(), _stop.Token).ConfigureAwait(false);
                }
                byte[] payload = Encoding.UTF8.GetBytes("""{"message":{"role":"assistant","content":"done"},"prompt_eval_count":3,"eval_count":2}""");
                byte[] header = Encoding.ASCII.GetBytes($"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {payload.Length}\r\nConnection: close\r\n\r\n");
                await stream.WriteAsync(header, _stop.Token).ConfigureAwait(false);
                await stream.WriteAsync(payload, _stop.Token).ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException) when (_stop.IsCancellationRequested) { }
    }

    public async ValueTask DisposeAsync()
    {
        await _stop.CancelAsync().ConfigureAwait(false);
        _listener.Stop();
        await _loop.ConfigureAwait(false);
        _stop.Dispose();
    }
}