package app import ( "context" "os" "path/filepath" "strings" "testing" ) // TestNew проверяет, что composition root собирается без ошибок. // Использует :memory: (? или file-based с t.TempDir). func TestNew(t *testing.T) { tmp := t.TempDir() dbPath := filepath.Join(tmp, "test.db") // Настраиваем минимальные env для обязательных полей t.Setenv("TG_TOKEN", "test:token") t.Setenv("TG_CHAT_ID", "12345") t.Setenv("RATATOSKR_DB", dbPath) // Загружаем без config-файла (дефолты + env) a, err := New("", "dev", "") if err != nil { t.Fatalf("New() err = %v", err) } if a.Store == nil { t.Fatal("Store не создан") } if a.Router == nil { t.Fatal("Router не создан") } if a.CoreCtx == nil { t.Fatal("Core не создан") } if a.Worker == nil { t.Fatal("Worker не создан") } if a.tg == nil { t.Fatal("Telegram-канал не создан") } t.Logf("app OK: db=%s", a.Config.Paths.DB) } // TestNew_MissingToken проверяет ошибку при пустом токене. func TestNew_MissingToken(t *testing.T) { tmp := t.TempDir() dbPath := filepath.Join(tmp, "test.db") t.Setenv("TG_TOKEN", "") t.Setenv("TG_CHAT_ID", "12345") t.Setenv("RATATOSKR_DB", dbPath) _, err := New("", "dev", "") if err == nil { t.Fatal("expected error for missing token") } t.Logf("got expected error: %v", err) } // TestNew_BadDB проверяет ошибку при пути к БД, который нельзя создать. func TestNew_BadDB(t *testing.T) { tmp := t.TempDir() dbPath := filepath.Join(tmp, "nonexistent", "test.db") // родителя нет t.Setenv("TG_TOKEN", "test:token") t.Setenv("TG_CHAT_ID", "12345") t.Setenv("RATATOSKR_DB", dbPath) _, err := New("", "dev", "") if err == nil { t.Fatal("expected error for invalid db path") } if !strings.Contains(err.Error(), "db open error") { t.Errorf("expected 'db open error' in message, got %v", err) } } // TestNew_CtxCancel проверяет, что Run завершается на отмене контекста. func TestNew_RunCtxCancel(t *testing.T) { tmp := t.TempDir() dbPath := filepath.Join(tmp, "test-runcancel.db") t.Setenv("TG_TOKEN", "test:token") t.Setenv("TG_CHAT_ID", "12345") t.Setenv("RATATOSKR_DB", dbPath) a, err := New("", "dev", "") if err != nil { t.Fatalf("New() err = %v", err) } ctx, cancel := context.WithCancel(context.Background()) cancel() // сразу отменяем err = a.Run(ctx) if err != nil { t.Fatalf("Run() expected nil, got %v", err) } t.Log("Run OK on cancelled context") } // TestMain — дёргает fatalf, нормально что os.Exit не тестируется. // Просто тест, что main.go компилируется. func TestMain(t *testing.T) { // main() не вызываем — он бы os.Exit _, err := os.Stat("../../cmd/ratatoskr/main.go") if err != nil { t.Fatalf("main.go not found: %v", err) } t.Log("main.go exists") } // TestNew_UpdateWiring проверяет: 1) update.base_url берётся из update-блока // (не git.base_url); 2) приоритет токена: вшитый updateToken > update.token. func TestNew_UpdateWiring(t *testing.T) { tmp := t.TempDir() dbPath := filepath.Join(tmp, "test.db") configPath := filepath.Join(tmp, "config.yaml") content := strings.Join([]string{ "telegram:", " token: \"test:token\"", " chat_id: \"12345\"", "git:", " base_url: \"http://local-git/\"", " token: \"git-token\"", "update:", " enabled: true", " base_url: \"https://hub.example.com\"", " token: \"cfg-update-token\"", "paths:", " db: \"" + dbPath + "\"", "", // пустая строка в конце }, "\n") if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil { t.Fatalf("write config: %v", err) } // 1) base_url из update-блока, а НЕ git a, err := New(configPath, "dev", "") if err != nil { t.Fatalf("New() err = %v", err) } if a.Updater == nil { t.Fatal("Updater не создан") } if want := "https://hub.example.com"; a.Updater.BaseURL != want { t.Errorf("BaseURL = %q, want %q (должен быть update.base_url, не git.base_url)", a.Updater.BaseURL, want) } // владелец пакета — жёсткая константа if a.Updater.Owner != packageOwner { t.Errorf("Owner = %q, want %q (константа)", a.Updater.Owner, packageOwner) } // без вшитого токена берём update.token из конфига if a.Updater.Token != "cfg-update-token" { t.Errorf("Token = %q, want cfg-update-token (при пустом вшитом)", a.Updater.Token) } // 2) вшитый updateToken перекрывает конфиг a2, err := New(configPath, "dev", "embedded-update-token") if err != nil { t.Fatalf("New() err = %v", err) } if a2.Updater.Token != "embedded-update-token" { t.Errorf("Token = %q, want embedded-update-token (вшитый приоритетнее)", a2.Updater.Token) } }