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
10 changes: 6 additions & 4 deletions cscglobal-caplugin.Tests/CSCGlobalCAPluginTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -838,10 +838,11 @@ public async Task Enroll_New_Success_ReturnsExternalValidation()
Assert.Equal((int)EndEntityStatus.EXTERNALVALIDATION, result.Status);
Assert.Equal("uuid-new", result.CARequestID);
// Command's enrollment UI doesn't surface StatusMessage on a successful/pending result -
// only EnrollmentContext is - so the flow summary must be attached there instead.
// only EnrollmentContext is - so the flow summary must be attached there instead, one
// bullet per step so it renders readably rather than as a single run-on blob.
Assert.NotNull(result.EnrollmentContext);
Assert.True(result.EnrollmentContext.ContainsKey("Flow Summary"));
Assert.Contains("Enroll-New", result.EnrollmentContext["Flow Summary"]);
Assert.True(result.EnrollmentContext.ContainsKey("Flow: Enroll-New"));
Assert.True(result.EnrollmentContext.Keys.Count(k => k.StartsWith("Flow Step ")) > 1);
}

[Fact]
Expand All @@ -867,7 +868,8 @@ public async Task Enroll_New_SuccessWithDcvDetails_KeepsDcvEntriesAlongsideFlowS
RequestFormat.PKCS10, EnrollmentType.New);

Assert.Equal("token", result.EnrollmentContext["_dnsauth.example.com"]);
Assert.True(result.EnrollmentContext.ContainsKey("Flow Summary"));
Assert.True(result.EnrollmentContext.ContainsKey("Flow: Enroll-New"));
Assert.True(result.EnrollmentContext.Keys.Count(k => k.StartsWith("Flow Step ")) > 1);
}

[Fact]
Expand Down
45 changes: 45 additions & 0 deletions cscglobal-caplugin.Tests/FlowLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,51 @@ public void EndBranch_WithoutBranch_DoesNotThrow()
flow.EndBranch();
}

[Fact]
public void GetSummaryEntries_OneEntryPerStepPlusHeader()
{
using var flow = new FlowLogger(NewLoggerMock().Object, "MyFlow");
flow.Step("StepOne");
flow.Fail("StepTwo", "boom");

var entries = flow.GetSummaryEntries();

Assert.True(entries.ContainsKey("Flow: MyFlow"));
Assert.Contains("FAILED", entries["Flow: MyFlow"]);
Assert.Equal(3, entries.Count); // header + 2 steps
Assert.Contains(entries, e => e.Key.Contains("StepOne") && e.Value.Contains("OK"));
Assert.Contains(entries, e => e.Key.Contains("StepTwo") && e.Value.Contains("boom"));
}

[Fact]
public void GetSummaryEntries_AllStepsSucceed_HeaderReportsOk()
{
using var flow = new FlowLogger(NewLoggerMock().Object, "MyFlow");
flow.Step("StepOne");
flow.Step("StepTwo");

var entries = flow.GetSummaryEntries();

Assert.Contains("[OK]", entries["Flow: MyFlow"]);
}

[Fact]
public void GetSummaryEntries_BranchChildren_IncludedAsSeparateEntries()
{
using var flow = new FlowLogger(NewLoggerMock().Object, "MyFlow");
flow.Branch("Inner");
flow.Step("NestedStep");
flow.Fail("NestedFail", "inner reason");
flow.EndBranch();
flow.Step("TopLevelStep");

var entries = flow.GetSummaryEntries();

Assert.Contains(entries, e => e.Key.Contains("NestedStep"));
Assert.Contains(entries, e => e.Key.Contains("NestedFail") && e.Value.Contains("inner reason"));
Assert.Contains(entries, e => e.Key.Contains("TopLevelStep"));
}

[Fact]
public void Dispose_NoSteps_DoesNotThrow()
{
Expand Down
6 changes: 5 additions & 1 deletion cscglobal-caplugin/CSCGlobalCAPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1011,8 +1011,12 @@ private static void AttachFlowSummary(EnrollmentResult? result, FlowLogger flow)
return;
}

