more test fixes
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
param(
|
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
|
$resolvedDirectory = Resolve-Path -LiteralPath $CaptureDirectory -ErrorAction Stop
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ These scripts help exercise the new .NET hosted cloud locally.
|
|||||||
Replays a sanitized HTTP fixture against a running local instance.
|
Replays a sanitized HTTP fixture against a running local instance.
|
||||||
- `Get-WebSocketCaptureSummary.ps1`
|
- `Get-WebSocketCaptureSummary.ps1`
|
||||||
Summarizes captured websocket telemetry events and exported live-run fixtures from the .NET cloud.
|
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`
|
- `Invoke-LiveJiboPrep.ps1`
|
||||||
Runs a small readiness checklist before the first physical Jibo test against the .NET cloud.
|
Runs a small readiness checklist before the first physical Jibo test against the .NET cloud.
|
||||||
- `Import-WebSocketCaptureFixture.ps1`
|
- `Import-WebSocketCaptureFixture.ps1`
|
||||||
|
|||||||
@@ -21,12 +21,30 @@ app.Use(async (context, next) =>
|
|||||||
return;
|
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<JiboWebSocketService>();
|
var webSocketService = context.RequestServices.GetRequiredService<JiboWebSocketService>();
|
||||||
var telemetrySink = context.RequestServices.GetRequiredService<IWebSocketTelemetrySink>();
|
var telemetrySink = context.RequestServices.GetRequiredService<IWebSocketTelemetrySink>();
|
||||||
using var socket = await context.WebSockets.AcceptWebSocketAsync();
|
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
|
var openEnvelope = new WebSocketMessageEnvelope
|
||||||
{
|
{
|
||||||
ConnectionId = Guid.NewGuid().ToString("N"),
|
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.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 envelope = await BuildEnvelopeAsync(context, cancellationToken);
|
||||||
var result = await service.DispatchAsync(envelope, cancellationToken);
|
var result = await service.DispatchAsync(envelope, cancellationToken);
|
||||||
|
await telemetrySink.RecordAsync(envelope, result, cancellationToken);
|
||||||
|
|
||||||
context.Response.StatusCode = result.StatusCode;
|
context.Response.StatusCode = result.StatusCode;
|
||||||
context.Response.ContentType = result.ContentType;
|
context.Response.ContentType = result.ContentType;
|
||||||
@@ -174,7 +193,14 @@ static string ResolveSocketKind(string host, PathString path)
|
|||||||
return "neo-hub-listen";
|
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)
|
static string? ResolveToken(HttpRequest request)
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"ExportFixtures": true,
|
"ExportFixtures": true,
|
||||||
"DirectoryPath": "captures/websocket"
|
"DirectoryPath": "captures/websocket"
|
||||||
|
},
|
||||||
|
"ProtocolTelemetry": {
|
||||||
|
"Enabled": true,
|
||||||
|
"DirectoryPath": "captures/http"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ public static class ServiceCollectionExtensions
|
|||||||
if (configuration is not null)
|
if (configuration is not null)
|
||||||
{
|
{
|
||||||
services.Configure<WebSocketTelemetryOptions>(configuration.GetSection("OpenJibo:Telemetry"));
|
services.Configure<WebSocketTelemetryOptions>(configuration.GetSection("OpenJibo:Telemetry"));
|
||||||
|
services.Configure<ProtocolTelemetryOptions>(configuration.GetSection("OpenJibo:ProtocolTelemetry"));
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddSingleton<ICloudStateStore, InMemoryCloudStateStore>();
|
services.AddSingleton<ICloudStateStore, InMemoryCloudStateStore>();
|
||||||
@@ -22,6 +23,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<ISttStrategy, SyntheticBufferedAudioSttStrategy>();
|
services.AddSingleton<ISttStrategy, SyntheticBufferedAudioSttStrategy>();
|
||||||
services.AddSingleton<ISttStrategySelector, DefaultSttStrategySelector>();
|
services.AddSingleton<ISttStrategySelector, DefaultSttStrategySelector>();
|
||||||
services.AddSingleton<IWebSocketTelemetrySink, FileWebSocketTelemetrySink>();
|
services.AddSingleton<IWebSocketTelemetrySink, FileWebSocketTelemetrySink>();
|
||||||
|
services.AddSingleton<IProtocolTelemetrySink, FileProtocolTelemetrySink>();
|
||||||
services.AddSingleton<ProtocolToTurnContextMapper>();
|
services.AddSingleton<ProtocolToTurnContextMapper>();
|
||||||
services.AddSingleton<ResponsePlanToSocketMessagesMapper>();
|
services.AddSingleton<ResponsePlanToSocketMessagesMapper>();
|
||||||
services.AddSingleton<WebSocketTurnFinalizationService>();
|
services.AddSingleton<WebSocketTurnFinalizationService>();
|
||||||
|
|||||||
@@ -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<FileProtocolTelemetrySink> logger,
|
||||||
|
IOptions<ProtocolTelemetryOptions> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user