|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"teraclone/internal/service"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestLogExplorerPagesPreserveFiltersAndRoleRestrictions(t *testing.T) {
|
|
|
|
|
app, err := service.NewAppService(filepath.Join(t.TempDir(), "frontend.db"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer app.Close()
|
|
|
|
|
handler, err := NewDeviceHandler(app, "http://127.0.0.1:1")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
handler.RegisterRoutes(mux)
|
|
|
|
|
for _, role := range []string{"administrator", "port_operator"} {
|
|
|
|
|
handler.sessions[role] = browserSession{Username: "test", BackendToken: "private-token", Role: role}
|
|
|
|
|
for _, path := range []string{"/logs/system", "/logs/ports", "/logs/links"} {
|
|
|
|
|
t.Run(role+path, func(t *testing.T) {
|
|
|
|
|
request := httptest.NewRequest(http.MethodGet, path, nil)
|
|
|
|
|
request.AddCookie(&http.Cookie{Name: sessionCookieName, Value: role})
|
|
|
|
|
response := httptest.NewRecorder()
|
|
|
|
|
mux.ServeHTTP(response, request)
|
|
|
|
|
if role == "port_operator" {
|
|
|
|
|
if response.Code != http.StatusForbidden {
|
|
|
|
|
t.Fatalf("operator log access = %d, want 403", response.Code)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if response.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("log page status = %d", response.Code)
|
|
|
|
|
}
|
|
|
|
|
body := response.Body.String()
|
|
|
|
|
for _, required := range []string{
|
|
|
|
|
`log-view.js?v=20260907-storage-logs-1`, `port-log-clear.js?v=20260907-storage-logs-1`, `integration.js?v=20260909-config-read-1`,
|
|
|
|
|
`name="keyword"`, `name="from"`, `name="to"`, `name="limit"`, `name="auto_refresh"`,
|
|
|
|
|
`data-ui-action="primary-action"`, `data-ui-action="secondary-action"`, "로그 다운로드", "검색 초기화",
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(body, required) {
|
|
|
|
|
t.Errorf("missing log UI element: %s", required)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(body, "private-token") {
|
|
|
|
|
t.Fatal("backend credential leaked")
|
|
|
|
|
}
|
|
|
|
|
if strings.Index(body, "log-view.js?") > strings.Index(body, "integration.js?") {
|
|
|
|
|
t.Fatal("log view must load before integration")
|
|
|
|
|
}
|
|
|
|
|
if strings.Index(body, "port-log-clear.js?") > strings.Index(body, "integration.js?") {
|
|
|
|
|
t.Fatal("port log clear must load before integration")
|
|
|
|
|
}
|
|
|
|
|
if path == "/logs/ports" {
|
|
|
|
|
for _, required := range []string{`value="CH01"`, `value="CH48"`, "RX/TX 데이터", "UP/DOWN 변경은 포트 상태 이벤트", "내보내기는 현재 검색 조건과 최대 다운로드 건수"} {
|
|
|
|
|
if !strings.Contains(body, required) {
|
|
|
|
|
t.Errorf("missing port log requirement: %s", required)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if path == "/logs/links" && (!strings.Contains(body, `value="transition"`) || !strings.Contains(body, "상태 변경 기록만 조회·내보내기")) {
|
|
|
|
|
t.Fatal("state event view is not changes-only")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|