2026-05-29 17:40:38 +00:00
package state
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
2026-06-09 20:58:35 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
2026-05-29 17:40:38 +00:00
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
)
func TestPathsUseRunIDAndWorkspace ( t * testing . T ) {
store := newTestStore ( t )
resolved := resolveDailyAt ( t , "2026-05-29T05:00:00-05:00" )
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
for _ , want := range [ ] string {
2026-06-15 16:43:06 +00:00
filepath . Join ( "snapshots" , "daily" , "2026-05-29" , "20260529T100000.000000000Z_daily.modules.json" ) ,
filepath . Join ( "snapshots" , "daily" , "2026-05-29" , "20260529T100000.000000000Z_daily.metadata.json" ) ,
filepath . Join ( "data-packages" , "daily" , "2026-05-29" , "20260529T100000.000000000Z_daily.data_package.yaml" ) ,
filepath . Join ( "preflight" , "daily" , "2026-05-29" , "20260529T100000.000000000Z_daily.render.json" ) ,
filepath . Join ( "notifications" , "daily" , "2026-05-29" , "20260529T100000.000000000Z_daily.distributor.json" ) ,
filepath . Join ( "reports" , "daily" , "20260529T100000.000000000Z_daily.md" ) ,
2026-05-29 17:40:38 +00:00
} {
if ! strings . Contains ( pathsString ( paths ) , want ) {
t . Fatalf ( "paths = %#v, want component %q" , paths , want )
}
}
}
2026-06-14 04:51:52 +00:00
func TestGeneratedTextArtifactPathsUseSnapshotTree ( t * testing . T ) {
store := newTestStore ( t )
2026-06-14 23:49:18 +00:00
tests := [ ] struct {
name string
resolved report . Resolved
group string
validDate string
runID string
} {
2026-06-15 16:43:06 +00:00
{
name : "daily" ,
resolved : resolveDailyAt ( t , "2026-05-29T05:00:00-05:00" ) ,
group : "daily" ,
validDate : "2026-05-29" ,
runID : "20260529T100000.000000000Z_daily" ,
} ,
2026-06-14 23:49:18 +00:00
{
name : "hourly" ,
resolved : resolveHourlyAt ( t , "2026-05-29T05:00:00-05:00" ) ,
group : "hourly" ,
validDate : "2026-05-29" ,
runID : "20260529T100000.000000000Z_hourly" ,
} ,
{
name : "tomorrow" ,
resolved : resolveTomorrowAt ( t , "2026-05-29T18:00:00-05:00" ) ,
group : "tomorrow" ,
validDate : "2026-05-30" ,
runID : "20260529T230000.000000000Z_tomorrow" ,
} ,
2026-06-15 14:54:37 +00:00
{
name : "today" ,
resolved : resolveTodayAt ( t , "2026-05-29T05:00:00-05:00" ) ,
group : "today" ,
validDate : "2026-05-29" ,
runID : "20260529T100000.000000000Z_today" ,
} ,
2026-06-14 04:51:52 +00:00
}
2026-06-14 23:49:18 +00:00
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
paths , err := store . Paths ( tt . resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
wants := map [ string ] string {
"DataPackage" : filepath . Join ( "data-packages" , tt . group , tt . validDate , tt . runID + ".data_package.yaml" ) ,
"RenderedReport" : filepath . Join ( "reports" , tt . group , tt . runID + ".md" ) ,
"GeneratedTextRaw" : filepath . Join ( "snapshots" , tt . group , tt . validDate , tt . runID + ".generated_text.raw.json" ) ,
"GeneratedTextResult" : filepath . Join ( "snapshots" , tt . group , tt . validDate , tt . runID + ".generated_text.run.json" ) ,
"GeneratedText" : filepath . Join ( "snapshots" , tt . group , tt . validDate , tt . runID + ".generated_text.json" ) ,
"RenderContext" : filepath . Join ( "snapshots" , tt . group , tt . validDate , tt . runID + ".render_context.json" ) ,
}
got := map [ string ] string {
"DataPackage" : paths . DataPackage ,
"RenderedReport" : paths . RenderedReport ,
"GeneratedTextRaw" : paths . GeneratedTextRaw ,
"GeneratedTextResult" : paths . GeneratedTextResult ,
"GeneratedText" : paths . GeneratedText ,
"RenderContext" : paths . RenderContext ,
}
for name , want := range wants {
if ! strings . Contains ( got [ name ] , want ) {
t . Fatalf ( "%s path = %q, want component %q" , name , got [ name ] , want )
}
}
} )
2026-06-14 04:51:52 +00:00
}
}
2026-05-29 17:40:38 +00:00
func TestSaveArtifactsAndMetadataRoundTrip ( t * testing . T ) {
store := newTestStore ( t )
resolved := resolveDailyAt ( t , "2026-05-29T05:00:00-05:00" )
2026-06-09 21:33:34 +00:00
briefingMetadata := stateBriefingMetadata ( resolved )
2026-06-15 20:23:02 +00:00
snapshot , err := module . NewSnapshot ( [ ] module . Output { {
ID : module . Metadata ,
StanzaName : "metadata" ,
Value : map [ string ] string { "run_id" : resolved . Metadata ( ) . RunID } ,
PromptValue : map [ string ] string { "prompt_run_id" : resolved . Metadata ( ) . RunID } ,
} } )
2026-06-09 21:08:17 +00:00
if err != nil {
t . Fatalf ( "NewSnapshot() error = %v" , err )
}
dataPackage , err := promptinput . Build ( promptinput . BuildRequest {
Metadata : promptinput . Metadata {
RunID : resolved . Metadata ( ) . RunID ,
ReportID : resolved . Definition . ID ,
2026-06-09 21:33:34 +00:00
Variant : briefingMetadata . Variant ,
2026-06-09 21:08:17 +00:00
PromptID : resolved . Definition . PromptID ,
GeneratedAt : resolved . GeneratedAt ,
Timezone : resolved . Timezone ,
ValidPeriod : resolved . ValidPeriod ,
} ,
Modules : snapshot ,
} )
2026-05-29 17:40:38 +00:00
if err != nil {
t . Fatalf ( "Build() error = %v" , err )
}
dataPackagePath , err := store . SaveDataPackage ( context . Background ( ) , resolved , dataPackage )
if err != nil {
t . Fatalf ( "SaveDataPackage() error = %v" , err )
}
2026-06-09 20:58:35 +00:00
moduleSnapshotPath , err := store . SaveModuleSnapshot ( context . Background ( ) , resolved , snapshot )
if err != nil {
t . Fatalf ( "SaveModuleSnapshot() error = %v" , err )
}
2026-05-29 20:33:35 +00:00
preflightPath , err := store . SavePreflight ( context . Background ( ) , resolved , PreflightArtifact { Stdout : ` { "ok":true} ` } )
2026-05-29 17:40:38 +00:00
if err != nil {
t . Fatalf ( "SavePreflight() error = %v" , err )
}
2026-06-07 20:21:26 -05:00
notificationPath , err := store . SaveDistributorNotification ( context . Background ( ) , resolved , DistributorNotificationArtifact {
RunID : resolved . Metadata ( ) . RunID ,
ReportID : resolved . Definition . ID ,
AttemptedAt : resolved . GeneratedAt ,
Endpoint : "https://distributor.example.test" ,
2026-06-08 07:04:31 -05:00
PipelineID : "weatherreporter.daily" ,
2026-06-07 20:21:26 -05:00
BundleID : "weatherreporter.home.daily.run" ,
IdempotencyKey : "weatherreporter.home.daily.run" ,
SourcePath : "/tmp/report.md" ,
2026-06-09 11:26:50 -05:00
BundlePaths : [ ] string { "2026-05-29/daily/report.md" } ,
2026-06-07 20:21:26 -05:00
BundleCreated : resolved . GeneratedAt ,
Status : "succeeded" ,
RunStatus : & DistributorRunStatus { RunID : "distributor-run" , Status : "succeeded" } ,
} )
if err != nil {
t . Fatalf ( "SaveDistributorNotification() error = %v" , err )
}
2026-05-29 17:47:42 +00:00
renderedReportPath , err := store . PrepareRenderedReport ( context . Background ( ) , resolved )
if err != nil {
t . Fatalf ( "PrepareRenderedReport() error = %v" , err )
}
if err := os . WriteFile ( renderedReportPath , [ ] byte ( "# Daily Report\n" ) , 0 o600 ) ; err != nil {
t . Fatalf ( "write rendered report: %v" , err )
}
2026-05-29 20:33:35 +00:00
var preflight PreflightArtifact
preflightData , err := os . ReadFile ( preflightPath )
if err != nil {
t . Fatalf ( "read preflight: %v" , err )
}
if err := json . Unmarshal ( preflightData , & preflight ) ; err != nil {
t . Fatalf ( "decode preflight: %v" , err )
}
if preflight . Stdout != ` { "ok":true} ` {
t . Fatalf ( "preflight stdout = %q, want render stdout" , preflight . Stdout )
}
2026-06-07 20:21:26 -05:00
var notification DistributorNotificationArtifact
notificationData , err := os . ReadFile ( notificationPath )
if err != nil {
t . Fatalf ( "read notification: %v" , err )
}
if err := json . Unmarshal ( notificationData , & notification ) ; err != nil {
t . Fatalf ( "decode notification: %v" , err )
}
2026-06-09 11:26:50 -05:00
if notification . SchemaVersion != DistributorNotificationSchemaVersion || notification . PipelineID != "weatherreporter.daily" || len ( notification . BundlePaths ) != 1 || notification . RunStatus == nil || notification . RunStatus . Status != "succeeded" {
2026-06-07 20:21:26 -05:00
t . Fatalf ( "notification = %#v, want persisted distributor status" , notification )
}
2026-05-29 17:40:38 +00:00
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
2026-06-09 21:33:34 +00:00
metadata := BuildMetadataFromBriefingMetadata ( resolved , briefingMetadata , ArtifactPaths {
2026-06-09 20:58:35 +00:00
ModuleSnapshot : moduleSnapshotPath ,
2026-05-29 17:40:38 +00:00
Metadata : paths . Metadata ,
DataPackage : dataPackagePath ,
Preflight : preflightPath ,
2026-05-29 17:47:42 +00:00
RenderedReport : renderedReportPath ,
2026-05-29 17:40:38 +00:00
} )
metadataPath , err := store . SaveMetadata ( context . Background ( ) , metadata )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
2026-06-09 21:33:34 +00:00
for _ , path := range [ ] string { moduleSnapshotPath , dataPackagePath , preflightPath , notificationPath , renderedReportPath , metadataPath } {
2026-05-29 17:40:38 +00:00
if _ , err := os . Stat ( path ) ; err != nil {
t . Fatalf ( "expected artifact %q: %v" , path , err )
}
}
2026-06-09 20:58:35 +00:00
loadedSnapshot , err := store . LoadModuleSnapshot ( context . Background ( ) , moduleSnapshotPath )
if err != nil {
t . Fatalf ( "LoadModuleSnapshot() error = %v" , err )
}
if loadedSnapshot . SchemaVersion != module . SnapshotSchemaVersion || len ( loadedSnapshot . Outputs ) != 1 {
t . Fatalf ( "loaded module snapshot = %#v, want one metadata output" , loadedSnapshot )
}
2026-06-15 20:23:02 +00:00
if loadedSnapshot . Outputs [ 0 ] . PromptValue != nil {
t . Fatalf ( "loaded module snapshot PromptValue = %#v, want omitted runtime value" , loadedSnapshot . Outputs [ 0 ] . PromptValue )
}
snapshotData , err := os . ReadFile ( moduleSnapshotPath )
if err != nil {
t . Fatalf ( "read module snapshot: %v" , err )
}
if strings . Contains ( string ( snapshotData ) , "prompt_run_id" ) || strings . Contains ( string ( snapshotData ) , "promptValue" ) || strings . Contains ( string ( snapshotData ) , "PromptValue" ) {
t . Fatalf ( "module snapshot JSON includes runtime-only prompt value:\n%s" , string ( snapshotData ) )
}
2026-06-09 21:08:17 +00:00
loadedDataPackage , err := store . LoadDataPackage ( context . Background ( ) , dataPackagePath )
if err != nil {
t . Fatalf ( "LoadDataPackage() error = %v" , err )
}
if loadedDataPackage . SchemaVersion != promptinput . SchemaVersion || loadedDataPackage . Briefing . Order [ 0 ] != "metadata" {
t . Fatalf ( "loaded data package = %#v, want YAML package with metadata stanza" , loadedDataPackage )
}
2026-06-15 20:27:28 +00:00
metadataStanza , ok := loadedDataPackage . Briefing . Values [ "metadata" ] . ( map [ string ] any )
if ! ok || metadataStanza [ "prompt_run_id" ] != resolved . Metadata ( ) . RunID {
t . Fatalf ( "loaded data package metadata = %#v, want runtime prompt value" , loadedDataPackage . Briefing . Values [ "metadata" ] )
}
2026-05-29 17:40:38 +00:00
var decoded Metadata
data , err := os . ReadFile ( metadataPath )
if err != nil {
t . Fatalf ( "read metadata: %v" , err )
}
if err := json . Unmarshal ( data , & decoded ) ; err != nil {
t . Fatalf ( "decode metadata: %v" , err )
}
if decoded . RunID != resolved . Metadata ( ) . RunID {
t . Fatalf ( "RunID = %q, want %q" , decoded . RunID , resolved . Metadata ( ) . RunID )
}
2026-06-09 21:33:34 +00:00
if decoded . ModuleSnapshotPath != moduleSnapshotPath || decoded . DataPackagePath != dataPackagePath || decoded . PreflightPath != preflightPath {
2026-05-29 17:40:38 +00:00
t . Fatalf ( "metadata paths = %#v, want saved artifact paths" , decoded )
}
2026-05-29 17:47:42 +00:00
if decoded . RenderedReportPath != renderedReportPath {
t . Fatalf ( "RenderedReportPath = %q, want %q" , decoded . RenderedReportPath , renderedReportPath )
}
2026-05-29 19:57:08 -05:00
if decoded . Location == nil || decoded . Location . Name != "Brentwood" || decoded . Location . Timezone != "America/Chicago" {
t . Fatalf ( "metadata location = %#v, want briefing location" , decoded . Location )
}
2026-05-29 18:25:38 -05:00
if strings . Contains ( string ( data ) , "MetadataPath" ) || strings . Contains ( string ( data ) , "metadataPath" ) {
t . Fatalf ( "metadata JSON includes runtime-only MetadataPath:\n%s" , string ( data ) )
}
2026-06-15 16:43:06 +00:00
if decoded . GeneratedTextSchemaID != "daily" {
t . Fatalf ( "GeneratedTextSchemaID = %q, want daily" , decoded . GeneratedTextSchemaID )
}
for _ , unexpected := range [ ] string { "generatedTextRawPath" , "generatedTextResultPath" , "generatedTextPath" , "renderContextPath" } {
2026-06-14 04:51:52 +00:00
if strings . Contains ( string ( data ) , unexpected ) {
2026-06-15 16:43:06 +00:00
t . Fatalf ( "metadata JSON includes unsaved generated-text path field %q:\n%s" , unexpected , string ( data ) )
2026-06-14 04:51:52 +00:00
}
}
}
func TestSaveGeneratedTextArtifactsAndMetadataRoundTrip ( t * testing . T ) {
store := newTestStore ( t )
resolved := resolveHourlyAt ( t , "2026-05-29T05:00:00-05:00" )
briefingMetadata := stateBriefingMetadata ( resolved )
rawPath , err := store . SaveGeneratedTextRaw ( context . Background ( ) , resolved , [ ] byte ( ` { "summary":"raw"} ` ) )
if err != nil {
t . Fatalf ( "SaveGeneratedTextRaw() error = %v" , err )
}
resultPath , err := store . SaveGeneratedTextResult ( context . Background ( ) , resolved , map [ string ] any {
"command" : [ ] string { "scriptorium" , "run" } ,
"status" : "succeeded" ,
} )
if err != nil {
t . Fatalf ( "SaveGeneratedTextResult() error = %v" , err )
}
2026-06-14 12:54:32 -05:00
generatedPath , err := store . SaveGeneratedText ( context . Background ( ) , resolved , [ ] byte ( ` { "summary":"Storm chances increase.","forecast_discussion":"A front will keep the region unsettled."} ` ) )
2026-06-14 04:51:52 +00:00
if err != nil {
t . Fatalf ( "SaveGeneratedText() error = %v" , err )
}
contextPath , err := store . SaveRenderContext ( context . Background ( ) , resolved , struct {
ReportTitle string ` json:"reportTitle" `
Location string ` json:"location" `
} {
ReportTitle : "Hourly Report" ,
Location : "Brentwood" ,
} )
if err != nil {
t . Fatalf ( "SaveRenderContext() error = %v" , err )
}
for _ , path := range [ ] string { rawPath , resultPath , generatedPath , contextPath } {
if _ , err := os . Stat ( path ) ; err != nil {
t . Fatalf ( "expected generated-text artifact %q: %v" , path , err )
}
}
rawData , err := store . LoadGeneratedText ( context . Background ( ) , rawPath )
if err != nil {
t . Fatalf ( "LoadGeneratedText() raw error = %v" , err )
}
if string ( rawData ) != ` { "summary":"raw"} ` {
t . Fatalf ( "raw data = %q, want saved bytes" , rawData )
}
generatedData , err := store . LoadGeneratedText ( context . Background ( ) , generatedPath )
if err != nil {
t . Fatalf ( "LoadGeneratedText() generated error = %v" , err )
}
2026-06-14 12:54:32 -05:00
if ! strings . Contains ( string ( generatedData ) , ` "forecast_discussion":"A front will keep the region unsettled." ` ) {
2026-06-14 04:51:52 +00:00
t . Fatalf ( "generated text = %q, want saved normalized JSON" , generatedData )
}
var runResult struct {
Command [ ] string ` json:"command" `
Status string ` json:"status" `
}
if err := store . LoadGeneratedTextResult ( context . Background ( ) , resultPath , & runResult ) ; err != nil {
t . Fatalf ( "LoadGeneratedTextResult() error = %v" , err )
}
if runResult . Status != "succeeded" || strings . Join ( runResult . Command , " " ) != "scriptorium run" {
t . Fatalf ( "run result = %#v, want saved result" , runResult )
}
var renderContext struct {
ReportTitle string ` json:"reportTitle" `
Location string ` json:"location" `
}
if err := store . LoadRenderContext ( context . Background ( ) , contextPath , & renderContext ) ; err != nil {
t . Fatalf ( "LoadRenderContext() error = %v" , err )
}
if renderContext . ReportTitle != "Hourly Report" || renderContext . Location != "Brentwood" {
t . Fatalf ( "render context = %#v, want saved context" , renderContext )
}
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
metadata := BuildMetadataFromBriefingMetadata ( resolved , briefingMetadata , ArtifactPaths {
ModuleSnapshot : paths . ModuleSnapshot ,
Metadata : paths . Metadata ,
DataPackage : paths . DataPackage ,
Preflight : paths . Preflight ,
RenderedReport : paths . RenderedReport ,
GeneratedTextRaw : rawPath ,
GeneratedTextResult : resultPath ,
GeneratedText : generatedPath ,
RenderContext : contextPath ,
} )
metadataPath , err := store . SaveMetadata ( context . Background ( ) , metadata )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
var decoded Metadata
metadataData , err := os . ReadFile ( metadataPath )
if err != nil {
t . Fatalf ( "read metadata: %v" , err )
}
if err := json . Unmarshal ( metadataData , & decoded ) ; err != nil {
t . Fatalf ( "decode metadata: %v" , err )
}
if decoded . GeneratedTextSchemaID != "hourly" {
t . Fatalf ( "GeneratedTextSchemaID = %q, want hourly" , decoded . GeneratedTextSchemaID )
}
if decoded . GeneratedTextRawPath != rawPath || decoded . GeneratedTextResultPath != resultPath || decoded . GeneratedTextPath != generatedPath || decoded . RenderContextPath != contextPath {
t . Fatalf ( "metadata generated-text paths = %#v, want saved artifact paths" , decoded )
}
2026-05-29 18:25:38 -05:00
}
func TestSaveMetadataUsesExplicitMetadataPath ( t * testing . T ) {
store := newTestStore ( t )
resolved := resolveDailyAt ( t , "2026-05-29T05:00:00-05:00" )
2026-06-09 21:33:34 +00:00
briefingMetadata := stateBriefingMetadata ( resolved )
2026-05-29 18:25:38 -05:00
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
}
otherDir := filepath . Join ( t . TempDir ( ) , "other-artifacts" )
derivedMetadataPath := filepath . Join ( otherDir , resolved . Metadata ( ) . RunID + ".metadata.json" )
2026-06-09 21:33:34 +00:00
metadata := BuildMetadataFromBriefingMetadata ( resolved , briefingMetadata , ArtifactPaths {
2026-06-09 20:58:35 +00:00
ModuleSnapshot : paths . ModuleSnapshot ,
2026-05-29 18:25:38 -05:00
Metadata : paths . Metadata ,
DataPackage : paths . DataPackage ,
Preflight : paths . Preflight ,
RenderedReport : paths . RenderedReport ,
} )
metadataPath , err := store . SaveMetadata ( context . Background ( ) , metadata )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
}
if metadataPath != paths . Metadata {
t . Fatalf ( "SaveMetadata() path = %q, want explicit metadata path %q" , metadataPath , paths . Metadata )
}
if _ , err := os . Stat ( paths . Metadata ) ; err != nil {
t . Fatalf ( "expected explicit metadata path %q: %v" , paths . Metadata , err )
}
if _ , err := os . Stat ( derivedMetadataPath ) ; ! os . IsNotExist ( err ) {
t . Fatalf ( "derived metadata path stat error = %v, want not exist" , err )
}
2026-05-29 17:40:38 +00:00
}
2026-05-29 20:44:25 +00:00
func TestFindPriorSnapshot ( t * testing . T ) {
2026-05-29 17:40:38 +00:00
store := newTestStore ( t )
first := resolveDailyAt ( t , "2026-05-29T05:00:00-05:00" )
second := resolveDailyAt ( t , "2026-05-29T08:00:00-05:00" )
2026-06-09 21:33:34 +00:00
paths := savePriorMetadata ( t , store , first , stateBriefingMetadata ( first ) )
2026-05-29 17:40:38 +00:00
2026-05-29 20:44:25 +00:00
prior , err := store . FindPriorSnapshot ( context . Background ( ) , second )
2026-05-29 17:40:38 +00:00
if err != nil {
2026-05-29 20:44:25 +00:00
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
2026-05-29 17:40:38 +00:00
}
if prior == nil {
2026-05-29 20:44:25 +00:00
t . Fatal ( "FindPriorSnapshot() = nil, want prior snapshot" )
2026-05-29 17:40:38 +00:00
}
if prior . Metadata . RunID != first . Metadata ( ) . RunID {
t . Fatalf ( "RunID = %q, want %q" , prior . Metadata . RunID , first . Metadata ( ) . RunID )
}
2026-06-09 21:20:53 +00:00
if prior . ModuleSnapshotPath != paths . ModuleSnapshot {
t . Fatalf ( "ModuleSnapshotPath = %q, want %q" , prior . ModuleSnapshotPath , paths . ModuleSnapshot )
}
2026-05-29 17:40:38 +00:00
}
2026-06-15 14:54:37 +00:00
func TestFindPriorSnapshotSupportsToday ( t * testing . T ) {
store := newTestStore ( t )
first := resolveTodayAt ( t , "2026-05-29T05:00:00-05:00" )
second := resolveTodayAt ( t , "2026-05-29T08:00:00-05:00" )
savePriorMetadata ( t , store , first , stateBriefingMetadata ( first ) )
prior , err := store . FindPriorSnapshot ( context . Background ( ) , second )
if err != nil {
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
}
if prior == nil {
t . Fatal ( "FindPriorSnapshot() = nil, want prior Today snapshot" )
}
if prior . Metadata . RunID != first . Metadata ( ) . RunID {
t . Fatalf ( "RunID = %q, want %q" , prior . Metadata . RunID , first . Metadata ( ) . RunID )
}
}
2026-05-29 20:44:25 +00:00
func TestFindPriorSnapshotUsesValidDate ( t * testing . T ) {
2026-05-29 17:53:03 +00:00
store := newTestStore ( t )
previousDate := resolveDailyAt ( t , "2026-05-28T05:00:00-05:00" )
currentDate := resolveDailyAt ( t , "2026-05-29T05:00:00-05:00" )
2026-06-09 21:33:34 +00:00
savePriorMetadata ( t , store , previousDate , stateBriefingMetadata ( previousDate ) )
2026-05-29 17:53:03 +00:00
2026-05-29 20:44:25 +00:00
prior , err := store . FindPriorSnapshot ( context . Background ( ) , currentDate )
2026-05-29 17:53:03 +00:00
if err != nil {
2026-05-29 20:44:25 +00:00
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
2026-05-29 17:53:03 +00:00
}
if prior != nil {
2026-05-29 20:44:25 +00:00
t . Fatalf ( "FindPriorSnapshot() = %#v, want nil for different valid date" , prior )
2026-05-29 17:53:03 +00:00
}
}
2026-05-29 18:08:50 +00:00
func TestFindPriorSnapshotSupportsThreeDay ( t * testing . T ) {
store := newTestStore ( t )
first := resolveThreeDayAt ( t , "2026-05-29T05:00:00-05:00" )
second := resolveThreeDayAt ( t , "2026-05-29T08:00:00-05:00" )
2026-06-09 21:33:34 +00:00
savePriorMetadata ( t , store , first , stateBriefingMetadata ( first ) )
2026-05-29 18:08:50 +00:00
prior , err := store . FindPriorSnapshot ( context . Background ( ) , second )
if err != nil {
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
}
if prior == nil {
t . Fatal ( "FindPriorSnapshot() = nil, want prior 3-day snapshot" )
}
if prior . Metadata . RunID != first . Metadata ( ) . RunID {
t . Fatalf ( "RunID = %q, want %q" , prior . Metadata . RunID , first . Metadata ( ) . RunID )
}
}
2026-05-29 18:16:10 +00:00
func TestFindPriorSnapshotSupportsWeekend ( t * testing . T ) {
store := newTestStore ( t )
first := resolveWeekendAt ( t , "2026-05-29T05:00:00-05:00" )
second := resolveWeekendAt ( t , "2026-05-29T08:00:00-05:00" )
2026-06-09 21:33:34 +00:00
savePriorMetadata ( t , store , first , stateBriefingMetadata ( first ) )
2026-05-29 18:16:10 +00:00
prior , err := store . FindPriorSnapshot ( context . Background ( ) , second )
if err != nil {
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
}
if prior == nil {
t . Fatal ( "FindPriorSnapshot() = nil, want prior weekend snapshot" )
}
if prior . Metadata . RunID != first . Metadata ( ) . RunID {
t . Fatalf ( "RunID = %q, want %q" , prior . Metadata . RunID , first . Metadata ( ) . RunID )
}
}
func TestFindPriorSnapshotSupportsNarrowedWeekendPeriod ( t * testing . T ) {
store := newTestStore ( t )
first := resolveWeekendAt ( t , "2026-05-29T19:00:00-05:00" )
second := resolveWeekendAt ( t , "2026-05-30T08:00:00-05:00" )
2026-06-09 21:33:34 +00:00
savePriorMetadata ( t , store , first , stateBriefingMetadata ( first ) )
2026-05-29 18:16:10 +00:00
prior , err := store . FindPriorSnapshot ( context . Background ( ) , second )
if err != nil {
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
}
if prior == nil {
t . Fatal ( "FindPriorSnapshot() = nil, want prior narrowed weekend snapshot" )
}
if prior . Metadata . RunID != first . Metadata ( ) . RunID {
t . Fatalf ( "RunID = %q, want %q" , prior . Metadata . RunID , first . Metadata ( ) . RunID )
}
}
2026-06-12 17:28:26 +00:00
func TestFindPriorSnapshotIgnoresRollingWindowReports ( t * testing . T ) {
store := newTestStore ( t )
2026-06-14 04:26:05 +00:00
first := resolveHourlyAt ( t , "2026-05-29T05:00:00-05:00" )
second := resolveHourlyAt ( t , "2026-05-29T06:00:00-05:00" )
2026-06-12 17:28:26 +00:00
savePriorMetadata ( t , store , first , stateBriefingMetadata ( first ) )
prior , err := store . FindPriorSnapshot ( context . Background ( ) , second )
if err != nil {
t . Fatalf ( "FindPriorSnapshot() error = %v" , err )
}
if prior != nil {
t . Fatalf ( "FindPriorSnapshot() = %#v, want nil for rolling-window comparison" , prior )
}
}
2026-05-29 17:40:38 +00:00
func TestFilesystemStoreRejectsUnsafeDirs ( t * testing . T ) {
cfg := config . Defaults ( ) . Workspace
cfg . Root = t . TempDir ( )
cfg . SnapshotsDir = "../snapshots"
_ , err := NewFilesystemStore ( cfg )
if err == nil {
t . Fatal ( "NewFilesystemStore() error = nil, want unsafe path error" )
}
if ! strings . Contains ( err . Error ( ) , "within workspace root" ) {
t . Fatalf ( "error = %q, want path safety context" , err . Error ( ) )
}
}
func newTestStore ( t * testing . T ) * FilesystemStore {
t . Helper ( )
cfg := config . Defaults ( ) . Workspace
cfg . Root = t . TempDir ( )
store , err := NewFilesystemStore ( cfg )
if err != nil {
t . Fatalf ( "NewFilesystemStore() error = %v" , err )
}
return store
}
func resolveDailyAt ( t * testing . T , value string ) report . Resolved {
t . Helper ( )
location , err := timeutil . LoadLocation ( "America/Chicago" )
if err != nil {
t . Fatalf ( "LoadLocation() error = %v" , err )
}
now , err := time . Parse ( time . RFC3339 , value )
if err != nil {
t . Fatalf ( "parse time: %v" , err )
}
2026-06-15 16:43:06 +00:00
resolved , err := report . DefaultRegistry ( ) . Resolve ( report . Daily , report . ResolveRequest {
2026-05-29 17:40:38 +00:00
Now : now ,
Location : location ,
2026-06-15 16:43:06 +00:00
Date : now ,
2026-05-29 17:40:38 +00:00
} )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return resolved
}
2026-06-15 14:54:37 +00:00
func resolveTodayAt ( t * testing . T , value string ) report . Resolved {
t . Helper ( )
location , err := timeutil . LoadLocation ( "America/Chicago" )
if err != nil {
t . Fatalf ( "LoadLocation() error = %v" , err )
}
now , err := time . Parse ( time . RFC3339 , value )
if err != nil {
t . Fatalf ( "parse time: %v" , err )
}
resolved , err := report . DefaultRegistry ( ) . Resolve ( report . Today , report . ResolveRequest {
Now : now ,
Location : location ,
} )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return resolved
}
2026-05-29 18:08:50 +00:00
func resolveThreeDayAt ( t * testing . T , value string ) report . Resolved {
t . Helper ( )
location , err := timeutil . LoadLocation ( "America/Chicago" )
if err != nil {
t . Fatalf ( "LoadLocation() error = %v" , err )
}
now , err := time . Parse ( time . RFC3339 , value )
if err != nil {
t . Fatalf ( "parse time: %v" , err )
}
resolved , err := report . DefaultRegistry ( ) . Resolve ( report . ThreeDay , report . ResolveRequest {
Now : now ,
Location : location ,
} )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return resolved
}
2026-05-29 18:16:10 +00:00
func resolveWeekendAt ( t * testing . T , value string ) report . Resolved {
t . Helper ( )
location , err := timeutil . LoadLocation ( "America/Chicago" )
if err != nil {
t . Fatalf ( "LoadLocation() error = %v" , err )
}
now , err := time . Parse ( time . RFC3339 , value )
if err != nil {
t . Fatalf ( "parse time: %v" , err )
}
resolved , err := report . DefaultRegistry ( ) . Resolve ( report . Weekend , report . ResolveRequest {
Now : now ,
Location : location ,
} )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return resolved
}
2026-06-14 04:26:05 +00:00
func resolveHourlyAt ( t * testing . T , value string ) report . Resolved {
2026-06-12 17:28:26 +00:00
t . Helper ( )
location , err := timeutil . LoadLocation ( "America/Chicago" )
if err != nil {
t . Fatalf ( "LoadLocation() error = %v" , err )
}
now , err := time . Parse ( time . RFC3339 , value )
if err != nil {
t . Fatalf ( "parse time: %v" , err )
}
2026-06-14 04:26:05 +00:00
resolved , err := report . DefaultRegistry ( ) . Resolve ( report . Hourly , report . ResolveRequest {
2026-06-12 17:28:26 +00:00
Now : now ,
Location : location ,
} )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return resolved
}
2026-06-14 23:49:18 +00:00
func resolveTomorrowAt ( t * testing . T , value string ) report . Resolved {
t . Helper ( )
location , err := timeutil . LoadLocation ( "America/Chicago" )
if err != nil {
t . Fatalf ( "LoadLocation() error = %v" , err )
}
now , err := time . Parse ( time . RFC3339 , value )
if err != nil {
t . Fatalf ( "parse time: %v" , err )
}
resolved , err := report . DefaultRegistry ( ) . Resolve ( report . Tomorrow , report . ResolveRequest {
Now : now ,
Location : location ,
} )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return resolved
}
2026-06-09 21:33:34 +00:00
func stateBriefingMetadata ( resolved report . Resolved ) briefing . Metadata {
return briefing . Metadata {
RunID : resolved . Metadata ( ) . RunID ,
ReportID : resolved . Definition . ID ,
Variant : "today" ,
PromptID : resolved . Definition . PromptID ,
GeneratedAt : resolved . GeneratedAt ,
Units : "us" ,
Timezone : resolved . Timezone ,
Location : & briefing . LocationContext {
ID : "home" ,
Name : "Brentwood" ,
Region : "St. Louis Metro" ,
Timezone : resolved . Timezone ,
2026-05-29 17:40:38 +00:00
} ,
2026-06-09 21:33:34 +00:00
ValidPeriod : resolved . ValidPeriod ,
2026-05-29 17:40:38 +00:00
}
}
2026-06-09 21:33:34 +00:00
func savePriorMetadata ( t * testing . T , store * FilesystemStore , resolved report . Resolved , metadata briefing . Metadata ) ArtifactPaths {
t . Helper ( )
paths , err := store . Paths ( resolved )
if err != nil {
t . Fatalf ( "Paths() error = %v" , err )
2026-05-29 18:08:50 +00:00
}
2026-06-09 21:33:34 +00:00
_ , err = store . SaveMetadata ( context . Background ( ) , BuildMetadataFromBriefingMetadata ( resolved , metadata , ArtifactPaths {
ModuleSnapshot : paths . ModuleSnapshot ,
Metadata : paths . Metadata ,
DataPackage : paths . DataPackage ,
Preflight : paths . Preflight ,
RenderedReport : paths . RenderedReport ,
} ) )
if err != nil {
t . Fatalf ( "SaveMetadata() error = %v" , err )
2026-05-29 18:16:10 +00:00
}
2026-06-09 21:33:34 +00:00
return paths
2026-05-29 18:16:10 +00:00
}
2026-05-29 17:40:38 +00:00
func pathsString ( paths ArtifactPaths ) string {
return strings . Join ( [ ] string {
paths . Metadata ,
2026-06-09 20:58:35 +00:00
paths . ModuleSnapshot ,
2026-05-29 17:40:38 +00:00
paths . DataPackage ,
paths . Preflight ,
2026-06-07 20:21:26 -05:00
paths . Notification ,
2026-05-29 17:40:38 +00:00
paths . RenderedReport ,
2026-06-14 04:51:52 +00:00
paths . GeneratedTextRaw ,
paths . GeneratedTextResult ,
paths . GeneratedText ,
paths . RenderContext ,
2026-05-29 17:40:38 +00:00
} , "\n" )
}