Finalize and close the distributor report path refactor roadmap
This commit is contained in:
@@ -1,126 +0,0 @@
|
|||||||
# Distributor Report Paths Roadmap
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This roadmap defines the target behavior for naming Markdown reports inside
|
|
||||||
distributor source bundles.
|
|
||||||
|
|
||||||
Weatherreporter currently uses one application-level
|
|
||||||
`notify.distributor.report_path_templates` list for every report type. That
|
|
||||||
model is too coarse for the intended output layout because different reports
|
|
||||||
need different archive paths, latest paths, and report-specific aliases.
|
|
||||||
|
|
||||||
## Locked Decisions
|
|
||||||
|
|
||||||
- Remove `notify.distributor.report_path_templates`; do not keep a legacy
|
|
||||||
fallback or compatibility alias.
|
|
||||||
- Report definitions own canonical distributor output path templates.
|
|
||||||
- Configuration may override distributor output path templates per report.
|
|
||||||
- The app layer resolves report-specific distributor bundle paths before
|
|
||||||
calling the distributor adapter.
|
|
||||||
- The distributor adapter continues to receive explicit source-to-bundle file
|
|
||||||
mappings and does not choose report types, render templates, scan
|
|
||||||
workspaces, or apply report routing policy.
|
|
||||||
- Managed Markdown report files remain the only distributor upload sources.
|
|
||||||
Optional local output copies are not uploaded.
|
|
||||||
- Batch distributor uploads, when enabled, use the same report-specific bundle
|
|
||||||
path resolver for each included report.
|
|
||||||
- Non-roadmap documentation must not describe this behavior until it is
|
|
||||||
implemented.
|
|
||||||
|
|
||||||
## Target Configuration Model
|
|
||||||
|
|
||||||
The global path template list is removed from `notify.distributor`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
notify:
|
|
||||||
distributor:
|
|
||||||
enabled: false
|
|
||||||
endpoint: https://distributor.example.com
|
|
||||||
token_env: DISTRIBUTOR_UPLOAD_TOKEN
|
|
||||||
timeout: 30s
|
|
||||||
failure_policy: error
|
|
||||||
pipeline_id_template: "weatherreporter.{report_id}"
|
|
||||||
bundle_id_template: "weatherreporter.{location_id}.{report_id}"
|
|
||||||
idempotency_key_template: "{bundle_id}.{run_id}"
|
|
||||||
```
|
|
||||||
|
|
||||||
Per-report overrides live under `reports.<report>.distributor`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
reports:
|
|
||||||
daily:
|
|
||||||
distributor:
|
|
||||||
path_templates:
|
|
||||||
- "daily/{valid_start_date}/{run_id}.md"
|
|
||||||
- "daily/{valid_start_date}/index.md"
|
|
||||||
```
|
|
||||||
|
|
||||||
If a report override is omitted, weatherreporter uses the defaults declared by
|
|
||||||
that report definition.
|
|
||||||
|
|
||||||
## Default Report Paths
|
|
||||||
|
|
||||||
Each generated report maps its managed Markdown source file to one or more
|
|
||||||
bundle-relative distributor paths.
|
|
||||||
|
|
||||||
| Report | Default distributor paths |
|
|
||||||
| --- | --- |
|
|
||||||
| `hourly` | `hourly/index.md` |
|
|
||||||
| `daily` | `daily/{valid_start_date}/{run_id}.md`; `daily/{valid_start_date}/index.md` |
|
|
||||||
| `today` | `daily/{valid_start_date}/{run_id}.md`; `daily/{valid_start_date}/index.md`; `today/index.md` |
|
|
||||||
| `tomorrow` | `daily/{valid_start_date}/{run_id}.md`; `daily/{valid_start_date}/index.md`; `tomorrow/index.md` |
|
|
||||||
| `three_day` | `three-day/{valid_start_date}/{run_id}.md`; `three-day/{valid_start_date}/index.md` |
|
|
||||||
| `weekend` | `weekend/{valid_start_date}/{run_id}.md`; `weekend/{valid_start_date}/index.md` |
|
|
||||||
| `storm` | `storm/{storm_id}/{run_id}.md`; `storm/{storm_id}/index.md` |
|
|
||||||
|
|
||||||
`storm_id` is derived from the resolved storm valid period until a future
|
|
||||||
explicit storm identity source exists.
|
|
||||||
|
|
||||||
## Template Values
|
|
||||||
|
|
||||||
Report path templates keep the existing report template values:
|
|
||||||
|
|
||||||
- `location_id`
|
|
||||||
- `report_id`
|
|
||||||
- `run_id`
|
|
||||||
- `artifact_group`
|
|
||||||
- `batch_output_name`
|
|
||||||
- `valid_start_date`
|
|
||||||
- `valid_end_date`
|
|
||||||
- `valid_start_time`
|
|
||||||
- `valid_end_time`
|
|
||||||
- `valid_start_stamp`
|
|
||||||
- `valid_end_stamp`
|
|
||||||
- `storm_id`
|
|
||||||
|
|
||||||
The initial `storm_id` value is `{valid_start_stamp}-{valid_end_stamp}` in the
|
|
||||||
effective report timezone. It is available for all single-report distributor
|
|
||||||
templates, but it renders as an empty value for non-storm reports.
|
|
||||||
|
|
||||||
Rendered paths must stay unique relative paths using `/` separators. They must
|
|
||||||
not contain backslashes, empty path segments, `.`, `..`, `manifest.json`, or
|
|
||||||
`.distributor.json`.
|
|
||||||
|
|
||||||
## Intended Final State
|
|
||||||
|
|
||||||
`internal/report.Definition` declares distributor path defaults alongside the
|
|
||||||
other report-owned behavior such as report ID, prompt ID, valid-period
|
|
||||||
resolution, module composition, comparison strategy, artifact group, and output
|
|
||||||
name.
|
|
||||||
|
|
||||||
`internal/config` owns per-report override loading and validation. Unknown
|
|
||||||
fields under report config continue to fail validation or YAML unmarshalling.
|
|
||||||
|
|
||||||
`internal/app` resolves bundle paths through one helper used by both
|
|
||||||
single-report notification and batch notification. That helper applies this
|
|
||||||
precedence:
|
|
||||||
|
|
||||||
1. `reports.<report>.distributor.path_templates`, when explicitly configured.
|
|
||||||
2. `report.Definition` distributor path defaults.
|
|
||||||
|
|
||||||
There is no third global fallback.
|
|
||||||
|
|
||||||
The distributor adapter API remains unchanged unless a separate adapter concern
|
|
||||||
requires it. It should still upload the explicit file mappings passed by the
|
|
||||||
app layer.
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
# Distributor Paths Implementation Plan
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This document is the staged implementation plan for
|
|
||||||
[distributor-paths.md](distributor-paths.md). It is written for an LLM coding
|
|
||||||
agent that will implement the feature in order.
|
|
||||||
|
|
||||||
The feature is complete only when `notify.distributor.report_path_templates` is
|
|
||||||
removed, report definitions provide distributor path defaults, per-report
|
|
||||||
config overrides work, and single-report plus batch distributor notifications
|
|
||||||
resolve bundle paths through the same report-aware code path.
|
|
||||||
|
|
||||||
## Ground Rules
|
|
||||||
|
|
||||||
- Review `docs/policy/architecture.md`, `docs/policy/development.md`, and
|
|
||||||
`docs/policy/documentation.md` before editing code.
|
|
||||||
- Keep distributor package types confined to `internal/adapters/distributor`.
|
|
||||||
- Do not add a compatibility fallback for
|
|
||||||
`notify.distributor.report_path_templates`.
|
|
||||||
- Do not upload optional `--out` or `--out-dir` copies to distributor.
|
|
||||||
- Keep all non-roadmap documentation changes in the stage that implements the
|
|
||||||
behavior being documented.
|
|
||||||
- Prefer small helpers in existing packages over a new package unless a clear
|
|
||||||
boundary emerges during implementation.
|
|
||||||
|
|
||||||
## Decisions Applied
|
|
||||||
|
|
||||||
- Per-report override key: use
|
|
||||||
`reports.<report>.distributor.path_templates`.
|
|
||||||
- `three_day` and `weekend` get defaults now:
|
|
||||||
`three-day/{valid_start_date}/{run_id}.md`,
|
|
||||||
`three-day/{valid_start_date}/index.md`,
|
|
||||||
`weekend/{valid_start_date}/{run_id}.md`, and
|
|
||||||
`weekend/{valid_start_date}/index.md`.
|
|
||||||
- Initial `storm_id`: derive from the resolved valid period as
|
|
||||||
`{valid_start_stamp}-{valid_end_stamp}` in the effective report timezone.
|
|
||||||
This avoids adding new CLI or upstream API requirements while still producing
|
|
||||||
stable storm paths for the same storm window.
|
|
||||||
- Allow `storm_id` in all single-report distributor templates. It renders empty
|
|
||||||
for non-storm reports.
|
|
||||||
|
|
||||||
## Stage 1: Report Defaults
|
|
||||||
|
|
||||||
Goal: make distributor path defaults part of report definitions without
|
|
||||||
changing runtime behavior yet.
|
|
||||||
|
|
||||||
Code changes:
|
|
||||||
|
|
||||||
- Add `DistributorPathTemplates []string` to `internal/report.Definition`.
|
|
||||||
- Populate defaults in every generated report definition:
|
|
||||||
`daily`, `today`, `tomorrow`, `hourly`, `three_day`, `weekend`, and `storm`.
|
|
||||||
- Ensure registry cloning preserves `DistributorPathTemplates` when module
|
|
||||||
overrides are applied.
|
|
||||||
- Keep this field report-owned; do not reference distributor adapter types from
|
|
||||||
`internal/report`.
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
|
|
||||||
- Add focused report tests that every generated report has at least one
|
|
||||||
distributor path template.
|
|
||||||
- Add table coverage for the exact default templates listed in
|
|
||||||
`docs/roadmap/distributor-paths.md`.
|
|
||||||
- Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/report
|
|
||||||
```
|
|
||||||
|
|
||||||
Completion criteria:
|
|
||||||
|
|
||||||
- Report definitions declare all default distributor bundle paths.
|
|
||||||
- No app behavior changes are required in this stage.
|
|
||||||
|
|
||||||
## Stage 2: Config Override Model
|
|
||||||
|
|
||||||
Goal: add per-report distributor path override configuration while preserving
|
|
||||||
the existing runtime path until the app layer is switched in Stage 3.
|
|
||||||
|
|
||||||
Code changes:
|
|
||||||
|
|
||||||
- Add `ReportDistributorConfig` under `internal/config`.
|
|
||||||
- Add `Distributor ReportDistributorConfig` to `ReportConfig`.
|
|
||||||
- Support only this YAML shape:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
reports:
|
|
||||||
daily:
|
|
||||||
distributor:
|
|
||||||
path_templates:
|
|
||||||
- "daily/{valid_start_date}/{run_id}.md"
|
|
||||||
```
|
|
||||||
|
|
||||||
- Track whether `path_templates` was explicitly set so omitted overrides can
|
|
||||||
fall back to report definition defaults.
|
|
||||||
- Reject unknown fields under both `reports.<report>` and
|
|
||||||
`reports.<report>.distributor`.
|
|
||||||
- Add a config helper that returns normalized overrides by `report.ID`, reusing
|
|
||||||
`report.IDForConfigKey` and duplicate report-key detection.
|
|
||||||
- Validate configured path templates with the same parser and path safety rules
|
|
||||||
used for rendered distributor paths.
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
|
|
||||||
- Add config load/unmarshal tests for per-report distributor overrides.
|
|
||||||
- Add validation tests for unknown fields, duplicate report aliases, unknown
|
|
||||||
template variables, absolute paths, `..`, `manifest.json`, duplicate rendered
|
|
||||||
paths inside one report override, and an explicitly empty override list.
|
|
||||||
- Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/config
|
|
||||||
```
|
|
||||||
|
|
||||||
Completion criteria:
|
|
||||||
|
|
||||||
- Config can express per-report distributor path overrides.
|
|
||||||
- Omitted overrides are distinguishable from explicit empty lists.
|
|
||||||
- No runtime notification path selection has been switched yet.
|
|
||||||
|
|
||||||
## Stage 3: Template Rendering
|
|
||||||
|
|
||||||
Goal: generalize distributor template rendering so it no longer hardcodes the
|
|
||||||
old global config field name and can render `storm_id`.
|
|
||||||
|
|
||||||
Code changes:
|
|
||||||
|
|
||||||
- Add `StormID string` to `config.DistributorTemplateValues`.
|
|
||||||
- Add `storm_id` to the allowed variables for single-report distributor
|
|
||||||
templates.
|
|
||||||
- Derive `StormID` in the app-layer template-value builder as
|
|
||||||
`{valid_start_stamp}-{valid_end_stamp}` for `report.Storm`; leave it empty
|
|
||||||
for other reports.
|
|
||||||
- Replace hardcoded error names such as
|
|
||||||
`notify.distributor.report_path_templates[0]` with caller-provided names such
|
|
||||||
as `reports.daily.distributor.path_templates[0]` or
|
|
||||||
`report.daily.distributor_path_templates[0]`.
|
|
||||||
- Keep rendered path validation in `internal/config` unless the implementation
|
|
||||||
reveals a cleaner existing boundary.
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
|
|
||||||
- Add rendering tests for `storm_id`.
|
|
||||||
- Update existing rendering tests so error messages reference the new caller
|
|
||||||
names rather than the removed global config field.
|
|
||||||
- Confirm duplicate path detection still reports the duplicate path.
|
|
||||||
- Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/config ./internal/app
|
|
||||||
```
|
|
||||||
|
|
||||||
Completion criteria:
|
|
||||||
|
|
||||||
- Rendering supports all variables in the feature roadmap.
|
|
||||||
- Rendering helpers can be used for both defaults and per-report overrides
|
|
||||||
without naming errors after the removed global field.
|
|
||||||
|
|
||||||
## Stage 4: App Notification Path Resolver
|
|
||||||
|
|
||||||
Goal: switch single-report and batch distributor notification to the
|
|
||||||
report-specific path resolver.
|
|
||||||
|
|
||||||
Code changes:
|
|
||||||
|
|
||||||
- Add one app-layer helper used by both `buildNotificationRequest` and
|
|
||||||
`buildBatchNotificationRequest`.
|
|
||||||
- Helper precedence:
|
|
||||||
1. explicit `reports.<report>.distributor.path_templates`;
|
|
||||||
2. `resolved.Definition.DistributorPathTemplates`.
|
|
||||||
- Return an actionable error if a report has neither an override nor defaults.
|
|
||||||
- Preserve existing single-report identity rendering for `pipeline_id_template`,
|
|
||||||
`bundle_id_template`, and `idempotency_key_template`.
|
|
||||||
- Preserve existing batch identity rendering under `notify.distributor.batch`.
|
|
||||||
- Preserve batch duplicate detection across all rendered bundle paths before
|
|
||||||
calling distributor.
|
|
||||||
- Ensure errors include report ID, RunID, source path where available, and the
|
|
||||||
rendered bundle path when relevant.
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
|
|
||||||
- Update single-report notification tests for default paths:
|
|
||||||
`hourly`, `daily`, `today`, `tomorrow`, `three_day`, `weekend`, and `storm`
|
|
||||||
where storm generation is currently testable.
|
|
||||||
- Add per-report override precedence tests.
|
|
||||||
- Add batch tests proving each included report uses its own defaults or
|
|
||||||
overrides.
|
|
||||||
- Keep or add a batch duplicate-path test. The current planned batches avoid
|
|
||||||
`today`/`tomorrow` collisions with future dated `daily` reports, but the
|
|
||||||
collision guard must remain explicit for future batch changes.
|
|
||||||
- Confirm notification source paths are still managed Markdown report paths,
|
|
||||||
not output copies.
|
|
||||||
- Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/app
|
|
||||||
```
|
|
||||||
|
|
||||||
Completion criteria:
|
|
||||||
|
|
||||||
- Distributor upload requests contain report-specific bundle paths.
|
|
||||||
- Single-report and batch notifications use the same path resolution rules.
|
|
||||||
- No distributor adapter API change is required.
|
|
||||||
|
|
||||||
## Stage 5: Remove The Legacy Global Field
|
|
||||||
|
|
||||||
Goal: hard-remove `notify.distributor.report_path_templates` from the codebase.
|
|
||||||
|
|
||||||
Code changes:
|
|
||||||
|
|
||||||
- Remove `ReportPathTemplates` from `DistributorNotifyConfig`.
|
|
||||||
- Remove its default from `internal/config/defaults.go`.
|
|
||||||
- Remove validation that requires or renders the global field.
|
|
||||||
- Add or update `DistributorNotifyConfig.UnmarshalYAML` so unknown fields in
|
|
||||||
`notify.distributor` fail during config parsing. This must explicitly reject
|
|
||||||
the removed `report_path_templates` key instead of silently ignoring it.
|
|
||||||
- Update or remove tests that asserted the old global default.
|
|
||||||
- Search for and remove remaining code references:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rg "ReportPathTemplates|report_path_templates"
|
|
||||||
```
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
|
|
||||||
- Add or update config tests proving the global field is no longer accepted.
|
|
||||||
- Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/config ./internal/app
|
|
||||||
```
|
|
||||||
|
|
||||||
Completion criteria:
|
|
||||||
|
|
||||||
- The legacy global path field is gone from structs, defaults, validation,
|
|
||||||
examples, docs, and tests.
|
|
||||||
- There is no compatibility fallback.
|
|
||||||
|
|
||||||
## Stage 6: Documentation And Examples
|
|
||||||
|
|
||||||
Goal: move implemented behavior from roadmap-only docs into maintained user and
|
|
||||||
internal docs.
|
|
||||||
|
|
||||||
Documentation changes:
|
|
||||||
|
|
||||||
- Update `docs/config.md`:
|
|
||||||
- remove `notify.distributor.report_path_templates`;
|
|
||||||
- document `reports.<report>.distributor.path_templates`;
|
|
||||||
- document default path behavior and template variables;
|
|
||||||
- document `storm_id` derivation.
|
|
||||||
- Update `docs/internal/distributor-adapter.md` so it says the app layer
|
|
||||||
resolves report-specific path templates before calling the adapter.
|
|
||||||
- Update `examples/config.yml` to remove the old global field and optionally
|
|
||||||
include one concise per-report override example if useful.
|
|
||||||
- Do not add unimplemented behavior outside `docs/roadmap/`.
|
|
||||||
|
|
||||||
Tests and checks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./internal/config ./internal/app ./internal/adapters/distributor
|
|
||||||
go test ./...
|
|
||||||
go run ./cmd/weatherreporter --help
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Completion criteria:
|
|
||||||
|
|
||||||
- Maintained docs and examples match implemented behavior.
|
|
||||||
- Example config still loads in the config test suite.
|
|
||||||
|
|
||||||
## Final Verification
|
|
||||||
|
|
||||||
Before considering the feature complete, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test ./...
|
|
||||||
go run ./cmd/weatherreporter --help
|
|
||||||
git diff --check
|
|
||||||
rg "ReportPathTemplates|report_path_templates" --glob '!docs/roadmap/**'
|
|
||||||
```
|
|
||||||
|
|
||||||
The final `rg` should find no implemented-code, maintained-doc, or example
|
|
||||||
references to the removed global field.
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
|
|
||||||
None. The prior roadmap questions are resolved above so the implementation can
|
|
||||||
proceed without additional product decisions.
|
|
||||||
@@ -202,6 +202,37 @@ func (c *DistributorNotifyConfig) UnmarshalYAML(value *yaml.Node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *DistributorBatchNotifyConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||||
|
if value.Kind != yaml.MappingNode {
|
||||||
|
return fmt.Errorf("notify distributor batch entry must be a mapping")
|
||||||
|
}
|
||||||
|
for i := 0; i < len(value.Content); i += 2 {
|
||||||
|
key := value.Content[i].Value
|
||||||
|
node := value.Content[i+1]
|
||||||
|
switch key {
|
||||||
|
case "enabled":
|
||||||
|
if err := node.Decode(&c.Enabled); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "pipeline_id_template":
|
||||||
|
if err := node.Decode(&c.PipelineIDTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "bundle_id_template":
|
||||||
|
if err := node.Decode(&c.BundleIDTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "idempotency_key_template":
|
||||||
|
if err := node.Decode(&c.IdempotencyKeyTemplate); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown notify distributor batch field %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ReportDistributorConfig) UnmarshalYAML(value *yaml.Node) error {
|
func (c *ReportDistributorConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||||
if value.Kind != yaml.MappingNode {
|
if value.Kind != yaml.MappingNode {
|
||||||
return fmt.Errorf("report distributor entry must be a mapping")
|
return fmt.Errorf("report distributor entry must be a mapping")
|
||||||
|
|||||||
@@ -751,6 +751,17 @@ reports:
|
|||||||
`,
|
`,
|
||||||
wantErr: `reports.daily.distributor.path_templates renders duplicate path "daily/index.md"`,
|
wantErr: `reports.daily.distributor.path_templates renders duplicate path "daily/index.md"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "NonStormStormIDEmptyPathSegment",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/{storm_id}/index.md"
|
||||||
|
`,
|
||||||
|
wantErr: "reports.daily.distributor.path_templates[0] must not render empty path segments",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "EmptyOverrideList",
|
name: "EmptyOverrideList",
|
||||||
yaml: `
|
yaml: `
|
||||||
@@ -776,6 +787,23 @@ reports:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReportDistributorPathOverrideStormIDValidation(t *testing.T) {
|
||||||
|
_, err := LoadFile(writeConfig(t, `
|
||||||
|
reports:
|
||||||
|
daily:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "daily/storm-{storm_id}.md"
|
||||||
|
storm:
|
||||||
|
distributor:
|
||||||
|
path_templates:
|
||||||
|
- "storm/{storm_id}/index.md"
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadFile() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReportDistributorPathOverridesConsistentForLoadedAndConstructedConfig(t *testing.T) {
|
func TestReportDistributorPathOverridesConsistentForLoadedAndConstructedConfig(t *testing.T) {
|
||||||
yaml := `
|
yaml := `
|
||||||
reports:
|
reports:
|
||||||
@@ -1087,6 +1115,46 @@ notify:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDistributorBatchNotifyRejectsUnknownFields(t *testing.T) {
|
||||||
|
_, err := LoadFile(writeConfig(t, `
|
||||||
|
notify:
|
||||||
|
distributor:
|
||||||
|
batch:
|
||||||
|
paths:
|
||||||
|
- index.md
|
||||||
|
`))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("LoadFile() error = nil, want unknown distributor batch field error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), `unknown notify distributor batch field "paths"`) {
|
||||||
|
t.Fatalf("error = %q, want unknown batch field rejection", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDistributorBatchNotifyPartialConfigPreservesDefaults(t *testing.T) {
|
||||||
|
cfg, err := LoadFile(writeConfig(t, `
|
||||||
|
notify:
|
||||||
|
distributor:
|
||||||
|
batch:
|
||||||
|
enabled: false
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadFile() error = %v", err)
|
||||||
|
}
|
||||||
|
if cfg.Notify.Distributor.Batch.Enabled {
|
||||||
|
t.Fatalf("Batch.Enabled = true, want false")
|
||||||
|
}
|
||||||
|
if cfg.Notify.Distributor.Batch.PipelineIDTemplate != "weatherreporter" {
|
||||||
|
t.Fatalf("Batch.PipelineIDTemplate = %q, want default", cfg.Notify.Distributor.Batch.PipelineIDTemplate)
|
||||||
|
}
|
||||||
|
if cfg.Notify.Distributor.Batch.BundleIDTemplate != "weatherreporter.{location_id}.{batch}" {
|
||||||
|
t.Fatalf("Batch.BundleIDTemplate = %q, want default", cfg.Notify.Distributor.Batch.BundleIDTemplate)
|
||||||
|
}
|
||||||
|
if cfg.Notify.Distributor.Batch.IdempotencyKeyTemplate != "{bundle_id}.{batch_run_id}" {
|
||||||
|
t.Fatalf("Batch.IdempotencyKeyTemplate = %q, want default", cfg.Notify.Distributor.Batch.IdempotencyKeyTemplate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisabledDistributorNotifyAcceptsMalformedBatchTemplates(t *testing.T) {
|
func TestDisabledDistributorNotifyAcceptsMalformedBatchTemplates(t *testing.T) {
|
||||||
cfg := Defaults()
|
cfg := Defaults()
|
||||||
cfg.Notify.Distributor.Enabled = false
|
cfg.Notify.Distributor.Enabled = false
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func traverseReportDistributorPathOverrides(cfg Config) (map[report.ID][]string,
|
|||||||
if !reportCfg.Distributor.pathTemplatesSet {
|
if !reportCfg.Distributor.pathTemplatesSet {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := validateReportDistributorPathTemplates(key, reportCfg.Distributor.PathTemplates); err != nil {
|
if err := validateReportDistributorPathTemplates(key, reportID, reportCfg.Distributor.PathTemplates); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
overrides[reportID] = append([]string(nil), reportCfg.Distributor.PathTemplates...)
|
overrides[reportID] = append([]string(nil), reportCfg.Distributor.PathTemplates...)
|
||||||
@@ -121,13 +121,21 @@ func traverseReportDistributorPathOverrides(cfg Config) (map[report.ID][]string,
|
|||||||
return overrides, nil
|
return overrides, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateReportDistributorPathTemplates(reportKey string, templates []string) error {
|
func validateReportDistributorPathTemplates(reportKey string, reportID report.ID, templates []string) error {
|
||||||
name := fmt.Sprintf("reports.%s.distributor.path_templates", reportKey)
|
name := fmt.Sprintf("reports.%s.distributor.path_templates", reportKey)
|
||||||
_, err := RenderDistributorReportPaths(name, templates, sampleDistributorTemplateValues())
|
_, err := RenderDistributorReportPaths(name, templates, sampleDistributorTemplateValuesForReport(reportID))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func sampleDistributorTemplateValues() DistributorTemplateValues {
|
func sampleDistributorTemplateValues() DistributorTemplateValues {
|
||||||
|
return sampleDistributorTemplateValuesForReport(report.Storm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleDistributorTemplateValuesForReport(reportID report.ID) DistributorTemplateValues {
|
||||||
|
stormID := ""
|
||||||
|
if reportID == report.Storm {
|
||||||
|
stormID = "2026-05-29T0000-2026-05-30T0000"
|
||||||
|
}
|
||||||
return DistributorTemplateValues{
|
return DistributorTemplateValues{
|
||||||
LocationID: "location",
|
LocationID: "location",
|
||||||
ReportID: "report",
|
ReportID: "report",
|
||||||
@@ -140,7 +148,7 @@ func sampleDistributorTemplateValues() DistributorTemplateValues {
|
|||||||
ValidEndTime: "0000",
|
ValidEndTime: "0000",
|
||||||
ValidStartStamp: "2026-05-29T0000",
|
ValidStartStamp: "2026-05-29T0000",
|
||||||
ValidEndStamp: "2026-05-30T0000",
|
ValidEndStamp: "2026-05-30T0000",
|
||||||
StormID: "2026-05-29T0000-2026-05-30T0000",
|
StormID: stormID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user