134 lines
9.0 KiB
Go
134 lines
9.0 KiB
Go
package processreport
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting"
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/contracts"
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/outcomes"
|
|
"gitea.maximumdirect.net/eric/audita/v3/internal/framework/runner"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestBuildPartialUnitsAndCombinedRecovery(t *testing.T) {
|
|
started := time.Date(2026, 9, 24, 12, 0, 0, 0, time.UTC)
|
|
completed := started.Add(time.Minute)
|
|
proposal := outcomes.Operation{Reference: outcomes.Reference{Kind: outcomes.OperationProposal}, Succeeded: true,
|
|
Counts: outcomes.ExecutionCounts{Available: true, ProviderAttempts: 2, TransientRetries: 1, StructuralRepairs: 1},
|
|
RecoveryReasons: []outcomes.RecoveryReason{outcomes.RecoveryServiceRecovered, outcomes.RecoveryOutputRepaired, outcomes.RecoveryProposalsFiltered},
|
|
Filtered: []outcomes.FilteredProposal{{ProposalIndex: 4, Reason: outcomes.ReasonInvalidOutput, Count: 1}, {ProposalIndex: 2, Reason: outcomes.ReasonInvalidOutput, Count: 1}}}
|
|
batch := 1
|
|
validation := outcomes.Operation{Reference: outcomes.Reference{Kind: outcomes.OperationValidation, ValidatorName: "review", ValidatorOrder: intPtr(1), BatchIndex: &batch}, FailureReason: outcomes.ReasonTransportFailed, ProposalIndices: []int{9, 3}}
|
|
inputFailure := outcomes.Operation{Reference: outcomes.Reference{Kind: outcomes.OperationValidationInput, ValidatorName: "review", ValidatorOrder: intPtr(1)}, FailureReason: outcomes.ReasonValidationInputTooLarge, ProposalIndices: []int{7}}
|
|
output := &runner.RunOutput{InventoryAvailable: true, WorkUnits: []runner.WorkUnitOutcome{
|
|
{ModuleIndex: 1, ModuleKey: "grammar", ModuleInstance: "grammar_2", Section: contracts.SectionMetadata{Index: 0, StartSegmentID: 1, EndSegmentID: 2}, State: outcomes.WorkNotStarted},
|
|
{ModuleIndex: 0, ModuleKey: "grammar", ModuleInstance: "grammar_1", Section: contracts.SectionMetadata{Index: 0, StartSegmentID: 1, EndSegmentID: 2}, State: outcomes.WorkCompleted, CorrectionsApplied: 1, Operations: []outcomes.Operation{validation, proposal, inputFailure}},
|
|
}}
|
|
snapshot := Build(BuildInput{RunID: "run-1", Status: reporting.StatusCompleted, StartedAt: started, CompletedAt: &completed,
|
|
ModuleSpecs: []contracts.ModuleRunSpec{{ModuleKey: "grammar", InstanceName: "grammar_1"}, {ModuleKey: "grammar", InstanceName: "grammar_2"}}, RunOutput: output})
|
|
if snapshot.Summary.WorkUnitsTotal == nil || *snapshot.Summary.WorkUnitsTotal != 2 || *snapshot.Summary.WorkUnitsWithErrors != 1 || *snapshot.Summary.Recoveries != 1 || *snapshot.Summary.ProposalResponsesWithFiltering != 1 || *snapshot.Summary.LLMOperations != 2 {
|
|
t.Fatalf("summary: %+v", snapshot.Summary)
|
|
}
|
|
if len(snapshot.Errors) != 1 || len(snapshot.Errors[0].Failures) != 2 || snapshot.Errors[0].Failures[0].Operation != "validation_input" || !reflect.DeepEqual(snapshot.Errors[0].Failures[1].ProposalIndices, []int{3, 9}) {
|
|
t.Fatalf("failure ordering: %+v", snapshot.Errors)
|
|
}
|
|
if len(snapshot.Recoveries) != 1 || !reflect.DeepEqual(snapshot.Recoveries[0].Reasons, []string{"output_repaired", "proposals_filtered", "service_recovered"}) || snapshot.Recoveries[0].FilteredProposals[0].ProposalIndex != 2 {
|
|
t.Fatalf("recovery: %+v", snapshot.Recoveries)
|
|
}
|
|
if snapshot.WorkUnits[0].ModuleInstance != "grammar_1" || snapshot.WorkUnits[1].State != "not_started" {
|
|
t.Fatalf("unit order: %+v", snapshot.WorkUnits)
|
|
}
|
|
errorsYAML, err := reporting.YAML(snapshot.ErrorsDocument())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var decoded map[string]any
|
|
if err := yaml.Unmarshal(errorsYAML, &decoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded["version"] != 1 || decoded["schema"] != "audita-errors" || decoded["status"] != "completed" {
|
|
t.Fatalf("envelope: %#v", decoded)
|
|
}
|
|
if strings.Contains(string(errorsYAML), "provider_attempts: null") || !strings.Contains(string(errorsYAML), "errors:") {
|
|
t.Fatal(string(errorsYAML))
|
|
}
|
|
}
|
|
|
|
func TestBuildReportsUnavailableDecisionsAndFilteredExtras(t *testing.T) {
|
|
started := time.Date(2026, 9, 25, 12, 0, 0, 0, time.UTC)
|
|
completed := started.Add(time.Second)
|
|
failedBatch, cleanBatch := 0, 1
|
|
output := &runner.RunOutput{InventoryAvailable: true, WorkUnits: []runner.WorkUnitOutcome{{
|
|
ModuleKey: "grammar", ModuleInstance: "grammar", Section: contracts.SectionMetadata{Index: 0, StartSegmentID: 1, EndSegmentID: 1},
|
|
State: outcomes.WorkCompleted, CorrectionsApplied: 1, Operations: []outcomes.Operation{
|
|
{Reference: outcomes.Reference{Kind: outcomes.OperationProposal}, Succeeded: true},
|
|
{Reference: outcomes.Reference{Kind: outcomes.OperationValidation, ValidatorName: "editorial_review", BatchIndex: &failedBatch}, Counts: outcomes.ExecutionCounts{Available: true, ProviderAttempts: 2, StructuralRepairs: 1}, FailureReason: outcomes.ReasonValidationDecisionsInvalid, ProposalIndices: []int{3, 1}},
|
|
{Reference: outcomes.Reference{Kind: outcomes.OperationValidation, ValidatorName: "editorial_review", BatchIndex: &cleanBatch}, Counts: outcomes.ExecutionCounts{Available: true, ProviderAttempts: 1}, Succeeded: true, RecoveryReasons: []outcomes.RecoveryReason{outcomes.RecoveryValidationDecisionsFiltered}},
|
|
},
|
|
}}}
|
|
snapshot := Build(BuildInput{RunID: "partial", Status: reporting.StatusCompleted, StartedAt: started, CompletedAt: &completed,
|
|
ModuleSpecs: []contracts.ModuleRunSpec{{ModuleKey: "grammar", InstanceName: "grammar"}}, RunOutput: output})
|
|
if snapshot.Summary.WorkUnitsWithErrors == nil || *snapshot.Summary.WorkUnitsWithErrors != 1 || *snapshot.Summary.CorrectionsApplied != 1 || *snapshot.Summary.Recoveries != 1 || *snapshot.Summary.ProposalResponsesWithFiltering != 0 || *snapshot.Summary.LLMOperations != 3 {
|
|
t.Fatalf("summary: %+v", snapshot.Summary)
|
|
}
|
|
if len(snapshot.Errors) != 1 || len(snapshot.Errors[0].Failures) != 1 || !reflect.DeepEqual(snapshot.Errors[0].Failures[0].ProposalIndices, []int{1, 3}) || snapshot.Errors[0].Failures[0].Reason != string(outcomes.ReasonValidationDecisionsInvalid) || snapshot.Errors[0].Failures[0].ExecutionCounts.StructuralRepairs == nil || *snapshot.Errors[0].Failures[0].ExecutionCounts.StructuralRepairs != 1 {
|
|
t.Fatalf("failure attribution: %+v", snapshot.Errors)
|
|
}
|
|
if len(snapshot.Recoveries) != 1 || !reflect.DeepEqual(snapshot.Recoveries[0].Reasons, []string{string(outcomes.RecoveryValidationDecisionsFiltered)}) || len(snapshot.Recoveries[0].FilteredProposals) != 0 {
|
|
t.Fatalf("recovery attribution: %+v", snapshot.Recoveries)
|
|
}
|
|
for _, document := range []any{snapshot.ErrorsDocument(), snapshot.RecoveriesDocument()} {
|
|
encoded, err := reporting.YAML(document)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var decoded map[string]any
|
|
if err := yaml.Unmarshal(encoded, &decoded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded["version"] != 1 || decoded["status"] != "completed" {
|
|
t.Fatalf("YAML envelope: %#v", decoded)
|
|
}
|
|
summary := decoded["summary"].(map[string]any)
|
|
if summary["work_units_with_errors"] != 1 || summary["recoveries"] != 1 || summary["proposal_responses_with_filtering"] != 0 {
|
|
t.Fatalf("YAML summary: %#v", summary)
|
|
}
|
|
if errors, ok := decoded["errors"].([]any); ok {
|
|
failure := errors[0].(map[string]any)["failures"].([]any)[0].(map[string]any)
|
|
if !reflect.DeepEqual(failure["proposal_indices"], []any{1, 3}) {
|
|
t.Fatalf("YAML failure indices: %#v", failure)
|
|
}
|
|
}
|
|
if recoveries, ok := decoded["recoveries"].([]any); ok {
|
|
recovery := recoveries[0].(map[string]any)
|
|
filtered, hasFiltered := recovery["filtered_proposals"]
|
|
if !reflect.DeepEqual(recovery["reasons"], []any{string(outcomes.RecoveryValidationDecisionsFiltered)}) || hasFiltered && len(filtered.([]any)) != 0 {
|
|
t.Fatalf("YAML recovery: %#v", recovery)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildInitializationNoWorkAndRunFailure(t *testing.T) {
|
|
start := time.Now().UTC()
|
|
initial := Build(BuildInput{RunID: "r", Status: reporting.StatusInProgress, StartedAt: start})
|
|
initialDoc := initial.ErrorsDocument()
|
|
if initialDoc.CompletedAt != nil || initialDoc.Summary.WorkUnitsTotal != nil || initialDoc.Errors == nil {
|
|
t.Fatalf("initial: %+v", initialDoc)
|
|
}
|
|
finished := Build(BuildInput{RunID: "r", Status: reporting.StatusCompleted, StartedAt: start, RunOutput: &runner.RunOutput{InventoryAvailable: true}})
|
|
if finished.Summary.WorkUnitsTotal == nil || *finished.Summary.WorkUnitsTotal != 0 || finished.RecoveriesDocument().Recoveries == nil {
|
|
t.Fatalf("empty: %+v", finished)
|
|
}
|
|
failed := Build(BuildInput{RunID: "r", Status: reporting.StatusFailed, StartedAt: start,
|
|
RunFailures: []RunFailure{{Phase: "report_write", Reason: outcomes.ReasonInternalFailure, OutputPath: "z.yml"}, {Phase: "processing", Reason: outcomes.ReasonCanceled}, {Phase: "report_write", Reason: outcomes.ReasonInternalFailure, OutputPath: "a.yml"}}})
|
|
if len(failed.Errors) != 3 || failed.Errors[0].Phase != "processing" || failed.Errors[1].OutputPath != "a.yml" || failed.Errors[2].OutputPath != "z.yml" || failed.Summary.WorkUnitsTotal != nil || *failed.Summary.RunErrors != 3 {
|
|
t.Fatalf("run failures: %+v", failed)
|
|
}
|
|
}
|
|
|
|
func intPtr(value int) *int { return &value }
|