107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"rmm-openwrt/server/internal/model"
|
|
)
|
|
|
|
func TestWriteLuCIErrorRendersSafeBrowserPage(t *testing.T) {
|
|
app := &App{deviceDomain: "routers.example", publicScheme: "https", publicURL: "https://rmm.example"}
|
|
req := httptest.NewRequest(http.MethodGet, "https://router.rmm.example/cgi-bin/luci/?token=must-not-leak", nil)
|
|
req.Header.Set("Accept", "text/html,application/xhtml+xml")
|
|
req = req.WithContext(context.WithValue(req.Context(), requestIDContextKey, "req-test-123"))
|
|
recorder := httptest.NewRecorder()
|
|
|
|
app.writeLuCIError(recorder, req, http.StatusGatewayTimeout, "internal proxy details")
|
|
|
|
if recorder.Code != http.StatusGatewayTimeout {
|
|
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusGatewayTimeout)
|
|
}
|
|
if got := recorder.Header().Get("Content-Type"); !strings.HasPrefix(got, "text/html") {
|
|
t.Fatalf("content type = %q, want HTML", got)
|
|
}
|
|
body := recorder.Body.String()
|
|
for _, expected := range []string{"LuCI не отвечает", "req-test-123", `href="/cgi-bin/luci/"`, `href="https://rmm.example/"`} {
|
|
if !strings.Contains(body, expected) {
|
|
t.Errorf("response does not contain %q", expected)
|
|
}
|
|
}
|
|
for _, unexpected := range []string{"must-not-leak", "internal proxy details"} {
|
|
if strings.Contains(body, unexpected) {
|
|
t.Errorf("response leaked %q", unexpected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRemoteSessionAccessStateChecksTunnelPort(t *testing.T) {
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer listener.Close()
|
|
_, portText, _ := net.SplitHostPort(listener.Addr().String())
|
|
port, _ := strconv.Atoi(portText)
|
|
now := time.Now().UTC()
|
|
app := &App{tunnelHTTPHost: "127.0.0.1"}
|
|
session := model.RemoteSession{Status: "active", LuCIPort: port, StartedAt: &now, ExpiresAt: now.Add(time.Minute)}
|
|
if got := app.remoteSessionAccessState(session); got != "ready" {
|
|
t.Fatalf("access state = %q, want ready", got)
|
|
}
|
|
_ = listener.Close()
|
|
if got := app.remoteSessionAccessState(session); got != "starting" {
|
|
t.Fatalf("fresh failed tunnel state = %q, want starting", got)
|
|
}
|
|
old := now.Add(-time.Minute)
|
|
session.StartedAt = &old
|
|
if got := app.remoteSessionAccessState(session); got != "unavailable" {
|
|
t.Fatalf("stale failed tunnel state = %q, want unavailable", got)
|
|
}
|
|
}
|
|
|
|
func TestWriteLuCIErrorKeepsJSONForAPIClients(t *testing.T) {
|
|
app := &App{deviceDomain: "rmm.example", publicScheme: "https"}
|
|
req := httptest.NewRequest(http.MethodPost, "/api/devices/device-1/access", nil)
|
|
req.Header.Set("Accept", "application/json")
|
|
recorder := httptest.NewRecorder()
|
|
|
|
app.writeLuCIError(recorder, req, http.StatusConflict, "LuCI session is not active")
|
|
|
|
if recorder.Code != http.StatusConflict {
|
|
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusConflict)
|
|
}
|
|
if got := recorder.Header().Get("Content-Type"); got != "application/json" {
|
|
t.Fatalf("content type = %q, want application/json", got)
|
|
}
|
|
if !strings.Contains(recorder.Body.String(), `"error":"LuCI session is not active"`) {
|
|
t.Fatalf("unexpected JSON response: %s", recorder.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestLuCIErrorCopyCoversUserFacingFailures(t *testing.T) {
|
|
for _, status := range []int{
|
|
http.StatusUnauthorized,
|
|
http.StatusForbidden,
|
|
http.StatusNotFound,
|
|
http.StatusConflict,
|
|
http.StatusTooManyRequests,
|
|
http.StatusBadGateway,
|
|
http.StatusGatewayTimeout,
|
|
http.StatusInternalServerError,
|
|
} {
|
|
t.Run(strconv.Itoa(status), func(t *testing.T) {
|
|
title, description, action := luciErrorCopy(status)
|
|
if title == "" || description == "" || action == "" {
|
|
t.Fatalf("status %d has incomplete UI copy", status)
|
|
}
|
|
})
|
|
}
|
|
}
|