BrandGhost
Test Vercel Eve Integrations in C#: Compatibility and Failure Recovery

Test Vercel Eve Integrations in C#: Compatibility and Failure Recovery

Vercel Eve testing needs more than a mocked successful answer. A .NET adapter can construct the right request while the server speaks an incompatible inspection schema. A first turn can succeed while a follow-up fails. A stream can reconnect correctly while the application processes an effect twice. Each is a different contract, and each needs evidence at the boundary where it can fail.

This article separates lightweight client checks, real-server compatibility probes, and recovery experiments. The example domain is a fictional equipment-maintenance assistant. Its data and actions are synthetic. There are no production credentials, customer records, or operational equipment in the examples.

I maintain NexusLabs.Eve, the independent .NET client used here. It is not an official Vercel SDK. The examples below are source-reviewed, not newly compiled or executed demonstrations; execution belongs in an isolated CI environment.

Choose the Proof Level for Vercel Eve Testing

An adapter test asks whether your application sends the intended request and interprets the resulting status. A protocol test asks whether the actual Eve server and client agree. A recovery test asks what survives an interruption. Calling all three "integration tests" can conceal which promise has actually been checked.

The public client architecture separates the caller-owned HTTP transport from sessions and response consumption. That gives an application a useful test seam, but substituting the transport does not instantiate the durable server runtime.

Proof level Useful assertion What it cannot establish alone
Application adapter Correct host, request, identity boundary, result mapping Actual server compatibility
HTTP contract Status handling, JSON validation, request shape Workflow persistence across a restart
Real pinned agent Health, inspection, first and second turns Every future server version
Recovery experiment Cursor/reconnect behavior and application checkpointing Exactly-once external effects
Business operation Repeated attempts preserve one logical outcome Correctness of a model's advice

These are proposed proof boundaries, not a claim that one test framework enforces them. A test name should make its boundary obvious. Health_ReadyPayload_ReturnsReady is more informative than AgentWorks.

For the surrounding transport-test mechanics, my mock HttpClient guide explains the role of custom handlers. Here, the focus is what those handlers can prove about an Eve integration.

Pin the Versions Before Writing Assertions

This example uses .NET 10 and NuGet NexusLabs.Eve version 0.1.0-alpha-0014, which is the alpha package represented by version-specific metadata. The reviewed source tag is v0.1.0-alpha.14.

The client's declared reference server is Eve 0.63.0, with stream protocol 25 and supported agent-info schemas 1 through 4, as recorded in its protocol constants. The public compatibility fixture pins Eve 0.63.0, AI SDK 7.0.105, and Node 24.x.

Those values are a test configuration, not an instruction to freeze application dependencies forever. An upgrade should produce a new verified matrix. Changing only the package label in prose does not produce that evidence.

The snapshot on October 1, 2026 identified npm Eve 0.69.0; its client guide expects agent-info schema 6, whereas the reviewed .NET source accepts 1-4. That is a concrete reason to test the pair rather than infer forward compatibility from a minimum supported version.

For small dependency-free checks, create a console project with these settings. It requires the .NET 10 SDK; it is not an ASP.NET application and does not need hosting packages.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>14.0</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="NexusLabs.Eve"
                      Version="0.1.0-alpha-0014" />
  </ItemGroup>
</Project>

The XML states what a CI job should restore. It does not prove that the job ran. Preserve that distinction in the article, release notes, and test report.

Use a Controlled HTTP Boundary

The client accepts a caller-owned HttpMessageInvoker, including HttpClient, according to the tagged client constructor. A controlled handler can therefore exercise the real client without a network dependency.

Put this file in the console project as ReplyHandler.cs:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace EveContractChecks;

public sealed class ReplyHandler(
    Func<HttpRequestMessage, HttpResponseMessage> reply)
    : HttpMessageHandler
{
    public int Calls { get; private set; }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        Calls++;

        return Task.FromResult(reply(request));
    }
}

