github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/amqp_test.go (about) 1 package reader 2 3 import ( 4 "flag" 5 "fmt" 6 "regexp" 7 "strings" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/Jeffail/benthos/v3/lib/log" 13 "github.com/Jeffail/benthos/v3/lib/metrics" 14 "github.com/Jeffail/benthos/v3/lib/types" 15 "github.com/ory/dockertest/v3" 16 amqp "github.com/rabbitmq/amqp091-go" 17 ) 18 19 func TestAMQPIntegration(t *testing.T) { 20 if m := flag.Lookup("test.run").Value.String(); m == "" || regexp.MustCompile(strings.Split(m, "/")[0]).FindString(t.Name()) == "" { 21 t.Skip("Skipping as execution was not requested explicitly using go test -run ^TestIntegration$") 22 } 23 24 if testing.Short() { 25 t.Skip("Skipping integration test in short mode") 26 } 27 28 pool, err := dockertest.NewPool("") 29 if err != nil { 30 t.Skipf("Could not connect to docker: %s", err) 31 } 32 pool.MaxWait = time.Second * 30 33 34 resource, err := pool.Run("rabbitmq", "latest", nil) 35 if err != nil { 36 t.Fatalf("Could not start resource: %s", err) 37 } 38 defer func() { 39 if err = pool.Purge(resource); err != nil { 40 t.Logf("Failed to clean up docker resource: %v", err) 41 } 42 }() 43 44 url := fmt.Sprintf("amqp://guest:guest@localhost:%v/", resource.GetPort("5672/tcp")) 45 46 if err = pool.Retry(func() error { 47 client, err := amqp.Dial(url) 48 if err == nil { 49 var mChan *amqp.Channel 50 if mChan, err = client.Channel(); err == nil { 51 err = mChan.ExchangeDeclare( 52 "test-exchange", // name of the exchange 53 "direct", // type 54 true, // durable 55 false, // delete when complete 56 false, // internal 57 false, // noWait 58 nil, // arguments 59 ) 60 } 61 client.Close() 62 } 63 64 return err 65 }); err != nil { 66 t.Fatalf("Could not connect to docker resource: %s", err) 67 } 68 69 t.Run("TestAMQPConnect", func(te *testing.T) { 70 testAMQPConnect(url, te) 71 }) 72 t.Run("TestAMQPDisconnect", func(te *testing.T) { 73 testAMQPDisconnect(url, te) 74 }) 75 } 76 77 func testAMQPConnect(url string, t *testing.T) { 78 exchange := "test-exchange" 79 key := "benthos-key" 80 81 conf := NewAMQPConfig() 82 conf.URL = url 83 conf.QueueDeclare.Enabled = true 84 conf.BindingsDeclare = append(conf.BindingsDeclare, AMQPBindingConfig{ 85 Exchange: exchange, 86 RoutingKey: key, 87 }) 88 89 m, err := NewAMQP(conf, log.Noop(), metrics.Noop()) 90 if err != nil { 91 t.Fatal(err) 92 } 93 94 if err = m.Connect(); err != nil { 95 t.Fatal(err) 96 } 97 98 defer func() { 99 m.CloseAsync() 100 if cErr := m.WaitForClose(time.Second); cErr != nil { 101 t.Error(cErr) 102 } 103 }() 104 105 var mIn *amqp.Connection 106 if mIn, err = amqp.Dial(url); err != nil { 107 t.Fatal(err) 108 } 109 110 var mChan *amqp.Channel 111 if mChan, err = mIn.Channel(); err != nil { 112 t.Fatalf("AMQP Channel: %s", err) 113 } 114 115 defer func() { 116 mChan.Close() 117 mIn.Close() 118 }() 119 120 N := 10 121 122 wg := sync.WaitGroup{} 123 wg.Add(N) 124 125 testMsgs := map[string]struct{}{} 126 for i := 0; i < N; i++ { 127 str := fmt.Sprintf("hello world: %v", i) 128 testMsgs[str] = struct{}{} 129 go func(testStr string) { 130 if pErr := mChan.Publish(exchange, key, false, false, amqp.Publishing{ 131 Headers: amqp.Table{ 132 "foo": "bar", 133 "root": amqp.Table{ 134 "foo": "bar2", 135 }, 136 }, 137 ContentType: "application/octet-stream", 138 ContentEncoding: "", 139 Body: []byte(testStr), 140 DeliveryMode: amqp.Transient, // 1=non-persistent, 2=persistent 141 Priority: 0, // 0-9 142 }); pErr != nil { 143 t.Error(pErr) 144 } 145 wg.Done() 146 }(str) 147 } 148 149 lMsgs := len(testMsgs) 150 for lMsgs > 0 { 151 var actM types.Message 152 actM, err = m.Read() 153 if err != nil { 154 t.Error(err) 155 } else { 156 act := string(actM.Get(0).Get()) 157 if _, exists := testMsgs[act]; !exists { 158 t.Errorf("Unexpected message: %v", act) 159 } 160 delete(testMsgs, act) 161 if act = actM.Get(0).Metadata().Get("foo"); act != "bar" { 162 t.Errorf("Wrong metadata returned: %v != bar", act) 163 } 164 if act = actM.Get(0).Metadata().Get("root_foo"); act != "bar2" { 165 t.Errorf("Wrong metadata returned: %v != bar2", act) 166 } 167 } 168 if err = m.Acknowledge(nil); err != nil { 169 t.Error(err) 170 } 171 lMsgs = len(testMsgs) 172 } 173 174 wg.Wait() 175 } 176 177 func testAMQPBatch(url string, t *testing.T) { 178 exchange := "test-exchange" 179 key := "benthos-key" 180 181 conf := NewAMQPConfig() 182 conf.URL = url 183 conf.QueueDeclare.Enabled = true 184 conf.MaxBatchCount = 10 185 conf.BindingsDeclare = append(conf.BindingsDeclare, AMQPBindingConfig{ 186 Exchange: exchange, 187 RoutingKey: key, 188 }) 189 190 m, err := NewAMQP(conf, log.Noop(), metrics.Noop()) 191 if err != nil { 192 t.Fatal(err) 193 } 194 195 if err = m.Connect(); err != nil { 196 t.Fatal(err) 197 } 198 199 defer func() { 200 m.CloseAsync() 201 if cErr := m.WaitForClose(time.Second); cErr != nil { 202 t.Error(cErr) 203 } 204 }() 205 206 var mIn *amqp.Connection 207 if mIn, err = amqp.Dial(url); err != nil { 208 t.Fatal(err) 209 } 210 211 var mChan *amqp.Channel 212 if mChan, err = mIn.Channel(); err != nil { 213 t.Fatalf("AMQP Channel: %s", err) 214 } 215 216 defer func() { 217 mChan.Close() 218 mIn.Close() 219 }() 220 221 N := 10 222 223 wg := sync.WaitGroup{} 224 wg.Add(N) 225 226 testMsgs := map[string]struct{}{} 227 for i := 0; i < N; i++ { 228 str := fmt.Sprintf("hello world: %v", i) 229 testMsgs[str] = struct{}{} 230 go func(testStr string) { 231 if pErr := mChan.Publish(exchange, key, false, false, amqp.Publishing{ 232 Headers: amqp.Table{ 233 "foo": "bar", 234 "root": amqp.Table{ 235 "foo": "bar2", 236 }, 237 }, 238 ContentType: "application/octet-stream", 239 ContentEncoding: "", 240 Body: []byte(testStr), 241 DeliveryMode: 1, // 1=non-persistent, 2=persistent 242 Priority: 0, // 0-9 243 }); pErr != nil { 244 t.Error(pErr) 245 } 246 wg.Done() 247 }(str) 248 } 249 250 wg.Wait() 251 252 lMsgs := len(testMsgs) 253 for lMsgs > 0 { 254 var actM types.Message 255 actM, err = m.Read() 256 if err != nil { 257 t.Error(err) 258 } else { 259 act := string(actM.Get(0).Get()) 260 if _, exists := testMsgs[act]; !exists { 261 t.Errorf("Unexpected message: %v", act) 262 } 263 delete(testMsgs, act) 264 if act = actM.Get(0).Metadata().Get("foo"); act != "bar" { 265 t.Errorf("Wrong metadata returned: %v != bar", act) 266 } 267 if act = actM.Get(0).Metadata().Get("root_foo"); act != "bar2" { 268 t.Errorf("Wrong metadata returned: %v != bar2", act) 269 } 270 } 271 if err = m.Acknowledge(nil); err != nil { 272 t.Error(err) 273 } 274 lMsgs = len(testMsgs) 275 } 276 } 277 278 func testAMQPDisconnect(url string, t *testing.T) { 279 conf := NewAMQPConfig() 280 conf.URL = url 281 conf.QueueDeclare.Enabled = true 282 283 m, err := NewAMQP(conf, log.Noop(), metrics.Noop()) 284 if err != nil { 285 t.Fatal(err) 286 } 287 288 if err = m.Connect(); err != nil { 289 t.Fatal(err) 290 } 291 292 wg := sync.WaitGroup{} 293 wg.Add(1) 294 go func() { 295 m.CloseAsync() 296 if cErr := m.WaitForClose(time.Second); cErr != nil { 297 t.Error(cErr) 298 } 299 wg.Done() 300 }() 301 302 if _, err = m.Read(); err != types.ErrTypeClosed && err != types.ErrNotConnected { 303 t.Errorf("Wrong error: %v != %v", err, types.ErrTypeClosed) 304 } 305 306 wg.Wait() 307 }