6fee3c5ff7
Код 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>
126 lines
4.3 KiB
Go
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())
|
|
}
|
|
}
|
|
}
|