Calls is mutable instrumentation owned by one test, not shared domain state. The handler is intentionally not a concurrent fixture. Create a fresh instance for each scenario instead of sharing it across unrelated runs.

The interesting test seam is not the counter. It is the response factory: the test chooses an exact status and body, while the real client still parses and validates them. That is stronger than mocking GetHealthAsync itself to return the value you hope to see.

If your application already uses named clients, keep that composition outside the business adapter. The IHttpClientFactory guide covers the lifetime pattern; tests should preserve transport ownership rather than disposing it while a response stream is active.

Check Success, Invalid Success, and HTTP Failure

The package distinguishes an invalid successful health payload from a non-success HTTP response: the former raises EveHealthResponseException, while the latter raises EveClientException, as documented by the health contract and error guide.

This distinction deserves a test. An HTTP 200 is not evidence that the body matches the protocol.

Put the following in Program.cs, beside ReplyHandler.cs. These checks use the real library and a fake transport, so they do not contact Eve or a model provider.

using System.Net;
using System.Net.Http;
using System.Text;
using NexusLabs.Eve;
using EveContractChecks;

await CheckReadyHealthAsync();
await CheckMalformedHealthAsync();
await CheckHttpFailureAsync();
Console.WriteLine("Client health contract checks passed.");

static HttpResponseMessage JsonReply(HttpStatusCode status, string json)
{
    return new HttpResponseMessage(status)
    {
        Content = new StringContent(json, Encoding.UTF8, "application/json"),
    };
}

static async Task CheckReadyHealthAsync()
{
    using var handler = new ReplyHandler(request =>
    {
        if (request.Method != HttpMethod.Get ||
            request.RequestUri?.AbsolutePath != "/eve/v1/health")
        {
            throw new InvalidOperationException("Unexpected health request.");
        }

        return JsonReply(
            HttpStatusCode.OK,
            """{"ok":true,"status":"ready","workflowId":"wrun_mock"}""");
    });
    using var transport = new HttpClient(handler);
    var client = new EveClient(
        transport,
        new EveClientOptions("https://agent.example.com"));

    var health = await client.GetHealthAsync(CancellationToken.None);
    if (health.Status != "ready" || handler.Calls != 1)
    {
        throw new InvalidOperationException("Ready health contract changed.");
    }
}

static async Task CheckMalformedHealthAsync()
{
    using var handler = new ReplyHandler(_ =>
        JsonReply(HttpStatusCode.OK, """{"ok":true}"""));
    using var transport = new HttpClient(handler);
    var client = new EveClient(
        transport,
        new EveClientOptions("https://agent.example.com"));

    await ExpectAsync<EveHealthResponseException>(
        async () =>
        {
            await client.GetHealthAsync(CancellationToken.None);
        });
}

static async Task CheckHttpFailureAsync()
{
    using var handler = new ReplyHandler(_ =>
        JsonReply(
            HttpStatusCode.Forbidden,
            """{"code":"access_denied","error":"Synthetic denial"}"""));
    using var transport = new HttpClient(handler);
    var client = new EveClient(
        transport,
        new EveClientOptions("https://agent.example.com"));

    var exception = await ExpectAsync<EveClientException>(
        async () =>
        {
            await client.GetHealthAsync(CancellationToken.None);
        });
    if (exception.StatusCode != HttpStatusCode.Forbidden ||
        exception.ErrorCode != "access_denied")
    {
        throw new InvalidOperationException("HTTP failure details changed.");
    }
}

static async Task<TException> ExpectAsync<TException>(Func<Task> operation)
    where TException : Exception
{
    try
    {
        await operation();
    }
    catch (TException exception)
    {
        return exception;
    }

    throw new InvalidOperationException(
        $"Expected {typeof(TException).Name} was not thrown.");
}

The final line is expected output if every check passes; it is not a published execution result. The returned exception is retained so the test can verify the status and stable machine-readable error code rather than merely count a thrown exception.

The ErrorCode property comes from the tagged exception implementation. Do not branch on an English message if a machine-readable code is the contract you need.

