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" && path != "/logs/ports" { 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") } }) } } }