code.pfad.fr/gohmekit@v0.2.1/hapip/notification/manager_test.go (about)

     1  package notification
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/go-kit/log"
    15  	"gotest.tools/v3/assert"
    16  )
    17  
    18  func TestManager(t *testing.T) {
    19  	m := Manager[string]{
    20  		Logger: log.NewNopLogger(),
    21  	}
    22  
    23  	ch := make(chan string)
    24  	s := http.Server{
    25  		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    26  			path := r.URL.Path
    27  			t.Log(path)
    28  			switch path {
    29  			case "/subscribe":
    30  				err := m.Subscribe(r.Context(), "123", true)
    31  				assert.NilError(t, err)
    32  			case "/unsubscribe":
    33  				err := m.Subscribe(r.Context(), "123", false)
    34  				assert.NilError(t, err)
    35  			case "/publish":
    36  				m.Publish(r.Context(), "123", bytes.NewReader([]byte("456")))
    37  			case "/wait":
    38  				ch <- "waiting"
    39  				assert.Equal(t, "published", <-ch)
    40  			case "/is-listening":
    41  				v, err := m.IsSubscribed(r.Context(), "123")
    42  				assert.NilError(t, err)
    43  				assert.Check(t, v)
    44  			default:
    45  				t.Fatalf("unexpected uri: %s", path)
    46  			}
    47  			_, _ = w.Write([]byte("ok"))
    48  		}),
    49  		ConnState:   m.ConnState,
    50  		ConnContext: m.ConnContext,
    51  	}
    52  
    53  	ln, err := net.Listen("tcp", ":0")
    54  	assert.NilError(t, err)
    55  	t.Cleanup(func() {
    56  		ln.Close()
    57  	})
    58  
    59  	go s.Serve(ln) //nolint:errcheck
    60  
    61  	addr := ln.Addr().String()
    62  	connA, err := net.Dial("tcp", addr)
    63  	assert.NilError(t, err)
    64  
    65  	connB, err := net.Dial("tcp", addr)
    66  	assert.NilError(t, err)
    67  
    68  	body := getRequest(t, connB, "/publish") // no one is listening yet
    69  	assert.Equal(t, "ok", body)
    70  
    71  	body = getRequest(t, connA, "/subscribe")
    72  	assert.Equal(t, "ok", body)
    73  
    74  	body = getRequest(t, connB, "/subscribe") // listen to self should not send notifications
    75  	assert.Equal(t, "ok", body)
    76  
    77  	time.Sleep(100 * time.Millisecond) // hack to wait for connA to be idle
    78  
    79  	body = getRequest(t, connB, "/publish")
    80  	assert.Equal(t, "ok", body)
    81  
    82  	buf := make([]byte, 3)
    83  	n, err := connA.Read(buf)
    84  	assert.NilError(t, err)
    85  	assert.Equal(t, 3, n)
    86  	assert.Equal(t, "456", string(buf))
    87  
    88  	body = getRequest(t, connA, "/is-listening")
    89  	assert.Equal(t, "ok", body)
    90  
    91  	go func() {
    92  		bodyWaited := getRequest(t, connA, "/wait")
    93  		assert.Equal(t, "ok", bodyWaited)
    94  		ch <- "waited"
    95  	}()
    96  
    97  	assert.Equal(t, "waiting", <-ch)
    98  	body = getRequest(t, connB, "/publish")
    99  	assert.Equal(t, "ok", body)
   100  	ch <- "published"
   101  
   102  	assert.Equal(t, "waited", <-ch)
   103  
   104  	body = getRequest(t, connA, "/unsubscribe")
   105  	assert.Equal(t, "ok", body)
   106  
   107  	time.Sleep(100 * time.Millisecond) // hack to wait for connA to be idle
   108  
   109  	body = getRequest(t, connB, "/publish") // no one is listening anymore
   110  	assert.Equal(t, "ok", body)
   111  
   112  	go func() {
   113  		time.Sleep(100 * time.Millisecond) // hack to wait for event to be sent
   114  		ch <- "waited"
   115  
   116  		buf, _ = io.ReadAll(connA)
   117  		ch <- string(buf)
   118  	}()
   119  
   120  	assert.Equal(t, "waited", <-ch)
   121  	err = connA.Close()
   122  	assert.NilError(t, err)
   123  	assert.Equal(t, "", <-ch)
   124  
   125  	err = connB.Close()
   126  	assert.NilError(t, err)
   127  }
   128  
   129  func getRequest(t *testing.T, c net.Conn, uri string) string {
   130  	_, err := c.Write([]byte(`GET ` + uri + ` HTTP/1.1
   131  Host: lights.local:12345
   132  
   133  `))
   134  	assert.NilError(t, err)
   135  
   136  	buf := bufio.NewReader(c)
   137  	resp, err := http.ReadResponse(buf, nil)
   138  	assert.NilError(t, err)
   139  
   140  	defer resp.Body.Close()
   141  	all, err := io.ReadAll(resp.Body)
   142  	assert.NilError(t, err)
   143  	return string(all)
   144  }
   145  
   146  type colaescerTest []byte
   147  
   148  func (ct colaescerTest) WriteTo(w io.Writer) (int64, error) {
   149  	n, err := w.Write(append(ct, '\n'))
   150  	return int64(n), err
   151  }
   152  func (ct colaescerTest) Coalesce(ev io.WriterTo) (Coalescer, error) {
   153  	switch last := ev.(type) {
   154  	case colaescerTest:
   155  		ct = append(ct, last...)
   156  	case *bytes.Buffer:
   157  		ct = append(ct, last.Bytes()...)
   158  	default:
   159  		panic("What the type?")
   160  	}
   161  	return ct, nil
   162  }
   163  
   164  var _ Coalescer = colaescerTest{}
   165  
   166  func TestCoalescer(t *testing.T) {
   167  	m := Manager[string]{
   168  		CoalesceDuration: 100 * time.Millisecond,
   169  		Logger:           log.NewNopLogger(),
   170  	}
   171  
   172  	s := http.Server{
   173  		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   174  			path := r.URL.Path
   175  			t.Log(path)
   176  			switch path {
   177  			case "/subscribe":
   178  				err := m.Subscribe(r.Context(), "123", true)
   179  				assert.NilError(t, err)
   180  			case "/publish-colaescer":
   181  				var ct colaescerTest = []byte("ct")
   182  				m.Publish(r.Context(), "123", ct)
   183  			case "/publish-immediate":
   184  				m.Publish(r.Context(), "123", bytes.NewBuffer([]byte("im")))
   185  			default:
   186  				t.Fatalf("unexpected uri: %s", path)
   187  			}
   188  			_, _ = w.Write([]byte("ok"))
   189  		}),
   190  		ConnState:   m.ConnState,
   191  		ConnContext: m.ConnContext,
   192  	}
   193  
   194  	ln, err := net.Listen("tcp", ":0")
   195  	assert.NilError(t, err)
   196  	t.Cleanup(func() {
   197  		ln.Close()
   198  	})
   199  
   200  	go s.Serve(ln) //nolint:errcheck
   201  
   202  	addr := ln.Addr().String()
   203  	connA, err := net.Dial("tcp", addr)
   204  	assert.NilError(t, err)
   205  
   206  	connB, err := net.Dial("tcp", addr)
   207  	assert.NilError(t, err)
   208  
   209  	body := getRequest(t, connA, "/subscribe")
   210  	assert.Equal(t, "ok", body)
   211  
   212  	time.Sleep(100 * time.Millisecond) // hack to wait for connA to be idle
   213  
   214  	// this one will go through immediately
   215  	body = getRequest(t, connB, "/publish-colaescer")
   216  	assert.Equal(t, "ok", body)
   217  
   218  	// should get throttled...
   219  	body = getRequest(t, connB, "/publish-colaescer")
   220  	assert.Equal(t, "ok", body)
   221  
   222  	// ...and merged
   223  	body = getRequest(t, connB, "/publish-colaescer")
   224  	assert.Equal(t, "ok", body)
   225  
   226  	err = connA.SetDeadline(time.Now().Add(110 * time.Millisecond))
   227  	assert.NilError(t, err)
   228  
   229  	buf, err := io.ReadAll(connA)
   230  	assert.Check(t, errors.Is(err, os.ErrDeadlineExceeded))
   231  	assert.Equal(t, "ct\nctct\n", string(buf))
   232  
   233  	// should get throttled...
   234  	body = getRequest(t, connB, "/publish-colaescer")
   235  	assert.Equal(t, "ok", body)
   236  
   237  	// ...and send immediately
   238  	body = getRequest(t, connB, "/publish-immediate")
   239  	assert.Equal(t, "ok", body)
   240  
   241  	err = connA.SetDeadline(time.Now().Add(10 * time.Millisecond))
   242  	assert.NilError(t, err)
   243  
   244  	buf, err = io.ReadAll(connA)
   245  	assert.Check(t, errors.Is(err, os.ErrDeadlineExceeded))
   246  	assert.Equal(t, "ctim\n", string(buf))
   247  
   248  	err = connA.Close()
   249  	assert.NilError(t, err)
   250  
   251  	err = connB.Close()
   252  	assert.NilError(t, err)
   253  }