Add data-aware batch planning
This commit is contained in:
@@ -265,7 +265,14 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro
|
||||
if now.IsZero() {
|
||||
now = time.Now()
|
||||
}
|
||||
resolvedReports, err := ResolveBatch(req, now)
|
||||
if _, err := report.BatchForCommandName(string(req.Batch)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collection, err := collectWeather(ctx, req.Config, req.Collector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plannedReports, err := planBatchReports(req, now, *collection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -280,12 +287,14 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro
|
||||
}
|
||||
startedAt := now
|
||||
result := &BatchResult{Batch: req.Batch, StartedAt: startedAt}
|
||||
for _, resolved := range resolvedReports {
|
||||
for _, planned := range plannedReports {
|
||||
resolved := planned.Resolved
|
||||
if !resolved.Definition.Generated {
|
||||
return nil, fmt.Errorf("run is not implemented")
|
||||
}
|
||||
}
|
||||
for _, resolved := range resolvedReports {
|
||||
for _, planned := range plannedReports {
|
||||
resolved := planned.Resolved
|
||||
item := batchReportResult(resolved)
|
||||
if paths, err := store.Paths(resolved); err == nil {
|
||||
item.DataPackagePath = paths.DataPackage
|
||||
|
||||
@@ -2237,7 +2237,7 @@ func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
|
||||
server := dailyBundleServer(t)
|
||||
cfg := dailyWorkspaceConfig(t, server)
|
||||
renderer := &selectiveRenderer{
|
||||
failRenderPrompt: "weather.three_day_outlook",
|
||||
failRenderPrompt: "weather.tomorrow_generated_text",
|
||||
runBody: "# Batch Report\n",
|
||||
}
|
||||
|
||||
@@ -2251,23 +2251,23 @@ func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
|
||||
t.Fatalf("RunBatchDetailed() error = %v", err)
|
||||
}
|
||||
|
||||
if result.Total != 3 || result.Succeeded != 2 || result.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 3/2/1", result.Total, result.Succeeded, result.Failed)
|
||||
if result.Total != 2 || result.Succeeded != 1 || result.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 2/1/1", result.Total, result.Succeeded, result.Failed)
|
||||
}
|
||||
if renderer.runCalls != 1 || renderer.structuredRunCalls != 1 {
|
||||
if renderer.runCalls != 0 || renderer.structuredRunCalls != 1 {
|
||||
t.Fatalf("renderer calls run=%d structured=%d, want successful reports to continue", renderer.runCalls, renderer.structuredRunCalls)
|
||||
}
|
||||
var failedThreeDay bool
|
||||
var failedTomorrow bool
|
||||
for _, item := range result.Reports {
|
||||
if item.ReportID == report.ThreeDay && item.Status == "failed" && strings.Contains(item.Error, "render failed") {
|
||||
failedThreeDay = true
|
||||
if item.ReportID == report.Tomorrow && item.Status == "failed" && strings.Contains(item.Error, "render failed") {
|
||||
failedTomorrow = true
|
||||
}
|
||||
if item.ReportID != report.ThreeDay && item.Status != "succeeded" {
|
||||
if item.ReportID != report.Tomorrow && item.Status != "succeeded" {
|
||||
t.Fatalf("report %s status = %s, want succeeded", item.ReportID, item.Status)
|
||||
}
|
||||
}
|
||||
if !failedThreeDay {
|
||||
t.Fatalf("reports = %#v, want failed 3-day item", result.Reports)
|
||||
if !failedTomorrow {
|
||||
t.Fatalf("reports = %#v, want failed Tomorrow item", result.Reports)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2276,7 +2276,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
|
||||
cfg := dailyNotificationConfig(t, server)
|
||||
notifier := &recordingNotifier{
|
||||
errByReport: map[report.ID]error{
|
||||
report.ThreeDay: errors.New("distributor unavailable"),
|
||||
report.Tomorrow: errors.New("distributor unavailable"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2291,26 +2291,26 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
|
||||
t.Fatalf("RunBatchDetailed() error = %v", err)
|
||||
}
|
||||
|
||||
if result.Total != 3 || result.Succeeded != 2 || result.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 3/2/1", result.Total, result.Succeeded, result.Failed)
|
||||
if result.Total != 2 || result.Succeeded != 1 || result.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 2/1/1", result.Total, result.Succeeded, result.Failed)
|
||||
}
|
||||
if len(notifier.requests) != 3 {
|
||||
if len(notifier.requests) != 2 {
|
||||
t.Fatalf("notification requests = %d, want one per generated report", len(notifier.requests))
|
||||
}
|
||||
var failedThreeDay bool
|
||||
var failedTomorrow bool
|
||||
for _, item := range result.Reports {
|
||||
if item.ReportID == report.ThreeDay {
|
||||
if item.ReportID == report.Tomorrow {
|
||||
if item.Status == "failed" && strings.Contains(item.Error, "notify report") && strings.Contains(item.Error, "distributor unavailable") {
|
||||
failedThreeDay = true
|
||||
failedTomorrow = true
|
||||
}
|
||||
if item.NotificationStatus != "failed" {
|
||||
t.Fatalf("3-day notification status = %q, want failed", item.NotificationStatus)
|
||||
t.Fatalf("Tomorrow notification status = %q, want failed", item.NotificationStatus)
|
||||
}
|
||||
if item.NotificationPipelineID != "weatherreporter.three-day" {
|
||||
t.Fatalf("3-day notification pipeline = %q, want weatherreporter.three-day", item.NotificationPipelineID)
|
||||
if item.NotificationPipelineID != "weatherreporter.tomorrow" {
|
||||
t.Fatalf("Tomorrow notification pipeline = %q, want weatherreporter.tomorrow", item.NotificationPipelineID)
|
||||
}
|
||||
if !strings.Contains(item.NotificationError, "distributor unavailable") {
|
||||
t.Fatalf("3-day notification error = %q, want distributor unavailable", item.NotificationError)
|
||||
t.Fatalf("Tomorrow notification error = %q, want distributor unavailable", item.NotificationError)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -2324,8 +2324,8 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
|
||||
t.Fatalf("report %s notification pipeline is empty", item.ReportID)
|
||||
}
|
||||
}
|
||||
if !failedThreeDay {
|
||||
t.Fatalf("reports = %#v, want notification failure on 3-day item", result.Reports)
|
||||
if !failedTomorrow {
|
||||
t.Fatalf("reports = %#v, want notification failure on Tomorrow item", result.Reports)
|
||||
}
|
||||
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
@@ -2338,7 +2338,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
|
||||
},
|
||||
Notifier: &recordingNotifier{
|
||||
errByReport: map[report.ID]error{
|
||||
report.ThreeDay: errors.New("distributor unavailable"),
|
||||
report.Tomorrow: errors.New("distributor unavailable"),
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -2346,7 +2346,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
|
||||
if !errors.As(err, &batchErr) {
|
||||
t.Fatalf("RunBatch() error = %T %v, want BatchError", err, err)
|
||||
}
|
||||
if batchErr.Result == nil || batchErr.Result.Failed != 1 || batchErr.Result.Succeeded != 2 {
|
||||
if batchErr.Result == nil || batchErr.Result.Failed != 1 || batchErr.Result.Succeeded != 1 {
|
||||
t.Fatalf("RunBatch() result = %#v, want notification failure aggregate", batchErr.Result)
|
||||
}
|
||||
}
|
||||
@@ -2404,7 +2404,7 @@ func TestRunBatchMorningUsesTodayOutputName(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("RunBatchDetailed() error = %v", err)
|
||||
}
|
||||
if result.Failed != 0 || len(result.Reports) != 3 {
|
||||
if result.Failed != 0 || len(result.Reports) != 2 {
|
||||
t.Fatalf("summary = %#v, want successful morning batch", result)
|
||||
}
|
||||
var todayItem *BatchReportResult
|
||||
@@ -2448,26 +2448,18 @@ func TestRunBatchDetailedUsesProvidedCollector(t *testing.T) {
|
||||
collector := &recordingCollector{err: errors.New("batch collector failed")}
|
||||
renderer := &recordingRenderer{}
|
||||
|
||||
result, err := RunBatchDetailed(context.Background(), BatchRequest{
|
||||
_, err := RunBatchDetailed(context.Background(), BatchRequest{
|
||||
Config: cfg,
|
||||
Batch: BatchMorning,
|
||||
Now: mustParse("2026-05-29T05:00:00-05:00"),
|
||||
Collector: collector,
|
||||
Renderer: renderer,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("RunBatchDetailed() error = %v", err)
|
||||
if err == nil || !strings.Contains(err.Error(), "batch collector failed") {
|
||||
t.Fatalf("RunBatchDetailed() error = %v, want fake collector error", err)
|
||||
}
|
||||
if result.Total == 0 || result.Failed != result.Total {
|
||||
t.Fatalf("batch result = %#v, want every report failed by collector", result)
|
||||
}
|
||||
if len(collector.requests) != result.Total {
|
||||
t.Fatalf("collector requests = %d, want %d", len(collector.requests), result.Total)
|
||||
}
|
||||
for _, reportResult := range result.Reports {
|
||||
if !strings.Contains(reportResult.Error, "batch collector failed") {
|
||||
t.Fatalf("report error = %q, want fake collector error", reportResult.Error)
|
||||
}
|
||||
if len(collector.requests) != 1 {
|
||||
t.Fatalf("collector requests = %d, want one planning collection", len(collector.requests))
|
||||
}
|
||||
if renderer.renderCalls != 0 || renderer.runCalls != 0 || renderer.structuredRunCalls != 0 {
|
||||
t.Fatalf("renderer calls render=%d run=%d structured=%d, want none after collection failure", renderer.renderCalls, renderer.runCalls, renderer.structuredRunCalls)
|
||||
|
||||
@@ -1,12 +1,85 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
type plannedBatchReport struct {
|
||||
Resolved report.Resolved
|
||||
OutputCopyName string
|
||||
}
|
||||
|
||||
func planBatchReports(req BatchRequest, now time.Time, collection collect.Result) ([]plannedBatchReport, error) {
|
||||
location, err := timeutil.LoadLocation(req.Config.WeatherAPI.Timezone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
batch, err := report.BatchForCommandName(string(req.Batch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
registry, err := reportRegistry(req.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolveReq := report.ResolveRequest{
|
||||
Now: now,
|
||||
Location: location,
|
||||
}
|
||||
var planned []plannedBatchReport
|
||||
switch batch {
|
||||
case report.Morning:
|
||||
planned, err = appendPlannedReport(planned, registry, report.Today, resolveReq, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
planned, err = appendPlannedReport(planned, registry, report.Tomorrow, resolveReq, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case report.Evening:
|
||||
planned, err = appendPlannedReport(planned, registry, report.Tomorrow, resolveReq, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown batch %q", batch)
|
||||
}
|
||||
|
||||
var hourly *weatherdata.ForecastRun
|
||||
if collection.Bundle != nil {
|
||||
hourly = collection.Bundle.Hourly
|
||||
}
|
||||
for _, date := range eligibleDailyDates(hourly, now, location) {
|
||||
dailyReq := resolveReq
|
||||
dailyReq.Date = date
|
||||
outputCopyName := "daily-" + date.In(location).Format(timeutil.DateLayout) + ".md"
|
||||
planned, err = appendPlannedReport(planned, registry, report.Daily, dailyReq, outputCopyName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return planned, nil
|
||||
}
|
||||
|
||||
func appendPlannedReport(planned []plannedBatchReport, registry report.Registry, id report.ID, req report.ResolveRequest, outputCopyName string) ([]plannedBatchReport, error) {
|
||||
resolved, err := registry.Resolve(id, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(planned, plannedBatchReport{
|
||||
Resolved: resolved,
|
||||
OutputCopyName: outputCopyName,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func eligibleDailyDates(hourly *weatherdata.ForecastRun, now time.Time, location *time.Location) []time.Time {
|
||||
if hourly == nil || location == nil || hourly.Product != "hourly" || len(hourly.Periods) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -5,10 +5,97 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
func TestPlanBatchReportsMorningOrder(t *testing.T) {
|
||||
location := mustLoadTestLocation(t, "America/Chicago")
|
||||
hourly := hourlyRun(fullDayPeriods(t, "2026-05-31", location)...)
|
||||
|
||||
planned, err := planBatchReports(BatchRequest{Config: planningConfig(), Batch: BatchMorning}, mustParse("2026-05-29T08:00:00-05:00"), collectionWithHourly(hourly))
|
||||
if err != nil {
|
||||
t.Fatalf("planBatchReports() error = %v", err)
|
||||
}
|
||||
|
||||
assertPlannedReportIDs(t, planned, report.Today, report.Tomorrow, report.Daily)
|
||||
}
|
||||
|
||||
func TestPlanBatchReportsEveningOrder(t *testing.T) {
|
||||
location := mustLoadTestLocation(t, "America/Chicago")
|
||||
hourly := hourlyRun(fullDayPeriods(t, "2026-05-31", location)...)
|
||||
|
||||
planned, err := planBatchReports(BatchRequest{Config: planningConfig(), Batch: BatchEvening}, mustParse("2026-05-29T18:00:00-05:00"), collectionWithHourly(hourly))
|
||||
if err != nil {
|
||||
t.Fatalf("planBatchReports() error = %v", err)
|
||||
}
|
||||
|
||||
assertPlannedReportIDs(t, planned, report.Tomorrow, report.Daily)
|
||||
}
|
||||
|
||||
func TestPlanBatchReportsDynamicDailyDatesStartAfterTomorrow(t *testing.T) {
|
||||
location := mustLoadTestLocation(t, "America/Chicago")
|
||||
periods := fullDayPeriods(t, "2026-05-30", location)
|
||||
periods = append(periods, fullDayPeriods(t, "2026-05-31", location)...)
|
||||
periods = append(periods, fullDayPeriods(t, "2026-06-01", location)...)
|
||||
|
||||
planned, err := planBatchReports(BatchRequest{Config: planningConfig(), Batch: BatchMorning}, mustParse("2026-05-29T08:00:00-05:00"), collectionWithHourly(hourlyRun(periods...)))
|
||||
if err != nil {
|
||||
t.Fatalf("planBatchReports() error = %v", err)
|
||||
}
|
||||
|
||||
daily := plannedDailyReports(planned)
|
||||
if len(daily) != 2 {
|
||||
t.Fatalf("daily reports = %#v, want two future Daily reports", daily)
|
||||
}
|
||||
assertPlanningPeriod(t, daily[0].Resolved.ValidPeriod, "2026-05-31T00:00:00-05:00", "2026-06-01T00:00:00-05:00")
|
||||
assertPlanningPeriod(t, daily[1].Resolved.ValidPeriod, "2026-06-01T00:00:00-05:00", "2026-06-02T00:00:00-05:00")
|
||||
}
|
||||
|
||||
func TestPlanBatchReportsMorningExcludesLegacyStaticReports(t *testing.T) {
|
||||
planned, err := planBatchReports(BatchRequest{Config: planningConfig(), Batch: BatchMorning}, mustParse("2026-05-29T08:00:00-05:00"), collect.Result{Bundle: &weatherdata.Bundle{}})
|
||||
if err != nil {
|
||||
t.Fatalf("planBatchReports() error = %v", err)
|
||||
}
|
||||
|
||||
for _, item := range planned {
|
||||
if item.Resolved.Definition.ID == report.ThreeDay || item.Resolved.Definition.ID == report.Weekend {
|
||||
t.Fatalf("morning plan includes %s, want no 3-Day or Weekend", item.Resolved.Definition.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanBatchReportsDynamicDailyOutputCopyNames(t *testing.T) {
|
||||
location := mustLoadTestLocation(t, "America/Chicago")
|
||||
hourly := hourlyRun(fullDayPeriods(t, "2026-05-31", location)...)
|
||||
|
||||
planned, err := planBatchReports(BatchRequest{Config: planningConfig(), Batch: BatchEvening}, mustParse("2026-05-29T18:00:00-05:00"), collectionWithHourly(hourly))
|
||||
if err != nil {
|
||||
t.Fatalf("planBatchReports() error = %v", err)
|
||||
}
|
||||
|
||||
daily := plannedDailyReports(planned)
|
||||
if len(daily) != 1 {
|
||||
t.Fatalf("daily reports = %#v, want one Daily report", daily)
|
||||
}
|
||||
if daily[0].OutputCopyName != "daily-2026-05-31.md" {
|
||||
t.Fatalf("OutputCopyName = %q, want date-qualified Daily name", daily[0].OutputCopyName)
|
||||
}
|
||||
if planned[0].OutputCopyName != "" {
|
||||
t.Fatalf("Tomorrow OutputCopyName = %q, want definition batch output name to apply later", planned[0].OutputCopyName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlanBatchReportsRejectsUnknownBatch(t *testing.T) {
|
||||
_, err := planBatchReports(BatchRequest{Config: planningConfig(), Batch: BatchKind("hourly")}, mustParse("2026-05-29T08:00:00-05:00"), collect.Result{Bundle: &weatherdata.Bundle{}})
|
||||
if err == nil || !strings.Contains(err.Error(), `unknown batch command "hourly"`) {
|
||||
t.Fatalf("planBatchReports() error = %v, want unknown batch command", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEligibleDailyDatesRequiresFullOrdinaryLocalDay(t *testing.T) {
|
||||
location := mustLoadTestLocation(t, "America/Chicago")
|
||||
hourly := hourlyRun(fullDayPeriods(t, "2026-05-31", location)...)
|
||||
@@ -142,6 +229,41 @@ func forecastRun(product string, periods ...weatherdata.ForecastPeriod) *weather
|
||||
}
|
||||
}
|
||||
|
||||
func collectionWithHourly(hourly *weatherdata.ForecastRun) collect.Result {
|
||||
return collect.Result{Bundle: &weatherdata.Bundle{Hourly: hourly}}
|
||||
}
|
||||
|
||||
func planningConfig() config.Config {
|
||||
cfg := config.Defaults()
|
||||
cfg.WeatherAPI.Timezone = "America/Chicago"
|
||||
return cfg
|
||||
}
|
||||
|
||||
func assertPlannedReportIDs(t *testing.T, got []plannedBatchReport, want ...report.ID) {
|
||||
t.Helper()
|
||||
gotIDs := make([]string, 0, len(got))
|
||||
for _, item := range got {
|
||||
gotIDs = append(gotIDs, string(item.Resolved.Definition.ID))
|
||||
}
|
||||
wantIDs := make([]string, 0, len(want))
|
||||
for _, id := range want {
|
||||
wantIDs = append(wantIDs, string(id))
|
||||
}
|
||||
if strings.Join(gotIDs, ",") != strings.Join(wantIDs, ",") {
|
||||
t.Fatalf("planned report IDs = [%s], want [%s]", strings.Join(gotIDs, ","), strings.Join(wantIDs, ","))
|
||||
}
|
||||
}
|
||||
|
||||
func plannedDailyReports(planned []plannedBatchReport) []plannedBatchReport {
|
||||
var daily []plannedBatchReport
|
||||
for _, item := range planned {
|
||||
if item.Resolved.Definition.ID == report.Daily {
|
||||
daily = append(daily, item)
|
||||
}
|
||||
}
|
||||
return daily
|
||||
}
|
||||
|
||||
func fullDayPeriods(t *testing.T, date string, location *time.Location) []weatherdata.ForecastPeriod {
|
||||
t.Helper()
|
||||
day := timeutil.CivilDay(mustParseLocalDate(t, date, location), location)
|
||||
@@ -198,6 +320,19 @@ func assertLocalDates(t *testing.T, got []time.Time, location *time.Location, wa
|
||||
}
|
||||
}
|
||||
|
||||
func assertPlanningPeriod(t *testing.T, period timeutil.Period, wantStart string, wantEnd string) {
|
||||
t.Helper()
|
||||
if !period.IsValid() {
|
||||
t.Fatalf("period = %#v, want valid", period)
|
||||
}
|
||||
if got := period.Start.Format(time.RFC3339); got != wantStart {
|
||||
t.Fatalf("Start = %s, want %s", got, wantStart)
|
||||
}
|
||||
if got := period.End.Format(time.RFC3339); got != wantEnd {
|
||||
t.Fatalf("End = %s, want %s", got, wantEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func mustLoadTestLocation(t *testing.T, name string) *time.Location {
|
||||
t.Helper()
|
||||
location, err := time.LoadLocation(name)
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestRunGenerateWeekendWritesMarkdownReport(t *testing.T) {
|
||||
assertFileContains(t, dataPackagePath, "derived_daypart_summaries:")
|
||||
}
|
||||
|
||||
func TestRunMorningIncludesWeekendExceptSunday(t *testing.T) {
|
||||
func TestRunMorningGeneratesTodayAndTomorrow(t *testing.T) {
|
||||
fixture := newCLIFixture(t, writeFakeScriptorium)
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
@@ -176,7 +176,9 @@ func TestRunMorningIncludesWeekendExceptSunday(t *testing.T) {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "today", "2026-05-29", "*.data_package.yaml")
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "weekend", "2026-05-29", "*.data_package.yaml")
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "tomorrow", "2026-05-30", "*.data_package.yaml")
|
||||
noArtifacts(t, fixture.workspaceRoot, "data-packages", "three-day", "2026-05-29", "*.data_package.yaml")
|
||||
noArtifacts(t, fixture.workspaceRoot, "data-packages", "weekend", "2026-05-29", "*.data_package.yaml")
|
||||
noArtifacts(t, fixture.workspaceRoot, "data-packages", "daily", "2026-05-29", "*.data_package.yaml")
|
||||
}
|
||||
|
||||
@@ -191,7 +193,7 @@ func TestRunMorningReportsPartialFailureAndContinues(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Fatal("Run() error = nil, want aggregate failure")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "1 of 3 reports failed") {
|
||||
if !strings.Contains(err.Error(), "1 of 2 reports failed") {
|
||||
t.Fatalf("Run() error = %q, want aggregate failure", err.Error())
|
||||
}
|
||||
|
||||
@@ -199,14 +201,14 @@ func TestRunMorningReportsPartialFailureAndContinues(t *testing.T) {
|
||||
if decodeErr := json.Unmarshal([]byte(output.stdout), &summary); decodeErr != nil {
|
||||
t.Fatalf("decode summary: %v\n%s", decodeErr, output.stdout)
|
||||
}
|
||||
if summary.Total != 3 || summary.Succeeded != 2 || summary.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 3/2/1", summary.Total, summary.Succeeded, summary.Failed)
|
||||
if summary.Total != 2 || summary.Succeeded != 1 || summary.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 2/1/1", summary.Total, summary.Succeeded, summary.Failed)
|
||||
}
|
||||
if !strings.Contains(output.stderr, "status=failed") || !strings.Contains(output.stderr, "status=succeeded") {
|
||||
t.Fatalf("stderr missing structured report logs:\n%s", output.stderr)
|
||||
}
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "today", "2026-05-29", "*.data_package.yaml")
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "weekend", "2026-05-29", "*.data_package.yaml")
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "tomorrow", "2026-05-30", "*.data_package.yaml")
|
||||
}
|
||||
|
||||
func TestBatchOutputIncludesNotificationDetails(t *testing.T) {
|
||||
@@ -413,7 +415,7 @@ func TestRunEveningReportsNotificationFailureWithoutToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunMorningGeneratesTodayAndThreeDayOnSunday(t *testing.T) {
|
||||
func TestRunMorningGeneratesTodayAndTomorrowOnSunday(t *testing.T) {
|
||||
fixture := newCLIFixture(t, writeFakeScriptorium)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
@@ -427,7 +429,9 @@ func TestRunMorningGeneratesTodayAndThreeDayOnSunday(t *testing.T) {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "today", "2026-05-31", "*.data_package.yaml")
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "three-day", "2026-05-31", "*.data_package.yaml")
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "data-packages", "tomorrow", "2026-06-01", "*.data_package.yaml")
|
||||
noArtifacts(t, fixture.workspaceRoot, "data-packages", "three-day", "2026-05-31", "*.data_package.yaml")
|
||||
noArtifacts(t, fixture.workspaceRoot, "data-packages", "weekend", "2026-05-31", "*.data_package.yaml")
|
||||
noArtifacts(t, fixture.workspaceRoot, "data-packages", "daily", "2026-05-31", "*.data_package.yaml")
|
||||
}
|
||||
|
||||
@@ -1377,7 +1381,7 @@ if [ "$1" = "render" ]; then
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if [ "$prompt" = "weather.three_day_outlook" ]; then
|
||||
if [ "$prompt" = "weather.tomorrow_generated_text" ]; then
|
||||
printf 'render failed\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user