package app import ( "context" "errors" "os" "path/filepath" "testing" "gitea.maximumdirect.net/eric/weatherreporter/internal/collect" "gitea.maximumdirect.net/eric/weatherreporter/internal/config" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" ) func TestResolveReportOutputPath(t *testing.T) { workingDir := t.TempDir() cfg := config.Defaults() now := workflowTime("2026-05-29T08:30:00-05:00") for _, test := range []struct { report ReportKind date string name string }{ {report: ReportDaily, date: "2026-05-30T12:00:00-05:00", name: "daily-2026-05-30.md"}, {report: ReportToday, date: "2026-05-29T12:00:00-05:00", name: "today.md"}, {report: ReportTomorrow, name: "tomorrow.md"}, {report: ReportHourly, name: "hourly.md"}, } { t.Run(string(test.report), func(t *testing.T) { req := GenerateRequest{Config: cfg, Report: test.report} if test.date != "" { req.Date = workflowTime(test.date) } resolved, err := ResolveGenerate(req, now) if err != nil { t.Fatalf("ResolveGenerate() error = %v", err) } path, err := resolveReportOutputPath(workingDir, "", resolved) if err != nil { t.Fatalf("resolveReportOutputPath() error = %v", err) } if path != filepath.Join(workingDir, test.name) { t.Fatalf("path = %q, want %q", path, filepath.Join(workingDir, test.name)) } }) } resolved, err := ResolveGenerate(GenerateRequest{ Config: cfg, Report: ReportDaily, Date: workflowTime("2026-05-30T12:00:00-05:00"), }, now) if err != nil { t.Fatalf("ResolveGenerate() error = %v", err) } absoluteDir := t.TempDir() for _, test := range []struct { override string want string }{ {override: filepath.Join("reports", "custom.md"), want: filepath.Join(workingDir, "reports", "custom.md")}, {override: filepath.Join(absoluteDir, "custom.md"), want: filepath.Join(absoluteDir, "custom.md")}, } { path, err := resolveReportOutputPath(workingDir, test.override, resolved) if err != nil { t.Fatalf("resolveReportOutputPath(%q) error = %v", test.override, err) } if path != filepath.Clean(test.want) { t.Fatalf("path = %q, want %q", path, filepath.Clean(test.want)) } } } func TestGenerateDetailedRejectsInvalidOutputBeforeCollection(t *testing.T) { cfg := workflowConfig(t) collector := &workflowCollector{err: errors.New("collection must not run")} _, err := GenerateDetailed(context.Background(), GenerateRequest{ Config: cfg, Report: ReportDaily, Date: workflowTime("2026-05-29T12:00:00-05:00"), Now: workflowTime("2026-05-29T08:30:00-05:00"), WorkingDir: t.TempDir(), OutputPath: t.TempDir(), Collector: collector, }) if err == nil || collector.calls != 0 { t.Fatalf("GenerateDetailed() error/calls = %v/%d, want invalid output before collection", err, collector.calls) } } func TestGenerateDetailedPreservesExistingOutputWhenGenerationFails(t *testing.T) { cfg := workflowConfig(t) cfg.Notify.Distributor.Enabled = false workingDir := t.TempDir() outputPath := filepath.Join(workingDir, "daily-2026-05-29.md") if err := os.WriteFile(outputPath, []byte("existing report"), 0o600); err != nil { t.Fatalf("write existing output: %v", err) } bundle := workflowBundle(t) result, err := GenerateDetailed(context.Background(), GenerateRequest{ Config: cfg, Report: ReportDaily, Date: workflowTime("2026-05-29T12:00:00-05:00"), Now: workflowTime("2026-05-29T08:30:00-05:00"), WorkingDir: workingDir, Collector: &workflowCollector{result: &collect.Result{Bundle: &bundle}}, Executor: &workflowExecutor{definition: report.DefaultRegistry().MustLookup(report.Daily), raw: []byte(`{}`)}, }) if err == nil || result == nil || result.OutputPath != "" { t.Fatalf("result/error/output = %#v/%v/%q, want failed generation without output publication", result, err, result.OutputPath) } data, readErr := os.ReadFile(outputPath) if readErr != nil || string(data) != "existing report" { t.Fatalf("output after failure = %q, error %v, want preserved content", data, readErr) } } func TestRunBatchDetailedUsesWorkingDirectoryForOutput(t *testing.T) { cfg := assembledBatchConfig(t, false) workingDir := t.TempDir() bundle := assembledBatchBundle(t, "2026-05-31") result, err := RunBatchDetailed(context.Background(), BatchRequest{ Config: cfg, Batch: BatchEvening, Now: workflowTime("2026-05-29T18:00:00-05:00"), WorkingDir: workingDir, Collector: &workflowCollector{result: &collect.Result{Bundle: &bundle}}, Executor: newAssembledBatchExecutor(), }) if err != nil || result == nil || result.Failed != 0 { t.Fatalf("RunBatchDetailed() result/error = %#v/%v", result, err) } if len(result.Reports) != 2 { t.Fatalf("reports = %#v, want Tomorrow and Daily", result.Reports) } if result.Reports[0].OutputPath != filepath.Join(workingDir, "tomorrow.md") || result.Reports[1].OutputPath != filepath.Join(workingDir, "daily-2026-05-31.md") { t.Fatalf("output paths = %#v, want working-directory defaults", result.Reports) } }