github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/branch_test.go (about) 1 package processor 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/Jeffail/benthos/v3/lib/log" 8 "github.com/Jeffail/benthos/v3/lib/message" 9 "github.com/Jeffail/benthos/v3/lib/metrics" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func TestBranchBasic(t *testing.T) { 15 type mockMsg struct { 16 content string 17 meta map[string]string 18 } 19 msg := func(content string, meta ...string) mockMsg { 20 t.Helper() 21 m := mockMsg{ 22 content: content, 23 meta: map[string]string{}, 24 } 25 for i, v := range meta { 26 if i%2 == 1 { 27 m.meta[meta[i-1]] = v 28 } 29 } 30 return m 31 } 32 33 tests := map[string]struct { 34 requestMap string 35 processorMap string 36 resultMap string 37 input []mockMsg 38 output []mockMsg 39 }{ 40 "empty request mapping": { 41 requestMap: "", 42 processorMap: "root.nested = this", 43 resultMap: "root.result = this.nested", 44 input: []mockMsg{ 45 msg(`{"value":"foobar"}`), 46 }, 47 output: []mockMsg{ 48 msg(`{"result":{"value":"foobar"},"value":"foobar"}`), 49 }, 50 }, 51 "empty result mapping": { 52 requestMap: "root.nested = this", 53 processorMap: "root = this", 54 resultMap: "", 55 input: []mockMsg{ 56 msg(`{"value":"foobar"}`), 57 }, 58 output: []mockMsg{ 59 msg(`{"value":"foobar"}`), 60 }, 61 }, 62 "copy metadata over only": { 63 requestMap: `meta foo = meta("foo")`, 64 processorMap: `meta foo = meta("foo") + " and this"`, 65 resultMap: `meta new_foo = meta("foo")`, 66 input: []mockMsg{ 67 msg( 68 `{"value":"foobar"}`, 69 "foo", "bar", 70 ), 71 }, 72 output: []mockMsg{ 73 msg( 74 `{"value":"foobar"}`, 75 "foo", "bar", 76 "new_foo", "bar and this", 77 ), 78 }, 79 }, 80 "do not carry error into branch": { 81 requestMap: `root = this`, 82 processorMap: `root = this 83 root.name_upper = this.name.uppercase()`, 84 resultMap: `root.result = if this.failme.bool(false) { 85 throw("this is a branch error") 86 } else { 87 this.name_upper 88 }`, 89 input: []mockMsg{ 90 msg( 91 `{"id":0,"name":"first"}`, 92 FailFlagKey, "this is a pre-existing failure", 93 ), 94 msg(`{"failme":true,"id":1,"name":"second"}`), 95 msg( 96 `{"failme":true,"id":2,"name":"third"}`, 97 FailFlagKey, "this is a pre-existing failure", 98 ), 99 }, 100 output: []mockMsg{ 101 msg( 102 `{"id":0,"name":"first","result":"FIRST"}`, 103 FailFlagKey, "this is a pre-existing failure", 104 ), 105 msg( 106 `{"failme":true,"id":1,"name":"second"}`, 107 FailFlagKey, "result mapping failed: failed assignment (line 1): this is a branch error", 108 ), 109 msg( 110 `{"failme":true,"id":2,"name":"third"}`, 111 FailFlagKey, "result mapping failed: failed assignment (line 1): this is a branch error", 112 ), 113 }, 114 }, 115 "map error into branch": { 116 requestMap: `root.err = error()`, 117 processorMap: `root.err = this.err.uppercase()`, 118 resultMap: `root.result_err = this.err`, 119 input: []mockMsg{ 120 msg( 121 `{"id":0,"name":"first"}`, 122 FailFlagKey, "this is a pre-existing failure", 123 ), 124 msg(`{"id":1,"name":"second"}`), 125 }, 126 output: []mockMsg{ 127 msg( 128 `{"id":0,"name":"first","result_err":"THIS IS A PRE-EXISTING FAILURE"}`, 129 FailFlagKey, "this is a pre-existing failure", 130 ), 131 msg(`{"id":1,"name":"second","result_err":""}`), 132 }, 133 }, 134 "filtered and failed mappings": { 135 requestMap: `root = match { 136 this.id == 0 => throw("i dont like zero"), 137 this.id == 3 => deleted(), 138 _ => {"name":this.name,"id":this.id} 139 }`, 140 processorMap: `root = this 141 root.name_upper = this.name.uppercase()`, 142 resultMap: `root.result = match { 143 this.id == 2 => throw("i dont like two either"), 144 _ => this.name_upper 145 }`, 146 input: []mockMsg{ 147 msg(`{"id":0,"name":"first"}`), 148 msg(`{"id":1,"name":"second"}`), 149 msg(`{"id":2,"name":"third"}`), 150 msg(`{"id":3,"name":"fourth"}`), 151 msg(`{"id":4,"name":"fifth"}`), 152 }, 153 output: []mockMsg{ 154 msg( 155 `{"id":0,"name":"first"}`, 156 FailFlagKey, 157 "request mapping failed: failed assignment (line 1): i dont like zero", 158 ), 159 msg(`{"id":1,"name":"second","result":"SECOND"}`), 160 msg( 161 `{"id":2,"name":"third"}`, 162 FailFlagKey, 163 "result mapping failed: failed assignment (line 1): i dont like two either", 164 ), 165 msg(`{"id":3,"name":"fourth"}`), 166 msg(`{"id":4,"name":"fifth","result":"FIFTH"}`), 167 }, 168 }, 169 "filter all requests": { 170 requestMap: `root = deleted()`, 171 processorMap: `root = this`, 172 resultMap: `root.result = this`, 173 input: []mockMsg{ 174 msg(`{"id":0,"name":"first"}`), 175 msg(`{"id":1,"name":"second"}`), 176 msg(`{"id":2,"name":"third"}`), 177 msg(`{"id":3,"name":"fourth"}`), 178 msg(`{"id":4,"name":"fifth"}`), 179 }, 180 output: []mockMsg{ 181 msg(`{"id":0,"name":"first"}`), 182 msg(`{"id":1,"name":"second"}`), 183 msg(`{"id":2,"name":"third"}`), 184 msg(`{"id":3,"name":"fourth"}`), 185 msg(`{"id":4,"name":"fifth"}`), 186 }, 187 }, 188 "filter during processing": { 189 requestMap: `root = if this.id == 3 { throw("foo") } else { this }`, 190 processorMap: `root = deleted()`, 191 resultMap: `root.result = this`, 192 input: []mockMsg{ 193 msg(`{"id":0,"name":"first"}`), 194 msg(`{"id":1,"name":"second"}`), 195 msg(`{"id":2,"name":"third"}`), 196 msg(`{"id":3,"name":"fourth"}`), 197 msg(`{"id":4,"name":"fifth"}`), 198 }, 199 output: []mockMsg{ 200 msg( 201 `{"id":0,"name":"first"}`, 202 FailFlagKey, 203 "child processors resulted in zero messages", 204 ), 205 msg( 206 `{"id":1,"name":"second"}`, 207 FailFlagKey, 208 "child processors resulted in zero messages", 209 ), 210 msg( 211 `{"id":2,"name":"third"}`, 212 FailFlagKey, 213 "child processors resulted in zero messages", 214 ), 215 msg( 216 `{"id":3,"name":"fourth"}`, 217 FailFlagKey, 218 "request mapping failed: failed assignment (line 1): foo", 219 ), 220 msg( 221 `{"id":4,"name":"fifth"}`, 222 FailFlagKey, 223 "child processors resulted in zero messages", 224 ), 225 }, 226 }, 227 "filter some during processing": { 228 requestMap: `root = if this.id == 3 { throw("foo") } else { this }`, 229 processorMap: `root = if this.id == 2 { deleted() }`, 230 resultMap: `root.result = this`, 231 input: []mockMsg{ 232 msg(`{"id":0,"name":"first"}`), 233 msg(`{"id":1,"name":"second"}`), 234 msg(`{"id":2,"name":"third"}`), 235 msg(`{"id":3,"name":"fourth"}`), 236 msg(`{"id":4,"name":"fifth"}`), 237 }, 238 output: []mockMsg{ 239 msg( 240 `{"id":0,"name":"first"}`, 241 FailFlagKey, 242 "message count from branch processors does not match request, started with 4 messages, finished with 5", 243 ), 244 msg( 245 `{"id":1,"name":"second"}`, 246 FailFlagKey, 247 "message count from branch processors does not match request, started with 4 messages, finished with 5", 248 ), 249 msg( 250 `{"id":2,"name":"third"}`, 251 FailFlagKey, 252 "message count from branch processors does not match request, started with 4 messages, finished with 5", 253 ), 254 msg( 255 `{"id":3,"name":"fourth"}`, 256 FailFlagKey, 257 "request mapping failed: failed assignment (line 1): foo", 258 ), 259 msg( 260 `{"id":4,"name":"fifth"}`, 261 FailFlagKey, 262 "message count from branch processors does not match request, started with 4 messages, finished with 5", 263 ), 264 }, 265 }, 266 } 267 268 for name, test := range tests { 269 test := test 270 t.Run(name, func(t *testing.T) { 271 t.Parallel() 272 273 procConf := NewConfig() 274 procConf.Type = TypeBloblang 275 procConf.Bloblang = BloblangConfig(test.processorMap) 276 277 conf := NewConfig() 278 conf.Type = TypeBranch 279 conf.Branch.RequestMap = test.requestMap 280 conf.Branch.Processors = append(conf.Branch.Processors, procConf) 281 conf.Branch.ResultMap = test.resultMap 282 283 proc, err := NewBranch(conf, nil, log.Noop(), metrics.Noop()) 284 require.NoError(t, err) 285 286 msg := message.New(nil) 287 for _, m := range test.input { 288 part := message.NewPart([]byte(m.content)) 289 if m.meta != nil { 290 for k, v := range m.meta { 291 part.Metadata().Set(k, v) 292 } 293 } 294 msg.Append(part) 295 } 296 297 outMsgs, res := proc.ProcessMessage(msg) 298 299 require.Nil(t, res) 300 require.Len(t, outMsgs, 1) 301 302 assert.Equal(t, len(test.output), outMsgs[0].Len()) 303 for i, out := range test.output { 304 comparePart := mockMsg{ 305 content: string(outMsgs[0].Get(i).Get()), 306 meta: map[string]string{}, 307 } 308 309 outMsgs[0].Get(i).Metadata().Iter(func(k, v string) error { 310 comparePart.meta[k] = v 311 return nil 312 }) 313 314 assert.Equal(t, out, comparePart) 315 } 316 317 // Ensure nothing changed 318 for i, m := range test.input { 319 doc, err := msg.Get(i).JSON() 320 if err == nil { 321 msg.Get(i).SetJSON(doc) 322 } 323 assert.Equal(t, m.content, string(msg.Get(i).Get())) 324 } 325 326 proc.CloseAsync() 327 assert.NoError(t, proc.WaitForClose(time.Second)) 328 }) 329 } 330 }