feat(config): отключаемый Telegram-канал (telegram.enabled)
Some checks failed
CI / test (push) Failing after 1m20s
CI / build-and-package (amd64, linux) (push) Failing after 1m5s
CI / build-and-package (amd64, windows) (push) Successful in 31s

- TelegramCfg.Enabled (дефолт true); при false канал не создаётся и не крепится в Router, long-poll не стартует — нет сетевых вызовов к api.telegram.org
- Validate требует token/chat_id только при enabled
- Load пресетит enabled=true до unmarshal (applyDefaults для bool не различает явный false)
- тесты, README и config.yaml.example обновлены
This commit is contained in:
ki.sagidullin
2026-08-23 17:36:05 +05:00
parent 4c0dc80aa8
commit d731a7429d
7 changed files with 95 additions and 33 deletions

View File

@@ -277,7 +277,7 @@ paths:
}
func TestLoad_MissingRequired(t *testing.T) {
// пустой конфиг — все обязательные поля отсутствуют
// пустой конфиг — все обязательные поля отсутствуют (telegram.enabled по дефолту true)
t.Setenv("TG_TOKEN", "")
t.Setenv("TG_CHAT_ID", "")
@@ -298,6 +298,41 @@ func TestLoad_MissingRequired(t *testing.T) {
}
}
func TestLoad_TelegramDisabledSkipsToken(t *testing.T) {
// enabled: false — token/chat_id не обязательны, конфиг валиден
t.Setenv("TG_TOKEN", "")
t.Setenv("TG_CHAT_ID", "")
yaml := `telegram:
enabled: false
token: ""
chat_id: ""
`
path := writeCfg(t, yaml)
cfg, err := Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.Telegram.Enabled {
t.Error("telegram.enabled = true, want false")
}
}
func TestLoad_TelegramEnabledDefaultTrue(t *testing.T) {
// enabled не задан — дефолт true, token обязателен
t.Setenv("TG_TOKEN", "")
t.Setenv("TG_CHAT_ID", "42")
yaml := `telegram:
chat_id: "42"
`
path := writeCfg(t, yaml)
_, err := Load(path)
if err == nil || !strings.Contains(err.Error(), "telegram.token") {
t.Errorf("ожидалась ошибка telegram.token (enabled по дефолту true), got %v", err)
}
}
func TestLoad_InvalidDuration(t *testing.T) {
t.Setenv("TG_TOKEN", "x")
t.Setenv("TG_CHAT_ID", "x")

View File

@@ -35,6 +35,10 @@ func Load(path string) (*Config, error) {
}
}
cfg := &Config{}
// Telegram по умолчанию включён (если в YAML не указан enabled).
// Делаем до unmarshal: yaml.Unmarshal перезапишет поле, только если ключ
// есть в документе, поэтому отсутствующий enabled останется true.
cfg.Telegram.Enabled = true
if !noFile {
expanded := os.Expand(string(raw), envLookup)
if err := yaml.Unmarshal([]byte(expanded), cfg); err != nil {

View File

@@ -76,9 +76,13 @@ type UpdateCfg struct {
CheckInterval Duration `yaml:"check_interval" default:"24h"`
}
// TelegramCfg — настройки Telegram-канала. Enabled=false отключает канал
// полностью: бот не опрашивается, сетевые вызовы к api.telegram.org не идут,
// token/chat_id не обязательны. По умолчанию включён (см. Load: presetTelegramEnabled).
type TelegramCfg struct {
Token string `yaml:"token" env:"TG_TOKEN"`
ChatID string `yaml:"chat_id" env:"TG_CHAT_ID"`
Enabled bool `yaml:"enabled"`
Token string `yaml:"token" env:"TG_TOKEN"`
ChatID string `yaml:"chat_id" env:"TG_CHAT_ID"`
}
type OpenCodeCfg struct {
@@ -121,11 +125,13 @@ type PathsCfg struct {
// (склеенную, все ошибки сразу) или C2 InvalidFormat.
func (c *Config) Validate() error {
var errs []error
if c.Telegram.Token == "" {
errs = append(errs, fmt.Errorf("%w: telegram.token", ErrMissingField))
}
if c.Telegram.ChatID == "" {
errs = append(errs, fmt.Errorf("%w: telegram.chat_id", ErrMissingField))
if c.Telegram.Enabled {
if c.Telegram.Token == "" {
errs = append(errs, fmt.Errorf("%w: telegram.token", ErrMissingField))
}
if c.Telegram.ChatID == "" {
errs = append(errs, fmt.Errorf("%w: telegram.chat_id", ErrMissingField))
}
}
if !c.Log.Debug() && !strings.EqualFold(c.Log.Level, "info") {
errs = append(errs, fmt.Errorf("%w: log.level (ожидается \"info\" или \"debug\")", ErrInvalidFormat))