Ctrl K
ComunidadGuías…

adguardhome/lifecycle_test.go

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

Archivo original ↗Preguntar a KiBot
package adguardhome

import (
	"context"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"
	"time"
)

func TestProviderHTTPRequestsCancelWithEitherContext(t *testing.T) {
	for _, method := range []string{http.MethodGet, http.MethodPost} {
		for _, cancelGlobal := range []bool{false, true} {
			name := method + "/operation"
			if cancelGlobal {
				name = method + "/global"
			}
			t.Run(name, func(t *testing.T) {
				started := make(chan struct{})
				cancelled := make(chan struct{})
				s := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
					if r.Method != method {
						t.Errorf("HTTP method=%s, want %s", r.Method, method)
					}
					if _, err := io.Copy(io.Discard, r.Body); err != nil {
						t.Errorf("read request body: %v", err)
						return
					}
					close(started)
					<-r.Context().Done()
					close(cancelled)
				}))
				defer s.Close()
				root, cancelRoot := context.WithCancel(context.Background())
				defer cancelRoot()
				op, cancelOp := context.WithCancel(context.Background())
				defer cancelOp()
				t.Setenv(envURL, s.URL)
				t.Setenv(envUser, "test")
				t.Setenv(envPassword, "test")
				t.Setenv(envUseDNSRewrite, "false")
				p, err := NewAdguardHomeProvider(root, false)
				if err != nil {
					t.Fatal(err)
				}
				result := make(chan error, 1)
				go func() {
					if method == http.MethodGet {
						_, err := p.Records(op)
						result <- err
					} else {
						result <- p.client.SaveFilteringRules(op, []string{"# test rule"})
					}
				}()
				select {
				case <-started:
				case <-time.After(5 * time.Second):
					t.Fatal("HTTP request did not start")
				}
				if cancelGlobal {
					cancelRoot()
				} else {
					cancelOp()
				}
				select {
				case err := <-result:
					if !errors.Is(err, context.Canceled) {
						t.Fatalf("HTTP request error=%v, want cancellation", err)
					}
				case <-time.After(time.Second):
					t.Fatal("HTTP request ignored cancellation")
				}
				select {
				case <-cancelled:
				case <-time.After(5 * time.Second):
					t.Fatal("server did not observe cancellation")
				}
			})
		}
	}
}

type lifecycleTestBody struct {
	ctx context.Context
	*strings.Reader
	closed bool
}

func (b *lifecycleTestBody) Read(p []byte) (int, error) {
	if err := b.ctx.Err(); err != nil {
		return 0, err
	}
	return b.Reader.Read(p)
}

func (b *lifecycleTestBody) Close() error {
	b.closed = true
	return nil
}

func TestClientKeepsContextAliveUntilBodyConsumed(t *testing.T) {
	c := newAdguardHomeClient(context.Background(), "http://adguard.test/control/", "test", "test", false)
	var body *lifecycleTestBody
	c.hc.Transport = contextTestTransport(func(r *http.Request) (*http.Response, error) {
		body = &lifecycleTestBody{ctx: r.Context(), Reader: strings.NewReader(`{"user_rules":["# present"]}`)}
		return &http.Response{StatusCode: http.StatusOK, Body: body}, nil
	})
	rules, err := c.GetFilteringRules(context.Background())
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(rules, []string{"# present"}) || !body.closed {
		t.Fatalf("response body was not fully consumed and closed: rules=%v closed=%t", rules, body.closed)
	}
	if !errors.Is(body.ctx.Err(), context.Canceled) {
		t.Fatal("request context was not released after the body was consumed")
	}
}

func TestClientRejectsRequestsAfterGlobalCancellation(t *testing.T) {
	root, cancel := context.WithCancel(context.Background())
	cancel()
	c := newAdguardHomeClient(root, "http://adguard.test/control/", "test", "test", false)
	requests := 0
	c.hc.Transport = contextTestTransport(func(r *http.Request) (*http.Response, error) {
		requests++
		return &http.Response{StatusCode: http.StatusOK, Body: &lifecycleTestBody{ctx: r.Context(), Reader: strings.NewReader(`{"user_rules":[]}`)}}, nil
	})
	if _, err := c.GetFilteringRules(context.Background()); !errors.Is(err, context.Canceled) {
		t.Fatalf("GET error=%v, want cancellation", err)
	}
	if err := c.SaveFilteringRules(context.Background(), []string{"# test rule"}); !errors.Is(err, context.Canceled) {
		t.Fatalf("POST error=%v, want cancellation", err)
	}
	if requests != 0 {
		t.Fatalf("sent %d HTTP requests after shutdown", requests)
	}
}