Files
Bridge-and-Join-s/modules/lk/lk_test.go
T
zuevav 6fee3c5ff7 Личный кабинет — модуль modules/lk: сборка в релизе, unit в комплекте, ключ вне unit-файла
Код bj-lk перенесён в modules/lk (cmd/bj-lk — запуск, флаг -version); релиз собирает bj-lk
со своей версией (LK_VERSION), unit bj-lk.service встроен в комплект установщика и берёт
адрес bj-server и ключ доступа из /etc/bj/lk.env, а не из unit-файла.

Исправлено: признаки «администратор» и «кабинет клиента» читались из общей переменной
последнего запроса (curReq) — при одновременной работе признак мог достаться чужому запросу.
Теперь из кук своего запроса. Без BJ_URL/BJ_KEY кабинет показывает подсказку настройки
вместо падения службы. Тесты: вход, портфель, health, флаг админа по запросу, без настройки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 01:21:01 +03:00

126 lines
4.3 KiB
Go

package lk
import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
// fakeBJ — API bj-server для кабинета: проверка ключа, вход и портфель.
func fakeBJ(t *testing.T) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
keyOK := func(w http.ResponseWriter, r *http.Request) bool {
if r.Header.Get("X-BJ-Key") != "lk-key" {
w.WriteHeader(http.StatusForbidden)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "неверный ключ модуля"})
return false
}
return true
}
mux.HandleFunc("/api/lk/login", func(w http.ResponseWriter, r *http.Request) {
if keyOK(w, r) {
_ = json.NewEncoder(w).Encode(map[string]any{"session": "sess-1", "full_name": "Мостовиков Тест", "is_admin": false})
}
})
mux.HandleFunc("/api/lk/portfolio", func(w http.ResponseWriter, r *http.Request) {
if keyOK(w, r) && r.Header.Get("X-BJ-Session") == "sess-1" {
_ = json.NewEncoder(w).Encode(map[string]any{"full_name": "Мостовиков Тест", "contract": "4519", "positions": []any{}})
}
})
mux.HandleFunc("/api/lk/ping", func(w http.ResponseWriter, r *http.Request) {
if keyOK(w, r) {
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
}
})
srv := httptest.NewServer(mux)
t.Cleanup(srv.Close)
return srv
}
func TestLoginPortfolioHealth(t *testing.T) {
bj := fakeBJ(t)
s, err := New(Config{BJURL: bj.URL + "/", BJKey: "lk-key", Title: "Кабинет · тест"})
if err != nil {
t.Fatal(err)
}
h := s.Handler()
form := url.Values{"login": {"client"}, "password": {"secret"}}
req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/" {
t.Fatalf("вход: код %d, Location %q", rec.Code, rec.Header().Get("Location"))
}
var sess *http.Cookie
for _, c := range rec.Result().Cookies() {
if c.Name == "bj_lk" {
sess = c
}
}
if sess == nil || sess.Value != "sess-1" || !sess.HttpOnly {
t.Fatalf("кука сессии: %+v", sess)
}
req = httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sess)
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
body := rec.Body.String()
if rec.Code != http.StatusOK || !strings.Contains(body, "Мостовиков Тест") || !strings.Contains(body, "Кабинет · тест") {
t.Fatalf("портфель: код %d: %s", rec.Code, body)
}
if strings.Contains(body, `href="/clients"`) {
t.Error("клиенту без признака администратора показан раздел «Клиенты»")
}
rec = httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/health", nil))
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"ok":true`) {
t.Fatalf("health: код %d: %s", rec.Code, rec.Body.String())
}
}
// Признак администратора берётся из кук своего запроса, а не предыдущего (прежде был общий curReq).
func TestAdminFlagPerRequest(t *testing.T) {
bj := fakeBJ(t)
s, err := New(Config{BJURL: bj.URL, BJKey: "lk-key"})
if err != nil {
t.Fatal(err)
}
h := s.Handler()
admin := httptest.NewRequest(http.MethodGet, "/login", nil)
admin.AddCookie(&http.Cookie{Name: "bj_lk_admin", Value: "1"})
h.ServeHTTP(httptest.NewRecorder(), admin)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{Name: "bj_lk", Value: "sess-1"})
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if strings.Contains(rec.Body.String(), `href="/clients"`) {
t.Fatal("признак администратора перешёл из чужого запроса")
}
}
func TestNotConfigured(t *testing.T) {
s, err := New(Config{BJURL: "http://127.0.0.1:8080"})
if err != nil {
t.Fatal(err)
}
if s.Configured() {
t.Fatal("без ключа кабинет не настроен")
}
for _, p := range []string{"/", "/health"} {
rec := httptest.NewRecorder()
s.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, p, nil))
if rec.Code != http.StatusServiceUnavailable || !strings.Contains(rec.Body.String(), "BJ_KEY") {
t.Fatalf("%s: код %d: %s", p, rec.Code, rec.Body.String())
}
}
}