Add data package export regression coverage
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user