package consumers_test import ( "bytes" "errors" "fmt" "os" "path/filepath" "reflect" "runtime" "strings" "testing" configkit "gitea.maximumdirect.net/eric/ConfigKit" "gitea.maximumdirect.net/eric/ConfigKit/examples/consumers" ) func Example_narratio() { result, err := consumers.LoadNarratio(fixture("narratio", "pipeline.yml")) artifact := result.Config.Artifacts["transcript"] fmt.Println(result.Config.ModelProfile, artifact.Output, result.Config.Timeout, artifact.Timeout, err) // Output: concise transcript.txt 6s 6s } func Example_notarius() { result, err := consumers.LoadNotarius(fixture("notarius", "config.yml")) window, ok := consumers.NotariusWindow(result.Config, 0) fmt.Println(result.Config.Version, result.Config.Bindings[0].Object, window, ok, err) // Output: 4 summary 12 true } func Example_notariusPaths() { result, err := consumers.LoadNotariusPaths(fixture("notarius-paths", "main.yml"), "/invocation") fmt.Println(filepath.Base(result.Config.Reference), err) // Output: imported.txt } func Example_weatherReporter() { defaults, defaultsErr := consumers.LoadWeather(configkit.NoSource()) result, err := consumers.LoadWeather(configkit.File(fixture("weather", "config.yml"))) discussion := result.Config.Reports["daily"].Modules[1] fmt.Println(defaults.Config.Units, result.Config.Units, discussion.Options.Sections[0], defaultsErr, err) // Output: metric us short_term } func Example_seriatim() { speakers, speakerErr := consumers.LoadSpeakerRules(fixture("seriatim", "speakers.yml")) autocorrect, autocorrectErr := consumers.LoadAutocorrectRules(fixture("seriatim", "autocorrect.yml")) name, found := consumers.SpeakerName(speakers.Config, "Guest: teh event occured") text := consumers.ApplyAutocorrect(autocorrect.Config, "teh event occured") fmt.Println(name, found, text, speakerErr, autocorrectErr) // Output: visitor true the event occurred } func Example_weatherStartup() { const blockedName = "CONFIGKIT_WEATHER_EXAMPLE_BLOCKED" const tokenName = "CONFIGKIT_WEATHER_EXAMPLE_TOKEN" restoreBlocked := preserveEnvironment(blockedName) restoreToken := preserveEnvironment(tokenName) defer restoreBlocked() defer restoreToken() _ = os.Setenv(blockedName, "") _ = os.Unsetenv(tokenName) directory, _ := os.MkdirTemp("", "configkit-weather-example-") defer os.RemoveAll(directory) _ = os.WriteFile(filepath.Join(directory, blockedName), []byte("unused-example-value\n"), 0o600) _ = os.WriteFile(filepath.Join(directory, tokenName), []byte("disposable-example-credential\n"), 0o600) report, bootstrapErr := configkit.LoadSecretsDir(directory) var reportOutput bytes.Buffer _, reportWriteErr := report.WriteTo(&reportOutput) result, loadErr := consumers.LoadWeather(configkit.Bytes("weather.yml", []byte("units: metric\ntoken: "+tokenName+"\n")), configkit.Override{Path: configkit.Keys("units"), Value: "us", Flag: "--units"}) var explanation bytes.Buffer explainErr := result.WriteExplanation(&explanation, configkit.Keys("units")) blocked, loaded := secretStatus(report, blockedName), secretStatus(report, tokenName) value, present := os.LookupEnv(tokenName) fmt.Printf("blocked=%s loaded=%s report=%t reference=%s explained=%t unchanged=%t errors=%t\n", blocked, loaded, reportOutput.Len() > 0, result.Config.Token.EnvName(), strings.Contains(explanation.String(), "--units"), present && value == "disposable-example-credential", bootstrapErr != nil || reportWriteErr != nil || loadErr != nil || explainErr != nil) // Output: blocked=skipped_existing loaded=loaded report=true reference=CONFIGKIT_WEATHER_EXAMPLE_TOKEN explained=true unchanged=true errors=false } func TestMaintainedConsumerFixturesPreserveDomainOutcomes(t *testing.T) { narratio, err := consumers.LoadNarratio(fixture("narratio", "pipeline.yml")) if err != nil || narratio.Config.Artifacts["transcript"].Output != "transcript.txt" || narratio.Config.ModelProfile != "concise" || len(narratio.Sources()) != 2 { t.Fatalf("Narratio shape = %#v, %v", narratio, err) } if origin, ok := narratio.Metadata().Lookup(configkit.Keys("artifacts", "transcript", "output")); !ok || origin.Origin.Layer != configkit.LayerResolution { t.Fatalf("artifact origin = %#v, %v", origin, ok) } overlaid, err := consumers.LoadNarratioOverlay(fixture("narratio", "pipeline.yml"), "testing") if err != nil || overlaid.Config.PollInterval.String() != "2m0s" || overlaid.Config.ModelProfile != "concise" || len(overlaid.Sources()) != 3 { t.Fatalf("configuration overlay and application model profile = %#v, %v", overlaid, err) } if source := overlaid.Sources()[2]; source.Label != "overlays/testing.yml" || source.Layer != configkit.LayerOverlay { t.Fatalf("Narratio overlay source = %#v", source) } notarius, err := consumers.LoadNotarius(fixture("notarius", "config.yml")) window, converted := consumers.NotariusWindow(notarius.Config, 0) if err != nil || !converted || window != 12 { t.Fatalf("Notarius shape = %#v, %v, %v", notarius, converted, err) } if _, ok := notarius.Config.Bindings[0].Options["window_units"].(int64); !ok { t.Fatalf("dynamic integer type = %T", notarius.Config.Bindings[0].Options["window_units"]) } integerNotarius, err := consumers.LoadNotarius(fixture("notarius", "integer-version.yml")) if err != nil || integerNotarius.Config.Version != notarius.Config.Version || integerNotarius.Config.Version != "4" { t.Fatalf("Notarius version forms = %#v / %#v, %v", notarius.Config, integerNotarius, err) } weather, err := consumers.LoadWeather(configkit.File(fixture("weather", "config.yml"))) if err != nil || weather.Config.Reports["daily"].Modules[1].Options.Sections[0] != "short_term" { t.Fatalf("WeatherReporter shape = %#v, %v", weather, err) } speakers, err := consumers.LoadSpeakerRules(fixture("seriatim", "speakers.yml")) if err != nil { t.Fatal(err) } name, ok := consumers.SpeakerName(speakers.Config, "Host: introduction") if !ok || name != "narrator" || speakers.Config.Rules[0].Prefix != "Host:" { t.Fatalf("ordered speaker rules = %#v, %q, %v", speakers.Config.Rules, name, ok) } } func TestMigrationFixturesFailWithStableCodesAndPaths(t *testing.T) { tests := []struct { name string load func() error code configkit.Code path string }{ { name: "unsupported application version", load: func() error { _, err := consumers.LoadNotarius(fixture("notarius", "unsupported-version.yml")) return err }, code: configkit.CodeVersionError, path: "/version", }, { name: "non-integer numeric version", load: func() error { _, err := consumers.LoadNotarius(fixture("notarius", "noninteger-version.yml")) return err }, code: configkit.CodeVersionType, path: "/version", }, { name: "module shorthand", load: func() error { _, err := consumers.LoadWeather(configkit.File(fixture("weather", "shorthand-module.yml"))) return err }, code: configkit.CodeDecodeType, path: "/reports/daily/modules/0", }, { name: "explicit empty modules", load: func() error { _, err := consumers.LoadWeather(configkit.File(fixture("weather", "empty-modules.yml"))) return err }, code: configkit.CodeValidateError, path: "/reports/daily/modules", }, { name: "explicit empty path templates", load: func() error { _, err := consumers.LoadWeather(configkit.File(fixture("weather", "empty-paths.yml"))) return err }, code: configkit.CodeValidateError, path: "/reports/daily/path_templates", }, { name: "explicit null required option", load: func() error { _, err := consumers.LoadWeather(configkit.File(fixture("weather", "null-window.yml"))) return err }, code: configkit.CodeValidateError, path: "/reports/daily/modules/0/options/window_units", }, { name: "strict unknown rule field", load: func() error { _, err := consumers.LoadSpeakerRules(fixture("seriatim", "unknown-field.yml")) return err }, code: configkit.CodeDecodeUnknownField, path: "/rules/0/color", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var loadError *configkit.LoadError if err := test.load(); !errors.As(err, &loadError) { t.Fatalf("error = %T %v", err, err) } diagnostics := loadError.Diagnostics() if len(diagnostics) == 0 || diagnostics[0].Code != test.code || diagnostics[0].Path.String() != test.path { t.Fatalf("diagnostics = %#v", diagnostics) } if strings.Contains(diagnostics[0].Message, "wrong-before-overlay") { t.Fatalf("diagnostic exposed raw parser input: %#v", diagnostics[0]) } }) } } func TestWeatherLoadedAndConstructedRulesAgreeWithoutMutation(t *testing.T) { loaded, err := consumers.LoadWeather(configkit.File(fixture("weather", "config.yml"))) if err != nil { t.Fatal(err) } reconstructed, err := consumers.LoadConstructedWeather(loaded.Config) if err != nil || !reflect.DeepEqual(reconstructed.Config, loaded.Config) { t.Fatalf("loaded/constructed outcomes differ: %#v, %v", reconstructed, err) } constructed := consumers.WeatherConfig{Units: loaded.Config.Units, Reports: map[string]consumers.ReportConfig{}} constructed.Reports["daily"] = consumers.ReportConfig{ Modules: []consumers.ModuleSpec{{Module: "discussion", Options: consumers.ModuleOptions{Sections: []string{" short_term "}}}}, PathTemplates: []string{"daily/{date}.md"}, } wantInput := constructed.Reports["daily"].Modules[0].Options.Sections[0] result, err := consumers.LoadConstructedWeather(constructed) if err != nil { t.Fatal(err) } if got := constructed.Reports["daily"].Modules[0].Options.Sections[0]; got != wantInput { t.Fatalf("constructed options mutated from %q to %q", wantInput, got) } if result.Config.Reports["daily"].Modules[0].Options.Sections[0] != "short_term" { t.Fatalf("normalized result = %#v", result.Config.Reports["daily"]) } empty := consumers.WeatherConfig{Units: "metric", Reports: map[string]consumers.ReportConfig{ "daily": {Modules: []consumers.ModuleSpec{}}, }} _, err = consumers.LoadConstructedWeather(empty) var loadError *configkit.LoadError if !errors.As(err, &loadError) || loadError.Diagnostics()[0].Code != configkit.CodeValidateError { t.Fatalf("constructed empty modules error = %v", err) } if !reflect.DeepEqual(empty.Reports["daily"].Modules, []consumers.ModuleSpec{}) { t.Fatal("constructed empty module input was mutated") } } func TestNotariusPathExampleUsesEntryProvenance(t *testing.T) { main := fixture("notarius-paths", "main.yml") declared := fixture("notarius-paths", "declarations", "path.yml") declaredCanonical := canonicalFixture(t, declared) wantImported := filepath.Join(filepath.Dir(declaredCanonical), "..", "assets", "imported.txt") result, err := consumers.LoadNotariusPaths(main, filepath.Join(t.TempDir(), "invocation")) if err != nil || result.Config.Reference != wantImported { t.Fatalf("imported reference = %#v, %v", result, err) } var effective bytes.Buffer if err := result.WriteEffective(&effective); err != nil || !strings.Contains(effective.String(), wantImported) || strings.Contains(effective.String(), "PATH_REFERENCE_SECRET_CANARY") { t.Fatalf("effective output = %q, %v", effective.String(), err) } explanation, found := result.Explain(configkit.Keys("reference")) if !found || len(explanation.History) == 0 || explanation.History[len(explanation.History)-1].Before.YAML != "../assets/imported.txt\n" || len(explanation.History[len(explanation.History)-1].Reasons) != 1 || explanation.History[len(explanation.History)-1].Reasons[0] != "resolved relative reference from entry source" { t.Fatalf("reference explanation = %#v, %v", explanation, found) } sensitive, found := result.Explain(configkit.Keys("sensitive")) var rendered bytes.Buffer writeErr := result.WriteExplanation(&rendered, configkit.Keys("sensitive")) if !found || !sensitive.Effective.Redacted || writeErr != nil || strings.Contains(fmt.Sprintf("%#v %s", sensitive, rendered.String()), "PATH_REFERENCE_SECRET_CANARY") { t.Fatalf("sensitive inspection = %#v, %q, %v", sensitive, rendered.String(), writeErr) } defaults := fixture("notarius-paths", "defaults.yml") defaultCanonical := canonicalFixture(t, defaults) defaultResult, err := consumers.LoadNotariusPaths(defaults, filepath.Join(t.TempDir(), "invocation")) wantDefault := filepath.Join(filepath.Dir(defaultCanonical), "defaults", "reference.txt") if err != nil || defaultResult.Config.Reference != wantDefault { t.Fatalf("default reference = %#v, %v", defaultResult, err) } invocation := filepath.Join(t.TempDir(), "caller") cliResult, err := consumers.LoadNotariusPaths(main, invocation, configkit.Override{Path: configkit.Keys("reference"), Value: "cli/reference.txt", Flag: "--reference"}) wantCLI := filepath.Join(invocation, "cli", "reference.txt") if err != nil || cliResult.Config.Reference != wantCLI { t.Fatalf("CLI reference = %#v, %v", cliResult, err) } missingBase, err := consumers.LoadNotariusPaths(main, "", configkit.Override{Path: configkit.Keys("reference"), Value: "cli/reference.txt", Flag: "--reference"}) if err != nil || missingBase.Config.Reference != "cli/reference.txt" || len(missingBase.Diagnostics()) != 1 || missingBase.Diagnostics()[0].Code != configkit.CodeResolveWarning { t.Fatalf("missing CLI base = %#v, %v", missingBase, err) } absolute := filepath.Join(t.TempDir(), "absolute.txt") absoluteResult, err := consumers.LoadNotariusPaths(main, invocation, configkit.Override{Path: configkit.Keys("reference"), Value: absolute, Flag: "--reference"}) if err != nil || absoluteResult.Config.Reference != absolute { t.Fatalf("absolute reference = %#v, %v", absoluteResult, err) } target := fixture("notarius-paths", "symlink-target.yml") link := filepath.Join(t.TempDir(), "linked-config.yml") if err := os.Symlink(target, link); err != nil { t.Skipf("symlink fixture unavailable: %v", err) } symlinkResult, err := consumers.LoadNotariusPaths(link, invocation) targetCanonical := canonicalFixture(t, target) wantSymlink := filepath.Join(filepath.Dir(targetCanonical), "..", "assets", "target.txt") if err != nil || symlinkResult.Config.Reference != wantSymlink { t.Fatalf("symlink reference = %#v, %v", symlinkResult, err) } } func fixture(parts ...string) string { _, file, _, _ := runtime.Caller(0) values := append([]string{filepath.Dir(file), "testdata"}, parts...) return filepath.Join(values...) } func canonicalFixture(t *testing.T, path string) string { t.Helper() canonical, err := filepath.EvalSymlinks(path) if err != nil { t.Fatal(err) } return canonical } func preserveEnvironment(name string) func() { value, present := os.LookupEnv(name) return func() { if present { _ = os.Setenv(name, value) } else { _ = os.Unsetenv(name) } } } func secretStatus(report *configkit.SecretsReport, name string) configkit.SecretFileStatus { for _, entry := range report.Entries() { if entry.Name == name { return entry.Status } } return "missing" }