fix: preserve completed automation evidence when quit is unavailable
This commit is contained in:
@@ -181,6 +181,7 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", document: document);
|
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", document: document);
|
||||||
RuntimeDiagnostics.Write("AnnotationTransactionStarted", operationId: operationId, commandName: commandName, document: document);
|
RuntimeDiagnostics.Write("AnnotationTransactionStarted", operationId: operationId, commandName: commandName, document: document);
|
||||||
var outcome = new FirstLimitedPipeAnnotationWriter().Write(document!, preflight.Operation, applyPermit.VisibilityState);
|
var outcome = new FirstLimitedPipeAnnotationWriter().Write(document!, preflight.Operation, applyPermit.VisibilityState);
|
||||||
|
FirstLimitedApplyAutomationCoordinator.RecordApplyOutcome(outcome);
|
||||||
details["ApplyOutcome"] = outcome.ToString();
|
details["ApplyOutcome"] = outcome.ToString();
|
||||||
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
|
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
|
||||||
details["AnnotationsSkipped"] = outcome == PipeAnnotationWriteOutcome.AlreadyExists ? 1 : 0;
|
details["AnnotationsSkipped"] = outcome == PipeAnnotationWriteOutcome.AlreadyExists ? 1 : 0;
|
||||||
|
|||||||
@@ -102,7 +102,16 @@ internal sealed record FirstApplyAutomationEvidence
|
|||||||
public int? CreateRun1 { get; init; }
|
public int? CreateRun1 { get; init; }
|
||||||
public int? CreateRun2 { get; init; }
|
public int? CreateRun2 { get; init; }
|
||||||
public int? SkipRun2 { get; init; }
|
public int? SkipRun2 { get; init; }
|
||||||
|
public string? ApplyOutcomeRun1 { get; init; }
|
||||||
|
public string? ApplyOutcomeRun2 { get; init; }
|
||||||
public bool? PermitAccepted { get; init; }
|
public bool? PermitAccepted { get; init; }
|
||||||
|
public bool? CommandsCompleted { get; init; }
|
||||||
|
public bool? InspectorsCompleted { get; init; }
|
||||||
|
public bool? EvidenceFlushed { get; init; }
|
||||||
|
public bool? ExitAttemptQueued { get; init; }
|
||||||
|
public bool? QuitCommandWillStart { get; init; }
|
||||||
|
public bool? QuitCommandEnded { get; init; }
|
||||||
|
public string? ExitFailureCode { get; init; }
|
||||||
public int? IdleCallbackCount { get; init; }
|
public int? IdleCallbackCount { get; init; }
|
||||||
public int? CoordinatorStartCount { get; init; }
|
public int? CoordinatorStartCount { get; init; }
|
||||||
// Local test evidence only. This is intentionally never copied into Git
|
// Local test evidence only. This is intentionally never copied into Git
|
||||||
@@ -116,7 +125,7 @@ internal sealed record FirstApplyAutomationEvidence
|
|||||||
internal sealed class FirstLimitedApplyAutomationCoordinator
|
internal sealed class FirstLimitedApplyAutomationCoordinator
|
||||||
{
|
{
|
||||||
private enum CoordinatorState { Created, Starting, Running, Completed, Failed, ExitQueued }
|
private enum CoordinatorState { Created, Starting, Running, Completed, Failed, ExitQueued }
|
||||||
private sealed record PxpResult(string? PlanId, IReadOnlyList<string> Candidates, string? View, int Create, int Skip, bool? PermitAccepted);
|
private sealed record PxpResult(string? PlanId, IReadOnlyList<string> Candidates, string? View, int Create, int Skip, bool? PermitAccepted, string? ApplyOutcome);
|
||||||
private static string? selectedTargetSnapshotId;
|
private static string? selectedTargetSnapshotId;
|
||||||
private static readonly List<PxpResult> pxpResults = [];
|
private static readonly List<PxpResult> pxpResults = [];
|
||||||
internal static string? SelectedTargetSnapshotId => selectedTargetSnapshotId;
|
internal static string? SelectedTargetSnapshotId => selectedTargetSnapshotId;
|
||||||
@@ -125,7 +134,13 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
internal static void RecordPxpOa(string? planId, IReadOnlyList<string> candidates, string? view, int create, int skip, bool? permitAccepted)
|
internal static void RecordPxpOa(string? planId, IReadOnlyList<string> candidates, string? view, int create, int skip, bool? permitAccepted)
|
||||||
{
|
{
|
||||||
if (string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal))
|
if (string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal))
|
||||||
pxpResults.Add(new(planId, candidates, view, create, skip, permitAccepted));
|
pxpResults.Add(new(planId, candidates, view, create, skip, permitAccepted, null));
|
||||||
|
}
|
||||||
|
internal static void RecordApplyOutcome(PipeAnnotationWriteOutcome outcome)
|
||||||
|
{
|
||||||
|
if (!string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal) || pxpResults.Count == 0) return;
|
||||||
|
var last = pxpResults[^1];
|
||||||
|
pxpResults[^1] = last with { ApplyOutcome = outcome.ToString() };
|
||||||
}
|
}
|
||||||
private FirstApplyAutomationConfiguration? config;
|
private FirstApplyAutomationConfiguration? config;
|
||||||
private Document? document;
|
private Document? document;
|
||||||
@@ -144,6 +159,10 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
private bool exitQueued;
|
private bool exitQueued;
|
||||||
private bool exitHandlerSubscribed;
|
private bool exitHandlerSubscribed;
|
||||||
private string? exceptionText;
|
private string? exceptionText;
|
||||||
|
private bool functionalCompleted;
|
||||||
|
private bool evidenceFlushed;
|
||||||
|
private bool exitAttemptQueued;
|
||||||
|
private string? exitFailureCode;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
@@ -293,6 +312,7 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
private void Complete(bool success, string? error, bool safeToExitNow = false)
|
private void Complete(bool success, string? error, bool safeToExitNow = false)
|
||||||
{
|
{
|
||||||
if (IsTerminal) return;
|
if (IsTerminal) return;
|
||||||
|
functionalCompleted = success;
|
||||||
Transition(success ? CoordinatorState.Completed : CoordinatorState.Failed, success ? "Completed" : "Failed");
|
Transition(success ? CoordinatorState.Completed : CoordinatorState.Failed, success ? "Completed" : "Failed");
|
||||||
UnsubscribeHandlers();
|
UnsubscribeHandlers();
|
||||||
TryWriteEvidence(success, error);
|
TryWriteEvidence(success, error);
|
||||||
@@ -330,16 +350,29 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
{
|
{
|
||||||
if (exitHandlerSubscribed) AcadApplication.Idle -= OnExitIdle;
|
if (exitHandlerSubscribed) AcadApplication.Idle -= OnExitIdle;
|
||||||
exitHandlerSubscribed = false;
|
exitHandlerSubscribed = false;
|
||||||
|
exitAttemptQueued = true;
|
||||||
|
if (!AcadApplication.IsQuiescent)
|
||||||
|
{
|
||||||
|
transitions.Add("QuitDispatchDeferredNotQuiescent");
|
||||||
|
TryWriteEvidence(functionalCompleted, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// LifecycleProbe opens no fixture document, but the real startup
|
// LifecycleProbe opens no fixture document, but the real startup
|
||||||
// document is available at this point (observed as Drawing1.dwg).
|
// document is available at this point (observed as Drawing1.dwg).
|
||||||
// Use that live document only to queue native QUIT; never retain it
|
// Use that live document only to queue native QUIT; never retain it
|
||||||
// past this callback or use it for fixture/Plant work.
|
// past this callback or use it for fixture/Plant work.
|
||||||
var exitDocument = document ?? AcadApplication.DocumentManager.MdiActiveDocument;
|
var exitDocument = AcadApplication.DocumentManager.MdiActiveDocument;
|
||||||
if (exitDocument is null) throw new InvalidOperationException("AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE");
|
if (exitDocument is null)
|
||||||
transitions.Add("NativeQuitCommandQueued");
|
{
|
||||||
|
exitFailureCode = "AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE";
|
||||||
|
transitions.Add("QuitDispatchSkippedNoLiveDocument");
|
||||||
|
TryWriteEvidence(functionalCompleted, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transitions.Add("QuitDispatchAttempted");
|
||||||
// Persist the exit boundary before handing control back to AutoCAD;
|
// Persist the exit boundary before handing control back to AutoCAD;
|
||||||
// process termination can otherwise prevent the final evidence flush.
|
// process termination can otherwise prevent the final evidence flush.
|
||||||
TryWriteEvidence(state == CoordinatorState.Completed, null);
|
TryWriteEvidence(functionalCompleted, null);
|
||||||
exitDocument.SendStringToExecute("_.QUIT _N ", true, false, false);
|
exitDocument.SendStringToExecute("_.QUIT _N ", true, false, false);
|
||||||
document = null;
|
document = null;
|
||||||
automationOpenedDocuments.Clear();
|
automationOpenedDocuments.Clear();
|
||||||
@@ -348,7 +381,11 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
{
|
{
|
||||||
// This is terminal cleanup: contain the exception and leave process
|
// This is terminal cleanup: contain the exception and leave process
|
||||||
// cleanup to Launcher rather than re-entering document APIs.
|
// cleanup to Launcher rather than re-entering document APIs.
|
||||||
FailFromCallback("ControlledExitIdle", exception, "AUTOMATION_CONTROLLED_EXIT_FAILED");
|
// A failed graceful-exit dispatch is not a failure of already
|
||||||
|
// completed PXP-OA/UNDO work. Launcher owns bounded forced cleanup.
|
||||||
|
exitFailureCode = "AUTOMATION_CONTROLLED_EXIT_FAILED:" + exception.GetType().Name;
|
||||||
|
transitions.Add("QuitDispatchFailed");
|
||||||
|
TryWriteEvidence(functionalCompleted, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +413,13 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
UnsubscribeHandlers();
|
UnsubscribeHandlers();
|
||||||
TryWriteEvidence(false, code + ":" + callback + ":" + exception.GetType().Name);
|
if (functionalCompleted)
|
||||||
|
{
|
||||||
|
exitFailureCode = code + ":" + callback + ":" + exception.GetType().Name;
|
||||||
|
transitions.Add("ExitCallbackFailed");
|
||||||
|
TryWriteEvidence(true, null);
|
||||||
|
}
|
||||||
|
else TryWriteEvidence(false, code + ":" + callback + ":" + exception.GetType().Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,17 +439,18 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
lifecycleHandlersSubscribed = false;
|
lifecycleHandlersSubscribed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryWriteEvidence(bool success, string? error)
|
private bool TryWriteEvidence(bool success, string? error)
|
||||||
{
|
{
|
||||||
try { WriteEvidence(success, error); }
|
try { evidenceFlushed = true; WriteEvidence(success, error); return true; }
|
||||||
catch (System.Exception exception) { RuntimeDiagnostics.Write("AutomationEvidenceWriteFailed", "ERROR", resultStatus: "AUTOMATION_EVIDENCE_WRITE_FAILED", exception: exception); }
|
catch (System.Exception exception) { evidenceFlushed = false; RuntimeDiagnostics.Write("AutomationEvidenceWriteFailed", "ERROR", resultStatus: "AUTOMATION_EVIDENCE_WRITE_FAILED", exception: exception); return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteEvidence(bool success, string? error)
|
private void WriteEvidence(bool success, string? error)
|
||||||
{
|
{
|
||||||
if (config is null) return;
|
if (config is null) return;
|
||||||
var first = pxpResults.ElementAtOrDefault(0); var second = pxpResults.ElementAtOrDefault(1);
|
var first = pxpResults.ElementAtOrDefault(0); var second = pxpResults.ElementAtOrDefault(1);
|
||||||
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = success, ErrorCode = error, DbmodBefore = before, DbmodAfterCommand1 = after1, DbmodAfterCommand2 = after2, DbmodAfterUndo = afterUndo, Baseline = baseline, AfterCommand1 = counts1, AfterCommand2 = counts2, AfterUndo = countsUndo, CompletedCommands = completed, PlanIdRun1 = first?.PlanId, PlanIdRun2 = second?.PlanId, PlanIdsEqual = first is not null && second is not null ? string.Equals(first.PlanId, second.PlanId, StringComparison.Ordinal) : null, EligibleCandidateCount = first?.Candidates.Count, SelectedCandidateHash = selectedTargetSnapshotId, ViewHash = first?.View, CreateRun1 = first?.Create, CreateRun2 = second?.Create, SkipRun2 = second?.Skip, PermitAccepted = first?.PermitAccepted, IdleCallbackCount = FirstLimitedApplyAutomationBootstrap.IdleCallbackCount, CoordinatorStartCount = FirstLimitedApplyAutomationBootstrap.CoordinatorStartCount, ExceptionText = exceptionText, PreviousState = previousState.ToString(), CurrentState = state.ToString(), StateTransitions = transitions };
|
var expectedCommands = string.Equals(config.Phase, "Apply", StringComparison.OrdinalIgnoreCase) ? 3 : 2;
|
||||||
|
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = success, ErrorCode = error, DbmodBefore = before, DbmodAfterCommand1 = after1, DbmodAfterCommand2 = after2, DbmodAfterUndo = afterUndo, Baseline = baseline, AfterCommand1 = counts1, AfterCommand2 = counts2, AfterUndo = countsUndo, CompletedCommands = completed, PlanIdRun1 = first?.PlanId, PlanIdRun2 = second?.PlanId, PlanIdsEqual = first is not null && second is not null ? string.Equals(first.PlanId, second.PlanId, StringComparison.Ordinal) : null, EligibleCandidateCount = first?.Candidates.Count, SelectedCandidateHash = selectedTargetSnapshotId, ViewHash = first?.View, CreateRun1 = first?.Create, CreateRun2 = second?.Create, SkipRun2 = second?.Skip, ApplyOutcomeRun1 = first?.ApplyOutcome, ApplyOutcomeRun2 = second?.ApplyOutcome, PermitAccepted = first?.PermitAccepted, CommandsCompleted = completed.Count >= expectedCommands, InspectorsCompleted = baseline is not null && counts1 is not null && counts2 is not null && (!string.Equals(config.Phase, "Apply", StringComparison.OrdinalIgnoreCase) || countsUndo is not null), EvidenceFlushed = evidenceFlushed, ExitAttemptQueued = exitAttemptQueued, ExitFailureCode = exitFailureCode, IdleCallbackCount = FirstLimitedApplyAutomationBootstrap.IdleCallbackCount, CoordinatorStartCount = FirstLimitedApplyAutomationBootstrap.CoordinatorStartCount, ExceptionText = exceptionText, PreviousState = previousState.ToString(), CurrentState = state.ToString(), StateTransitions = transitions };
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(config.EvidencePath)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(config.EvidencePath)!);
|
||||||
File.WriteAllText(config.EvidencePath, JsonSerializer.Serialize(evidence));
|
File.WriteAllText(config.EvidencePath, JsonSerializer.Serialize(evidence));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ public sealed class FoundationTests
|
|||||||
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
Assert.Contains("RequestControlledExit", source);
|
Assert.Contains("RequestControlledExit", source);
|
||||||
Assert.Contains("AcadApplication.Idle += OnExitIdle", source);
|
Assert.Contains("AcadApplication.Idle += OnExitIdle", source);
|
||||||
Assert.Contains("NativeQuitCommandQueued", source);
|
Assert.Contains("QuitDispatchAttempted", source);
|
||||||
Assert.Contains("exitDocument.SendStringToExecute(\"_.QUIT _N \", true, false, false);", source);
|
Assert.Contains("exitDocument.SendStringToExecute(\"_.QUIT _N \", true, false, false);", source);
|
||||||
Assert.Contains("var exitDocument = document ?? AcadApplication.DocumentManager.MdiActiveDocument;", source);
|
Assert.Contains("var exitDocument = AcadApplication.DocumentManager.MdiActiveDocument;", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact] public void Callback_AfterDocumentDestroyed_DoesNotAccessDocument()
|
[Fact] public void Callback_AfterDocumentDestroyed_DoesNotAccessDocument()
|
||||||
@@ -119,7 +119,7 @@ public sealed class FoundationTests
|
|||||||
{
|
{
|
||||||
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
Assert.Contains("AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE", source);
|
Assert.Contains("AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE", source);
|
||||||
Assert.Contains("if (exitDocument is null) throw new InvalidOperationException", source);
|
Assert.Contains("QuitDispatchSkippedNoLiveDocument", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact] public void TerminalState_IgnoresFurtherCallbacks()
|
[Fact] public void TerminalState_IgnoresFurtherCallbacks()
|
||||||
@@ -159,7 +159,7 @@ public sealed class FoundationTests
|
|||||||
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
var controlledExit = source[source.IndexOf("private void RequestControlledExit", StringComparison.Ordinal)..];
|
var controlledExit = source[source.IndexOf("private void RequestControlledExit", StringComparison.Ordinal)..];
|
||||||
Assert.DoesNotContain("document.CloseAndDiscard", controlledExit);
|
Assert.DoesNotContain("document.CloseAndDiscard", controlledExit);
|
||||||
Assert.Contains("var exitDocument = document ?? AcadApplication.DocumentManager.MdiActiveDocument;", controlledExit);
|
Assert.Contains("var exitDocument = AcadApplication.DocumentManager.MdiActiveDocument;", controlledExit);
|
||||||
Assert.Contains("document = null;", controlledExit);
|
Assert.Contains("document = null;", controlledExit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +170,40 @@ public sealed class FoundationTests
|
|||||||
Assert.Contains("RequestControlledExit(safeToExitNow);", source);
|
Assert.Contains("RequestControlledExit(safeToExitNow);", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact] public void TerminalEvidence_IsFlushedBeforeExitAttempt()
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
var exit = source[source.IndexOf("private void DispatchNativeQuit", StringComparison.Ordinal)..];
|
||||||
|
Assert.True(exit.IndexOf("TryWriteEvidence(functionalCompleted, null);", StringComparison.Ordinal) < exit.IndexOf("SendStringToExecute(\"_.QUIT _N ", StringComparison.Ordinal));
|
||||||
|
Assert.Contains("EvidenceFlushed = evidenceFlushed", source);
|
||||||
|
Assert.Contains("CommandsCompleted", source);
|
||||||
|
Assert.Contains("InspectorsCompleted", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] public void ExitFailure_DoesNotInvalidateCompletedFunctionalEvidence()
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
Assert.Contains("if (functionalCompleted)", source);
|
||||||
|
Assert.Contains("ExitFailureCode = exitFailureCode", source);
|
||||||
|
Assert.Contains("TryWriteEvidence(functionalCompleted, null);", source);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] public void ExitDispatch_DoesNotUseUnavailableDocument()
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
var exit = source[source.IndexOf("private void DispatchNativeQuit", StringComparison.Ordinal)..];
|
||||||
|
Assert.Contains("AcadApplication.IsQuiescent", exit);
|
||||||
|
Assert.Contains("QuitDispatchSkippedNoLiveDocument", exit);
|
||||||
|
Assert.DoesNotContain("document ?? AcadApplication.DocumentManager", exit);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] public void ProductionRuntime_DoesNotEnableForceAfterEvidence()
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
Assert.DoesNotContain("ForceAfterEvidence", source);
|
||||||
|
Assert.Contains("P_REC_ANNOT_AUTOMATION", source);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));
|
[Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));
|
||||||
[Fact] public void LegacyAliases_AreRegistered() => Assert.True(File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-AAV") && File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-OA"));
|
[Fact] public void LegacyAliases_AreRegistered() => Assert.True(File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-AAV") && File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")).Contains("PXP-OA"));
|
||||||
[Fact] public void RibbonCommand_ReferencesRegisteredCommand() => Assert.Contains("PHP_REC_ANNOT ", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));
|
[Fact] public void RibbonCommand_ReferencesRegisteredCommand() => Assert.Contains("PHP_REC_ANNOT ", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));
|
||||||
|
|||||||
Reference in New Issue
Block a user