@@ -57,3 +57,140 @@ def test_delete(self, stack, store):
5757 h .wait (h .LONG_DELAY )
5858 h .assert_status (resp , 200 , 204 )
5959 h .wait (h .SHORT_DELAY )
60+
61+
62+ def _create_branch (stack , uid , environments = None ):
63+ branch = {"uid" : uid , "source" : "main" }
64+ if environments is not None :
65+ branch ["publish_config" ] = {"environments" : environments }
66+ return stack .branch ().create ({"branch" : branch })
67+
68+
69+ def _branch (response ):
70+ return h .body (response ).get ("branch" , {})
71+
72+
73+ def _branch_environments (response ):
74+ return (_branch (response ).get ("publish_config" ) or {}).get ("environments" )
75+
76+
77+ def _fetch_when_ready (stack , uid ):
78+ # Branch provisioning is async: poll until the fetch succeeds
79+ resp = None
80+ for _ in range (10 ):
81+ resp = stack .branch (uid ).fetch ()
82+ if resp .status_code == 200 :
83+ return resp
84+ h .wait (h .SHORT_DELAY )
85+ return resp
86+
87+
88+ def _delete_created_branch (stack , uid ):
89+ # Same retry as TestBranchDelete: a freshly created branch can briefly be "not valid" (422, code 905).
90+ resp = None
91+ for _ in range (4 ):
92+ branch = stack .branch (uid )
93+ branch .add_param ("force" , "true" )
94+ resp = branch .delete ()
95+ if resp .status_code in (200 , 204 ):
96+ return
97+ h .wait (h .LONG_DELAY )
98+ raise AssertionError (f"branch { uid } was not deleted after retries; status { resp .status_code } " )
99+
100+
101+ def _not_found (response ):
102+ return response .status_code , h .body (response ).get ("error_code" )
103+
104+
105+ @pytest .fixture (scope = "class" )
106+ def env_names (stack ):
107+ """Three environments of this class, so a list of two is a strict subset; deleted after."""
108+ names = [h .generate_valid_uid ("bev" ) for _ in range (3 )]
109+ for name in names :
110+ data = {"environment" : {"name" : name , "urls" : [{"url" : "https://example.com" , "locale" : "en-us" }]}}
111+ h .assert_status (stack .environments ().create (data ), 201 )
112+ yield names
113+ for name in names :
114+ stack .environments (name ).delete ()
115+
116+
117+ @pytest .fixture (scope = "class" )
118+ def nonexistent (stack ):
119+ """The response for a branch that does not exist (dev11: 422, error_code 905)."""
120+ resp = stack .branch (h .generate_valid_uid ("nob" )).fetch ()
121+ assert resp .status_code != 200
122+ return _not_found (resp )
123+
124+
125+ class TestBranchEnvironmentList :
126+ """Branch create with publish_config.environments. Each test deletes its branch (stack branch cap)."""
127+
128+ def test_create_two_names_clones_only_those (self , stack , env_names ):
129+ wanted = env_names [:2 ]
130+ uid = h .generate_valid_uid ("bev" )
131+ resp = _create_branch (stack , uid , wanted )
132+ try :
133+ h .assert_status (resp , 201 )
134+ assert sorted (_branch_environments (resp )) == sorted (wanted )
135+ fetched = _fetch_when_ready (stack , uid )
136+ h .assert_status (fetched , 200 )
137+ assert sorted (_branch_environments (fetched )) == sorted (wanted )
138+ assert env_names [2 ] not in _branch_environments (fetched )
139+ finally :
140+ if resp .status_code == 201 :
141+ _delete_created_branch (stack , uid )
142+
143+ def test_create_one_name_reads_back_that_one (self , stack , env_names ):
144+ uid = h .generate_valid_uid ("bev" )
145+ resp = _create_branch (stack , uid , env_names [:1 ])
146+ try :
147+ h .assert_status (resp , 201 )
148+ fetched = _fetch_when_ready (stack , uid )
149+ h .assert_status (fetched , 200 )
150+ assert _branch_environments (fetched ) == env_names [:1 ]
151+ finally :
152+ if resp .status_code == 201 :
153+ _delete_created_branch (stack , uid )
154+
155+ def test_create_empty_list_clones_none (self , stack , env_names ):
156+ uid = h .generate_valid_uid ("bev" )
157+ resp = _create_branch (stack , uid , [])
158+ try :
159+ h .assert_status (resp , 201 )
160+ assert _branch_environments (resp ) == []
161+ fetched = _fetch_when_ready (stack , uid )
162+ h .assert_status (fetched , 200 )
163+ assert "publish_config" in _branch (fetched )
164+ assert _branch_environments (fetched ) == []
165+ finally :
166+ if resp .status_code == 201 :
167+ _delete_created_branch (stack , uid )
168+
169+ def test_create_without_list_reads_back_no_publish_config (self , stack , env_names ):
170+ uid = h .generate_valid_uid ("bev" )
171+ resp = _create_branch (stack , uid )
172+ try :
173+ h .assert_status (resp , 201 )
174+ fetched = _fetch_when_ready (stack , uid )
175+ h .assert_status (fetched , 200 )
176+ assert "publish_config" not in _branch (fetched )
177+ finally :
178+ if resp .status_code == 201 :
179+ _delete_created_branch (stack , uid )
180+
181+ @pytest .mark .parametrize ("case" , ["unknown" , "other_case" , "mix" ])
182+ def test_rejected_names_return_422_and_create_no_branch (self , stack , env_names , nonexistent , case ):
183+ environments = {
184+ "unknown" : [h .generate_valid_uid ("nos" )],
185+ "other_case" : [env_names [0 ].upper ()],
186+ "mix" : [env_names [0 ], h .generate_valid_uid ("nos" )],
187+ }[case ]
188+ uid = h .generate_valid_uid ("bev" )
189+ resp = _create_branch (stack , uid , environments )
190+ fetched = stack .branch (uid ).fetch ()
191+ if fetched .status_code == 200 :
192+ _delete_created_branch (stack , uid )
193+ h .assert_status (resp , 422 )
194+ assert h .body (resp ).get ("error_message" )
195+ assert "publish_config.environments" in h .body (resp ).get ("errors" , {})
196+ assert _not_found (fetched ) == nonexistent , "a rejected create must leave no branch behind"
0 commit comments