11import contextlib
22import re
3- from collections .abc import AsyncGenerator , Iterable
3+ from collections .abc import AsyncGenerator , Callable , Iterable
4+ from contextlib import AbstractAsyncContextManager
45from http import HTTPStatus
56
67import httpx
78import pytest
89from sqlalchemy import text
910from sqlalchemy .ext .asyncio import AsyncConnection
1011
12+ from tests .conftest import temporary_records
1113from tests .users import OWNER_USER , ApiKey
1214
1315
16+ @pytest .fixture
17+ def temporary_tags (
18+ expdb_test : AsyncConnection ,
19+ ) -> Callable [..., AbstractAsyncContextManager [None ]]:
20+ @contextlib .asynccontextmanager
21+ async def _temporary_tags (
22+ tags : Iterable [str ], setup_id : int , * , persist : bool = False
23+ ) -> AsyncGenerator [None ]:
24+ insert_queries = [
25+ (
26+ "INSERT INTO setup_tag(`id`,`tag`,`uploader`) VALUES (:setup_id, :tag, :user_id);" ,
27+ {"setup_id" : setup_id , "tag" : tag , "user_id" : OWNER_USER .user_id },
28+ )
29+ for tag in tags
30+ ]
31+ delete_queries = [
32+ (
33+ "DELETE FROM setup_tag WHERE `id`=:setup_id AND `tag`=:tag" ,
34+ {"setup_id" : setup_id , "tag" : tag },
35+ )
36+ for tag in tags
37+ ]
38+ async with temporary_records (
39+ connection = expdb_test ,
40+ insert_queries = insert_queries ,
41+ delete_queries = delete_queries ,
42+ persist = persist ,
43+ ):
44+ yield
45+
46+ return _temporary_tags
47+
48+
1449@pytest .mark .parametrize (
1550 "api_key" ,
1651 [ApiKey .ADMIN , ApiKey .SOME_USER , ApiKey .OWNER_USER ],
@@ -26,33 +61,11 @@ async def test_setup_untag_response_is_identical_when_tag_exists(
2661 other_tags : list [str ],
2762 py_api : httpx .AsyncClient ,
2863 php_api : httpx .AsyncClient ,
29- expdb_test : AsyncConnection ,
64+ temporary_tags : Callable [..., AbstractAsyncContextManager [ None ]] ,
3065) -> None :
3166 setup_id = 1
3267 tag = "totally_new_tag_for_migration_testing"
3368
34- @contextlib .asynccontextmanager
35- async def temporary_tags (
36- tags : Iterable [str ], setup_id : int , * , persist : bool = False
37- ) -> AsyncGenerator [None ]:
38- for tag in tags :
39- await expdb_test .execute (
40- text (
41- "INSERT INTO setup_tag(`id`,`tag`,`uploader`) VALUES (:setup_id, :tag, :user_id);" # noqa: E501
42- ),
43- parameters = {"setup_id" : setup_id , "tag" : tag , "user_id" : OWNER_USER .user_id },
44- )
45- if persist :
46- await expdb_test .commit ()
47- yield
48- for tag in tags :
49- await expdb_test .execute (
50- text ("DELETE FROM setup_tag WHERE `id`=:setup_id AND `tag`=:tag" ),
51- parameters = {"setup_id" : setup_id , "tag" : tag },
52- )
53- if persist :
54- await expdb_test .commit ()
55-
5669 all_tags = [tag , * other_tags ]
5770 async with temporary_tags (tags = all_tags , setup_id = setup_id , persist = True ):
5871 original = await php_api .post (
@@ -147,3 +160,112 @@ async def test_setup_untag_response_is_identical_tag_doesnt_exist(
147160 r"Setup \d+ does not have tag '\S+'." ,
148161 new .json ()["detail" ],
149162 )
163+
164+
165+ @pytest .mark .parametrize (
166+ "api_key" ,
167+ [ApiKey .ADMIN , ApiKey .SOME_USER ],
168+ ids = ["Administrator" , "non-owner" ],
169+ )
170+ @pytest .mark .parametrize (
171+ "other_tags" ,
172+ [[], ["some_other_tag" ], ["foo_some_other_tag" , "bar_some_other_tag" ]],
173+ ids = ["none" , "one tag" , "two tags" ],
174+ )
175+ async def test_setup_tag_response_is_identical_when_tag_doesnt_exist ( # noqa: PLR0913
176+ api_key : str ,
177+ other_tags : list [str ],
178+ py_api : httpx .AsyncClient ,
179+ php_api : httpx .AsyncClient ,
180+ expdb_test : AsyncConnection ,
181+ temporary_tags : Callable [..., AbstractAsyncContextManager [None ]],
182+ ) -> None :
183+ setup_id = 1
184+ tag = "totally_new_tag_for_migration_testing"
185+
186+ async with temporary_tags (tags = other_tags , setup_id = setup_id , persist = True ):
187+ original = await php_api .post (
188+ "/setup/tag" ,
189+ data = {"api_key" : api_key , "tag" : tag , "setup_id" : setup_id },
190+ )
191+
192+ await expdb_test .execute (
193+ text ("DELETE FROM setup_tag WHERE `id`=:setup_id AND `tag`=:tag" ),
194+ parameters = {"setup_id" : setup_id , "tag" : tag },
195+ )
196+ await expdb_test .commit ()
197+
198+ async with temporary_tags (tags = other_tags , setup_id = setup_id ):
199+ new = await py_api .post (
200+ f"/setup/tag?api_key={ api_key } " ,
201+ json = {"setup_id" : setup_id , "tag" : tag },
202+ )
203+
204+ assert new .status_code == HTTPStatus .OK
205+ assert original .status_code == new .status_code
206+ original_tag = original .json ()["setup_tag" ]
207+ new_tag = new .json ()["setup_tag" ]
208+ assert original_tag ["id" ] == new_tag ["id" ]
209+ if tags := original_tag .get ("tag" ):
210+ if isinstance (tags , str ):
211+ assert tags == new_tag ["tag" ][0 ]
212+ else :
213+ assert set (tags ) == set (new_tag ["tag" ])
214+ else :
215+ assert new_tag ["tag" ] == []
216+
217+
218+ async def test_setup_tag_response_is_identical_setup_doesnt_exist (
219+ py_api : httpx .AsyncClient ,
220+ php_api : httpx .AsyncClient ,
221+ ) -> None :
222+ setup_id = 999999
223+ tag = "totally_new_tag_for_migration_testing"
224+ api_key = ApiKey .SOME_USER
225+
226+ original = await php_api .post (
227+ "/setup/tag" ,
228+ data = {"api_key" : api_key , "tag" : tag , "setup_id" : setup_id },
229+ )
230+
231+ new = await py_api .post (
232+ f"/setup/tag?api_key={ api_key } " ,
233+ json = {"setup_id" : setup_id , "tag" : tag },
234+ )
235+
236+ assert original .status_code == HTTPStatus .PRECONDITION_FAILED
237+ assert new .status_code == HTTPStatus .NOT_FOUND
238+ assert original .json ()["error" ]["message" ] == "Entity not found."
239+ assert original .json ()["error" ]["code" ] == new .json ()["code" ]
240+ assert re .match (
241+ r"Setup \d+ not found." ,
242+ new .json ()["detail" ],
243+ )
244+
245+
246+ async def test_setup_tag_response_is_identical_tag_already_exists (
247+ py_api : httpx .AsyncClient ,
248+ php_api : httpx .AsyncClient ,
249+ temporary_tags : Callable [..., AbstractAsyncContextManager [None ]],
250+ ) -> None :
251+ setup_id = 1
252+ tag = "totally_new_tag_for_migration_testing"
253+ api_key = ApiKey .SOME_USER
254+
255+ async with temporary_tags (tags = [tag ], setup_id = setup_id , persist = True ):
256+ original = await php_api .post (
257+ "/setup/tag" ,
258+ data = {"api_key" : api_key , "tag" : tag , "setup_id" : setup_id },
259+ )
260+
261+ # In Python, since PHP committed it, it's also there for Python test context
262+ new = await py_api .post (
263+ f"/setup/tag?api_key={ api_key } " ,
264+ json = {"setup_id" : setup_id , "tag" : tag },
265+ )
266+
267+ assert original .status_code == HTTPStatus .INTERNAL_SERVER_ERROR
268+ assert new .status_code == HTTPStatus .CONFLICT
269+ assert original .json ()["error" ]["code" ] == new .json ()["code" ]
270+ assert original .json ()["error" ]["message" ] == "Entity already tagged by this tag."
271+ assert new .json ()["detail" ] == f"Setup { setup_id } already has tag { tag !r} ."
0 commit comments