// One EnrollmentContext entry per step (rather than one entry holding the whole
// multi-line summary) so Command's bulleted rendering shows a readable line per step
// instead of a single run-on blob.
result.EnrollmentContext ??= new Dictionary<string, string>();
result.EnrollmentContext["Flow Summary"] = flow.GetSummary();
foreach (var entry in flow.GetSummaryEntries())
result.EnrollmentContext[entry.Key] = entry.Value;
}

//done
Expand Down
47 changes: 47 additions & 0 deletions cscglobal-caplugin/FlowLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
}

/// <summary>Record a completed step.</summary>
public FlowLogger Step(string name, string detail = null)

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var step = new FlowStep { Name = name, Status = FlowStepStatus.Success, Detail = detail };
AddStep(step);
Expand All @@ -65,7 +65,7 @@
}

/// <summary>Record a step that executes an action and times it.</summary>
public FlowLogger Step(string name, Action action, string detail = null)

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var sw = Stopwatch.StartNew();
var step = new FlowStep { Name = name, Detail = detail };
Expand Down Expand Up @@ -95,7 +95,7 @@
}

/// <summary>Record an async step that executes and times it.</summary>
public async Task<FlowLogger> StepAsync(string name, Func<Task> action, string detail = null)

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var sw = Stopwatch.StartNew();
var step = new FlowStep { Name = name, Detail = detail };
Expand Down Expand Up @@ -125,7 +125,7 @@
}

/// <summary>Record a failed step without throwing.</summary>
public FlowLogger Fail(string name, string reason = null)

Check warning on line 128 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 128 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 128 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var step = new FlowStep { Name = name, Status = FlowStepStatus.Failed, Detail = reason };
AddStep(step);
Expand All @@ -135,7 +135,7 @@
}

/// <summary>Record a skipped step.</summary>
public FlowLogger Skip(string name, string reason = null)

Check warning on line 138 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 138 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 138 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var step = new FlowStep { Name = name, Status = FlowStepStatus.Skipped, Detail = reason };
AddStep(step);
Expand Down Expand Up @@ -252,6 +252,53 @@
sb.AppendLine($"{indent}{icon} {step.Name}{elapsed}{detail}");
}

/// <summary>
/// Same information as <see cref="GetSummary" />, but as one entry per step instead of a
/// single multi-line block. Intended for callers (e.g. EnrollmentResult.EnrollmentContext)
/// whose rendering surface displays a dictionary as a bulleted list and doesn't respect
/// embedded newlines - each step becomes its own bullet instead of one run-on line.
/// </summary>
public Dictionary<string, string> GetSummaryEntries()
{
var allSteps = _steps.Concat(_steps.SelectMany(s => s.Children)).ToList();
var hasFailures = allSteps.Any(s => s.Status == FlowStepStatus.Failed);
var overallStatus = hasFailures ? "FAILED" : "OK";
var succeeded = allSteps.Count(s => s.Status == FlowStepStatus.Success);
var failed = allSteps.Count(s => s.Status == FlowStepStatus.Failed);
var skipped = allSteps.Count(s => s.Status == FlowStepStatus.Skipped);

var entries = new Dictionary<string, string>
{
[$"Flow: {_flowName}"] =
$"[{overallStatus}] {_totalTimer.ElapsedMilliseconds}ms total - " +
$"{allSteps.Count} steps ({succeeded} ok, {failed} failed, {skipped} skipped)"
};

var stepNumber = 0;
foreach (var step in _steps)
{
stepNumber++;
AddSummaryEntry(entries, step, stepNumber, false);

foreach (var child in step.Children)
{
stepNumber++;
AddSummaryEntry(entries, child, stepNumber, true);
}
}

return entries;
}

private static void AddSummaryEntry(Dictionary<string, string> entries, FlowStep step, int stepNumber, bool indent)
{
var icon = GetStatusIcon(step.Status);
var time = step.ElapsedMs > 0 ? $" ({step.ElapsedMs}ms)" : "";
var detail = !string.IsNullOrEmpty(step.Detail) ? $" - {step.Detail}" : "";
var prefix = indent ? " " : "";
entries[$"Flow Step {stepNumber:00}: {prefix}{step.Name}"] = $"{icon}{time}{detail}";
}

private static string GetStatusIcon(FlowStepStatus status)
{
return status switch
Expand Down
Loading