104 lines
5.3 KiB
Go
104 lines
5.3 KiB
Go
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/weatherdata"
|
|
)
|
|
|
|
type generationCollector struct {
|
|
bundle *weatherdata.Bundle
|
|
err error
|
|
}
|
|
|
|
func (c generationCollector) Run(context.Context, collect.Request) (*collect.Result, error) {
|
|
return &collect.Result{Bundle: c.bundle}, c.err
|
|
}
|
|
|
|
type generationExecutor struct{ called bool }
|
|
|
|
func (generationExecutor) InspectPrompt(_ context.Context, id, version string) (promptexec.PromptInspection, error) {
|
|
definition := report.DefaultRegistry().MustLookup(report.Daily)
|
|
return promptexec.PromptInspection{PromptID: id, PromptVersion: version, PromptHash: "prompt-hash", DefaultProfileID: "fixture", Inputs: []promptexec.InputDefinition{{Name: "data_package", Required: true, ContentType: "application/yaml"}}, Output: promptexec.OutputContract{Format: "json", ValidationMode: "json_schema", SchemaPath: definition.GeneratedTextSchemaID + ".generated_text.schema.json"}}, nil
|
|
}
|
|
func (generationExecutor) InspectProfile(_ context.Context, id string) (promptexec.ProfileInspection, error) {
|
|
return promptexec.ProfileInspection{ProfileID: id, BackendID: "fixture", ModelName: "fixture-model"}, nil
|
|
}
|
|
func (e *generationExecutor) Execute(_ context.Context, req promptexec.ExecuteRequest, callback promptexec.PreparationCallback) (*promptexec.Execution, error) {
|
|
stamp := 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: "fixture", ModelName: "fixture-model", StartedAt: stamp, EndedAt: stamp}, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
e.called = true
|
|
return &promptexec.Execution{RunID: "provider-run", PromptID: req.PromptID, PromptVersion: req.PromptVersion, PromptHash: "prompt-hash", RenderedPromptHash: "rendered-hash", ProfileID: req.ProfileID, BackendID: "fixture", ModelName: "fixture-model", StartedAt: stamp, EndedAt: stamp, 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."}`), Validation: promptexec.NewValidation(promptexec.ValidationPassed, "json_schema", "daily.generated_text.schema.json", nil)}, nil
|
|
}
|
|
|
|
func TestGenerateDetailedPublishesOnlySelectedOutput(t *testing.T) {
|
|
cfg := config.Defaults()
|
|
cfg.WeatherAPI.Timezone, cfg.Location.ID = "America/Chicago", "home"
|
|
bundle := generationBundle(t)
|
|
executor := &generationExecutor{}
|
|
workingDir := t.TempDir()
|
|
result, err := GenerateDetailed(context.Background(), GenerateRequest{Config: cfg, Report: ReportDaily, Date: generationTime("2026-05-29T12:00:00-05:00"), Now: generationTime("2026-05-29T08:30:00-05:00"), WorkingDir: workingDir, Collector: generationCollector{bundle: &bundle}, Executor: executor})
|
|
if err != nil {
|
|
t.Fatalf("GenerateDetailed() error = %v", err)
|
|
}
|
|
if !executor.called || result.OutputPath != filepath.Join(workingDir, "daily-2026-05-29.md") || result.ValidationStatus != promptexec.ValidationPassed || result.ProfileID == "" || result.BackendID == "" || result.ModelName == "" {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(workingDir, "workspace")); !os.IsNotExist(err) {
|
|
t.Fatalf("workspace state = %v, want absent", err)
|
|
}
|
|
data, err := os.ReadFile(result.OutputPath)
|
|
if err != nil || len(data) == 0 {
|
|
t.Fatalf("output = %q, error = %v", data, err)
|
|
}
|
|
}
|
|
|
|
func TestGenerateDetailedReturnsResolvedResultWhenCollectionFails(t *testing.T) {
|
|
cfg := config.Defaults()
|
|
cfg.WeatherAPI.Timezone, cfg.Location.ID = "America/Chicago", "home"
|
|
collectionErr := errors.New("weather source unavailable")
|
|
result, err := GenerateDetailed(context.Background(), GenerateRequest{
|
|
Config: cfg, Report: ReportDaily, Date: generationTime("2026-05-29T12:00:00-05:00"), Now: generationTime("2026-05-29T08:30:00-05:00"),
|
|
WorkingDir: t.TempDir(), Collector: generationCollector{err: collectionErr}, Executor: &generationExecutor{},
|
|
})
|
|
if !errors.Is(err, collectionErr) {
|
|
t.Fatalf("GenerateDetailed() error = %v, want %v", err, collectionErr)
|
|
}
|
|
if result == nil || result.ReportID != report.Daily || result.RunID == "" || result.ProfileID != "fixture" || result.BackendID != "fixture" || result.ModelName != "fixture-model" || result.OutputPath != "" {
|
|
t.Fatalf("result = %#v", result)
|
|
}
|
|
}
|
|
|
|
func generationBundle(t *testing.T) weatherdata.Bundle {
|
|
t.Helper()
|
|
data, err := os.ReadFile(filepath.Join("..", "forecast", "testdata", "daily_bundle.json"))
|
|
if err != nil {
|
|
t.Fatalf("read bundle fixture: %v", err)
|
|
}
|
|
var bundle weatherdata.Bundle
|
|
if err := json.Unmarshal(data, &bundle); err != nil {
|
|
t.Fatalf("decode bundle fixture: %v", err)
|
|
}
|
|
return bundle
|
|
}
|
|
func generationTime(value string) time.Time {
|
|
parsed, _ := time.Parse(time.RFC3339, value)
|
|
return parsed
|
|
}
|
|
|
|
var _ promptexec.Executor = (*generationExecutor)(nil)
|
|
var _ Collector = generationCollector{}
|
|
var _ = report.Daily
|