Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
57 changes: 57 additions & 0 deletions Inngest.Tests/SyncRegistrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,63 @@ public async Task HandleSync_InBandMode_IncludesInspection()
Assert.True(inspection.GetProperty("has_event_key").GetBoolean());
Assert.True(inspection.GetProperty("has_signing_key").GetBoolean());
Assert.Equal("dev", inspection.GetProperty("mode").GetString());

// capabilities.trust_probe must be advertised — without it Inngest Cloud
// rejects registrations with 400 "Invalid response returned".
Assert.True(inspection.TryGetProperty("capabilities", out var capabilities));
Assert.Equal("v1", capabilities.GetProperty("trust_probe").GetString());
Assert.Equal("v1", capabilities.GetProperty("in_band_sync").GetString());
}

[Fact]
public async Task HandleSync_OutOfBand_RegisterPayloadIncludesTrustProbeCapability()
{
// Arrange — out-of-band sync POSTs to {apiOrigin}/fn/register. Inngest
// Cloud rejects this with 400 "Invalid response returned" when the
// payload omits capabilities.trust_probe. Capture the outbound POST
// body and assert capabilities are present.
var options = new InngestOptions
{
AppId = "test-app",
IsDev = false,
EventKey = "test-event-key",
SigningKey = "signkey-prod-abc123"
};
options.ApplyEnvironmentFallbacks();

var registry = new InngestFunctionRegistry(options.AppId!);
registry.RegisterFunction(typeof(SyncTestFunction));

var services = new ServiceCollection();
services.AddSingleton<SyncTestFunction>();
var serviceProvider = services.BuildServiceProvider();

var handler = new MockHttpMessageHandler();
handler.QueueResponse(HttpStatusCode.OK, "{\"modified\":true}");

var client = new InngestClient(
options,
registry,
serviceProvider,
new HttpClient(handler),
NullLogger<InngestClient>.Instance);

var context = CreateHttpContext("PUT");
// No X-Inngest-Sync-Kind header => out-of-band path.

// Act
await client.HandleRequestAsync(context);

// Assert
var registerRequest = handler.Requests.Single(r =>
r.RequestUri != null && r.RequestUri.AbsolutePath.EndsWith("/fn/register"));
var sentBody = await registerRequest.Content!.ReadAsStringAsync();
var payload = JsonSerializer.Deserialize<JsonElement>(sentBody);

Assert.True(payload.TryGetProperty("capabilities", out var capabilities),
$"register payload missing 'capabilities' field: {sentBody}");
Assert.Equal("v1", capabilities.GetProperty("trust_probe").GetString());
Assert.Equal("v1", capabilities.GetProperty("in_band_sync").GetString());
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion Inngest/Inngest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<PackageReleaseNotes>https://github.com/jmarbutt/InngestDotNet/releases</PackageReleaseNotes>

<!-- Version is set by CI, default for local builds -->
<Version>1.4.4</Version>
<Version>1.4.5</Version>

<!-- Build settings -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
26 changes: 21 additions & 5 deletions Inngest/InngestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class InngestClient : IInngestClient
private readonly string _environment;
private readonly bool _isDev;
private readonly bool _disableCronTriggersInDev;
private readonly string _sdkVersion = "1.4.4";
private readonly string _sdkVersion = "1.4.5";
private readonly string _appId;
private readonly ILogger _logger;
private readonly IInngestFunctionRegistry? _registry;
Expand Down Expand Up @@ -1058,10 +1058,14 @@ private async Task HandleSyncRequest(HttpContext context)
{
_logger.LogInformation("In-band sync: returning {FunctionCount} functions with URL: {Url}", fnArray.Count, url);

// Build capabilities
// Build capabilities. trust_probe is required by Inngest Cloud as of the
// 2026-05 cloud rollout; without it /fn/register returns 400 "Invalid
// response returned". in_band_sync advertises that we can satisfy a
// signed in-band PUT.
var capabilities = new Dictionary<string, string>
{
["in_band_sync"] = "v1"
["in_band_sync"] = "v1",
["trust_probe"] = "v1"
};

// Build inspection data
Expand Down Expand Up @@ -1163,7 +1167,9 @@ private async Task HandleSyncRequest(HttpContext context)
}

// Out-of-band sync - POST to Inngest API
// Create register payload according to the SDK specification
// Create register payload according to the SDK specification.
// capabilities.trust_probe must be declared; Inngest Cloud rejects
// registrations without it with 400 "Invalid response returned".
var registerPayload = new
{
url = url,
Expand All @@ -1172,7 +1178,12 @@ private async Task HandleSyncRequest(HttpContext context)
sdk = $"cs:v{_sdkVersion}",
v = "0.1",
framework = "aspnetcore",
functions = fnArray
functions = fnArray,
capabilities = new Dictionary<string, string>
{
["trust_probe"] = "v1",
["in_band_sync"] = "v1"
}
};

// Log information about registration for debugging
Expand Down Expand Up @@ -1617,6 +1628,11 @@ private async Task HandleIntrospectionRequest(HttpContext context)
responseObj["api_origin"] = _apiOrigin;
responseObj["event_api_origin"] = _eventApiOrigin;
responseObj["app_id"] = _appId;
responseObj["capabilities"] = new Dictionary<string, string>
{
["trust_probe"] = "v1",
["in_band_sync"] = "v1"
};
responseObj["env"] = _environment;
responseObj["event_key_hash"] = eventKeyHash;
responseObj["framework"] = "aspnetcore";
Expand Down
Loading