package app import ( "context" "os" "path/filepath" "testing" "gitea.maximumdirect.net/eric/weatherreporter/internal/config" ) func TestRunBatchDetailedKeepsSuccessfulOutputAndSkipsNotificationAfterPartialFailure(t *testing.T) { bundle := generationBundle(t) bundle.Hourly.Periods = bundle.Hourly.Periods[:1] notifier := &generationNotifier{} executor := &generationExecutor{failedPrompt: generationDefinitionForPrompt("weather.tomorrow_generated_text").PromptID} result, err := RunBatchDetailed(context.Background(), BatchRequest{ Config: generationDistributorConfig(), Batch: BatchMorning, Now: generationTime("2026-05-29T08:30:00-05:00"), WorkingDir: t.TempDir(), OutputDir: t.TempDir(), Collector: &generationCollector{bundle: &bundle}, Executor: executor, Notifier: notifier, }) if err != nil || result == nil || result.Total != 2 || result.Succeeded != 1 || result.Failed != 1 || result.Notification == nil || result.Notification.Status != "skipped" || notifier.batchCalls != 0 { t.Fatalf("RunBatchDetailed() result/error/notifier = %#v/%v/%#v", result, err, notifier) } if result.Reports[0].Status != "succeeded" || result.Reports[0].OutputPath == "" || result.Reports[1].Status != "failed" || result.Reports[1].OutputPath != "" { t.Fatalf("report results = %#v", result.Reports) } if data, readErr := os.ReadFile(result.Reports[0].OutputPath); readErr != nil || len(data) == 0 { t.Fatalf("successful output = %q, error = %v", data, readErr) } } func TestRunBatchDetailedNotifiesOnlyAfterAllOutputsExist(t *testing.T) { bundle := generationBundle(t) bundle.Hourly.Periods = bundle.Hourly.Periods[:1] outputDir := t.TempDir() notifier := &generationNotifier{} result, err := RunBatchDetailed(context.Background(), BatchRequest{ Config: generationDistributorConfig(), Batch: BatchMorning, Now: generationTime("2026-05-29T08:30:00-05:00"), WorkingDir: t.TempDir(), OutputDir: outputDir, Collector: &generationCollector{bundle: &bundle}, Executor: &generationExecutor{}, Notifier: notifier, }) if err != nil || result == nil || result.Total != 2 || result.Succeeded != 2 || result.Failed != 0 || notifier.batchCalls != 1 || result.Notification == nil || result.Notification.Status != "succeeded" { t.Fatalf("RunBatchDetailed() result/error/notifier = %#v/%v/%#v", result, err, notifier) } if len(notifier.batchRequest.Files) < 2 || len(notifier.batchRequest.IncludedReports) != 2 { t.Fatalf("batch notification = %#v", notifier.batchRequest) } if result.Reports[0].OutputPath == result.Reports[1].OutputPath { t.Fatalf("batch reports share output path %q", result.Reports[0].OutputPath) } for _, file := range notifier.batchRequest.Files { if filepath.Dir(file.SourcePath) != outputDir || file.BundlePath == "" { t.Fatalf("notification file = %#v", file) } if _, statErr := os.Stat(file.SourcePath); statErr != nil { t.Fatalf("notification source %q: %v", file.SourcePath, statErr) } } } func generationDistributorConfig() config.Config { cfg := generationConfig() cfg.Notify.Distributor.Enabled = true cfg.Notify.Distributor.PipelineIDTemplate = "weather" return cfg }