Files
HY2XS_flamy/apps/service/config_traffic_stats_test.go
T
founder b315001288 fix(admin): различать достижимость Traffic Stats API и соответствие профилю
Признак на странице конфигурации отвечал только на вопрос «достучится ли
админка», поэтому 0.0.0.0 показывался как норма — хотя внутренний control plane
при нём опубликован на всех интерфейсах, а оркестратор такой конфигурации не
создаёт. Состояний теперь четыре: канон профиля, wildcard, не-канонический
loopback и недостижимый адрес.

Backend не тронут: он по-прежнему отвечает только на вопрос достижимости —
превращать лишнюю публикацию в отказ обслуживания значило бы отключить всех
пиров. Исправлено ложное утверждение в его комментарии: пустой хост `:36712` в
Go означает все интерфейсы, а не loopback.

Удалены мёртвые фразы common.wait/enableSuccess/disableSuccess — остатки
операций запуска, остановки и смены версии Hysteria, которых у панели нет.
2026-09-03 03:39:42 +05:00

97 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"os"
"path/filepath"
"strings"
"testing"
"hy2xs-admin/model/constant"
)
// Адрес Traffic Stats API — межкомпонентный контракт, и админка обязана
// заметить его нарушение, а не работать «как будто там loopback».
//
// Ломается это одной строкой в `/etc/hysteria/config.yaml`: Hysteria начинает
// слушать другой адрес, админка продолжает стучаться в 127.0.0.1, и весь контур
// доступа выключается fail-closed — ни один пир не проходит авторизацию, потому
// что лимит устройств проверить нечем. Отказ обязан называть причину.
func TestParseTrafficStatsPortAcceptsReachableAddresses(t *testing.T) {
cases := []struct {
name string
listen string
want int64
}{
{"канонический профиль", "127.0.0.1:36712", 36712},
{"пробелы по краям", " 127.0.0.1:36712 ", 36712},
{"другой адрес loopback", "127.0.0.5:36712", 36712},
// Wildcard включает loopback: обмен состоится, и отказывать в
// обслуживании из-за лишней публикации внутреннего API нельзя —
// это отключило бы всех пиров. Запрет на wildcard принадлежит
// оркестратору, который конфиг и создаёт, а сообщает о нём панель.
{"wildcard", "0.0.0.0:36712", 36712},
// Пустой хост — тот же wildcard, а не loopback: в Go `:36712`
// означает все интерфейсы.
{"хост не задан", ":36712", 36712},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := parseTrafficStatsPort(tc.listen)
if err != nil {
t.Fatalf("адрес %q отклонён: %v", tc.listen, err)
}
if got != tc.want {
t.Fatalf("порт %d, ожидался %d", got, tc.want)
}
})
}
}
func TestParseTrafficStatsPortRejectsUnreachableAddresses(t *testing.T) {
cases := []struct {
name string
listen string
}{
{"адрес LAN", "192.168.1.10:36712"},
{"публичный адрес", "203.0.113.10:36712"},
{"имя хоста", "hysteria.internal:36712"},
{"пустая строка", ""},
{"порт вне диапазона", "127.0.0.1:70000"},
{"без порта", "127.0.0.1"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if _, err := parseTrafficStatsPort(tc.listen); err == nil {
t.Fatalf("недостижимый адрес %q принят", tc.listen)
}
})
}
}
// Отказ обязан называть и адрес, и способ починки: оператор, получивший
// «connection refused» без объяснения, ищет проблему в Hysteria.
func TestGetHysteria2ApiPortExplainsNonLoopbackListen(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yaml")
content := "listen: \":443\"\ntrafficStats:\n listen: \"192.168.1.10:36712\"\n secret: \"s\"\n"
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("не удалось записать тестовый конфиг: %v", err)
}
previous := constant.Hysteria2ConfigPath
constant.Hysteria2ConfigPath = path
t.Cleanup(func() { constant.Hysteria2ConfigPath = previous })
_, err := GetHysteria2ApiPort()
if err == nil {
t.Fatal("админка приняла адрес, по которому не может обратиться")
}
if !strings.Contains(err.Error(), "192.168.1.10") {
t.Errorf("сообщение не называет фактический адрес: %q", err.Error())
}
if !strings.Contains(err.Error(), "reconfigure") {
t.Errorf("сообщение не называет способ починки: %q", err.Error())
}
}