96 lines
3.0 KiB
Go
96 lines
3.0 KiB
Go
package console
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestHandlerServesSelfContainedConsoleWithSecurityHeaders(t *testing.T) {
|
||
|
|
handler, err := NewHandler("http://127.0.0.1:8889/")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
request := httptest.NewRequest(http.MethodGet, "/sense-console/", nil)
|
||
|
|
response := httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(response, request)
|
||
|
|
if response.Code != http.StatusOK {
|
||
|
|
t.Fatalf("unexpected status: %d", response.Code)
|
||
|
|
}
|
||
|
|
body := response.Body.String()
|
||
|
|
for _, required := range []string{"YoVision Sense", "/sense-console/app.css", "/sense-console/app.js"} {
|
||
|
|
if !strings.Contains(body, required) {
|
||
|
|
t.Fatalf("console HTML lacks %q", required)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, forbidden := range []string{"http://", "https://", "<script>", "<style>"} {
|
||
|
|
if strings.Contains(body, forbidden) {
|
||
|
|
t.Fatalf("console HTML contains forbidden inline/external marker %q", forbidden)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
csp := response.Header().Get("Content-Security-Policy")
|
||
|
|
if !strings.Contains(csp, "frame-src http://127.0.0.1:8889") ||
|
||
|
|
!strings.Contains(csp, "frame-ancestors 'none'") {
|
||
|
|
t.Fatalf("unexpected CSP: %s", csp)
|
||
|
|
}
|
||
|
|
if response.Header().Get("Cache-Control") != "no-store" ||
|
||
|
|
response.Header().Get("X-Content-Type-Options") != "nosniff" {
|
||
|
|
t.Fatal("security headers are incomplete")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestHandlerReturnsBoundedRuntimeConfig(t *testing.T) {
|
||
|
|
handler, err := NewHandler("http://localhost:8889")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
response := httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/sense-console/config", nil))
|
||
|
|
var payload struct {
|
||
|
|
WebRTCBaseURL string `json:"webrtc_base_url"`
|
||
|
|
PageSize int `json:"page_size"`
|
||
|
|
MaxActivePreviews int `json:"max_active_previews"`
|
||
|
|
RecordingAndPlayback bool `json:"recording_and_playback"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if payload.WebRTCBaseURL != "http://localhost:8889" || payload.PageSize != 16 ||
|
||
|
|
payload.MaxActivePreviews != 4 || payload.RecordingAndPlayback {
|
||
|
|
t.Fatalf("unexpected runtime config: %+v", payload)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestHandlerRejectsRemoteOrCredentialedPreviewBase(t *testing.T) {
|
||
|
|
for _, value := range []string{
|
||
|
|
"https://media.example", "http://user@127.0.0.1:8889", "http://127.0.0.1:8889/path",
|
||
|
|
} {
|
||
|
|
if _, err := NewHandler(value); err == nil {
|
||
|
|
t.Fatalf("invalid preview base was accepted: %s", value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestHandlerRejectsWritesAndUnknownAssets(t *testing.T) {
|
||
|
|
handler, err := NewHandler("http://127.0.0.1:8889")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, test := range []struct {
|
||
|
|
method string
|
||
|
|
path string
|
||
|
|
want int
|
||
|
|
}{
|
||
|
|
{http.MethodPost, "/sense-console/", http.StatusMethodNotAllowed},
|
||
|
|
{http.MethodGet, "/sense-console/missing", http.StatusNotFound},
|
||
|
|
} {
|
||
|
|
response := httptest.NewRecorder()
|
||
|
|
handler.ServeHTTP(response, httptest.NewRequest(test.method, test.path, nil))
|
||
|
|
if response.Code != test.want {
|
||
|
|
t.Fatalf("%s %s: got %d, want %d", test.method, test.path, response.Code, test.want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|