@@ -285,6 +285,252 @@ func TestApplyTo_CopyVersionToHeader(t *testing.T) {
285285 NodeMatchesFile (t , node , "testdata/openapi-version-header-expected.yaml" )
286286}
287287
288+ func TestApplyTo_UpdateArrayAppend (t * testing.T ) {
289+ t .Parallel ()
290+
291+ tests := []struct {
292+ name string
293+ inputYAML string
294+ overlayYAML string
295+ expectedTags int
296+ expectedNames []string
297+ }{
298+ {
299+ name : "single object appended to array" ,
300+ inputYAML : `openapi: 3.1.0
301+ info:
302+ title: Test API
303+ version: 1.0.0
304+ tags:
305+ - name: existing
306+ description: This tag already exists
307+ ` ,
308+ overlayYAML : `overlay: 1.1.0
309+ info:
310+ title: Append single object
311+ version: 1.0.0
312+ actions:
313+ - target: "$.tags"
314+ update:
315+ name: newTag1
316+ description: This tag should be appended
317+ ` ,
318+ expectedTags : 2 ,
319+ expectedNames : []string {"existing" , "newTag1" },
320+ },
321+ {
322+ name : "multiple objects appended to array via separate actions" ,
323+ inputYAML : `openapi: 3.1.0
324+ info:
325+ title: Test API
326+ version: 1.0.0
327+ tags:
328+ - name: existing
329+ description: This tag already exists
330+ ` ,
331+ overlayYAML : `overlay: 1.1.0
332+ info:
333+ title: Append multiple objects
334+ version: 1.0.0
335+ actions:
336+ - target: "$.tags"
337+ update:
338+ name: newTag1
339+ description: First appended tag
340+ - target: "$.tags"
341+ update:
342+ name: newTag2
343+ description: Second appended tag
344+ ` ,
345+ expectedTags : 3 ,
346+ expectedNames : []string {"existing" , "newTag1" , "newTag2" },
347+ },
348+ {
349+ name : "array appended to array" ,
350+ inputYAML : `openapi: 3.1.0
351+ info:
352+ title: Test API
353+ version: 1.0.0
354+ tags:
355+ - name: existing
356+ description: This tag already exists
357+ ` ,
358+ overlayYAML : `overlay: 1.1.0
359+ info:
360+ title: Append array to array
361+ version: 1.0.0
362+ actions:
363+ - target: "$.tags"
364+ update:
365+ - name: newTag1
366+ description: First appended tag
367+ - name: newTag2
368+ description: Second appended tag
369+ ` ,
370+ expectedTags : 3 ,
371+ expectedNames : []string {"existing" , "newTag1" , "newTag2" },
372+ },
373+ }
374+
375+ for _ , tt := range tests {
376+ t .Run (tt .name , func (t * testing.T ) {
377+ t .Parallel ()
378+
379+ // Parse the input spec
380+ var specNode yaml.Node
381+ err := yaml .Unmarshal ([]byte (tt .inputYAML ), & specNode )
382+ require .NoError (t , err , "unmarshal input spec should succeed" )
383+
384+ // Parse the overlay
385+ var o overlay.Overlay
386+ err = yaml .Unmarshal ([]byte (tt .overlayYAML ), & o )
387+ require .NoError (t , err , "unmarshal overlay should succeed" )
388+
389+ // Apply the overlay
390+ err = o .ApplyTo (& specNode )
391+ require .NoError (t , err , "apply overlay should succeed" )
392+
393+ // Find the tags node in the result
394+ root := specNode .Content [0 ] // DocumentNode -> MappingNode
395+ var tagsNode * yaml.Node
396+ for i := 0 ; i < len (root .Content ); i += 2 {
397+ if root .Content [i ].Value == "tags" {
398+ tagsNode = root .Content [i + 1 ]
399+ break
400+ }
401+ }
402+
403+ require .NotNil (t , tagsNode , "tags node should exist" )
404+ assert .Equal (t , yaml .SequenceNode , tagsNode .Kind , "tags should remain a sequence/array" )
405+ assert .Len (t , tagsNode .Content , tt .expectedTags , "tags array should have expected number of elements" )
406+
407+ // Verify each tag name
408+ for i , expectedName := range tt .expectedNames {
409+ require .Greater (t , len (tagsNode .Content ), i , "should have enough tag elements" )
410+ tagNode := tagsNode .Content [i ]
411+ assert .Equal (t , yaml .MappingNode , tagNode .Kind , "each tag should be a mapping node" )
412+
413+ // Find the "name" key in the tag
414+ var nameValue string
415+ for j := 0 ; j < len (tagNode .Content ); j += 2 {
416+ if tagNode .Content [j ].Value == "name" {
417+ nameValue = tagNode .Content [j + 1 ].Value
418+ break
419+ }
420+ }
421+ assert .Equal (t , expectedName , nameValue , "tag name at index " + strconv .Itoa (i )+ " should match" )
422+ }
423+ })
424+ }
425+ }
426+
427+ func TestApplyTo_UpdateNestedArrayAppend (t * testing.T ) {
428+ t .Parallel ()
429+
430+ tests := []struct {
431+ name string
432+ inputYAML string
433+ overlayYAML string
434+ expectedYAML string
435+ }{
436+ {
437+ name : "nested array in object merge is concatenated" ,
438+ inputYAML : `openapi: 3.1.0
439+ info:
440+ title: Test API
441+ version: 1.0.0
442+ paths:
443+ /pets:
444+ get:
445+ parameters:
446+ - name: limit
447+ in: query
448+ ` ,
449+ overlayYAML : `overlay: 1.1.0
450+ info:
451+ title: Append nested array
452+ version: 1.0.0
453+ actions:
454+ - target: "$.paths['/pets'].get"
455+ update:
456+ parameters:
457+ - name: offset
458+ in: query
459+ ` ,
460+ expectedYAML : `openapi: 3.1.0
461+ info:
462+ title: Test API
463+ version: 1.0.0
464+ paths:
465+ /pets:
466+ get:
467+ parameters:
468+ - name: limit
469+ in: query
470+ - name: offset
471+ in: query
472+ ` ,
473+ },
474+ {
475+ name : "scalar update appended to array" ,
476+ inputYAML : `openapi: 3.1.0
477+ info:
478+ title: Test API
479+ version: 1.0.0
480+ paths:
481+ /pets:
482+ get:
483+ tags:
484+ - pets
485+ ` ,
486+ overlayYAML : `overlay: 1.1.0
487+ info:
488+ title: Append scalar to array
489+ version: 1.0.0
490+ actions:
491+ - target: "$.paths['/pets'].get.tags"
492+ update: admin
493+ ` ,
494+ expectedYAML : `openapi: 3.1.0
495+ info:
496+ title: Test API
497+ version: 1.0.0
498+ paths:
499+ /pets:
500+ get:
501+ tags:
502+ - pets
503+ - admin
504+ ` ,
505+ },
506+ }
507+
508+ for _ , tt := range tests {
509+ t .Run (tt .name , func (t * testing.T ) {
510+ t .Parallel ()
511+
512+ var specNode yaml.Node
513+ err := yaml .Unmarshal ([]byte (tt .inputYAML ), & specNode )
514+ require .NoError (t , err , "unmarshal input spec should succeed" )
515+
516+ var o overlay.Overlay
517+ err = yaml .Unmarshal ([]byte (tt .overlayYAML ), & o )
518+ require .NoError (t , err , "unmarshal overlay should succeed" )
519+
520+ err = o .ApplyTo (& specNode )
521+ require .NoError (t , err , "apply overlay should succeed" )
522+
523+ var buf bytes.Buffer
524+ enc := yaml .NewEncoder (& buf )
525+ enc .SetIndent (2 )
526+ err = enc .Encode (& specNode )
527+ require .NoError (t , err , "encode result should succeed" )
528+
529+ assert .Equal (t , tt .expectedYAML , buf .String (), "output should match expected YAML" )
530+ })
531+ }
532+ }
533+
288534func TestApplyToOld (t * testing.T ) {
289535 t .Parallel ()
290536
0 commit comments