Files
AutopilotMonitor/src/Backend/AutopilotMonitor.Functions.Tests/GetScriptDisplayNamesFunctionTests.cs
T
Oliver Kieselbach 54d4c0bd4c Script names: an incomplete lookup leaves a trace and reports its time limit
A lookup that ends without an answer for every ref, because one of the
resolver's time limits fired or Graph or the token context failed, writes
one Warning with the reasons. These paths logged at Information or not at
all, and nothing collects worker logs below Warning.

The response carries timedOut when a time limit cut the lookup short, the
request may carry retry, and the request row gets ScriptNameLookup
(timedOut / retry / retryTimedOut), so a follow-up lookup can be told from
a first one.

Also:
- The token cap is 3 s instead of 1.5 s: the first mint on a cold instance
  often needs longer, and a lookup without a token returns no name at all.
- A not-found id is kept as long as a name (7 days) instead of being asked
  again after every cleanup run.
- A list pull whose budget ends between two pages is no longer stored as
  the tenant's complete list.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 15:07:10 +01:00

56 lines
2.7 KiB
C#

using System.Net;
using System.Text.Json;
using AutopilotMonitor.Functions.Functions.Graph;
using AutopilotMonitor.Functions.Security;
using AutopilotMonitor.Functions.Services.GraphResolution;
using AutopilotMonitor.Shared.Models.Graph;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
namespace AutopilotMonitor.Functions.Tests;
/// <summary>
/// POST tenants/{tenantId}/scripts/display-names: a lookup that ran into a time limit says so in
/// its answer, so the portal asks once more, and the request row tells a cut first lookup, a
/// follow-up and a follow-up that was cut again apart. A first lookup within its limits is the
/// normal case and carries neither the flag nor a marker.
/// </summary>
public class GetScriptDisplayNamesFunctionTests
{
private const string TenantId = "00000000-0000-4000-8000-000000000002";
private const string Ref = "Platform:1f9d3a52-4c1e-4b7a-9a01-52f3aaaa1001";
[Theory]
[InlineData(false, false, null)]
[InlineData(true, false, "timedOut")]
[InlineData(false, true, "retry")]
[InlineData(true, true, "retryTimedOut")]
public async Task A_cut_lookup_is_flagged_in_the_answer_and_marked_on_the_request_row(
bool timedOut, bool isFollowUp, string? expectedMarker)
{
var resolver = new Mock<IScriptDisplayNameResolver>();
resolver
.Setup(r => r.ResolveAsync(TenantId, It.IsAny<IReadOnlyCollection<ScriptRef>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((string _, IReadOnlyCollection<ScriptRef> refs, CancellationToken _) =>
new ScriptDisplayNameLookup(refs.ToDictionary(r => r, _ => (string?)null), timedOut));
var function = new GetScriptDisplayNamesFunction(NullLogger<GetScriptDisplayNamesFunction>.Instance, resolver.Object);
var body = "{\"refs\":[\"" + Ref + "\"]" + (isFollowUp ? ",\"retry\":true" : "") + "}";
var (req, context) = EndpointHarness.Request(TenantId, jsonBody: body);
var response = await function.Run(req, TenantId);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
response.Body.Position = 0;
using var doc = JsonDocument.Parse(response.Body);
Assert.True(doc.RootElement.GetProperty("refs").TryGetProperty(Ref, out _));
// The key is left out, not false, when no time limit fired.
Assert.Equal(timedOut, doc.RootElement.TryGetProperty("timedOut", out var flag));
if (timedOut) Assert.True(flag.GetBoolean());
if (expectedMarker == null)
Assert.False(context.Items.ContainsKey(RequestRowMarkers.ScriptNameLookupKey));
else
Assert.Equal(expectedMarker, context.Items[RequestRowMarkers.ScriptNameLookupKey]);
}
}