Files
djk/internal/app/ui_playback_proxy_integration_test.go
T

149 lines
4.8 KiB
Go

package app
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
func TestPlaybackNginxFlushesMediaAndPreservesCookies(t *testing.T) {
nginx, err := exec.LookPath("nginx")
if err != nil || runtime.GOOS == "windows" {
t.Skip("local Nginx is unavailable")
}
upstream := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.URL.Path == "/ready" {
writer.WriteHeader(http.StatusNoContent)
return
}
cookie, err := request.Cookie("synthetic_viewer")
if err != nil || cookie.Value != "local-fixture" {
http.Error(writer, "missing fixture identity", http.StatusUnauthorized)
return
}
if request.URL.Path == "/stream" && !playbackRequestAllowed(writer, request, http.MethodGet) {
return
}
http.SetCookie(writer, &http.Cookie{Name: "synthetic_seen", Value: "yes", Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode})
writer.Header().Set("Content-Type", "video/mp4")
_, _ = io.WriteString(writer, "synthetic-init")
writer.(http.Flusher).Flush()
select {
case <-request.Context().Done():
case <-time.After(5 * time.Second):
}
}))
defer upstream.Close()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
address := listener.Addr().String()
_ = listener.Close()
directory := t.TempDir()
configuration := fmt.Sprintf(`worker_processes 1;
error_log stderr;
pid "%[1]s/nginx.pid";
lock_file "%[1]s/nginx.lock";
events { worker_connections 32; }
http {
access_log off;
client_body_temp_path "%[1]s/client_temp";
proxy_temp_path "%[1]s/proxy_temp";
fastcgi_temp_path "%[1]s/fastcgi_temp";
uwsgi_temp_path "%[1]s/uwsgi_temp";
scgi_temp_path "%[1]s/scgi_temp";
server {
listen %[2]s;
location / {
proxy_pass %[3]s;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}
}
`, directory, address, upstream.URL)
path := filepath.Join(directory, "nginx.conf")
if err := os.WriteFile(path, []byte(configuration), 0600); err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
command := exec.CommandContext(ctx, nginx, "-e", "stderr", "-p", directory+string(os.PathSeparator), "-c", path, "-g", "daemon off; master_process off;")
output := &cappedStringWriter{limit: 8192}
command.Stderr = output
command.WaitDelay = time.Second
if err := command.Start(); err != nil {
t.Fatal(err)
}
done := make(chan struct{})
var exitErr error
go func() { exitErr = command.Wait(); close(done) }()
defer func() { cancel(); <-done }()
client := &http.Client{Transport: &http.Transport{Proxy: nil}, Timeout: time.Second}
defer client.CloseIdleConnections()
base := "http://" + address
deadline := time.Now().Add(3 * time.Second)
for {
response, err := client.Get(base + "/ready")
if err == nil {
response.Body.Close()
break
}
select {
case <-done:
if strings.Contains(output.String(), "sysctlbyname") && strings.Contains(output.String(), "Operation not permitted") {
t.Skip("local Nginx cannot read TCP settings in the sandbox; run this integration test with system access")
}
t.Fatal("Nginx fixture exited", exitErr, output.String())
default:
}
if time.Now().After(deadline) {
t.Fatal("Nginx fixture did not start", err)
}
time.Sleep(20 * time.Millisecond)
}
for _, route := range []string{"buffered", "stream"} {
t.Run(route, func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, base+"/"+route, nil)
request.Header.Set("Sec-Fetch-Site", "same-origin")
request.Header.Set("Origin", "https://library.example.invalid")
request.AddCookie(&http.Cookie{Name: "synthetic_viewer", Value: "local-fixture"})
started := time.Now()
response, err := client.Do(request)
var body []byte
if err == nil {
defer response.Body.Close()
body = make([]byte, len("synthetic-init"))
_, err = io.ReadFull(response.Body, body)
}
if route == "buffered" {
if err == nil {
t.Logf("default buffering also forwarded this fixture in %s; buffering alone does not establish a deployment failure", time.Since(started))
}
return
}
if err != nil || string(body) != "synthetic-init" || response.StatusCode != http.StatusOK {
t.Fatal("Nginx buffered or rejected media initialization", err, string(body))
}
if !strings.Contains(response.Header.Get("Cache-Control"), "no-store") || !strings.Contains(response.Header.Get("Set-Cookie"), "synthetic_seen=yes") {
t.Fatal("proxy lost privacy or browser identity headers", response.Header)
}
t.Logf("first media bytes arrived through Nginx in %s before upstream completion", time.Since(started))
})
}
}