These checks deliberately avoid a full fake agent-info document. Copying a large schema into a stub can give false confidence when the production server has moved on. Use a real pinned server to establish that broader contract.

Add a Real-Server Layer to Vercel Eve Testing

Mocking HTTP proves how the client interprets the response you supplied. It does not prove that the real runtime will produce that response. For Vercel Eve testing, the public repository's compatibility guide describes a deterministic fixture that exercises the client against a real Eve server.

Use that public fixture as a reference for an isolated CI job, not as permission to point tests at a production agent. The existing fixture manifest pins dependencies, and its probe script documents the executable compatibility path.

A useful probe sequence starts with health and inspection, creates a conversation, consumes its first response, and sends a second message through the same session. Then it exercises whichever capabilities the application actually uses: structured output, input responses, catch-up reads, or guarded cancellation.

The second turn matters because initial creation and existing-session delivery are different protocol paths; the compatibility guide records historical cutovers where first-turn success did not prove follow-up compatibility. A one-request smoke test would miss that failure.

Keep application integration tests separate from the package's own contract suite. Your adapter still needs assertions for user/session ownership, final-output validation, and how an HTTP or protocol failure becomes an application response. My ASP.NET Core integration-testing guide covers that higher layer.

Design Recovery Experiments Around Commit Boundaries

For a resumable stream, vary the interruption point rather than sleeping for a hopeful amount of time. Stop consumption after an observed event, preserve the application's checkpoint, recreate the handle, and examine what the resumed read delivers. The tagged streaming guide defines absolute cursors, bounded catch-up reads, and stable event identity.

Test complete events and partial transport reads separately. An incomplete NDJSON line and a fully consumed event are different boundaries. Also test the point after the application processes an event but before its checkpoint is saved. A transport cursor alone does not make those two application actions atomic.

Separate replay of the same durable event from a re-executed workflow step. The pinned execution model explains that an interrupted step can execute again and emit new event identities. Event deduplication therefore cannot substitute for business-operation idempotency.

For a synthetic "record approved checklist" action, use an application operation key independent of stream event identity. In the proposed experiment, interrupt after the mock endpoint records that key, retry the step, and assert that the same logical result is returned without another mock effect.

Approval and idempotency answer different questions. The pinned tool guidance explicitly distinguishes human authorization from protection against repeated side effects. Tests should prove both rather than treating one approved request as permission for every repeated attempt.

Distinguish Failure Channels

The package's error guide distinguishes HTTP failures, malformed successful protocol responses, and streamed terminal session failure. The last can return EveTurnStatus.Failed through GetOutcomeAsync instead of throwing a transport exception.

A failure matrix should therefore include a non-success HTTP response, a malformed successful response, and a legitimate stream that ends in failure. A broad catch returning an empty DTO would collapse all three into a misleading success.

Local cancellation is another separate case: detaching stream consumption does not itself stop the durable turn, according to the cancellation contract. Test local cancellation and guarded remote CancelAsync independently, and verify the stream boundary after the remote request.

Do not add retries merely because a test occasionally fails. First determine whether the cause is a protocol mismatch, a deterministic validation error, or a transient transport condition. My HttpClient resilience guide provides context for retry ownership; it is not a license to retry every agent delivery.

Make the Upgrade Gate Reproducible

A compatibility report should record the package restored, the server started, the fixture revision, and the scenarios exercised. Otherwise, "tested with Eve" can mean a mocked response, a different server release, or one request to a development endpoint. Those are not equivalent observations.

For this example, the proposed baseline report identifies the alpha NuGet version and the pinned 0.63.0 fixture. A candidate upgrade gets a second report rather than overwriting the baseline's identity. The client compatibility policy is the public reference for the intended pairing, not evidence that a new candidate passed your application's tests.

Keep the model response deterministic when evaluating transport behavior. A test that needs a specific tool request or approval should not depend on a live model deciding to make that request on every run. The fixture can arrange the required boundary directly. A separate evaluation suite can examine model choice under varied prompts.

