Correct artifact path bookkeeping

This commit is contained in:
2026-07-31 16:37:00 +00:00
parent b96f40e5ca
commit 25782447eb
8 changed files with 792 additions and 845 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -343,13 +343,6 @@ func RunBatchDetailed(ctx context.Context, req BatchRequest) (*BatchResult, erro
for _, planned := range plannedReports {
resolved := planned.Resolved
item := batchReportResult(planned)
if paths, err := store.Paths(resolved); err == nil {
item.DataPackagePath = paths.DataPackage
item.PreparationPath = paths.Preparation
item.ExecutionPath = paths.Execution
item.ReportPath = paths.RenderedReport
item.MetadataPath = paths.Metadata
}
outputPath := plannedBatchOutputPath(req.OutputDir, planned)
reportResult, err := generatePromptReport(ctx, promptReportRequest{
GenerateRequest: GenerateRequest{
@@ -540,6 +533,7 @@ type finalizeRenderedReportRequest struct {
Store state.Store
Resolved report.Resolved
Metadata state.Metadata
MetadataPath string
ManagedReportPath string
OutputPath string
Notifier Notifier
@@ -563,27 +557,24 @@ func finalizeRenderedReport(ctx context.Context, req finalizeRenderedReportReque
return finalizeRenderedReportResult{}, fmt.Errorf("managed report path is required for report %q", req.Resolved.Definition.ID)
}
metadata := req.Metadata
metadata.RenderedReportPath = req.ManagedReportPath
outputPath := req.ManagedReportPath
result := finalizeRenderedReportResult{Metadata: req.Metadata, MetadataPath: req.MetadataPath}
if req.OutputPath != "" {
outputPath = req.OutputPath
if req.GenerationErr == nil && req.OutputPath != req.ManagedReportPath {
if err := fileutil.CopyFileAtomic(req.ManagedReportPath, req.OutputPath); err != nil {
return finalizeRenderedReportResult{}, err
return result, err
}
}
result.OutputPath = req.OutputPath
}
metadata := req.Metadata
metadata.RenderedReportPath = req.ManagedReportPath
metadataPath, err := req.Store.SaveMetadata(ctx, metadata)
if err != nil {
return finalizeRenderedReportResult{}, err
}
result := finalizeRenderedReportResult{
OutputPath: outputPath,
Metadata: metadata,
MetadataPath: metadataPath,
return result, err
}
result.Metadata = metadata
result.MetadataPath = metadataPath
if req.GenerationErr != nil {
return result, req.GenerationErr
}
@@ -593,14 +584,16 @@ func finalizeRenderedReport(ctx context.Context, req finalizeRenderedReportReque
notification, notificationPath, err := notifyReport(ctx, req.Config, req.Resolved, req.ManagedReportPath, metadata, req.Notifier, req.Store)
if notificationPath != "" {
result.NotificationPath = notificationPath
result.Notification = notification
metadata.NotificationPath = notificationPath
result.Metadata = metadata
metadataPath, saveErr := req.Store.SaveMetadata(ctx, metadata)
if saveErr != nil {
return finalizeRenderedReportResult{}, saveErr
return result, saveErr
}
result.Metadata = metadata
result.MetadataPath = metadataPath
result.NotificationPath = notificationPath
}
result.Notification = notification
if err != nil {

View File

@@ -2,7 +2,9 @@ package app
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
@@ -39,6 +41,28 @@ func TestRunBatchDetailedInspectsEveryCandidateBeforeCollection(t *testing.T) {
}
}
func TestCopyBatchReportPathsLeavesUnreachedPathsEmpty(t *testing.T) {
item := BatchReportResult{}
copyBatchReportPaths(&item, &ReportResult{
DataPackagePath: "/runs/daily/data_package.yaml",
PreparationPath: "/runs/daily/preparation.json",
})
data, err := json.Marshal(item)
if err != nil {
t.Fatalf("Marshal() error = %v", err)
}
text := string(data)
for _, omitted := range []string{"executionPath", "reportPath", "outputPath", "metadataPath", "notificationPath"} {
if strings.Contains(text, omitted) {
t.Fatalf("batch item includes unreached field %q:\n%s", omitted, text)
}
}
if !strings.Contains(text, "dataPackagePath") || !strings.Contains(text, "preparationPath") {
t.Fatalf("batch item omits reached paths:\n%s", text)
}
}
type collectorFunc func(context.Context, collect.Request) (*collect.Result, error)
func (f collectorFunc) Run(ctx context.Context, req collect.Request) (*collect.Result, error) {

View File

@@ -0,0 +1,272 @@
package app
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
"time"
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec"
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
"gitea.maximumdirect.net/eric/weatherreporter/internal/state"
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
)
const (
failPromptExecution = "prompt execution"
failMetadata = "metadata"
)
type failingPersistenceStore struct {
state.Store
failOperation string
failMetadataCall int
metadataCalls int
}
func (s *failingPersistenceStore) SavePromptExecution(ctx context.Context, resolved report.Resolved, artifact state.PromptExecutionArtifact) (string, error) {
if s.failOperation == failPromptExecution {
return "", errors.New("injected prompt execution persistence failure")
}
return s.Store.SavePromptExecution(ctx, resolved, artifact)
}
func (s *failingPersistenceStore) SaveMetadata(ctx context.Context, metadata state.Metadata) (string, error) {
s.metadataCalls++
if s.failOperation == failMetadata && s.metadataCalls == s.failMetadataCall {
return "", errors.New("injected metadata persistence failure")
}
return s.Store.SaveMetadata(ctx, metadata)
}
type artifactPathExecutor struct {
beforePreparationErr error
afterPreparationErr error
validation promptexec.ValidationStatus
}
func (e artifactPathExecutor) InspectPrompt(context.Context, string, string) (promptexec.PromptInspection, error) {
return promptexec.PromptInspection{}, errors.New("unexpected inspection")
}
func (e artifactPathExecutor) InspectProfile(context.Context, string) (promptexec.ProfileInspection, error) {
return promptexec.ProfileInspection{}, errors.New("unexpected inspection")
}
func (e artifactPathExecutor) Execute(_ context.Context, req promptexec.ExecuteRequest, callback promptexec.PreparationCallback) (*promptexec.Execution, error) {
if e.beforePreparationErr != nil {
return nil, e.beforePreparationErr
}
now := time.Date(2026, 5, 29, 15, 0, 0, 0, time.UTC)
if err := callback(promptexec.Preparation{
PromptID: req.PromptID, PromptVersion: req.PromptVersion, PromptHash: "prompt-hash",
RenderedPromptHash: "rendered-hash", ProfileID: req.ProfileID, BackendID: "test",
ModelName: "test-model", DataPackagePath: req.DataPackagePath, StartedAt: now, EndedAt: now,
}, nil); err != nil {
return nil, err
}
if e.afterPreparationErr != nil {
return nil, e.afterPreparationErr
}
validation := e.validation
if validation == "" {
validation = promptexec.ValidationPassed
}
return &promptexec.Execution{
RunID: "provider-run", PromptID: req.PromptID, PromptVersion: req.PromptVersion,
PromptHash: "prompt-hash", RenderedPromptHash: "rendered-hash", ProfileID: req.ProfileID,
BackendID: "test", ModelName: "test-model", GeneratedHash: "generated-hash",
StartedAt: now, EndedAt: now, DataPackagePath: req.DataPackagePath,
RawOutput: []byte(`{"summary":"Showers are possible during the selected day.","forecast_discussion":["A front will keep rain chances in the forecast."],"precipitation_timing":"Rain is most likely during the afternoon.","confidence":"Medium"}`),
Validation: promptexec.NewValidation(validation, "json_schema", "daily.generated_text.schema.json", nil),
}, nil
}
type successfulNotifier struct{}
func (successfulNotifier) Notify(context.Context, NotificationRequest) (*NotificationResult, error) {
return &NotificationResult{RunID: "notification-run", Status: "succeeded", UploadStatus: "accepted"}, nil
}
func TestGeneratePromptReportReturnsOnlyReachedArtifactPaths(t *testing.T) {
tests := []struct {
name string
failOperation string
failMetadataCall int
outputCopy bool
notify bool
want reachedPromptArtifacts
}{
{name: "preparation then metadata", failOperation: failMetadata, failMetadataCall: 1, want: reachedPromptArtifacts{preparation: true}},
{name: "raw output then execution", failOperation: failPromptExecution, want: reachedPromptArtifacts{preparation: true, metadata: true, raw: true}},
{name: "execution then metadata", failOperation: failMetadata, failMetadataCall: 2, want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true}},
{name: "normalized output then metadata", failOperation: failMetadata, failMetadataCall: 3, want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true, normalized: true}},
{name: "render context then metadata", failOperation: failMetadata, failMetadataCall: 4, want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true, normalized: true, renderContext: true}},
{name: "managed report then metadata", failOperation: failMetadata, failMetadataCall: 5, want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true, normalized: true, renderContext: true, report: true}},
{name: "output copy then metadata", failOperation: failMetadata, failMetadataCall: 5, outputCopy: true, want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true, normalized: true, renderContext: true, report: true, output: true}},
{name: "notification then metadata", failOperation: failMetadata, failMetadataCall: 6, notify: true, want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true, normalized: true, renderContext: true, report: true, notification: true}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req, paths := promptArtifactRequest(t, artifactPathExecutor{})
store := &failingPersistenceStore{Store: req.Store, failOperation: test.failOperation, failMetadataCall: test.failMetadataCall}
req.Store = store
if test.outputCopy {
req.OutputPath = filepath.Join(t.TempDir(), "daily.md")
paths.output = req.OutputPath
}
if test.notify {
req.Config.Notify.Distributor.Enabled = true
req.Notifier = successfulNotifier{}
req.noNotify = false
}
result, err := generatePromptReport(context.Background(), req)
if err == nil || result == nil {
t.Fatalf("generatePromptReport() result/error = %#v/%v, want partial result and failure", result, err)
}
assertReachedPromptArtifacts(t, result, paths, test.want)
})
}
}
func TestGeneratePromptReportFailureReceiptsExposeReachedPaths(t *testing.T) {
tests := []struct {
name string
executor artifactPathExecutor
want reachedPromptArtifacts
}{
{
name: "preparation failure",
executor: artifactPathExecutor{beforePreparationErr: promptexec.NewError(promptexec.Generation, "prepare failed", nil)},
want: reachedPromptArtifacts{preparation: true, metadata: true},
},
{
name: "operational execution failure",
executor: artifactPathExecutor{afterPreparationErr: promptexec.NewError(promptexec.Generation, "provider failed", nil)},
want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true},
},
{
name: "completed validation rejection",
executor: artifactPathExecutor{validation: promptexec.ValidationFailed},
want: reachedPromptArtifacts{preparation: true, execution: true, metadata: true, raw: true},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req, paths := promptArtifactRequest(t, test.executor)
result, err := generatePromptReport(context.Background(), req)
if err == nil || result == nil {
t.Fatalf("generatePromptReport() result/error = %#v/%v, want partial result and failure", result, err)
}
assertReachedPromptArtifacts(t, result, paths, test.want)
})
}
}
type promptArtifactPaths struct {
state.ArtifactPaths
output string
}
type reachedPromptArtifacts struct {
preparation bool
execution bool
metadata bool
raw bool
normalized bool
renderContext bool
report bool
output bool
notification bool
}
func promptArtifactRequest(t *testing.T, executor promptexec.Executor) (promptReportRequest, promptArtifactPaths) {
t.Helper()
cfg := config.Defaults()
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg, Report: ReportDaily, 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)
}
bundleData, err := os.ReadFile(filepath.Join("..", "forecast", "testdata", "daily_bundle.json"))
if err != nil {
t.Fatalf("read daily fixture: %v", err)
}
var bundle weatherdata.Bundle
if err := json.Unmarshal(bundleData, &bundle); err != nil {
t.Fatalf("decode daily fixture: %v", err)
}
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
paths, err := filesystemStore.Paths(resolved)
if err != nil {
t.Fatalf("Paths() error = %v", err)
}
debugWriter, err := state.NewPromptDebugWriter("")
if err != nil {
t.Fatalf("NewPromptDebugWriter() error = %v", err)
}
return promptReportRequest{
GenerateRequest: GenerateRequest{Config: cfg, Report: ReportDaily, Executor: executor, Store: filesystemStore},
Resolved: resolved, Collection: collect.Result{Bundle: &bundle},
Inspection: PromptInspectionResult{
PromptID: resolved.Definition.PromptID, PromptVersion: resolved.Definition.PromptVersion,
PromptHash: "prompt-hash", ProfileID: "test-profile", BackendID: "test", ModelName: "test-model",
},
DebugWriter: debugWriter, noNotify: true,
}, promptArtifactPaths{ArtifactPaths: paths}
}
func assertReachedPromptArtifacts(t *testing.T, result *ReportResult, paths promptArtifactPaths, want reachedPromptArtifacts) {
t.Helper()
if result.ModuleSnapshotPath != paths.ModuleSnapshot || result.DataPackagePath != paths.DataPackage {
t.Fatalf("base paths = module %q data %q, want %q and %q", result.ModuleSnapshotPath, result.DataPackagePath, paths.ModuleSnapshot, paths.DataPackage)
}
if result.Metadata.ModuleSnapshotPath != paths.ModuleSnapshot || result.Metadata.DataPackagePath != paths.DataPackage || result.Metadata.MetadataPath != paths.Metadata {
t.Fatalf("metadata base paths = %#v, want reached module/data paths and metadata destination", result.Metadata)
}
checks := []struct {
name string
got string
metadataGot string
inMetadata bool
path string
want bool
}{
{"preparation", result.PreparationPath, result.Metadata.PreparationPath, true, paths.Preparation, want.preparation},
{"execution", result.ExecutionPath, result.Metadata.ExecutionPath, true, paths.Execution, want.execution},
{"metadata", result.MetadataPath, "", false, paths.Metadata, want.metadata},
{"raw", result.GeneratedTextRawPath, result.Metadata.GeneratedTextRawPath, true, paths.GeneratedTextRaw, want.raw},
{"normalized", result.GeneratedTextPath, result.Metadata.GeneratedTextPath, true, paths.GeneratedText, want.normalized},
{"render context", result.RenderContextPath, result.Metadata.RenderContextPath, true, paths.RenderContext, want.renderContext},
{"report", result.ReportPath, result.Metadata.RenderedReportPath, true, paths.RenderedReport, want.report},
{"output", result.OutputPath, "", false, paths.output, want.output},
{"notification", result.NotificationPath, result.Metadata.NotificationPath, true, paths.Notification, want.notification},
}
for _, check := range checks {
if check.want && check.got != check.path {
t.Errorf("%s path = %q, want reached path %q", check.name, check.got, check.path)
}
if check.want && check.inMetadata && check.metadataGot != check.path {
t.Errorf("metadata %s path = %q, want reached path %q", check.name, check.metadataGot, check.path)
}
if !check.want && check.got != "" {
t.Errorf("%s path = %q, want empty because artifact was not reached", check.name, check.got)
}
if !check.want && check.inMetadata && check.metadataGot != "" {
t.Errorf("metadata %s path = %q, want empty because artifact was not reached", check.name, check.metadataGot)
}
}
}

