package configreload import ( "errors" "os" "path/filepath" "testing" ) func write(t *testing.T, dir, content string) string { t.Helper() p := filepath.Join(dir, "cfg.json") if err := os.WriteFile(p, []byte(content), 0o600); err != nil { t.Fatalf("write: %v", err) } return p } func parse(cfg map[string]string) func([]byte) (map[string]string, error) { return func(b []byte) (map[string]string, error) { if len(b) == 0 { return nil, errors.New("empty") } return map[string]string{"v": string(b)}, nil } } func TestLoader_CachesUntilContentChanges(t *testing.T) { dir := t.TempDir() p := write(t, dir, "one") l := New(p, parse(nil)) if v, err := l.Get(); err != nil || v["v"] != "one" { t.Fatalf("first get: %v %v", v, err) } // Не изменилось — отдаётся из кэша (нет системных вызовов на парсинг). if v, err := l.Get(); err != nil || v["v"] != "one" { t.Fatalf("cached get: %v %v", v, err) } // Изменилось содержимое на то же по размеру → хэш ловит. if err := os.WriteFile(p, []byte("two"), 0o600); err != nil { t.Fatalf("rewrite: %v", err) } if v, err := l.Get(); err != nil || v["v"] != "two" { t.Fatalf("after change: %v %v", v, err) } } func TestLoader_LastGoodOnInvalid(t *testing.T) { dir := t.TempDir() p := write(t, dir, "good") l := New(p, parse(nil)) if _, err := l.Get(); err != nil { t.Fatalf("get: %v", err) } // Пишем невалидный (пустой) файл — парсер вернёт ошибку => last-good. if err := os.WriteFile(p, []byte(""), 0o600); err != nil { t.Fatalf("write empty: %v", err) } v, err := l.Get() if err == nil { t.Fatal("expected parse error") } if v["v"] != "good" { t.Fatalf("expected last-good value, got %v", v) } } func TestLoader_NotFoundReset(t *testing.T) { dir := t.TempDir() p := write(t, dir, "x") l := New(p, parse(nil)) if _, err := l.Get(); err != nil { t.Fatalf("get: %v", err) } if err := os.Remove(p); err != nil { t.Fatalf("remove: %v", err) } if _, err := l.Get(); !errors.Is(err, ErrNotFound) { t.Fatalf("expected ErrNotFound, got %v", err) } // Восстановили файл — снова загружается. write(t, dir, "y") p2 := filepath.Join(dir, "cfg.json") if v, err := l.Get(); err != nil || v["v"] != "y" { t.Fatalf("after restore: %v %v", v, err) } else if p2 == "" { t.Fatal("unreachable") } } func TestLoader_Concurrent(t *testing.T) { dir := t.TempDir() p := write(t, dir, "start") l := New(p, parse(nil)) done := make(chan struct{}) for i := 0; i < 20; i++ { go func() { defer func() { done <- struct{}{} }() _, _ = l.Get() }() } for i := 0; i < 20; i++ { <-done } // После конкурентных чтений — кэш валиден. if v, err := l.Get(); err != nil || v["v"] != "start" { t.Fatalf("after concurrent: %v %v", v, err) } }