diff --git a/cscglobal-caplugin.Tests/CSCGlobalCAPluginTests.cs b/cscglobal-caplugin.Tests/CSCGlobalCAPluginTests.cs index c3402e6..a062965 100644 --- a/cscglobal-caplugin.Tests/CSCGlobalCAPluginTests.cs +++ b/cscglobal-caplugin.Tests/CSCGlobalCAPluginTests.cs @@ -837,6 +837,56 @@ 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. + Assert.NotNull(result.EnrollmentContext); + Assert.True(result.EnrollmentContext.ContainsKey("Flow Summary")); + Assert.Contains("Enroll-New", result.EnrollmentContext["Flow Summary"]); + } + + [Fact] + public async Task Enroll_New_SuccessWithDcvDetails_KeepsDcvEntriesAlongsideFlowSummary() + { + var mockClient = new Mock(); + mockClient.Setup(c => c.SubmitGetCustomFields()).ReturnsAsync(new List()); + mockClient.Setup(c => c.SubmitRegistrationAsync(It.IsAny())).ReturnsAsync(new RegistrationResponse + { + Result = new Result + { + CommonName = "dcv.example.com", + Status = new Status { Uuid = "uuid-dcv" }, + DcvDetails = new List + { + new DcvDetail { CName = new CName { Name = "_dnsauth.example.com", Value = "token" } } + } + } + }); + + var plugin = MakePlugin(mockClient); + var result = await plugin.Enroll("csr", "CN=test", new Dictionary(), ProductInfo(), + RequestFormat.PKCS10, EnrollmentType.New); + + Assert.Equal("token", result.EnrollmentContext["_dnsauth.example.com"]); + Assert.True(result.EnrollmentContext.ContainsKey("Flow Summary")); + } + + [Fact] + public async Task Enroll_New_RegistrationErrorFromCsc_PrependsFlowSummaryToStatusMessage() + { + var mockClient = new Mock(); + mockClient.Setup(c => c.SubmitGetCustomFields()).ReturnsAsync(new List()); + mockClient.Setup(c => c.SubmitRegistrationAsync(It.IsAny())).ReturnsAsync(new RegistrationResponse + { + RegistrationError = new RegistrationError { Description = "Open order in progress" } + }); + + var plugin = MakePlugin(mockClient); + var result = await plugin.Enroll("csr", "CN=test", new Dictionary(), ProductInfo(), + RequestFormat.PKCS10, EnrollmentType.New); + + Assert.Equal((int)EndEntityStatus.FAILED, result.Status); + Assert.Contains("Enroll-New", result.StatusMessage); + Assert.Contains("Open order in progress", result.StatusMessage); } [Fact] diff --git a/cscglobal-caplugin/CSCGlobalCAPlugin.cs b/cscglobal-caplugin/CSCGlobalCAPlugin.cs index 457ef42..c84f909 100644 --- a/cscglobal-caplugin/CSCGlobalCAPlugin.cs +++ b/cscglobal-caplugin/CSCGlobalCAPlugin.cs @@ -711,10 +711,12 @@ await flow.StepAsync("PollForIssuance", async () => if (newPolled != null) { flow.Step("PollResult", "issued during poll window"); + AttachFlowSummary(newPolled, flow); Logger.MethodExit(LogLevel.Debug); return newPolled; } + AttachFlowSummary(enrollResult, flow); Logger.MethodExit(LogLevel.Debug); return enrollResult; @@ -864,6 +866,7 @@ await flow.StepAsync("PollForIssuance", async () => { renewPolled = await TryPollForIssuedCertAsync(renewResult?.CARequestID); }); + AttachFlowSummary(renewPolled ?? renewResult, flow); Logger.MethodExit(LogLevel.Debug); return renewPolled ?? renewResult; } @@ -943,6 +946,7 @@ await flow.StepAsync("PollForIssuance", async () => { reissuePolled = await TryPollForIssuedCertAsync(reissueResult?.CARequestID); }); + AttachFlowSummary(reissuePolled ?? reissueResult, flow); Logger.MethodExit(LogLevel.Debug); return reissuePolled ?? reissueResult; } @@ -987,6 +991,30 @@ await flow.StepAsync("PollForIssuance", async () => } } + // CSC Global business-level failures (e.g. "Open order in progress") come back from + // RequestManager as a terse StatusMessage with no context on what the plugin actually did + // before hitting that error - prepend the flow's step-by-step summary so the message shown + // to the requester in Command explains what ran, not just how it ended. Command's enrollment + // UI does not surface StatusMessage on a successful/pending result at all - only + // EnrollmentContext is - so attach the summary there instead, as its own entry alongside + // whatever DCV instructions came back. Must be called after TryPublishCnameDcvAsync, which + // treats every EnrollmentContext entry as a candidate DNS record to publish - calling this + // first would make it try to publish "Flow Summary" as a CNAME. + private static void AttachFlowSummary(EnrollmentResult? result, FlowLogger flow) + { + if (result == null) + return; + + if (result.Status == (int)EndEntityStatus.FAILED) + { + result.StatusMessage = $"{flow.GetSummary()}\n\n{result.StatusMessage}"; + return; + } + + result.EnrollmentContext ??= new Dictionary(); + result.EnrollmentContext["Flow Summary"] = flow.GetSummary(); + } + //done public async Task Ping() {