Add Today generate command workflow
This commit is contained in:
10
docs/cli.md
10
docs/cli.md
@@ -20,6 +20,7 @@ the managed Markdown report after final metadata is saved.
|
||||
```text
|
||||
weatherreporter --help
|
||||
weatherreporter generate daily [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate three-day [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
@@ -38,15 +39,15 @@ weatherreporter inspect sources [--config PATH] RUN_ID
|
||||
Implemented `generate` commands write a JSON module snapshot, YAML data package,
|
||||
preflight artifact, managed Markdown report, and metadata under the configured
|
||||
workspace. `--out` writes an extra Markdown copy for the operator; distributor
|
||||
notification uses the managed report path, not the extra copy. `generate
|
||||
tomorrow` and `generate hourly` write managed generated-text artifacts,
|
||||
notification uses the managed report path, not the extra copy. `generate today`,
|
||||
`generate tomorrow`, and `generate hourly` write managed generated-text artifacts,
|
||||
validate structured text from Scriptorium, and render the managed Markdown
|
||||
report from embedded templates. `generate hourly` covers the next six hours in
|
||||
the effective report timezone and does not accept date or event window flags.
|
||||
`generate storm` requires explicit event-window bounds with `--start` and
|
||||
`--end`.
|
||||
|
||||
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
|
||||
`run morning` generates Today Report and the 3-Day Outlook, plus Weekend Outlook
|
||||
except on Sunday. `run evening` generates the Tomorrow Report. Batch
|
||||
runs continue independent reports after a failure, print a JSON summary to
|
||||
stdout, write compact status lines to stderr, and return nonzero when any report
|
||||
@@ -70,7 +71,7 @@ They do not fetch weather data or invoke `scriptorium`.
|
||||
- `--tz NAME`: override configured Weather API timezone for `generate` and `run`.
|
||||
- `--out PATH`: write an extra Markdown report copy where supported by the `generate` command.
|
||||
- `--out-dir PATH`: write extra Markdown report copies for `run morning` and `run evening`.
|
||||
- `--date YYYY-MM-DD`: optional date for `generate daily`; defaults to the current local date in the configured timezone.
|
||||
- `--date YYYY-MM-DD`: optional date for `generate daily` and `generate today`; defaults to the current local date in the configured timezone.
|
||||
- `--start TIME`: required start time for `generate storm`.
|
||||
- `--end TIME`: required end time for `generate storm`.
|
||||
- `--limit N`: maximum records for `inspect reports`; defaults to `20`, and `0` means no limit.
|
||||
@@ -85,6 +86,7 @@ are no distributor-specific CLI flags.
|
||||
|
||||
```sh
|
||||
weatherreporter generate tomorrow --out ./tomorrow.md
|
||||
weatherreporter generate today --out ./today.md
|
||||
weatherreporter generate hourly
|
||||
weatherreporter generate three-day --out ./three-day.md
|
||||
weatherreporter generate weekend --out ./weekend.md
|
||||
|
||||
@@ -30,6 +30,7 @@ type ReportKind string
|
||||
|
||||
const (
|
||||
ReportDaily ReportKind = ReportKind(report.CommandNameDaily)
|
||||
ReportToday ReportKind = ReportKind(report.CommandNameToday)
|
||||
ReportTomorrow ReportKind = ReportKind(report.CommandNameTomorrow)
|
||||
ReportHourly ReportKind = ReportKind(report.CommandNameHourly)
|
||||
ReportThreeDay ReportKind = ReportKind(report.CommandNameThreeDay)
|
||||
|
||||
@@ -597,6 +597,108 @@ func TestGenerateHourlyReportCopiesOutputAndNotifiesManagedReport(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTodayReportCopiesOutputAndNotifiesTodayTemplateValues(t *testing.T) {
|
||||
server := dailyBundleServer(t)
|
||||
cfg := config.Defaults()
|
||||
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
||||
cfg.WeatherAPI.Timezone = "America/Chicago"
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
cfg.Notify.Distributor.Enabled = true
|
||||
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{report_id}.{artifact_group}"
|
||||
cfg.Notify.Distributor.BundleIDTemplate = "{artifact_group}.{batch_output_name}.{report_id}"
|
||||
cfg.Notify.Distributor.IdempotencyKeyTemplate = "{bundle_id}.{run_id}"
|
||||
cfg.Notify.Distributor.ReportPathTemplates = []string{"{valid_start_date}/{artifact_group}/{batch_output_name}"}
|
||||
resolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
Report: ReportToday,
|
||||
Date: mustParse("2026-05-29T12:00:00-05:00"),
|
||||
}, mustParse("2026-05-29T05:00:00-05:00"))
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||
}
|
||||
outputPath := filepath.Join(t.TempDir(), "today-copy.md")
|
||||
notifier := &recordingNotifier{
|
||||
result: &NotificationResult{
|
||||
RunID: "distributor-run",
|
||||
Status: "succeeded",
|
||||
UploadStatus: "accepted",
|
||||
PipelineID: "reports",
|
||||
Report: []byte(`{"actions":[{"action":"replace_older"}]}`),
|
||||
},
|
||||
}
|
||||
renderer := &recordingRenderer{
|
||||
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
|
||||
structuredRunBody: validTodayGeneratedTextJSON(),
|
||||
}
|
||||
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
Config: cfg,
|
||||
Resolved: resolved,
|
||||
OutputPath: outputPath,
|
||||
Renderer: renderer,
|
||||
Notifier: notifier,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateReport() error = %v", err)
|
||||
}
|
||||
if result.OutputPath != outputPath {
|
||||
t.Fatalf("OutputPath = %q, want requested copy %q", result.OutputPath, outputPath)
|
||||
}
|
||||
assertPathsExist(t, result.ReportPath, outputPath, result.NotificationPath)
|
||||
reportData, err := os.ReadFile(result.ReportPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read managed report: %v", err)
|
||||
}
|
||||
copyData, err := os.ReadFile(outputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read output copy: %v", err)
|
||||
}
|
||||
if string(copyData) != string(reportData) {
|
||||
t.Fatalf("output copy differs from managed report")
|
||||
}
|
||||
if len(notifier.requests) != 1 {
|
||||
t.Fatalf("notification requests = %d, want 1", len(notifier.requests))
|
||||
}
|
||||
req := notifier.requests[0]
|
||||
if req.ReportID != report.Today {
|
||||
t.Fatalf("notification ReportID = %q, want today", req.ReportID)
|
||||
}
|
||||
if req.ReportPath != result.ReportPath {
|
||||
t.Fatalf("notification ReportPath = %q, want managed report path %q", req.ReportPath, result.ReportPath)
|
||||
}
|
||||
if req.ReportPath == outputPath {
|
||||
t.Fatalf("notification used output copy %q, want managed report path", outputPath)
|
||||
}
|
||||
if req.PipelineID != "weatherreporter.today.today" {
|
||||
t.Fatalf("PipelineID = %q, want Today report and artifact values", req.PipelineID)
|
||||
}
|
||||
if req.BundleID != "today.today.md.today" {
|
||||
t.Fatalf("BundleID = %q, want Today artifact group, output name, and report id", req.BundleID)
|
||||
}
|
||||
wantBundlePaths := []string{"2026-05-29/today/today.md"}
|
||||
if strings.Join(req.BundlePaths, "\n") != strings.Join(wantBundlePaths, "\n") {
|
||||
t.Fatalf("BundlePaths = %#v, want %#v", req.BundlePaths, wantBundlePaths)
|
||||
}
|
||||
notificationData, err := os.ReadFile(result.NotificationPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read notification artifact: %v", err)
|
||||
}
|
||||
var notificationArtifact state.DistributorNotificationArtifact
|
||||
if err := json.Unmarshal(notificationData, ¬ificationArtifact); err != nil {
|
||||
t.Fatalf("decode notification artifact: %v", err)
|
||||
}
|
||||
if notificationArtifact.ReportID != report.Today ||
|
||||
notificationArtifact.PipelineID != "weatherreporter.today.today" ||
|
||||
notificationArtifact.BundleID != "today.today.md.today" ||
|
||||
notificationArtifact.SourcePath != result.ReportPath ||
|
||||
strings.Join(notificationArtifact.BundlePaths, "\n") != strings.Join(wantBundlePaths, "\n") ||
|
||||
notificationArtifact.RunStatus == nil ||
|
||||
!strings.Contains(string(notificationArtifact.RunStatus.Report), "replace_older") {
|
||||
t.Fatalf("notification artifact = %#v, want Today managed-source notification", notificationArtifact)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateHourlyReportNotificationFailureFailsReport(t *testing.T) {
|
||||
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
|
||||
notifier.err = errors.New("upload rejected")
|
||||
@@ -1350,7 +1452,7 @@ func TestGenerateTodayReportUsesTodayIdentityAndRecentChanges(t *testing.T) {
|
||||
}
|
||||
priorResolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
Report: ReportKind(report.CommandNameToday),
|
||||
Report: ReportToday,
|
||||
Date: mustParse("2026-05-29T12:00:00-05:00"),
|
||||
}, mustParse("2026-05-29T04:00:00-05:00"))
|
||||
if err != nil {
|
||||
@@ -1360,7 +1462,7 @@ func TestGenerateTodayReportUsesTodayIdentityAndRecentChanges(t *testing.T) {
|
||||
|
||||
currentResolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
Report: ReportKind(report.CommandNameToday),
|
||||
Report: ReportToday,
|
||||
Date: mustParse("2026-05-29T12:00:00-05:00"),
|
||||
}, mustParse("2026-05-29T05:00:00-05:00"))
|
||||
if err != nil {
|
||||
@@ -2302,6 +2404,49 @@ func TestRunBatchUsesOutputDirectory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBatchMorningUsesTodayOutputName(t *testing.T) {
|
||||
server := dailyBundleServer(t)
|
||||
cfg := config.Defaults()
|
||||
cfg.WeatherAPI.BaseURL = server.URL + "/"
|
||||
cfg.WeatherAPI.Timezone = "America/Chicago"
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
outputDir := filepath.Join(t.TempDir(), "reports")
|
||||
|
||||
result, err := RunBatchDetailed(context.Background(), BatchRequest{
|
||||
Config: cfg,
|
||||
Batch: BatchMorning,
|
||||
Now: mustParse("2026-05-29T05:00:00-05:00"),
|
||||
OutputDir: outputDir,
|
||||
Renderer: &selectiveRenderer{runBody: "# Batch Report\n"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("RunBatchDetailed() error = %v", err)
|
||||
}
|
||||
if result.Failed != 0 || len(result.Reports) != 3 {
|
||||
t.Fatalf("summary = %#v, want successful morning batch", result)
|
||||
}
|
||||
var todayItem *BatchReportResult
|
||||
for i := range result.Reports {
|
||||
if result.Reports[i].ReportID == report.Today {
|
||||
todayItem = &result.Reports[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if todayItem == nil {
|
||||
t.Fatalf("reports = %#v, want Today item", result.Reports)
|
||||
}
|
||||
want := filepath.Join(outputDir, "today.md")
|
||||
if todayItem.OutputPath != want {
|
||||
t.Fatalf("Today OutputPath = %q, want %q", todayItem.OutputPath, want)
|
||||
}
|
||||
if _, err := os.Stat(want); err != nil {
|
||||
t.Fatalf("expected Today output copy %q: %v", want, err)
|
||||
}
|
||||
if strings.Contains(todayItem.ReportPath, outputDir) {
|
||||
t.Fatalf("Today ReportPath = %q, want managed report path separate from output copy", todayItem.ReportPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchOutputPathUsesHourlyOutputName(t *testing.T) {
|
||||
definition, err := report.DefaultRegistry().Lookup(report.Hourly)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,6 +18,7 @@ const helpText = `weatherreporter prepares weather reports from normalized forec
|
||||
Usage:
|
||||
weatherreporter --help
|
||||
weatherreporter generate daily [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate three-day [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
@@ -216,7 +217,7 @@ func (r Runner) resolveGenerate(args []string) (app.GenerateRequest, error) {
|
||||
}
|
||||
|
||||
switch reportKind {
|
||||
case app.ReportDaily:
|
||||
case app.ReportDaily, app.ReportToday:
|
||||
if opts.Date == "" {
|
||||
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
|
||||
} else {
|
||||
@@ -278,7 +279,7 @@ func parseGenerateFlags(report app.ReportKind, args []string) (generateOptions,
|
||||
fs.SetOutput(io.Discard)
|
||||
opts := generateOptions{}
|
||||
addCommonFlags(fs, &opts.commonOptions, true)
|
||||
if report == app.ReportDaily {
|
||||
if report == app.ReportDaily || report == app.ReportToday {
|
||||
fs.StringVar(&opts.Date, "date", "", "report date in YYYY-MM-DD")
|
||||
}
|
||||
if report == app.ReportStorm {
|
||||
|
||||
@@ -26,6 +26,9 @@ func TestRunHelpLongFlag(t *testing.T) {
|
||||
if !strings.Contains(output.stdout, "generate daily") {
|
||||
t.Fatalf("help output missing generate command:\n%s", output.stdout)
|
||||
}
|
||||
if !strings.Contains(output.stdout, "generate today") {
|
||||
t.Fatalf("help output missing today generate command:\n%s", output.stdout)
|
||||
}
|
||||
if !strings.Contains(output.stdout, "weatherreporter generate hourly") {
|
||||
t.Fatalf("help output missing hourly generate command:\n%s", output.stdout)
|
||||
}
|
||||
@@ -570,6 +573,66 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunGenerateTodayWritesGeneratedTextReport(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
scriptoriumPath := writeStructuredOutputScriptorium(t, tempDir)
|
||||
workspaceRoot := filepath.Join(tempDir, "workspace")
|
||||
configPath := writeTestConfig(t, server, scriptoriumPath, workspaceRoot)
|
||||
outPath := filepath.Join(tempDir, "today.md")
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
err := runner.Run(context.Background(), []string{
|
||||
"generate", "today",
|
||||
"--config", configPath,
|
||||
"--date", "2026-05-29",
|
||||
"--out", outPath,
|
||||
}, &stdout, &stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
report, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"# Today's Weather",
|
||||
"Today starts with showers before improving.",
|
||||
"Morning showers should taper as drier air arrives.",
|
||||
} {
|
||||
if !strings.Contains(string(report), want) {
|
||||
t.Fatalf("today report output missing %q:\n%s", want, string(report))
|
||||
}
|
||||
}
|
||||
dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "today", "2026-05-29", "*.data_package.yaml")
|
||||
dataPackage, err := os.ReadFile(dataPackagePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read managed data package: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(dataPackage), "id: today") ||
|
||||
!strings.Contains(string(dataPackage), "prompt_id: weather.today_generated_text") ||
|
||||
!strings.Contains(string(dataPackage), "today_planning:") {
|
||||
t.Fatalf("data package output missing Today content:\n%s", string(dataPackage))
|
||||
}
|
||||
dailyPackages, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "daily", "2026-05-29", "*.data_package.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("glob daily packages: %v", err)
|
||||
}
|
||||
if len(dailyPackages) != 0 {
|
||||
t.Fatalf("daily packages = %#v, want generate today to stay separate", dailyPackages)
|
||||
}
|
||||
rawGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "today", "2026-05-29", "*.generated_text.raw.json")
|
||||
validatedGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "today", "2026-05-29", "*.generated_text.json")
|
||||
renderContextPath := oneArtifact(t, workspaceRoot, "snapshots", "today", "2026-05-29", "*.render_context.json")
|
||||
managedReportPath := oneArtifact(t, workspaceRoot, "reports", "today", "*.md")
|
||||
assertFileContains(t, rawGeneratedTextPath, `"summary": "Today starts with showers before improving."`)
|
||||
assertFileContains(t, validatedGeneratedTextPath, `"summary":"Today starts with showers before improving."`)
|
||||
assertFileContains(t, renderContextPath, `"Title": "Today's Weather"`)
|
||||
assertFileContains(t, managedReportPath, "# Today's Weather")
|
||||
}
|
||||
|
||||
func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
@@ -627,6 +690,65 @@ func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
assertFileContains(t, managedReportPath, "# Hourly Report")
|
||||
}
|
||||
|
||||
func TestRunInspectTodayArtifacts(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
scriptoriumPath := writeFakeScriptorium(t, tempDir)
|
||||
configPath := filepath.Join(tempDir, "config.yml")
|
||||
workspaceRoot := filepath.Join(tempDir, "workspace")
|
||||
configBody := "weather_api:\n base_url: " + server.URL + "/\n timezone: America/Chicago\nscriptorium:\n binary: " + scriptoriumPath + "\nworkspace:\n root: " + workspaceRoot + "\n"
|
||||
if err := os.WriteFile(configPath, []byte(configBody), 0o600); err != nil {
|
||||
t.Fatalf("write config: %v", err)
|
||||
}
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
err := runner.Run(context.Background(), []string{
|
||||
"generate", "today",
|
||||
"--config", configPath,
|
||||
"--date", "2026-05-29",
|
||||
}, &stdout, &stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("Run(generate) error = %v", err)
|
||||
}
|
||||
dataPackageMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "today", "2026-05-29", "*.data_package.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("glob data package: %v", err)
|
||||
}
|
||||
if len(dataPackageMatches) != 1 {
|
||||
t.Fatalf("data package files = %#v, want one", dataPackageMatches)
|
||||
}
|
||||
runID := strings.TrimSuffix(filepath.Base(dataPackageMatches[0]), ".data_package.yaml")
|
||||
|
||||
stdout.Reset()
|
||||
err = runner.Run(context.Background(), []string{"inspect", "reports", "--config", configPath, "--limit", "1"}, &stdout, &stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("Run(inspect reports) error = %v", err)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), runID) || !strings.Contains(stdout.String(), `"reportId": "today"`) {
|
||||
t.Fatalf("inspect reports output missing Today run:\n%s", stdout.String())
|
||||
}
|
||||
|
||||
var sourcesOutput string
|
||||
for _, command := range []string{"metadata", "modules", "data-package", "sources"} {
|
||||
stdout.Reset()
|
||||
err = runner.Run(context.Background(), []string{"inspect", command, "--config", configPath, runID}, &stdout, &stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("Run(inspect %s) error = %v", command, err)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), runID) || !strings.Contains(stdout.String(), "today") {
|
||||
t.Fatalf("inspect %s output missing Today run id:\n%s", command, stdout.String())
|
||||
}
|
||||
if command == "sources" {
|
||||
sourcesOutput = stdout.String()
|
||||
}
|
||||
}
|
||||
if !strings.Contains(sourcesOutput, `"name": "narrative"`) || strings.Contains(sourcesOutput, `"name": "daily"`) {
|
||||
t.Fatalf("inspect sources output missing narrative source or has unexpected daily source:\n%s", sourcesOutput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInspectGeneratedArtifacts(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
@@ -753,6 +875,7 @@ func TestResolveGenerateCommands(t *testing.T) {
|
||||
want app.ReportKind
|
||||
}{
|
||||
{name: "daily", args: []string{"daily", "--date", "2026-05-29"}, want: app.ReportDaily},
|
||||
{name: "today", args: []string{"today", "--date", "2026-05-29"}, want: app.ReportToday},
|
||||
{name: "tomorrow", args: []string{"tomorrow"}, want: app.ReportTomorrow},
|
||||
{name: "hourly", args: []string{"hourly"}, want: app.ReportHourly},
|
||||
{name: "three-day", args: []string{"three-day"}, want: app.ReportThreeDay},
|
||||
@@ -778,7 +901,7 @@ func TestResolveGenerateSupportsEveryReportCommandName(t *testing.T) {
|
||||
for _, name := range report.CommandNames() {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
args := []string{name}
|
||||
if name == report.CommandNameDaily {
|
||||
if name == report.CommandNameDaily || name == report.CommandNameToday {
|
||||
args = append(args, "--date", "2026-05-29")
|
||||
}
|
||||
if name == report.CommandNameStorm {
|
||||
@@ -865,6 +988,39 @@ func TestResolveGenerateDailyDefaultsDateInConfiguredTimezone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateTodayDate(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
defaultReq, err := runner.resolveGenerate([]string{"today"})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGenerate(default) error = %v", err)
|
||||
}
|
||||
if defaultReq.Report != app.ReportToday {
|
||||
t.Fatalf("Report = %q, want today", defaultReq.Report)
|
||||
}
|
||||
if got := defaultReq.Date.Format(timeutil.DateLayout); got != "2026-05-29" {
|
||||
t.Fatalf("default Date = %s, want 2026-05-29", got)
|
||||
}
|
||||
|
||||
explicitReq, err := runner.resolveGenerate([]string{"today", "--date", "2026-05-30", "--tz", "UTC"})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGenerate(explicit) error = %v", err)
|
||||
}
|
||||
if got := explicitReq.Date.Format(timeutil.DateLayout); got != "2026-05-30" {
|
||||
t.Fatalf("explicit Date = %s, want 2026-05-30", got)
|
||||
}
|
||||
resolved, err := app.ResolveGenerate(explicitReq, explicitReq.Now)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||
}
|
||||
if resolved.Definition.ID != report.Today {
|
||||
t.Fatalf("resolved ID = %q, want today", resolved.Definition.ID)
|
||||
}
|
||||
if got := resolved.ValidPeriod.Start.Format(time.RFC3339); got != "2026-05-30T00:00:00Z" {
|
||||
t.Fatalf("valid period start = %s, want explicit UTC date", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user