View File

@@ -41,7 +41,7 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
if err != nil {
return nil, err
}
result := &ReportResult{ReportPath: paths.RenderedReport}
result := &ReportResult{}
priorSnapshot, err := store.FindPriorSnapshot(ctx, req.Resolved)
if err != nil {
@@ -70,15 +70,8 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
result.RecentChanges = recent
briefingMetadata := briefing.BuildMetadata(briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected))
metadata := state.BuildPromptMetadataFromBriefingMetadata(req.Resolved, briefingMetadata, state.ArtifactPaths{
ModuleSnapshot: moduleSnapshotPath,
Metadata: paths.Metadata,
DataPackage: paths.DataPackage,
Preparation: paths.Preparation,
Execution: paths.Execution,
RenderedReport: paths.RenderedReport,
GeneratedTextRaw: paths.GeneratedTextRaw,
GeneratedText: paths.GeneratedText,
RenderContext: paths.RenderContext,
ModuleSnapshot: moduleSnapshotPath,
Metadata: paths.Metadata,
})
result.Metadata = metadata
dataPackage, err := promptinput.Build(promptinput.BuildRequest{
@@ -179,11 +172,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, saveErr
}
metadata.PreparationPath = path
result.PreparationPath = path
result.Metadata = metadata
metadataPath, saveErr := store.SaveMetadata(ctx, metadata)
if saveErr != nil {
return result, saveErr
}
result.PreparationPath, result.Metadata, result.MetadataPath = path, metadata, metadataPath
result.MetadataPath = metadataPath
return result, generatedReportError(req.Resolved, metadata.RunID, "prepare prompt", executeErr)
}
if promptexec.CategoryOf(executeErr) != "" {
@@ -193,11 +188,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, saveErr
}
metadata.ExecutionPath = path
result.ExecutionPath = path
result.Metadata = metadata
metadataPath, saveErr := store.SaveMetadata(ctx, metadata)
if saveErr != nil {
return result, saveErr
}
result.ExecutionPath, result.Metadata, result.MetadataPath = path, metadata, metadataPath
result.MetadataPath = metadataPath
}
return result, generatedReportError(req.Resolved, metadata.RunID, "execute prompt", executeErr)
}
@@ -209,11 +206,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, saveErr
}
metadata.ExecutionPath = executionPath
result.ExecutionPath = executionPath
result.Metadata = metadata
metadataPath, saveErr := store.SaveMetadata(ctx, metadata)
if saveErr != nil {
return result, saveErr
}
result.ExecutionPath, result.Metadata, result.MetadataPath = executionPath, metadata, metadataPath
result.MetadataPath = metadataPath
return result, generatedReportError(req.Resolved, metadata.RunID, "execute prompt", err)
}
debugPath, err := req.DebugWriter.WriteExecution(debugRef, *execution)
@@ -232,11 +231,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, saveErr
}
metadata.ExecutionPath = executionPath
result.ExecutionPath = executionPath
result.Metadata = metadata
metadataPath, saveErr := store.SaveMetadata(ctx, metadata)
if saveErr != nil {
return result, saveErr
}
result.ExecutionPath, result.Metadata, result.MetadataPath = executionPath, metadata, metadataPath
result.MetadataPath = metadataPath
return result, generatedReportError(req.Resolved, metadata.RunID, "validate prompt execution", err)
}
rawPath, err := store.SaveGeneratedTextRaw(ctx, req.Resolved, execution.RawOutput)
@@ -244,6 +245,8 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, err
}
result.GeneratedTextRawPath = rawPath
metadata.GeneratedTextRawPath = rawPath
result.Metadata = metadata
executionArtifact := state.PromptExecutionArtifact{
SchemaVersion: state.PromptExecutionSchemaVersion,
ReportID: req.Resolved.Definition.ID, RunID: metadata.RunID,
@@ -262,12 +265,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, err
}
metadata.ExecutionPath = executionPath
metadata.GeneratedTextRawPath = rawPath
result.ExecutionPath = executionPath
result.Metadata = metadata
metadataPath, err := store.SaveMetadata(ctx, metadata)
if err != nil {
return result, err
}
result.ExecutionPath, result.Metadata, result.MetadataPath = executionPath, metadata, metadataPath
result.MetadataPath = metadataPath
if execution.Validation.Status == promptexec.ValidationFailed {
return result, generatedReportError(req.Resolved, metadata.RunID, "validate prompt execution", promptexec.NewError(promptexec.ValidationRejected, "prompt output did not satisfy its schema", nil))
}
@@ -281,11 +285,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, err
}
metadata.GeneratedTextPath = generatedTextPath
result.GeneratedTextPath = generatedTextPath
result.Metadata = metadata
metadataPath, err = store.SaveMetadata(ctx, metadata)
if err != nil {
return result, err
}
result.GeneratedTextPath, result.Metadata, result.MetadataPath = generatedTextPath, metadata, metadataPath
result.MetadataPath = metadataPath
renderContext, err := handler.BuildRenderContext(briefingMetadata, moduleSnapshot, reportFacts.Collected, reportFacts.Derived, generatedText)
if err != nil {
@@ -296,11 +302,13 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, err
}
metadata.RenderContextPath = renderContextPath
result.RenderContextPath = renderContextPath
result.Metadata = metadata
metadataPath, err = store.SaveMetadata(ctx, metadata)
if err != nil {
return result, err
}
result.RenderContextPath, result.Metadata, result.MetadataPath = renderContextPath, metadata, metadataPath
result.MetadataPath = metadataPath
rendered, err := handler.Render(renderContext)
if err != nil {
@@ -314,8 +322,10 @@ func generatePromptReport(ctx context.Context, req promptReportRequest) (*Report
return result, err
}
result.ReportPath = reportPath
metadata.RenderedReportPath = reportPath
result.Metadata = metadata
finalized, err := finalizeRenderedReport(ctx, finalizeRenderedReportRequest{
Config: req.Config, Store: store, Resolved: req.Resolved, Metadata: metadata,
Config: req.Config, Store: store, Resolved: req.Resolved, Metadata: metadata, MetadataPath: result.MetadataPath,
ManagedReportPath: reportPath, OutputPath: req.OutputPath, Notifier: req.Notifier, noNotify: req.noNotify,
})
result.OutputPath, result.NotificationPath = finalized.OutputPath, finalized.NotificationPath

View File

@@ -113,6 +113,31 @@ func TestNewGenerateSummaryOmitsNotificationWhenNotAttempted(t *testing.T) {
}
}
func TestNewGenerateSummaryOmitsUnreachedArtifactPaths(t *testing.T) {
result := &app.ReportResult{
DataPackagePath: "/runs/daily/data_package.yaml",
PreparationPath: "/runs/daily/preparation.json",
Metadata: state.Metadata{
ReportID: report.Daily,
RunID: "20260529T133000Z_daily",
},
}
data, err := json.Marshal(newGenerateSummary(result, errors.New("metadata write failed")))
if err != nil {
t.Fatalf("Marshal() error = %v", err)
}
text := string(data)
for _, omitted := range []string{"executionPath", "reportPath", "outputPath", "metadataPath", "generatedTextRawPath", "generatedTextPath", "renderContextPath", "notificationPath"} {
if strings.Contains(text, omitted) {
t.Fatalf("partial summary includes unreached field %q:\n%s", omitted, text)
}
}
if !strings.Contains(text, "dataPackagePath") || !strings.Contains(text, "preparationPath") {
t.Fatalf("partial summary omits reached paths:\n%s", text)
}
}
func TestNewGenerateSummaryForNotificationFailure(t *testing.T) {
generatedAt := time.Date(2026, 5, 29, 13, 30, 0, 0, time.UTC)
result := &app.ReportResult{

View File

@@ -182,9 +182,8 @@ func BuildPromptMetadataFromBriefingMetadata(resolved report.Resolved, briefingM
Location: copyLocation(briefingMetadata.Location), SourceLocationID: briefingMetadata.SourceLocationID,
SourceLocation: briefingMetadata.SourceLocation, Sources: briefingMetadata.Sources,
SourceWarnings: briefingMetadata.SourceWarnings, ModuleSnapshotPath: paths.ModuleSnapshot,
DataPackagePath: paths.DataPackage, RenderedReportPath: paths.RenderedReport,
GeneratedTextSchemaID: resolved.Definition.GeneratedTextSchemaID, GeneratedTextRawPath: paths.GeneratedTextRaw,
GeneratedTextPath: paths.GeneratedText, RenderContextPath: paths.RenderContext,
DataPackagePath: paths.DataPackage,
GeneratedTextSchemaID: resolved.Definition.GeneratedTextSchemaID,
}
}

View File

@@ -0,0 +1,38 @@
package state
import (
"testing"
"time"
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
)
func TestBuildPromptMetadataIncludesOnlyExistingArtifacts(t *testing.T) {
resolved, err := report.DefaultRegistry().Resolve(report.Daily, report.ResolveRequest{
Now: time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC),
Date: time.Date(2026, 5, 29, 0, 0, 0, 0, time.UTC), Location: time.UTC,
})
if err != nil {
t.Fatalf("Resolve() error = %v", err)
}
metadata := BuildPromptMetadataFromBriefingMetadata(resolved, briefing.Metadata{}, ArtifactPaths{
ModuleSnapshot: "/saved/modules.json",
Metadata: "/destination/metadata.json",
DataPackage: "/saved/data.yaml",
Preparation: "/future/preparation.json",
Execution: "/future/execution.json",
Notification: "/future/notification.json",
RenderedReport: "/future/report.md",
GeneratedTextRaw: "/future/raw.json",
GeneratedText: "/future/generated.json",
RenderContext: "/future/context.json",
})
if metadata.ModuleSnapshotPath != "/saved/modules.json" || metadata.DataPackagePath != "/saved/data.yaml" || metadata.MetadataPath != "/destination/metadata.json" {
t.Fatalf("existing paths = %#v, want module, data package, and metadata destination", metadata)
}
if metadata.PreparationPath != "" || metadata.ExecutionPath != "" || metadata.NotificationPath != "" || metadata.RenderedReportPath != "" || metadata.GeneratedTextRawPath != "" || metadata.GeneratedTextPath != "" || metadata.RenderContextPath != "" {
t.Fatalf("metadata includes paths for unreached artifacts: %#v", metadata)
}
}