Record intentional failures alongside successes. An inspection-schema mismatch should fail explicitly. A declined request should preserve its status. A retired session should not silently become a new session because a helper decided to recover. When a scenario is absent from the application's requirements, state that it was not exercised rather than producing a green label for it.

The gate should also examine the adapter's behavior after a failure. Did it retain the caller's conversation identity? Did it accidentally expose the raw response body in a user-facing error? Did a reconnect acquire the required credentials again? Does an incomplete result remain an explicit incomplete state? These are proposed application assertions; choose them from the adapter's documented responsibilities.

Finally, separate environment failures from contract failures. A fixture that never started did not disprove protocol compatibility, but it also supplied no positive compatibility evidence. Preserve that result as a blocked check. Do not turn a setup exception into a skipped success or repeatedly retry it until the distinction disappears.

This approach costs more than updating a package and hoping a smoke request passes. Its benefit is a release decision with a stated scope: the chosen pair passed the scenarios the application actually depends on. It still cannot certify every future Eve version or every possible model behavior.

Keep Diagnostics Useful Without Exposing Payloads

A useful Vercel Eve testing failure report can identify the declared versions, route kind, status code, observed event kind, and test checkpoint. It usually does not need an entire prompt, raw credential header, or model transcript.

Treat detailed payload capture as a separate, controlled debugging decision. Build synthetic failure fixtures and verify that the application's logging path omits secret fields. Testing redaction with invented tokens is safer than assuming a logging helper never emits sensitive data.

An assertion should distinguish absence of a field from successful redaction, too. If no error path ran, an empty log is not proof that the error formatter is safe. Cause a controlled failure and check the resulting diagnostic.

For correlation and logging concepts beyond this boundary test, see my OpenTelemetry guide. Do not confuse a trace with proof that an external operation ran only once.

Frequently Asked Questions

Can mocked HTTP prove that an Eve session is durable?

No. It proves client behavior against the fake response. Durability requires a real runtime and a controlled interruption experiment; the execution model places that responsibility in server-side workflow execution.

Why test the second turn?

Existing-session delivery has its own protocol behavior, including delivery correlation, as documented in the compatibility guide. First-turn creation alone does not exercise it.

Does a minimum Eve version guarantee newer releases work?

No. The declared protocol constants identify a reference release and accepted schemas. Validate the selected newer pair rather than treating the minimum as open-ended certification.

Should protocol validation failures be retried?

Not as a generic recovery policy. The streaming guide distinguishes deterministic protocol failures from reconnect behavior. Diagnose the actual contract mismatch before changing retry policy.

Is a streamed failure an HTTP exception?

Not necessarily. The error guide describes session.failed as protocol data that produces a failed turn outcome. Assert that path explicitly.

Can event deduplication guarantee exactly-once tool effects?

No. Re-executed interrupted steps can emit new event IDs under the durable execution contract. Give logical business operations their own repeat-safety mechanism.

A Useful Verification Boundary

Vercel Eve testing is strongest when every passing assertion has a narrow meaning. Controlled handlers exercise client parsing. Real pinned servers establish protocol agreement. Interruption experiments examine recovery. Application operation keys address repeated effects.

Keep those results separate in CI reports. Record the pair actually tested, preserve the original failure, and state what was not executed. That is more useful than a green "agent works" label whose scope nobody can explain.

Vercel Eve Tutorial for C# Developers: Build an ASP.NET Core Agent Client

Follow this Vercel Eve tutorial to connect ASP.NET Core to a pinned agent, manage transport ownership, and read equipment summaries with explicit failures.

Vercel Eve Sessions and Streaming in C#: Recover a Disconnected Client

Learn how Vercel Eve sessions recover disconnected C# clients with persisted cursors, bounded streaming, replay boundaries, and explicit cancellation choices.

Vercel Eve Explained: Durable AI Agents and C# Integration

Understand Vercel Eve's durable agent runtime, filesystem-first design, replay boundaries, and how C# applications connect through a version-pinned HTTP client.

An error has occurred. This application may no longer respond until reloaded. Reload