Build batch distributor upload requests

This commit is contained in:
2026-06-17 20:48:38 +00:00
parent a82f03feb8
commit 2b3bcdd4f1
3 changed files with 288 additions and 8 deletions

View File

@@ -2330,6 +2330,121 @@ func TestBatchResultJSONIncludesNotification(t *testing.T) {
}
}
func TestBuildBatchNotificationRequestIncludesEveningReports(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyNotificationConfig(t, server)
cfg.Notify.Distributor.ReportPathTemplates = []string{
"archive/{valid_start_date}/{artifact_group}/{run_id}.md",
"latest/{batch_output_name}",
}
cfg.Notify.Distributor.Batch.PipelineIDTemplate = "weatherreporter.{batch}.{batch_started_date}"
cfg.Notify.Distributor.Batch.BundleIDTemplate = "weatherreporter.{location_id}.{batch}"
cfg.Notify.Distributor.Batch.IdempotencyKeyTemplate = "{bundle_id}.{batch_run_id}"
startedAt := mustParse("2026-05-29T18:00:00-05:00")
planned, reports := plannedBatchNotificationReports(t, cfg, BatchEvening, startedAt, "2026-05-31", "2026-06-01")
runID := batchRunID(startedAt, BatchEvening)
req, err := buildBatchNotificationRequest(cfg, BatchEvening, runID, startedAt, reports, planned)
if err != nil {
t.Fatalf("buildBatchNotificationRequest() error = %v", err)
}
if req.Batch != BatchEvening || req.RunID != runID {
t.Fatalf("batch identity = %s/%s, want %s/%s", req.Batch, req.RunID, BatchEvening, runID)
}
if req.PipelineID != "weatherreporter.evening.2026-05-29" {
t.Fatalf("PipelineID = %q, want rendered batch pipeline", req.PipelineID)
}
if req.BundleID != "weatherreporter.home.evening" {
t.Fatalf("BundleID = %q, want rendered batch bundle id", req.BundleID)
}
wantKey := "weatherreporter.home.evening." + runID
if req.IdempotencyKey != wantKey {
t.Fatalf("IdempotencyKey = %q, want %q", req.IdempotencyKey, wantKey)
}
if !req.CreatedAt.Equal(startedAt) {
t.Fatalf("CreatedAt = %s, want %s", req.CreatedAt, startedAt)
}
if len(req.IncludedReports) != 3 {
t.Fatalf("IncludedReports = %d, want 3", len(req.IncludedReports))
}
if len(req.Files) != 6 {
t.Fatalf("Files = %d, want two mappings per report", len(req.Files))
}
wantBundlePaths := map[string]struct{}{}
for _, plannedReport := range planned {
resolved := plannedReport.Resolved
runID := resolved.Metadata().RunID
outputName := plannedReport.OutputCopyName
if outputName == "" {
outputName = resolved.Definition.BatchOutputName
}
wantBundlePaths[fmt.Sprintf("archive/%s/%s/%s.md", resolved.ValidPeriod.Start.In(mustLoadTestLocation(t, cfg.WeatherAPI.Timezone)).Format(timeutil.DateLayout), resolved.Definition.ArtifactGroup, runID)] = struct{}{}
wantBundlePaths["latest/"+outputName] = struct{}{}
}
gotBundlePaths := map[string]struct{}{}
gotSourcePaths := map[string]struct{}{}
for _, file := range req.Files {
gotBundlePaths[file.BundlePath] = struct{}{}
gotSourcePaths[file.SourcePath] = struct{}{}
}
for want := range wantBundlePaths {
if _, ok := gotBundlePaths[want]; !ok {
t.Fatalf("bundle paths = %#v, missing %q", gotBundlePaths, want)
}
}
for _, item := range reports {
if _, ok := gotSourcePaths[item.ReportPath]; !ok {
t.Fatalf("source paths = %#v, missing managed report path %q", gotSourcePaths, item.ReportPath)
}
}
uploadReq := batchDistributorUploadRequest(req)
if uploadReq.PipelineID != req.PipelineID || uploadReq.BundleID != req.BundleID || uploadReq.IdempotencyKey != req.IdempotencyKey || !uploadReq.CreatedAt.Equal(startedAt) {
t.Fatalf("upload request = %#v, want batch notification identity", uploadReq)
}
if len(uploadReq.Files) != len(req.Files) {
t.Fatalf("upload files = %d, want %d", len(uploadReq.Files), len(req.Files))
}
}
func TestBuildBatchNotificationRequestRejectsDuplicateBundlePaths(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyNotificationConfig(t, server)
cfg.Notify.Distributor.ReportPathTemplates = []string{"index.md"}
startedAt := mustParse("2026-05-29T18:00:00-05:00")
planned, reports := plannedBatchNotificationReports(t, cfg, BatchEvening, startedAt, "2026-05-31")
_, err := buildBatchNotificationRequest(cfg, BatchEvening, batchRunID(startedAt, BatchEvening), startedAt, reports, planned)
if err == nil {
t.Fatal("buildBatchNotificationRequest() error = nil, want duplicate path error")
}
for _, want := range []string{"duplicate bundle path", "index.md", "report", "run", "source path"} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("error = %q, want %q", err.Error(), want)
}
}
}
func TestBuildBatchNotificationRequestRejectsMissingReportPath(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyNotificationConfig(t, server)
startedAt := mustParse("2026-05-29T18:00:00-05:00")
planned, reports := plannedBatchNotificationReports(t, cfg, BatchEvening, startedAt, "2026-05-31")
reports[0].ReportPath = ""
_, err := buildBatchNotificationRequest(cfg, BatchEvening, batchRunID(startedAt, BatchEvening), startedAt, reports, planned)
if err == nil {
t.Fatal("buildBatchNotificationRequest() error = nil, want missing path error")
}
for _, want := range []string{"missing managed report path", string(reports[0].ReportID), reports[0].RunID} {
if !strings.Contains(err.Error(), want) {
t.Fatalf("error = %q, want %q", err.Error(), want)
}
}
}
func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyWorkspaceConfig(t, server)
@@ -2712,6 +2827,27 @@ func dailyNotificationConfig(t *testing.T, server *httptest.Server) config.Confi
return cfg
}
func plannedBatchNotificationReports(t *testing.T, cfg config.Config, batch BatchKind, now time.Time, futureDailyDates ...string) ([]plannedBatchReport, []BatchReportResult) {
t.Helper()
collection := collectionWithFutureDailyForTest(t, cfg, futureDailyDates...)
planned, err := planBatchRun(BatchRequest{Config: cfg, Batch: batch}, now, collection)
if err != nil {
t.Fatalf("planBatchRun() error = %v", err)
}
reportDir := filepath.Join(t.TempDir(), "managed-reports")
results := make([]BatchReportResult, 0, len(planned))
for _, item := range planned {
metadata := item.Resolved.Metadata()
results = append(results, BatchReportResult{
ReportID: item.Resolved.Definition.ID,
RunID: metadata.RunID,
Status: "succeeded",
ReportPath: filepath.Join(reportDir, string(item.Resolved.Definition.ID), metadata.RunID+".md"),
})
}
return planned, results
}
func collectionForTest(t *testing.T, cfg config.Config) collect.Result {
t.Helper()
bundle, err := FetchBundle(context.Background(), FetchBundleRequest{Config: cfg})