package cli import ( "context" "errors" "os" "strconv" "strings" "sync" "time" "gitea.maximumdirect.net/eric/audita/internal/framework/contracts" "gitea.maximumdirect.net/eric/audita/internal/framework/proposal_generation" "gitea.maximumdirect.net/eric/audita/internal/framework/validators" ) const ( subprocessTestLLMModeEnv = "AUDITA_SUBPROCESS_TEST_LLM_MODE" subprocessTestRunTimeoutMSEnv = "AUDITA_SUBPROCESS_TEST_RUN_TIMEOUT_MS" ) // ConfigureSubprocessTestHooksFromEnv enables deterministic test-only hooks for // subprocess integration tests that run through the Go test binary helper path. func ConfigureSubprocessTestHooksFromEnv() { if !shouldUseNoOpLLMClientForTests() { return } mode := strings.TrimSpace(os.Getenv(subprocessTestLLMModeEnv)) if mode != "" { client := &subprocessTestLLMClient{mode: mode} processProposalLLMClient = client processValidationLLMClient = client } timeoutMSRaw := strings.TrimSpace(os.Getenv(subprocessTestRunTimeoutMSEnv)) if timeoutMSRaw == "" { return } timeoutMS, err := strconv.Atoi(timeoutMSRaw) if err != nil || timeoutMS <= 0 { return } processRunnerContext = func() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), time.Duration(timeoutMS)*time.Millisecond) } } type subprocessTestLLMClient struct { mode string mu sync.Mutex proposals int } func (c *subprocessTestLLMClient) CompleteStructured(ctx context.Context, req contracts.StructuredCompletionRequest, out any) (contracts.StructuredCompletionResponse, error) { _ = req switch c.mode { case "backend_error": return contracts.StructuredCompletionResponse{}, errors.New("synthetic backend failure") case "block_until_cancel": <-ctx.Done() return contracts.StructuredCompletionResponse{}, ctx.Err() case "malformed_structured": switch target := out.(type) { case *proposal_generation.StructuredCorrectionSet: *target = proposal_generation.StructuredCorrectionSet{ Corrections: []proposal_generation.StructuredCorrectionProposal{ {TargetSegmentID: 0, OriginalText: "x", CorrectedText: "y", Confidence: 0.99}, }, } case *validators.LLMValidationResponse: *target = validators.LLMValidationResponse{ Validations: []validators.LLMValidationDecision{ {CorrectionIndex: 999, Approved: true, Confidence: 0.9, Reason: "bad index"}, }, } } case "mid_pipeline_fail": switch target := out.(type) { case *proposal_generation.StructuredCorrectionSet: c.mu.Lock() c.proposals++ proposalCall := c.proposals c.mu.Unlock() if proposalCall >= 3 { *target = proposal_generation.StructuredCorrectionSet{ Corrections: []proposal_generation.StructuredCorrectionProposal{ {TargetSegmentID: 0, OriginalText: "x", CorrectedText: "y", Confidence: 0.99}, }, } } else { *target = proposal_generation.StructuredCorrectionSet{ Corrections: []proposal_generation.StructuredCorrectionProposal{ {TargetSegmentID: 1, OriginalText: "Segment", CorrectedText: "Segment", Confidence: 0.99}, }, } } case *validators.LLMValidationResponse: *target = validators.LLMValidationResponse{Validations: nil} } default: switch target := out.(type) { case *proposal_generation.StructuredCorrectionSet: *target = proposal_generation.StructuredCorrectionSet{Corrections: nil} case *validators.LLMValidationResponse: *target = validators.LLMValidationResponse{Validations: nil} } } return contracts.StructuredCompletionResponse{}, nil }