@@ -745,3 +745,106 @@ def test_root_upsert_on_empty_raises_patch_error(self):
745745 PatchError , match = "Cannot replace root of an empty document"
746746 ):
747747 doc .upsert (value = 42 )
748+
749+
750+ class TestEnsureInList :
751+ def test_scalar_already_present_noop (self ):
752+ doc = Document ("items:\n - a\n - b\n " )
753+ result = doc .ensure_in_list ("items" , value = "a" )
754+ assert result .source == doc .source
755+
756+ def test_scalar_missing_appends (self ):
757+ doc = Document ("items:\n - a\n - b\n " )
758+ result = doc .ensure_in_list ("items" , value = "c" )
759+ assert result ["items" ] == ["a" , "b" , "c" ]
760+
761+ def test_integer_value (self ):
762+ doc = Document ("ports:\n - 8080\n - 9090\n " )
763+ result = doc .ensure_in_list ("ports" , value = 8080 )
764+ assert result .source == doc .source
765+
766+ def test_path_missing_creates_list (self ):
767+ doc = Document ("name: foo\n " )
768+ result = doc .ensure_in_list ("items" , value = "first" )
769+ assert result ["items" ] == ["first" ]
770+
771+ def test_path_not_a_list_raises (self ):
772+ doc = Document ("name: foo\n " )
773+ with pytest .raises (NodeTypeError ):
774+ doc .ensure_in_list ("name" , value = "bar" )
775+
776+ def test_path_is_mapping_raises (self ):
777+ doc = Document ("config:\n host: localhost\n port: 8080\n " )
778+ with pytest .raises (NodeTypeError ):
779+ doc .ensure_in_list ("config" , value = "x" )
780+
781+ def test_idempotent (self ):
782+ doc = Document ("items:\n - a\n " )
783+ result1 = doc .ensure_in_list ("items" , value = "b" )
784+ result2 = result1 .ensure_in_list ("items" , value = "b" )
785+ assert result1 .source == result2 .source
786+
787+ def test_where_match_found_noop (self ):
788+ doc = Document (
789+ "repos:\n - repo: https://a\n rev: v1\n - repo: https://b\n rev: v2\n "
790+ )
791+ result = doc .ensure_in_list (
792+ "repos" ,
793+ where = {"repo" : "https://a" },
794+ value = {"repo" : "https://a" , "rev" : "v1" },
795+ )
796+ assert result .source == doc .source
797+
798+ def test_where_no_match_appends (self ):
799+ doc = Document ("repos:\n - repo: https://a\n rev: v1\n " )
800+ result = doc .ensure_in_list (
801+ "repos" ,
802+ where = {"repo" : "https://b" },
803+ value = {"repo" : "https://b" , "rev" : "v2" },
804+ )
805+ assert result ["repos" , 1 ] == {"repo" : "https://b" , "rev" : "v2" }
806+
807+ def test_where_empty_raises (self ):
808+ doc = Document ("items:\n - a\n " )
809+ with pytest .raises (ValueError , match = "where must be a non-empty dict" ):
810+ doc .ensure_in_list ("items" , where = {}, value = "x" )
811+
812+ def test_where_items_not_dicts_appends (self ):
813+ doc = Document ("items:\n - a\n - b\n " )
814+ result = doc .ensure_in_list (
815+ "items" ,
816+ where = {"name" : "foo" },
817+ value = {"name" : "foo" },
818+ )
819+ assert result ["items" ] == ["a" , "b" , {"name" : "foo" }]
820+
821+ def test_where_multiple_keys_all_must_match (self ):
822+ doc = Document ("items:\n - name: foo\n ver: 1\n - name: bar\n ver: 2\n " )
823+ result = doc .ensure_in_list (
824+ "items" ,
825+ where = {"name" : "foo" , "ver" : 2 },
826+ value = {"name" : "foo" , "ver" : 2 },
827+ )
828+ # Neither item matches both (foo has ver=1, bar has ver=2)
829+ assert len (result ["items" ]) == 3
830+
831+ def test_flow_sequence_appends (self ):
832+ doc = Document ("items: [a, b]\n " )
833+ result = doc .ensure_in_list ("items" , value = "c" )
834+ assert result ["items" ] == ["a" , "b" , "c" ]
835+
836+ def test_flow_sequence_noop (self ):
837+ doc = Document ("items: [a, b]\n " )
838+ result = doc .ensure_in_list ("items" , value = "a" )
839+ assert result .source == doc .source
840+
841+ def test_nested_path_missing_creates (self ):
842+ doc = Document ("config:\n name: foo\n " )
843+ result = doc .ensure_in_list ("config" , "hooks" , value = "pre-commit" )
844+ assert result ["config" , "hooks" ] == ["pre-commit" ]
845+
846+ def test_deeply_nested_path (self ):
847+ doc = Document ("a:\n b: 1\n " )
848+ result = doc .ensure_in_list ("a" , "c" , value = "x" )
849+ assert result ["a" , "c" ] == ["x" ]
850+ assert result ["a" , "b" ] == 1
0 commit comments