1package transit
2
3import (
4	"context"
5	"encoding/json"
6	"reflect"
7	"testing"
8
9	"github.com/hashicorp/vault/sdk/logical"
10	"github.com/mitchellh/mapstructure"
11)
12
13// Case1: Ensure that batch encryption did not affect the normal flow of
14// encrypting the plaintext with a pre-existing key.
15func TestTransit_BatchEncryptionCase1(t *testing.T) {
16	var resp *logical.Response
17	var err error
18
19	b, s := createBackendWithStorage(t)
20
21	// Create the policy
22	policyReq := &logical.Request{
23		Operation: logical.UpdateOperation,
24		Path:      "keys/existing_key",
25		Storage:   s,
26	}
27	resp, err = b.HandleRequest(context.Background(), policyReq)
28	if err != nil || (resp != nil && resp.IsError()) {
29		t.Fatalf("err:%v resp:%#v", err, resp)
30	}
31
32	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA==" // "the quick brown fox"
33
34	encData := map[string]interface{}{
35		"plaintext": plaintext,
36	}
37
38	encReq := &logical.Request{
39		Operation: logical.UpdateOperation,
40		Path:      "encrypt/existing_key",
41		Storage:   s,
42		Data:      encData,
43	}
44	resp, err = b.HandleRequest(context.Background(), encReq)
45	if err != nil || (resp != nil && resp.IsError()) {
46		t.Fatalf("err:%v resp:%#v", err, resp)
47	}
48
49	keyVersion := resp.Data["key_version"].(int)
50	if keyVersion != 1 {
51		t.Fatalf("unexpected key version; got: %d, expected: %d", keyVersion, 1)
52	}
53
54	ciphertext := resp.Data["ciphertext"]
55
56	decData := map[string]interface{}{
57		"ciphertext": ciphertext,
58	}
59	decReq := &logical.Request{
60		Operation: logical.UpdateOperation,
61		Path:      "decrypt/existing_key",
62		Storage:   s,
63		Data:      decData,
64	}
65	resp, err = b.HandleRequest(context.Background(), decReq)
66	if err != nil || (resp != nil && resp.IsError()) {
67		t.Fatalf("err:%v resp:%#v", err, resp)
68	}
69
70	if resp.Data["plaintext"] != plaintext {
71		t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
72	}
73}
74
75// Case2: Ensure that batch encryption did not affect the normal flow of
76// encrypting the plaintext with the key upserted.
77func TestTransit_BatchEncryptionCase2(t *testing.T) {
78	var resp *logical.Response
79	var err error
80	b, s := createBackendWithStorage(t)
81
82	// Upsert the key and encrypt the data
83	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
84
85	encData := map[string]interface{}{
86		"plaintext": plaintext,
87	}
88
89	encReq := &logical.Request{
90		Operation: logical.CreateOperation,
91		Path:      "encrypt/upserted_key",
92		Storage:   s,
93		Data:      encData,
94	}
95	resp, err = b.HandleRequest(context.Background(), encReq)
96	if err != nil || (resp != nil && resp.IsError()) {
97		t.Fatalf("err:%v resp:%#v", err, resp)
98	}
99
100	keyVersion := resp.Data["key_version"].(int)
101	if keyVersion != 1 {
102		t.Fatalf("unexpected key version; got: %d, expected: %d", keyVersion, 1)
103	}
104
105	ciphertext := resp.Data["ciphertext"]
106	decData := map[string]interface{}{
107		"ciphertext": ciphertext,
108	}
109
110	policyReq := &logical.Request{
111		Operation: logical.ReadOperation,
112		Path:      "keys/upserted_key",
113		Storage:   s,
114	}
115
116	resp, err = b.HandleRequest(context.Background(), policyReq)
117	if err != nil || (resp != nil && resp.IsError()) {
118		t.Fatalf("err:%v resp:%#v", err, resp)
119	}
120
121	decReq := &logical.Request{
122		Operation: logical.UpdateOperation,
123		Path:      "decrypt/upserted_key",
124		Storage:   s,
125		Data:      decData,
126	}
127	resp, err = b.HandleRequest(context.Background(), decReq)
128	if err != nil || (resp != nil && resp.IsError()) {
129		t.Fatalf("err:%v resp:%#v", err, resp)
130	}
131
132	if resp.Data["plaintext"] != plaintext {
133		t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
134	}
135}
136
137// Case3: If batch encryption input is not base64 encoded, it should fail.
138func TestTransit_BatchEncryptionCase3(t *testing.T) {
139	var err error
140
141	b, s := createBackendWithStorage(t)
142
143	batchInput := `[{"plaintext":"dGhlIHF1aWNrIGJyb3duIGZveA=="}]`
144	batchData := map[string]interface{}{
145		"batch_input": batchInput,
146	}
147
148	batchReq := &logical.Request{
149		Operation: logical.CreateOperation,
150		Path:      "encrypt/upserted_key",
151		Storage:   s,
152		Data:      batchData,
153	}
154	_, err = b.HandleRequest(context.Background(), batchReq)
155	if err == nil {
156		t.Fatal("expected an error")
157	}
158}
159
160// Case4: Test batch encryption with an existing key
161func TestTransit_BatchEncryptionCase4(t *testing.T) {
162	var resp *logical.Response
163	var err error
164
165	b, s := createBackendWithStorage(t)
166
167	policyReq := &logical.Request{
168		Operation: logical.UpdateOperation,
169		Path:      "keys/existing_key",
170		Storage:   s,
171	}
172	resp, err = b.HandleRequest(context.Background(), policyReq)
173	if err != nil || (resp != nil && resp.IsError()) {
174		t.Fatalf("err:%v resp:%#v", err, resp)
175	}
176
177	batchInput := []interface{}{
178		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
179		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
180	}
181
182	batchData := map[string]interface{}{
183		"batch_input": batchInput,
184	}
185	batchReq := &logical.Request{
186		Operation: logical.UpdateOperation,
187		Path:      "encrypt/existing_key",
188		Storage:   s,
189		Data:      batchData,
190	}
191	resp, err = b.HandleRequest(context.Background(), batchReq)
192	if err != nil || (resp != nil && resp.IsError()) {
193		t.Fatalf("err:%v resp:%#v", err, resp)
194	}
195
196	batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
197
198	decReq := &logical.Request{
199		Operation: logical.UpdateOperation,
200		Path:      "decrypt/existing_key",
201		Storage:   s,
202	}
203
204	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
205
206	for _, item := range batchResponseItems {
207		if item.KeyVersion != 1 {
208			t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
209		}
210
211		decReq.Data = map[string]interface{}{
212			"ciphertext": item.Ciphertext,
213		}
214		resp, err = b.HandleRequest(context.Background(), decReq)
215		if err != nil || (resp != nil && resp.IsError()) {
216			t.Fatalf("err:%v resp:%#v", err, resp)
217		}
218
219		if resp.Data["plaintext"] != plaintext {
220			t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
221		}
222	}
223}
224
225// Case5: Test batch encryption with an existing derived key
226func TestTransit_BatchEncryptionCase5(t *testing.T) {
227	var resp *logical.Response
228	var err error
229
230	b, s := createBackendWithStorage(t)
231
232	policyData := map[string]interface{}{
233		"derived": true,
234	}
235
236	policyReq := &logical.Request{
237		Operation: logical.UpdateOperation,
238		Path:      "keys/existing_key",
239		Storage:   s,
240		Data:      policyData,
241	}
242
243	resp, err = b.HandleRequest(context.Background(), policyReq)
244	if err != nil || (resp != nil && resp.IsError()) {
245		t.Fatalf("err:%v resp:%#v", err, resp)
246	}
247
248	batchInput := []interface{}{
249		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
250		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
251	}
252
253	batchData := map[string]interface{}{
254		"batch_input": batchInput,
255	}
256
257	batchReq := &logical.Request{
258		Operation: logical.UpdateOperation,
259		Path:      "encrypt/existing_key",
260		Storage:   s,
261		Data:      batchData,
262	}
263	resp, err = b.HandleRequest(context.Background(), batchReq)
264	if err != nil || (resp != nil && resp.IsError()) {
265		t.Fatalf("err:%v resp:%#v", err, resp)
266	}
267
268	batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
269
270	decReq := &logical.Request{
271		Operation: logical.UpdateOperation,
272		Path:      "decrypt/existing_key",
273		Storage:   s,
274	}
275
276	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
277
278	for _, item := range batchResponseItems {
279		if item.KeyVersion != 1 {
280			t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
281		}
282
283		decReq.Data = map[string]interface{}{
284			"ciphertext": item.Ciphertext,
285			"context":    "dmlzaGFsCg==",
286		}
287		resp, err = b.HandleRequest(context.Background(), decReq)
288		if err != nil || (resp != nil && resp.IsError()) {
289			t.Fatalf("err:%v resp:%#v", err, resp)
290		}
291
292		if resp.Data["plaintext"] != plaintext {
293			t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
294		}
295	}
296}
297
298// Case6: Test batch encryption with an upserted non-derived key
299func TestTransit_BatchEncryptionCase6(t *testing.T) {
300	var resp *logical.Response
301	var err error
302
303	b, s := createBackendWithStorage(t)
304
305	batchInput := []interface{}{
306		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
307		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
308	}
309
310	batchData := map[string]interface{}{
311		"batch_input": batchInput,
312	}
313	batchReq := &logical.Request{
314		Operation: logical.CreateOperation,
315		Path:      "encrypt/upserted_key",
316		Storage:   s,
317		Data:      batchData,
318	}
319	resp, err = b.HandleRequest(context.Background(), batchReq)
320	if err != nil || (resp != nil && resp.IsError()) {
321		t.Fatalf("err:%v resp:%#v", err, resp)
322	}
323
324	batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
325
326	decReq := &logical.Request{
327		Operation: logical.UpdateOperation,
328		Path:      "decrypt/upserted_key",
329		Storage:   s,
330	}
331
332	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
333
334	for _, responseItem := range batchResponseItems {
335		var item EncryptBatchResponseItem
336		if err := mapstructure.Decode(responseItem, &item); err != nil {
337			t.Fatal(err)
338		}
339
340		if item.KeyVersion != 1 {
341			t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
342		}
343
344		decReq.Data = map[string]interface{}{
345			"ciphertext": item.Ciphertext,
346		}
347		resp, err = b.HandleRequest(context.Background(), decReq)
348		if err != nil || (resp != nil && resp.IsError()) {
349			t.Fatalf("err:%v resp:%#v", err, resp)
350		}
351
352		if resp.Data["plaintext"] != plaintext {
353			t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
354		}
355	}
356}
357
358// Case7: Test batch encryption with an upserted derived key
359func TestTransit_BatchEncryptionCase7(t *testing.T) {
360	var resp *logical.Response
361	var err error
362
363	b, s := createBackendWithStorage(t)
364
365	batchInput := []interface{}{
366		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
367		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
368	}
369
370	batchData := map[string]interface{}{
371		"batch_input": batchInput,
372	}
373	batchReq := &logical.Request{
374		Operation: logical.CreateOperation,
375		Path:      "encrypt/upserted_key",
376		Storage:   s,
377		Data:      batchData,
378	}
379	resp, err = b.HandleRequest(context.Background(), batchReq)
380	if err != nil || (resp != nil && resp.IsError()) {
381		t.Fatalf("err:%v resp:%#v", err, resp)
382	}
383
384	batchResponseItems := resp.Data["batch_results"].([]EncryptBatchResponseItem)
385
386	decReq := &logical.Request{
387		Operation: logical.UpdateOperation,
388		Path:      "decrypt/upserted_key",
389		Storage:   s,
390	}
391
392	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
393
394	for _, item := range batchResponseItems {
395		if item.KeyVersion != 1 {
396			t.Fatalf("unexpected key version; got: %d, expected: %d", item.KeyVersion, 1)
397		}
398
399		decReq.Data = map[string]interface{}{
400			"ciphertext": item.Ciphertext,
401			"context":    "dmlzaGFsCg==",
402		}
403		resp, err = b.HandleRequest(context.Background(), decReq)
404		if err != nil || (resp != nil && resp.IsError()) {
405			t.Fatalf("err:%v resp:%#v", err, resp)
406		}
407
408		if resp.Data["plaintext"] != plaintext {
409			t.Fatalf("bad: plaintext. Expected: %q, Actual: %q", plaintext, resp.Data["plaintext"])
410		}
411	}
412}
413
414// Case8: If plaintext is not base64 encoded, encryption should fail
415func TestTransit_BatchEncryptionCase8(t *testing.T) {
416	var resp *logical.Response
417	var err error
418
419	b, s := createBackendWithStorage(t)
420
421	// Create the policy
422	policyReq := &logical.Request{
423		Operation: logical.UpdateOperation,
424		Path:      "keys/existing_key",
425		Storage:   s,
426	}
427	resp, err = b.HandleRequest(context.Background(), policyReq)
428	if err != nil || (resp != nil && resp.IsError()) {
429		t.Fatalf("err:%v resp:%#v", err, resp)
430	}
431
432	batchInput := []interface{}{
433		map[string]interface{}{"plaintext": "simple_plaintext"},
434	}
435	batchData := map[string]interface{}{
436		"batch_input": batchInput,
437	}
438	batchReq := &logical.Request{
439		Operation: logical.UpdateOperation,
440		Path:      "encrypt/existing_key",
441		Storage:   s,
442		Data:      batchData,
443	}
444	resp, err = b.HandleRequest(context.Background(), batchReq)
445	if err != nil || (resp != nil && resp.IsError()) {
446		t.Fatalf("err:%v resp:%#v", err, resp)
447	}
448
449	plaintext := "simple plaintext"
450
451	encData := map[string]interface{}{
452		"plaintext": plaintext,
453	}
454
455	encReq := &logical.Request{
456		Operation: logical.UpdateOperation,
457		Path:      "encrypt/existing_key",
458		Storage:   s,
459		Data:      encData,
460	}
461	resp, err = b.HandleRequest(context.Background(), encReq)
462	if err == nil {
463		t.Fatal("expected an error")
464	}
465}
466
467// Case9: If both plaintext and batch inputs are supplied, plaintext should be
468// ignored.
469func TestTransit_BatchEncryptionCase9(t *testing.T) {
470	var resp *logical.Response
471	var err error
472
473	b, s := createBackendWithStorage(t)
474
475	batchInput := []interface{}{
476		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
477		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
478	}
479	plaintext := "dGhlIHF1aWNrIGJyb3duIGZveA=="
480	batchData := map[string]interface{}{
481		"batch_input": batchInput,
482		"plaintext":   plaintext,
483	}
484	batchReq := &logical.Request{
485		Operation: logical.CreateOperation,
486		Path:      "encrypt/upserted_key",
487		Storage:   s,
488		Data:      batchData,
489	}
490	resp, err = b.HandleRequest(context.Background(), batchReq)
491	if err != nil || (resp != nil && resp.IsError()) {
492		t.Fatalf("err:%v resp:%#v", err, resp)
493	}
494
495	_, ok := resp.Data["ciphertext"]
496	if ok {
497		t.Fatal("ciphertext field should not be set")
498	}
499}
500
501// Case10: Inconsistent presence of 'context' in batch input should be caught
502func TestTransit_BatchEncryptionCase10(t *testing.T) {
503	var err error
504
505	b, s := createBackendWithStorage(t)
506
507	batchInput := []interface{}{
508		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="},
509		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
510	}
511
512	batchData := map[string]interface{}{
513		"batch_input": batchInput,
514	}
515
516	batchReq := &logical.Request{
517		Operation: logical.CreateOperation,
518		Path:      "encrypt/upserted_key",
519		Storage:   s,
520		Data:      batchData,
521	}
522	_, err = b.HandleRequest(context.Background(), batchReq)
523	if err == nil {
524		t.Fatalf("expected an error")
525	}
526}
527
528// Case11: Incorrect inputs for context and nonce should not fail the operation
529func TestTransit_BatchEncryptionCase11(t *testing.T) {
530	var err error
531
532	b, s := createBackendWithStorage(t)
533
534	batchInput := []interface{}{
535		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "dmlzaGFsCg=="},
536		map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "context": "not-encoded"},
537	}
538
539	batchData := map[string]interface{}{
540		"batch_input": batchInput,
541	}
542	batchReq := &logical.Request{
543		Operation: logical.CreateOperation,
544		Path:      "encrypt/upserted_key",
545		Storage:   s,
546		Data:      batchData,
547	}
548	_, err = b.HandleRequest(context.Background(), batchReq)
549	if err != nil {
550		t.Fatal(err)
551	}
552}
553
554// Case12: Invalid batch input
555func TestTransit_BatchEncryptionCase12(t *testing.T) {
556	var err error
557	b, s := createBackendWithStorage(t)
558
559	batchInput := []interface{}{
560		map[string]interface{}{},
561		"unexpected_interface",
562	}
563
564	batchData := map[string]interface{}{
565		"batch_input": batchInput,
566	}
567	batchReq := &logical.Request{
568		Operation: logical.CreateOperation,
569		Path:      "encrypt/upserted_key",
570		Storage:   s,
571		Data:      batchData,
572	}
573	_, err = b.HandleRequest(context.Background(), batchReq)
574	if err == nil {
575		t.Fatalf("expected an error")
576	}
577}
578
579// Test that the fast path function decodeBatchRequestItems behave like mapstructure.Decode() to decode []BatchRequestItem.
580func TestTransit_decodeBatchRequestItems(t *testing.T) {
581	tests := []struct {
582		name string
583		src  interface{}
584		dest []BatchRequestItem
585	}{
586		// basic edge cases of nil values
587		{name: "nil-nil", src: nil, dest: nil},
588		{name: "nil-empty", src: nil, dest: []BatchRequestItem{}},
589		{name: "empty-nil", src: []interface{}{}, dest: nil},
590		{
591			name: "src-nil",
592			src:  []interface{}{map[string]interface{}{}},
593			dest: nil,
594		},
595		// empty src & dest
596		{
597			name: "src-dest",
598			src:  []interface{}{map[string]interface{}{}},
599			dest: []BatchRequestItem{},
600		},
601		// empty src but with already populated dest, mapstructure discard pre-populated data.
602		{
603			name: "src-dest_pre_filled",
604			src:  []interface{}{map[string]interface{}{}},
605			dest: []BatchRequestItem{{}},
606		},
607		// two test per properties to test valid and invalid input
608		{
609			name: "src_plaintext-dest",
610			src:  []interface{}{map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA=="}},
611			dest: []BatchRequestItem{},
612		},
613		{
614			name: "src_plaintext_invalid-dest",
615			src:  []interface{}{map[string]interface{}{"plaintext": 666}},
616			dest: []BatchRequestItem{},
617		},
618		{
619			name: "src_ciphertext-dest",
620			src:  []interface{}{map[string]interface{}{"ciphertext": "dGhlIHF1aWNrIGJyb3duIGZveA=="}},
621			dest: []BatchRequestItem{},
622		},
623		{
624			name: "src_ciphertext_invalid-dest",
625			src:  []interface{}{map[string]interface{}{"ciphertext": 666}},
626			dest: []BatchRequestItem{},
627		},
628		{
629			name: "src_key_version-dest",
630			src:  []interface{}{map[string]interface{}{"key_version": 1}},
631			dest: []BatchRequestItem{},
632		},
633		{
634			name: "src_key_version_invalid-dest",
635			src:  []interface{}{map[string]interface{}{"key_version": "666"}},
636			dest: []BatchRequestItem{},
637		},
638		{
639			name: "src_key_version_invalid-number-dest",
640			src:  []interface{}{map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "key_version": json.Number("1.1")}},
641			dest: []BatchRequestItem{},
642		},
643		{
644			name: "src_nonce-dest",
645			src:  []interface{}{map[string]interface{}{"nonce": "dGVzdGNvbnRleHQ="}},
646			dest: []BatchRequestItem{},
647		},
648		{
649			name: "src_nonce_invalid-dest",
650			src:  []interface{}{map[string]interface{}{"nonce": 666}},
651			dest: []BatchRequestItem{},
652		},
653		{
654			name: "src_context-dest",
655			src:  []interface{}{map[string]interface{}{"context": "dGVzdGNvbnRleHQ="}},
656			dest: []BatchRequestItem{},
657		},
658		{
659			name: "src_context_invalid-dest",
660			src:  []interface{}{map[string]interface{}{"context": 666}},
661			dest: []BatchRequestItem{},
662		},
663		{
664			name: "src_multi_order-dest",
665			src: []interface{}{
666				map[string]interface{}{"context": "1"},
667				map[string]interface{}{"context": "2"},
668				map[string]interface{}{"context": "3"},
669			},
670			dest: []BatchRequestItem{},
671		},
672		{
673			name: "src_multi_with_invalid-dest",
674			src: []interface{}{
675				map[string]interface{}{"context": "1"},
676				map[string]interface{}{"context": "2", "key_version": "666"},
677				map[string]interface{}{"context": "3"},
678			},
679			dest: []BatchRequestItem{},
680		},
681		{
682			name: "src_multi_with_multi_invalid-dest",
683			src: []interface{}{
684				map[string]interface{}{"context": "1"},
685				map[string]interface{}{"context": "2", "key_version": "666"},
686				map[string]interface{}{"context": "3", "key_version": "1337"},
687			},
688			dest: []BatchRequestItem{},
689		},
690		{
691			name: "src_plaintext-nil-nonce",
692			src:  []interface{}{map[string]interface{}{"plaintext": "dGhlIHF1aWNrIGJyb3duIGZveA==", "nonce": "null"}},
693			dest: []BatchRequestItem{},
694		},
695	}
696	for _, tt := range tests {
697		t.Run(tt.name, func(t *testing.T) {
698			expectedDest := append(tt.dest[:0:0], tt.dest...) // copy of the dest state
699			expectedErr := mapstructure.Decode(tt.src, &expectedDest)
700
701			gotErr := decodeBatchRequestItems(tt.src, &tt.dest)
702			gotDest := tt.dest
703
704			if !reflect.DeepEqual(expectedErr, gotErr) {
705				t.Errorf("decodeBatchRequestItems unexpected error value, want: '%v', got: '%v'", expectedErr, gotErr)
706			}
707
708			if !reflect.DeepEqual(expectedDest, gotDest) {
709				t.Errorf("decodeBatchRequestItems unexpected dest value, want: '%v', got: '%v'", expectedDest, gotDest)
710			}
711		})
712	}
713}
714