Ctrl K
ComunidadGuías…

internal/middleware/read_deadline_middleware_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package middleware

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gorilla/mux"
)

// stallingBody sends a first byte and then never sends the rest, the way a
// slowloris client does.
type stallingBody struct {
	sentFirst bool
	release   chan struct{}
}

func (b *stallingBody) Read(p []byte) (int, error) {
	if !b.sentFirst {
		b.sentFirst = true
		p[0] = '{'
		return 1, nil
	}
	<-b.release
	return 0, io.EOF
}

func (b *stallingBody) Close() error { return nil }

// TestReadDeadlineRefusesABodyThatNeverArrives runs against a real server,
// since the deadline lives on the connection and a ResponseRecorder has none.
func TestReadDeadlineRefusesABodyThatNeverArrives(t *testing.T) {
	router := mux.NewRouter()
	router.Use(NewReadDeadlineMiddleware(150 * time.Millisecond))
	readErr := make(chan error, 1)
	router.HandleFunc("/v1/logs", func(w http.ResponseWriter, r *http.Request) {
		_, err := io.ReadAll(r.Body)
		readErr <- err
		w.WriteHeader(http.StatusOK)
	}).Methods(http.MethodPost)

	server := httptest.NewServer(router)
	defer server.Close()

	body := &stallingBody{release: make(chan struct{})}
	defer close(body.release)
	req, err := http.NewRequest(http.MethodPost, server.URL+"/v1/logs", body)
	if err != nil {
		t.Fatalf("building the request: %v", err)
	}
	// -1 forces chunked encoding, so the server keeps reading instead of trusting a length.
	req.ContentLength = -1

	start := time.Now()
	resp, err := server.Client().Do(req)
	if resp != nil {
		resp.Body.Close()
	}
	elapsed := time.Since(start)

	select {
	case err := <-readErr:
		if err == nil {
			t.Fatal("the handler read a body that was never sent: no deadline applied")
		}
	case <-time.After(2 * time.Second):
		t.Fatal("nothing gave up: the request is still being held")
	}
	if elapsed > 2*time.Second {
		t.Fatalf("the deadline did not bite, the request ran for %s", elapsed)
	}
}

// TestReadDeadlineLetsANormalBodyThrough checks a body sent in one go is not
// affected by the deadline.
func TestReadDeadlineLetsANormalBodyThrough(t *testing.T) {
	router := mux.NewRouter()
	router.Use(NewReadDeadlineMiddleware(5 * time.Second))
	router.HandleFunc("/v1/logs", func(w http.ResponseWriter, r *http.Request) {
		payload, err := io.ReadAll(r.Body)
		if err != nil {
			t.Errorf("reading a body that was sent whole: %v", err)
		}
		if string(payload) != `{"resourceLogs":[]}` {
			t.Errorf("the handler must see the body unchanged, got %q", payload)
		}
		w.WriteHeader(http.StatusOK)
	}).Methods(http.MethodPost)

	server := httptest.NewServer(router)
	defer server.Close()

	resp, err := server.Client().Post(
		server.URL+"/v1/logs", "application/json", stringReader(`{"resourceLogs":[]}`),
	)
	if err != nil {
		t.Fatalf("an honest request must succeed: %v", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
}

// TestReadDeadlineIsSilentWhereItCannotApply checks that a ResponseWriter
// that cannot carry a deadline, like httptest.ResponseRecorder, does not
// turn into a 500.
func TestReadDeadlineIsSilentWhereItCannotApply(t *testing.T) {
	router := mux.NewRouter()
	router.Use(NewReadDeadlineMiddleware(time.Second))
	router.HandleFunc("/v1/logs", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusAccepted)
	}).Methods(http.MethodPost)

	recorder := httptest.NewRecorder()
	router.ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/v1/logs", nil))
	if recorder.Code != http.StatusAccepted {
		t.Fatalf("the handler must still run, got %d", recorder.Code)
	}
}

type stringReaderT struct{ s string }

func (r *stringReaderT) Read(p []byte) (int, error) {
	if r.s == "" {
		return 0, io.EOF
	}
	n := copy(p, r.s)
	r.s = r.s[n:]
	return n, nil
}

func stringReader(s string) io.Reader { return &stringReaderT{s: s} }