From 771919c7bb1a1f2ed48800cf111bb1ec1c8d9b81 Mon Sep 17 00:00:00 2001 From: Jacob Dubin Date: Tue, 14 Apr 2026 21:38:50 -0500 Subject: [PATCH] more test fixes --- .../cloud/Get-WebSocketCaptureSummary.ps1 | 2 +- OpenJibo/scripts/cloud/README.md | 2 + .../dotnet/src/Jibo.Cloud.Api/Program.cs | 34 ++++++++- .../src/Jibo.Cloud.Api/appsettings.json | 4 + .../Abstractions/IProtocolTelemetrySink.cs | 8 ++ .../Services/NullProtocolTelemetrySink.cs | 9 +++ .../ServiceCollectionExtensions.cs | 2 + .../Telemetry/FileProtocolTelemetrySink.cs | 74 +++++++++++++++++++ .../Telemetry/ProtocolTelemetryOptions.cs | 7 ++ 9 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IProtocolTelemetrySink.cs create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/NullProtocolTelemetrySink.cs create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/FileProtocolTelemetrySink.cs create mode 100644 OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/ProtocolTelemetryOptions.cs diff --git a/OpenJibo/scripts/cloud/Get-WebSocketCaptureSummary.ps1 b/OpenJibo/scripts/cloud/Get-WebSocketCaptureSummary.ps1 index 4801f52..e5c4cc3 100644 --- a/OpenJibo/scripts/cloud/Get-WebSocketCaptureSummary.ps1 +++ b/OpenJibo/scripts/cloud/Get-WebSocketCaptureSummary.ps1 @@ -1,5 +1,5 @@ param( - [string]$CaptureDirectory = "..\..\src\Jibo.Cloud\dotnet\src\Jibo.Cloud.Api\bin\Debug\net10.0\captures\websocket" + [string]$CaptureDirectory = "..\..\captures\websocket" ) $resolvedDirectory = Resolve-Path -LiteralPath $CaptureDirectory -ErrorAction Stop diff --git a/OpenJibo/scripts/cloud/README.md b/OpenJibo/scripts/cloud/README.md index d6a5611..027fc3c 100644 --- a/OpenJibo/scripts/cloud/README.md +++ b/OpenJibo/scripts/cloud/README.md @@ -8,6 +8,8 @@ These scripts help exercise the new .NET hosted cloud locally. Replays a sanitized HTTP fixture against a running local instance. - `Get-WebSocketCaptureSummary.ps1` Summarizes captured websocket telemetry events and exported live-run fixtures from the .NET cloud. +- repo-root `captures/http/` + Structured HTTP request/response telemetry for live robot startup comparison. - `Invoke-LiveJiboPrep.ps1` Runs a small readiness checklist before the first physical Jibo test against the .NET cloud. - `Import-WebSocketCaptureFixture.ps1` diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Program.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Program.cs index 72233f3..9b97b86 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Program.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Program.cs @@ -21,12 +21,30 @@ app.Use(async (context, next) => return; } + var kind = ResolveSocketKind(context.Request.Host.Host, context.Request.Path); + var token = ResolveToken(context.Request); + if (kind == "unknown") + { + context.Response.StatusCode = StatusCodes.Status404NotFound; + return; + } + + if (kind == "api-socket" && string.IsNullOrWhiteSpace(token)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + + if ((kind is "neo-hub-listen" or "neo-hub-proactive") && string.IsNullOrWhiteSpace(token)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + var webSocketService = context.RequestServices.GetRequiredService(); var telemetrySink = context.RequestServices.GetRequiredService(); using var socket = await context.WebSockets.AcceptWebSocketAsync(); - var kind = ResolveSocketKind(context.Request.Host.Host, context.Request.Path); - var token = ResolveToken(context.Request); var openEnvelope = new WebSocketMessageEnvelope { ConnectionId = Guid.NewGuid().ToString("N"), @@ -89,10 +107,11 @@ app.Use(async (context, next) => app.MapGet("/health", () => Results.Json(new { ok = true, service = "OpenJibo Cloud Api" })); -app.MapMethods("/{**path}", ["GET", "POST", "PUT"], async (HttpContext context, JiboCloudProtocolService service, CancellationToken cancellationToken) => +app.MapMethods("/{**path}", ["GET", "POST", "PUT"], async (HttpContext context, JiboCloudProtocolService service, IProtocolTelemetrySink telemetrySink, CancellationToken cancellationToken) => { var envelope = await BuildEnvelopeAsync(context, cancellationToken); var result = await service.DispatchAsync(envelope, cancellationToken); + await telemetrySink.RecordAsync(envelope, result, cancellationToken); context.Response.StatusCode = result.StatusCode; context.Response.ContentType = result.ContentType; @@ -174,7 +193,14 @@ static string ResolveSocketKind(string host, PathString path) return "neo-hub-listen"; } - return "openjibo"; + if (host.Equals("openjibo.com", StringComparison.OrdinalIgnoreCase) || + host.Equals("openjibo.ai", StringComparison.OrdinalIgnoreCase) || + host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) + { + return "openjibo"; + } + + return "unknown"; } static string? ResolveToken(HttpRequest request) diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/appsettings.json b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/appsettings.json index 91b2d43..a507257 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/appsettings.json +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/appsettings.json @@ -4,6 +4,10 @@ "Enabled": true, "ExportFixtures": true, "DirectoryPath": "captures/websocket" + }, + "ProtocolTelemetry": { + "Enabled": true, + "DirectoryPath": "captures/http" } } } diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IProtocolTelemetrySink.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IProtocolTelemetrySink.cs new file mode 100644 index 0000000..077a861 --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Abstractions/IProtocolTelemetrySink.cs @@ -0,0 +1,8 @@ +using Jibo.Cloud.Domain.Models; + +namespace Jibo.Cloud.Application.Abstractions; + +public interface IProtocolTelemetrySink +{ + Task RecordAsync(ProtocolEnvelope envelope, ProtocolDispatchResult result, CancellationToken cancellationToken = default); +} diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/NullProtocolTelemetrySink.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/NullProtocolTelemetrySink.cs new file mode 100644 index 0000000..f3f8e4c --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Application/Services/NullProtocolTelemetrySink.cs @@ -0,0 +1,9 @@ +using Jibo.Cloud.Application.Abstractions; +using Jibo.Cloud.Domain.Models; + +namespace Jibo.Cloud.Application.Services; + +public sealed class NullProtocolTelemetrySink : IProtocolTelemetrySink +{ + public Task RecordAsync(ProtocolEnvelope envelope, ProtocolDispatchResult result, CancellationToken cancellationToken = default) => Task.CompletedTask; +} diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs index 31b9810..1580465 100644 --- a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ public static class ServiceCollectionExtensions if (configuration is not null) { services.Configure(configuration.GetSection("OpenJibo:Telemetry")); + services.Configure(configuration.GetSection("OpenJibo:ProtocolTelemetry")); } services.AddSingleton(); @@ -22,6 +23,7 @@ public static class ServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/FileProtocolTelemetrySink.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/FileProtocolTelemetrySink.cs new file mode 100644 index 0000000..8e916b8 --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/FileProtocolTelemetrySink.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using Jibo.Cloud.Application.Abstractions; +using Jibo.Cloud.Domain.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Jibo.Cloud.Infrastructure.Telemetry; + +public sealed class FileProtocolTelemetrySink( + ILogger logger, + IOptions options) : IProtocolTelemetrySink +{ + private readonly SemaphoreSlim _writeLock = new(1, 1); + + public async Task RecordAsync(ProtocolEnvelope envelope, ProtocolDispatchResult result, CancellationToken cancellationToken = default) + { + if (!options.Value.Enabled) + { + return; + } + + var directory = Path.GetFullPath(options.Value.DirectoryPath, AppContext.BaseDirectory); + Directory.CreateDirectory(directory); + var filePath = Path.Combine(directory, $"{DateTimeOffset.UtcNow:yyyyMMdd}.events.ndjson"); + + var payload = new + { + capturedUtc = DateTimeOffset.UtcNow, + request = new + { + envelope.RequestId, + envelope.Transport, + envelope.Method, + envelope.HostName, + envelope.Path, + envelope.ServicePrefix, + envelope.Operation, + envelope.DeviceId, + envelope.CorrelationId, + envelope.FirmwareVersion, + envelope.ApplicationVersion, + envelope.Headers, + envelope.BodyText + }, + response = new + { + result.StatusCode, + result.ContentType, + result.Headers, + result.BodyText + } + }; + + var line = JsonSerializer.Serialize(payload) + Environment.NewLine; + + await _writeLock.WaitAsync(cancellationToken); + try + { + await File.AppendAllTextAsync(filePath, line, cancellationToken); + } + finally + { + _writeLock.Release(); + } + + logger.LogInformation( + "HTTP telemetry {Method} {Host}{Path} target={Target} status={StatusCode}", + envelope.Method, + envelope.HostName, + envelope.Path, + $"{envelope.ServicePrefix}.{envelope.Operation}".Trim('.'), + result.StatusCode); + } +} diff --git a/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/ProtocolTelemetryOptions.cs b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/ProtocolTelemetryOptions.cs new file mode 100644 index 0000000..68a1e77 --- /dev/null +++ b/OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/ProtocolTelemetryOptions.cs @@ -0,0 +1,7 @@ +namespace Jibo.Cloud.Infrastructure.Telemetry; + +public sealed class ProtocolTelemetryOptions +{ + public bool Enabled { get; set; } = true; + public string DirectoryPath { get; set; } = "captures/http"; +}