Files
ratatoskr-go/internal/ui/log_panel_test.go

188 lines
6.6 KiB
Go

package ui
import "testing"
// NilLogPanel — no-op контракт (спец 12.8): Append ничего не делает, безопасно
// вызывается из горутин Hub и из тестов без Fyne.
func TestNilLogPanelAppendNoop(t *testing.T) {
p := NewNilLogPanel()
p.Append(LogLevelError, "line 1")
p.Append("", "")
p.Append(LogLevelDebug, "line 3")
}
// NormalizeLogLevel приводит любые уровни к каноническим четырём:
// синонимы маппятся, неизвестные/пустые значения считаются info.
func TestNormalizeLogLevel(t *testing.T) {
cases := []struct {
in string
want string
}{
{LogLevelError, LogLevelError},
{"ERROR", LogLevelError},
{" err ", LogLevelError},
{"fatal", LogLevelError},
{LogLevelWarning, LogLevelWarning},
{"WARN", LogLevelWarning},
{"Warning", LogLevelWarning},
{LogLevelInfo, LogLevelInfo},
{"INFO", LogLevelInfo},
{LogLevelDebug, LogLevelDebug},
{"DEBUG", LogLevelDebug},
{"trace", LogLevelDebug},
// события LogWriter без явного уровня и прочий мусор → info
{"log", LogLevelInfo},
{"", LogLevelInfo},
{" ", LogLevelInfo},
{"unknown", LogLevelInfo},
}
for _, c := range cases {
if got := NormalizeLogLevel(c.in); got != c.want {
t.Errorf("NormalizeLogLevel(%q) = %q, want %q", c.in, got, c.want)
}
}
}
// LogLevels — ровно четыре уровня фильтра в фиксированном порядке.
func TestLogLevelsContract(t *testing.T) {
want := []string{LogLevelError, LogLevelWarning, LogLevelInfo, LogLevelDebug}
if len(LogLevels) != len(want) {
t.Fatalf("len(LogLevels) = %d, want %d", len(LogLevels), len(want))
}
for i := range want {
if LogLevels[i] != want[i] {
t.Fatalf("LogLevels = %v, want %v", LogLevels, want)
}
}
}
// allLevels — набор «отмечены все уровни» (состояние фильтра по умолчанию).
func allLevels() map[string]bool {
m := make(map[string]bool, len(LogLevels))
for _, lvl := range LogLevels {
m[lvl] = true
}
return m
}
// По умолчанию (все уровни отмечены) рендерится полный лог; уровни
// нормализуются при добавлении.
func TestLogBufferRenderAllByDefault(t *testing.T) {
b := NewLogBuffer(1000)
b.Append("error", "e1")
b.Append("warn", "w1") // синоним → warning
b.Append("log", "i1") // неизвестный уровень → info
b.Append("trace", "d1") // синоним → debug
got := b.Render(allLevels())
want := "e1\nw1\ni1\nd1\n"
if got != want {
t.Fatalf("Render(all) = %q, want %q", got, want)
}
entries := b.Entries()
wantLvls := []string{LogLevelError, LogLevelWarning, LogLevelInfo, LogLevelDebug}
for i, e := range entries {
if e.Level != wantLvls[i] {
t.Fatalf("entry[%d].Level = %q, want %q", i, e.Level, wantLvls[i])
}
}
}
// Показываются только записи выбранных уровней.
func TestLogBufferRenderSelectedOnly(t *testing.T) {
b := NewLogBuffer(1000)
b.Append(LogLevelError, "err line")
b.Append(LogLevelWarning, "warn line")
b.Append(LogLevelInfo, "info line")
b.Append(LogLevelDebug, "debug line")
onlyErrors := map[string]bool{LogLevelError: true}
if got, want := b.Render(onlyErrors), "err line\n"; got != want {
t.Fatalf("Render(error only) = %q, want %q", got, want)
}
errorsAndDebug := map[string]bool{LogLevelError: true, LogLevelDebug: true}
if got, want := b.Render(errorsAndDebug), "err line\ndebug line\n"; got != want {
t.Fatalf("Render(error+debug) = %q, want %q", got, want)
}
}
// Ни один уровень не выбран — лента пуста (и это не ошибка).
func TestLogBufferRenderEmptySelection(t *testing.T) {
b := NewLogBuffer(1000)
b.Append(LogLevelError, "err line")
b.Append(LogLevelInfo, "info line")
if got := b.Render(map[string]bool{}); got != "" {
t.Fatalf("Render(none) = %q, want empty", got)
}
if got := b.Render(nil); got != "" {
t.Fatalf("Render(nil) = %q, want empty", got)
}
}
// Фильтрация применяется и к уже загруженным записям: смена набора уровней
// меняет результат рендера того же буфера без новых Append.
func TestLogBufferRefilterLoadedEntries(t *testing.T) {
b := NewLogBuffer(1000)
b.Append(LogLevelError, "err line")
b.Append(LogLevelInfo, "info line")
all := allLevels()
if got, want := b.Render(all), "err line\ninfo line\n"; got != want {
t.Fatalf("Render(all) = %q, want %q", got, want)
}
delete(all, LogLevelInfo)
if got, want := b.Render(all), "err line\n"; got != want {
t.Fatalf("Render(error) after refilter = %q, want %q", got, want)
}
all[LogLevelInfo] = true // вернули галочку — запись снова видима
if got, want := b.Render(all), "err line\ninfo line\n"; got != want {
t.Fatalf("Render(all) after re-check = %q, want %q", got, want)
}
}
// Новые записи сразу попадают в ленту своего уровня.
func TestLogBufferNewEntriesVisibleImmediately(t *testing.T) {
b := NewLogBuffer(1000)
sel := map[string]bool{LogLevelError: true}
b.Append(LogLevelError, "first")
if got, want := b.Render(sel), "first\n"; got != want {
t.Fatalf("Render = %q, want %q", got, want)
}
b.Append(LogLevelInfo, "noise") // невидимый уровень — ленту не меняет
if got, want := b.Render(sel), "first\n"; got != want {
t.Fatalf("Render after invisible append = %q, want %q", got, want)
}
b.Append(LogLevelError, "second")
if got, want := b.Render(sel), "first\nsecond\n"; got != want {
t.Fatalf("Render after visible append = %q, want %q", got, want)
}
}
// Буфер ограничен: старые записи вытесняются, суммарный объём ≤ лимита.
func TestLogBufferBounded(t *testing.T) {
b := NewLogBuffer(10)
for i := 0; i < 5; i++ {
b.Append(LogLevelInfo, "0123456789") // каждая запись — ровно лимит
}
entries := b.Entries()
if len(entries) != 1 {
t.Fatalf("len(entries) = %d, want 1 (старые вытеснены)", len(entries))
}
if entries[0].Text != "0123456789" {
t.Fatalf("text = %q", entries[0].Text)
}
// одиночная запись длиннее лимита обрезается до последних maxLen символов
b2 := NewLogBuffer(4)
b2.Append(LogLevelInfo, "abcdefgh")
if got := b2.Entries()[0].Text; got != "efgh" {
t.Fatalf("oversized entry text = %q, want %q", got, "efgh")
}
}