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 }