Add data package export regression coverage

This commit is contained in:
2026-06-15 20:42:48 +00:00
parent 9261431329
commit 2792933833
2 changed files with 203 additions and 0 deletions

View File

@@ -309,6 +309,80 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
}
}
func TestGeneratedTemplateReportsUseRichArtifactsAndCuratedDataPackages(t *testing.T) {
server := dailyBundleServer(t)
tests := []struct {
name string
kind ReportKind
date time.Time
now time.Time
prompt string
}{
{
name: "today",
kind: ReportToday,
date: mustParse("2026-05-29T12:00:00-05:00"),
now: mustParse("2026-05-29T05:00:00-05:00"),
prompt: "weather.today_generated_text",
},
{
name: "tomorrow",
kind: ReportTomorrow,
now: mustParse("2026-05-29T18:00:00-05:00"),
prompt: "weather.tomorrow_generated_text",
},
{
name: "daily",
kind: ReportDaily,
date: mustParse("2026-05-29T12:00:00-05:00"),
now: mustParse("2026-05-29T05:00:00-05:00"),
prompt: "weather.daily_generated_text",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
Report: tt.kind,
Date: tt.date,
}, tt.now)
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
}
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: renderer,
})
if err != nil {
t.Fatalf("GenerateReport() error = %v", err)
}
if renderer.renderCalls != 1 || renderer.structuredRunCalls != 1 || renderer.runCalls != 0 {
t.Fatalf("renderer calls render=%d structured=%d run=%d, want generated-template workflow", renderer.renderCalls, renderer.structuredRunCalls, renderer.runCalls)
}
if renderer.renderRequest.PromptID != tt.prompt || renderer.structuredRunRequest.PromptID != tt.prompt {
t.Fatalf("prompt IDs render=%q structured=%q, want %q", renderer.renderRequest.PromptID, renderer.structuredRunRequest.PromptID, tt.prompt)
}
if renderer.renderRequest.DataPackagePath != result.DataPackagePath {
t.Fatalf("render DataPackagePath = %q, want managed path %q", renderer.renderRequest.DataPackagePath, result.DataPackagePath)
}
if renderer.structuredRunRequest.DataPackagePath != result.DataPackagePath {
t.Fatalf("structured run DataPackagePath = %q, want managed path %q", renderer.structuredRunRequest.DataPackagePath, result.DataPackagePath)
}
assertRichPromptHelperArtifacts(t, result)
assertCuratedPromptDataPackage(t, result)
})
}
}
func TestGenerateReportIncludesSPCConvectivePromptStanzas(t *testing.T) {
server := dailyBundleServerWithConvectiveResponse(t, qualifyingConvectiveOutlooksResponse)
cfg := dailyTestConfig(t, server)
@@ -2655,6 +2729,98 @@ func readDataPackageForTest(t *testing.T, result *ReportResult) []byte {
return data
}
func assertRichPromptHelperArtifacts(t *testing.T, result *ReportResult) {
t.Helper()
snapshotData, err := os.ReadFile(result.ModuleSnapshotPath)
if err != nil {
t.Fatalf("read module snapshot: %v", err)
}
for _, want := range []string{
`"condition_text_lower"`,
`"hour_label"`,
`"text_description_lower"`,
`"mention_precipitation"`,
`"temperature_phrase_f"`,
`"dominant_condition_lower"`,
`"dominant_condition_display"`,
`"max_pop_time_label"`,
} {
if !strings.Contains(string(snapshotData), want) {
t.Fatalf("module snapshot missing rich helper field %q:\n%s", want, string(snapshotData))
}
}
renderContext, err := os.ReadFile(result.RenderContextPath)
if err != nil {
t.Fatalf("read render context: %v", err)
}
for _, want := range []string{
`"condition_text_lower"`,
`"hour_label"`,
`"text_description_lower"`,
`"mention_precipitation"`,
`"temperature_phrase_f"`,
`"dominant_condition_lower"`,
`"dominant_condition_display"`,
`"max_pop_time_label"`,
} {
if !strings.Contains(string(renderContext), want) {
t.Fatalf("render context missing rich helper field %q:\n%s", want, string(renderContext))
}
}
}
func assertCuratedPromptDataPackage(t *testing.T, result *ReportResult) {
t.Helper()
data := readDataPackageForTest(t, result)
pkg, err := promptinput.LoadYAML(data)
if err != nil {
t.Fatalf("decode data package: %v", err)
}
current, ok := pkg.Briefing.Values["current_conditions"].(map[string]any)
if !ok {
t.Fatalf("current_conditions = %#v, want prompt map", pkg.Briefing.Values["current_conditions"])
}
assertMapOmitsKeys(t, "current_conditions", current, "condition_text_lower", "wind_direction_text")
hourly, ok := pkg.Briefing.Values["hourly_forecast"].(map[string]any)
if !ok {
t.Fatalf("hourly_forecast = %#v, want prompt map", pkg.Briefing.Values["hourly_forecast"])
}
periods, ok := hourly["periods"].([]any)
if !ok || len(periods) == 0 {
t.Fatalf("hourly_forecast.periods = %#v, want prompt periods", hourly["periods"])
}
firstPeriod, ok := periods[0].(map[string]any)
if !ok {
t.Fatalf("hourly first period = %#v, want prompt map", periods[0])
}
assertMapOmitsKeys(t, "hourly_forecast.periods[0]", firstPeriod, "hour_label", "text_description_lower", "mention_precipitation")
dayparts, ok := pkg.Briefing.Values["derived_daypart_summaries"].(map[string]any)
if !ok {
t.Fatalf("derived_daypart_summaries = %#v, want prompt map", pkg.Briefing.Values["derived_daypart_summaries"])
}
morning, ok := dayparts["morning"].(map[string]any)
if !ok {
t.Fatalf("derived_daypart_summaries.morning = %#v, want prompt map", dayparts["morning"])
}
if morning["max_pop_time"] != "6:00 AM" {
t.Fatalf("derived_daypart_summaries.morning.max_pop_time = %#v, want friendly label", morning["max_pop_time"])
}
assertMapOmitsKeys(t, "derived_daypart_summaries.morning", morning, "temperature_phrase_f", "dominant_condition_lower", "dominant_condition_display", "max_pop_time_label")
}
func assertMapOmitsKeys(t *testing.T, name string, value map[string]any, keys ...string) {
t.Helper()
for _, key := range keys {
if _, ok := value[key]; ok {
t.Fatalf("%s contains helper field %q: %#v", name, key, value)
}
}
}
func assertNoStaleModuleIntervalKeys(t *testing.T, values map[string]any) {
t.Helper()
for name, value := range values {

View File

@@ -59,6 +59,43 @@ func TestDefaultReportModulesBuildSnapshots(t *testing.T) {
}
}
func TestDefaultModuleDefinitionsDeclarePromptExportPolicy(t *testing.T) {
customExporters := map[module.ID]struct{}{
module.CurrentConditions: {},
module.HourlyForecast: {},
module.DerivedDaypartSummaries: {},
}
passThroughExporters := map[module.ID]struct{}{
module.Metadata: {},
module.NarrativeForecast: {},
module.DerivedDailySummary: {},
module.PrecipTiming: {},
module.AlertDigest: {},
module.SPCConvectiveOutlooks: {},
module.AreaForecastDiscussion: {},
module.SPCConvectiveDiscussion: {},
module.WeatherStory: {},
module.OutdoorWindows: {},
module.TodayPlanning: {},
module.TomorrowPlanning: {},
module.DailyPlanning: {},
}
for _, definition := range defaultModuleDefinitions() {
_, custom := customExporters[definition.ID]
_, passThrough := passThroughExporters[definition.ID]
if custom == passThrough {
t.Fatalf("module %q exporter policy custom=%v passThrough=%v, want exactly one policy", definition.ID, custom, passThrough)
}
if custom && definition.PromptExporter == nil {
t.Fatalf("module %q PromptExporter = nil, want custom prompt exporter", definition.ID)
}
if passThrough && definition.PromptExporter != nil {
t.Fatalf("module %q PromptExporter is set, want default pass-through", definition.ID)
}
}
}
func TestModuleRegistryAddsPassThroughPromptValue(t *testing.T) {
registry, err := NewModuleRegistry([]ModuleDefinition{
{