@@ -16,6 +16,7 @@ import (
1616
1717 "github.com/escoffier-labs/agent-notify/internal/canonical"
1818 "github.com/escoffier-labs/agent-notify/internal/channels"
19+ "github.com/escoffier-labs/agent-notify/internal/config"
1920)
2021
2122type leakingErrorChannel struct {
@@ -442,6 +443,99 @@ func TestRun_DoctorJSONReportsMissingConfigAsUnconfigured(t *testing.T) {
442443 }
443444}
444445
446+ func TestBuildRegistryRejectsNonPositiveTimeout (t * testing.T ) {
447+ // A non-positive timeout must never reach channel construction: the
448+ // registry build fails with a config error naming the offending key.
449+ // The webhook URL is required so the pre-fix RED state reaches channel
450+ // construction instead of failing earlier with missing credentials.
451+ t .Setenv ("DISCORD_WEBHOOK_URL" , "https://discord.test/webhook/123" )
452+ for _ , timeout := range []int {- 5 , 0 } {
453+ cfg := & config.Config {
454+ Channels : map [string ]config.ChannelConfig {
455+ "discord-main" : {Type : "discord" , WebhookURLEnv : "DISCORD_WEBHOOK_URL" },
456+ },
457+ Defaults : config.Defaults {TimeoutSeconds : timeout },
458+ }
459+ _ , err := buildRegistry (cfg , []string {"discord-main" })
460+ if err == nil {
461+ t .Fatalf ("timeout %d: expected buildRegistry error, got nil" , timeout )
462+ }
463+ if ! strings .Contains (err .Error (), "defaults.timeout_seconds" ) {
464+ t .Errorf ("timeout %d: error should name defaults.timeout_seconds, got %v" , timeout , err )
465+ }
466+ }
467+ }
468+
469+ func TestRun_DoctorJSONFailsOnNegativeTimeout (t * testing.T ) {
470+ // doctor must surface an invalid negative timeout as a FAIL check on
471+ // defaults.timeout_seconds, not silently normalize it away.
472+ cfgPath := filepath .Join (t .TempDir (), "config.toml" )
473+ if err := os .WriteFile (cfgPath , []byte (`
474+ [defaults]
475+ timeout_seconds = -5
476+
477+ [channels.discord-main]
478+ type = "discord"
479+ webhook_url_env = "DISCORD_WEBHOOK_URL"
480+ ` ), 0o644 ); err != nil {
481+ t .Fatal (err )
482+ }
483+
484+ code , stdout , stderr := runMain (t ,
485+ []string {"agent-notify" , "doctor" , "--json" , "--config" , cfgPath },
486+ "" ,
487+ map [string ]string {"DISCORD_WEBHOOK_URL" : "https://discord.test/webhook/123" },
488+ )
489+ if code != 2 {
490+ t .Fatalf ("exit = %d, want 2 (stderr=%s)" , code , stderr )
491+ }
492+ var payload map [string ]interface {}
493+ if err := json .Unmarshal ([]byte (stdout ), & payload ); err != nil {
494+ t .Fatalf ("invalid json: %v\n %s" , err , stdout )
495+ }
496+ found := false
497+ for _ , c := range payload ["checks" ].([]interface {}) {
498+ check := c .(map [string ]interface {})
499+ if check ["name" ] == "defaults.timeout_seconds" {
500+ found = true
501+ if check ["status" ] != "FAIL" {
502+ t .Fatalf ("defaults.timeout_seconds status = %v, want FAIL" , check ["status" ])
503+ }
504+ }
505+ }
506+ if ! found {
507+ t .Fatalf ("expected a defaults.timeout_seconds check in %#v" , payload ["checks" ])
508+ }
509+ }
510+
511+ func TestRun_SendRejectsNegativeTimeout (t * testing.T ) {
512+ // The send path must refuse a negative timeout with a config error
513+ // instead of constructing channels with a non-positive deadline.
514+ cfgPath := filepath .Join (t .TempDir (), "config.toml" )
515+ if err := os .WriteFile (cfgPath , []byte (`
516+ [defaults]
517+ timeout_seconds = -5
518+
519+ [channels.discord-main]
520+ type = "discord"
521+ webhook_url_env = "DISCORD_WEBHOOK_URL"
522+ ` ), 0o644 ); err != nil {
523+ t .Fatal (err )
524+ }
525+
526+ code , _ , stderr := runMain (t ,
527+ []string {"agent-notify" , "--config" , cfgPath , "build done" },
528+ "" ,
529+ map [string ]string {"DISCORD_WEBHOOK_URL" : "https://discord.test/webhook/123" },
530+ )
531+ if code != exitConfig {
532+ t .Fatalf ("exit = %d, want %d (stderr=%s)" , code , exitConfig , stderr )
533+ }
534+ if ! strings .Contains (stderr , "defaults.timeout_seconds" ) {
535+ t .Fatalf ("stderr should name defaults.timeout_seconds, got %s" , stderr )
536+ }
537+ }
538+
445539func TestRun_StatusJSONWarnsForInactiveChannelMissingEnv (t * testing.T ) {
446540 cfgPath := filepath .Join (t .TempDir (), "config.toml" )
447541 if err := os .WriteFile (cfgPath , []byte (`
0 commit comments