444 lines
14 KiB
Go
444 lines
14 KiB
Go
package state
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/scriptorium"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
|
"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{
|
|
filepath.Join("snapshots", "daily", "2026-05-29", "20260529T100000.000000000Z_daily_today.briefing.json"),
|
|
filepath.Join("snapshots", "daily", "2026-05-29", "20260529T100000.000000000Z_daily_today.metadata.json"),
|
|
filepath.Join("data-packages", "daily", "2026-05-29", "20260529T100000.000000000Z_daily_today.data_package.json"),
|
|
filepath.Join("preflight", "daily", "2026-05-29", "20260529T100000.000000000Z_daily_today.render.json"),
|
|
filepath.Join("reports", "daily", "20260529T100000.000000000Z_daily_today.md"),
|
|
} {
|
|
if !strings.Contains(pathsString(paths), want) {
|
|
t.Fatalf("paths = %#v, want component %q", paths, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSaveArtifactsAndMetadataRoundTrip(t *testing.T) {
|
|
store := newTestStore(t)
|
|
resolved := resolveDailyAt(t, "2026-05-29T05:00:00-05:00")
|
|
briefingPackage := stateBriefingPackage(resolved)
|
|
dataPackage, err := promptinput.Build(briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("Build() error = %v", err)
|
|
}
|
|
|
|
briefingPath, err := store.SaveBriefing(context.Background(), resolved, briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveBriefing() error = %v", err)
|
|
}
|
|
dataPackagePath, err := store.SaveDataPackage(context.Background(), resolved, dataPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveDataPackage() error = %v", err)
|
|
}
|
|
preflightPath, err := store.SavePreflight(context.Background(), resolved, &scriptorium.RenderResult{Stdout: `{"ok":true}`})
|
|
if err != nil {
|
|
t.Fatalf("SavePreflight() error = %v", err)
|
|
}
|
|
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"), 0o600); err != nil {
|
|
t.Fatalf("write rendered report: %v", err)
|
|
}
|
|
paths, err := store.Paths(resolved)
|
|
if err != nil {
|
|
t.Fatalf("Paths() error = %v", err)
|
|
}
|
|
metadata := BuildMetadata(resolved, briefingPackage, ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: dataPackagePath,
|
|
Preflight: preflightPath,
|
|
RenderedReport: renderedReportPath,
|
|
})
|
|
metadataPath, err := store.SaveMetadata(context.Background(), metadata)
|
|
if err != nil {
|
|
t.Fatalf("SaveMetadata() error = %v", err)
|
|
}
|
|
|
|
for _, path := range []string{briefingPath, dataPackagePath, preflightPath, renderedReportPath, metadataPath} {
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("expected artifact %q: %v", path, err)
|
|
}
|
|
}
|
|
loadedBriefing, err := store.LoadBriefing(context.Background(), briefingPath)
|
|
if err != nil {
|
|
t.Fatalf("LoadBriefing() error = %v", err)
|
|
}
|
|
if loadedBriefing.Metadata.RunID != resolved.Metadata().RunID {
|
|
t.Fatalf("loaded briefing RunID = %q, want %q", loadedBriefing.Metadata.RunID, resolved.Metadata().RunID)
|
|
}
|
|
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)
|
|
}
|
|
if decoded.BriefingPath != briefingPath || decoded.DataPackagePath != dataPackagePath || decoded.PreflightPath != preflightPath {
|
|
t.Fatalf("metadata paths = %#v, want saved artifact paths", decoded)
|
|
}
|
|
if decoded.RenderedReportPath != renderedReportPath {
|
|
t.Fatalf("RenderedReportPath = %q, want %q", decoded.RenderedReportPath, renderedReportPath)
|
|
}
|
|
}
|
|
|
|
func TestFindPriorDailySnapshot(t *testing.T) {
|
|
store := newTestStore(t)
|
|
first := resolveDailyAt(t, "2026-05-29T05:00:00-05:00")
|
|
second := resolveDailyAt(t, "2026-05-29T08:00:00-05:00")
|
|
briefingPackage := stateBriefingPackage(first)
|
|
briefingPath, err := store.SaveBriefing(context.Background(), first, briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveBriefing() error = %v", err)
|
|
}
|
|
paths, err := store.Paths(first)
|
|
if err != nil {
|
|
t.Fatalf("Paths() error = %v", err)
|
|
}
|
|
_, err = store.SaveMetadata(context.Background(), BuildMetadata(first, briefingPackage, ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: paths.DataPackage,
|
|
Preflight: paths.Preflight,
|
|
RenderedReport: paths.RenderedReport,
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("SaveMetadata() error = %v", err)
|
|
}
|
|
|
|
prior, err := store.FindPriorDailySnapshot(context.Background(), second)
|
|
if err != nil {
|
|
t.Fatalf("FindPriorDailySnapshot() error = %v", err)
|
|
}
|
|
if prior == nil {
|
|
t.Fatal("FindPriorDailySnapshot() = nil, want prior snapshot")
|
|
}
|
|
if prior.Metadata.RunID != first.Metadata().RunID {
|
|
t.Fatalf("RunID = %q, want %q", prior.Metadata.RunID, first.Metadata().RunID)
|
|
}
|
|
if prior.BriefingPath != briefingPath {
|
|
t.Fatalf("BriefingPath = %q, want %q", prior.BriefingPath, briefingPath)
|
|
}
|
|
}
|
|
|
|
func TestFindPriorDailySnapshotUsesValidDate(t *testing.T) {
|
|
store := newTestStore(t)
|
|
previousDate := resolveDailyAt(t, "2026-05-28T05:00:00-05:00")
|
|
currentDate := resolveDailyAt(t, "2026-05-29T05:00:00-05:00")
|
|
briefingPackage := stateBriefingPackage(previousDate)
|
|
briefingPath, err := store.SaveBriefing(context.Background(), previousDate, briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveBriefing() error = %v", err)
|
|
}
|
|
paths, err := store.Paths(previousDate)
|
|
if err != nil {
|
|
t.Fatalf("Paths() error = %v", err)
|
|
}
|
|
_, err = store.SaveMetadata(context.Background(), BuildMetadata(previousDate, briefingPackage, ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: paths.DataPackage,
|
|
Preflight: paths.Preflight,
|
|
RenderedReport: paths.RenderedReport,
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("SaveMetadata() error = %v", err)
|
|
}
|
|
|
|
prior, err := store.FindPriorDailySnapshot(context.Background(), currentDate)
|
|
if err != nil {
|
|
t.Fatalf("FindPriorDailySnapshot() error = %v", err)
|
|
}
|
|
if prior != nil {
|
|
t.Fatalf("FindPriorDailySnapshot() = %#v, want nil for different valid date", prior)
|
|
}
|
|
}
|
|
|
|
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")
|
|
briefingPackage := stateThreeDayBriefingPackage(first)
|
|
briefingPath, err := store.SaveBriefing(context.Background(), first, briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveBriefing() error = %v", err)
|
|
}
|
|
paths, err := store.Paths(first)
|
|
if err != nil {
|
|
t.Fatalf("Paths() error = %v", err)
|
|
}
|
|
_, err = store.SaveMetadata(context.Background(), BuildMetadata(first, briefingPackage, ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: paths.DataPackage,
|
|
Preflight: paths.Preflight,
|
|
RenderedReport: paths.RenderedReport,
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("SaveMetadata() error = %v", err)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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")
|
|
briefingPackage := stateWeekendBriefingPackage(first)
|
|
briefingPath, err := store.SaveBriefing(context.Background(), first, briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveBriefing() error = %v", err)
|
|
}
|
|
paths, err := store.Paths(first)
|
|
if err != nil {
|
|
t.Fatalf("Paths() error = %v", err)
|
|
}
|
|
_, err = store.SaveMetadata(context.Background(), BuildMetadata(first, briefingPackage, ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: paths.DataPackage,
|
|
Preflight: paths.Preflight,
|
|
RenderedReport: paths.RenderedReport,
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("SaveMetadata() error = %v", err)
|
|
}
|
|
|
|
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")
|
|
briefingPackage := stateWeekendBriefingPackage(first)
|
|
briefingPath, err := store.SaveBriefing(context.Background(), first, briefingPackage)
|
|
if err != nil {
|
|
t.Fatalf("SaveBriefing() error = %v", err)
|
|
}
|
|
paths, err := store.Paths(first)
|
|
if err != nil {
|
|
t.Fatalf("Paths() error = %v", err)
|
|
}
|
|
_, err = store.SaveMetadata(context.Background(), BuildMetadata(first, briefingPackage, ArtifactPaths{
|
|
Briefing: briefingPath,
|
|
Metadata: paths.Metadata,
|
|
DataPackage: paths.DataPackage,
|
|
Preflight: paths.Preflight,
|
|
RenderedReport: paths.RenderedReport,
|
|
}))
|
|
if err != nil {
|
|
t.Fatalf("SaveMetadata() error = %v", err)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
resolved, err := report.DefaultRegistry().Resolve(report.DailyToday, report.ResolveRequest{
|
|
Now: now,
|
|
Location: location,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
return resolved
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func stateBriefingPackage(resolved report.Resolved) briefing.Package {
|
|
return briefing.Package{
|
|
Metadata: briefing.Metadata{
|
|
SchemaVersion: briefing.SchemaVersion,
|
|
RunID: resolved.Metadata().RunID,
|
|
ReportID: resolved.Definition.ID,
|
|
Variant: "today",
|
|
PromptID: resolved.Definition.PromptID,
|
|
GeneratedAt: resolved.GeneratedAt,
|
|
Units: "us",
|
|
Timezone: resolved.Timezone,
|
|
ValidPeriod: resolved.ValidPeriod,
|
|
},
|
|
Daily: &briefing.Daily{ForecastSummaryDate: "2026-05-29"},
|
|
}
|
|
}
|
|
|
|
func stateThreeDayBriefingPackage(resolved report.Resolved) briefing.Package {
|
|
return briefing.Package{
|
|
Metadata: briefing.Metadata{
|
|
SchemaVersion: briefing.SchemaVersion,
|
|
RunID: resolved.Metadata().RunID,
|
|
ReportID: resolved.Definition.ID,
|
|
PromptID: resolved.Definition.PromptID,
|
|
GeneratedAt: resolved.GeneratedAt,
|
|
Units: "us",
|
|
Timezone: resolved.Timezone,
|
|
ValidPeriod: resolved.ValidPeriod,
|
|
},
|
|
ThreeDay: &briefing.ThreeDay{
|
|
Days: []briefing.OutlookDay{{Date: "2026-05-29"}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func stateWeekendBriefingPackage(resolved report.Resolved) briefing.Package {
|
|
return briefing.Package{
|
|
Metadata: briefing.Metadata{
|
|
SchemaVersion: briefing.SchemaVersion,
|
|
RunID: resolved.Metadata().RunID,
|
|
ReportID: resolved.Definition.ID,
|
|
PromptID: resolved.Definition.PromptID,
|
|
GeneratedAt: resolved.GeneratedAt,
|
|
Units: "us",
|
|
Timezone: resolved.Timezone,
|
|
ValidPeriod: resolved.ValidPeriod,
|
|
},
|
|
Weekend: &briefing.Weekend{
|
|
Days: []briefing.OutlookDay{{Date: "2026-05-30"}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func pathsString(paths ArtifactPaths) string {
|
|
return strings.Join([]string{
|
|
paths.Briefing,
|
|
paths.Metadata,
|
|
paths.DataPackage,
|
|
paths.Preflight,
|
|
paths.RenderedReport,
|
|
}, "\n")
|
|
}
|