You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

78 lines
3.6 KiB

package handlers
import (
"encoding/json"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"teraclone/internal/service"
)
// Opt-in visual fixture, compiled only into tests. No connection to the device,
// no persistent credentials, and no writes through the HTTP surface.
func TestLogExplorerPreview(t *testing.T) {
if os.Getenv("TERA_LOG_PREVIEW") != "1" {
t.Skip("set TERA_LOG_PREVIEW=1 for a five-minute loopback-only preview")
}
app, err := service.NewAppService(filepath.Join(t.TempDir(), "preview.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)
}
handler.sessions["preview-only"] = browserSession{Username: "UI 미리보기 · 테스트 데이터", BackendToken: "not-a-device-token", Role: "administrator", WorkspaceID: "log-preview"}
mux := http.NewServeMux()
handler.RegisterRoutes(mux)
listener, err := net.Listen("tcp", "127.0.0.1:19083")
if err != nil {
t.Fatal(err)
}
server := &http.Server{ReadHeaderTimeout: 3 * time.Second, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Preview is read-only; no logs were deleted", http.StatusMethodNotAllowed)
return
}
if strings.HasPrefix(r.URL.Path, "/api/v1/") {
w.Header().Set("Content-Type", "application/json")
var payload any = []any{}
switch r.URL.Path {
case "/api/v1/system/status":
payload = map[string]any{"system_time": time.Now().UTC(), "cpu_usage_percent": 15, "memory_usage_percent": 28, "memory_total_bytes": 2000000000, "memory_used_bytes": 560000000, "memory_available_bytes": 1440000000, "storage": map[string]any{"path": "/userdata", "total_bytes": 8000000000, "used_bytes": 1600000000, "available_bytes": 6000000000, "usage_percent": 20}}
case "/api/v1/network":
payload = map[string]any{"host_name": "미리보기", "operating_mode": "Single", "interfaces": []any{}}
case "/api/v1/logs/system":
payload = []map[string]any{{"timestamp": time.Now().UTC(), "category": "system", "severity": "info", "source": "preview", "message": "테스트: 일반 시스템 이벤트"}, {"timestamp": time.Now().UTC(), "category": "system", "severity": "warning", "source": "authentication", "message": "테스트: 보존 감사 기록", "audit_protected": true, "audit_id": "preview-audit"}}
case "/api/v1/logs/links":
payload = []map[string]any{{"timestamp": time.Now().UTC(), "event_type": "snapshot", "resource": "CH18", "state": "up", "message": "must not display"}, {"timestamp": time.Now().UTC(), "event_type": "transition", "resource": "CH18", "previous_state": "down", "state": "up", "signal": "RX", "message": "테스트: 상태 변경"}}
case "/api/v1/time/settings":
payload = map[string]any{"timezone": "Asia/Seoul"}
case "/api/v1/logs/port":
channel := r.URL.Query().Get("channel")
if channel == "" || channel == "CH18" {
payload = []map[string]any{{"timestamp": time.Now().UTC(), "channel": "CH18", "event_type": "traffic", "direction": "RX", "actor": "preview", "message": "테스트 데이터: 장비 응답을 수신했습니다."}}
}
}
_ = json.NewEncoder(w).Encode(payload)
return
}
if !strings.HasPrefix(r.URL.Path, "/static/") && r.URL.Path != "/" && r.URL.Path != "/logs/ports" && r.URL.Path != "/logs/system" && r.URL.Path != "/logs/links" {
http.NotFound(w, r)
return
}
r.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "preview-only"})
mux.ServeHTTP(w, r)
})}
defer server.Close()
go server.Serve(listener)
t.Log("READ_ONLY_PREVIEW http://127.0.0.1:19083/logs/ports")
<-time.After(5 * time.Minute)
}