File: tools\Aspire.TestTools\MochaReader.cs
Web Access
Project: src\tools\AnalyzeCiFailure\AnalyzeCiFailure.csproj (AnalyzeCiFailure)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using System.Text.Json;
 
namespace Aspire.TestTools;
 
public static class MochaReader
{
    public static MochaReport Read(string filePath)
    {
        using var document = JsonDocument.Parse(File.ReadAllText(filePath));
        try
        {
            var root = document.RootElement;
            if (root.ValueKind != JsonValueKind.Object ||
                !root.TryGetProperty("tests", out var tests) || tests.ValueKind != JsonValueKind.Array ||
                !root.TryGetProperty("failures", out var failures) || failures.ValueKind != JsonValueKind.Array)
            {
                throw new JsonException("Test result does not have the expected Mocha reporter shape");
            }
 
            // extension/scripts/e2e-mocha-reporter.cjs emits:
            // {"tests":[{"fullTitle":"suite test"}],
            //  "failures":[{"fullTitle":"suite test","err":{"name":"AssertionError","message":"...","stack":"..."}}]}
            // Hook failures may not appear in completed tests. Preserve them so callers
            // can distinguish a broken harness from a completed test assertion.
            var completed = tests.EnumerateArray().Select(ReadName).ToImmutableArray();
            var failed = failures.EnumerateArray().Select(failure =>
            {
                var name = ReadName(failure);
                if (name.Length == 0 || !failure.TryGetProperty("err", out var error) || error.ValueKind != JsonValueKind.Object)
                {
                    throw new JsonException("Mocha failure has an invalid shape");
                }
 
                return new MochaFailure(
                    name,
                    OptionalText(error, "name"),
                    OptionalText(error, "message") ?? "",
                    OptionalText(error, "stack") ?? "");
            }).ToImmutableArray();
 
            return new MochaReport(completed, failed);
        }
        catch (InvalidOperationException ex)
        {
            throw new JsonException("Mocha report contains malformed Unicode text", ex);
        }
    }
 
    private static string ReadName(JsonElement test)
    {
        if (test.ValueKind != JsonValueKind.Object)
        {
            throw new JsonException("Mocha test has an invalid shape");
        }
        var title = test.TryGetProperty("fullTitle", out var fullTitle) &&
            fullTitle.ValueKind is not (JsonValueKind.Null or JsonValueKind.False)
                ? fullTitle
                : test.TryGetProperty("title", out var fallback) ? fallback : default;
        if (title.ValueKind != JsonValueKind.String)
        {
            throw new JsonException("Mocha test name must be a string");
        }
 
        return title.GetString()!;
    }
 
    private static string? OptionalText(JsonElement value, string property)
    {
        if (!value.TryGetProperty(property, out var field) || field.ValueKind == JsonValueKind.Null)
        {
            return null;
        }
        if (field.ValueKind != JsonValueKind.String)
        {
            throw new JsonException($"Mocha {property} must be a string");
        }
 
        return field.GetString();
    }
}
 
public sealed record MochaReport(ImmutableArray<string> CompletedTests, ImmutableArray<MochaFailure> Failures);
public sealed record MochaFailure(string Name, string? ErrorName, string ErrorMessage, string StackTrace);