1111from __future__ import annotations
1212
1313import json
14+ from dataclasses import dataclass
1415
1516import pytest
1617
3132ZH_HANS_TRANSLATION = "复杂 QuickBook 测试夹具"
3233
3334
35+ @dataclass (frozen = True )
36+ class CreatedProjectComponent :
37+ project_slug : str
38+ component_slug : str
39+
40+
41+ @pytest .fixture (scope = "class" )
42+ def created_project_component (weblate_api : WeblateAPI ) -> CreatedProjectComponent :
43+ """Project + QuickBook component for round-trip tests."""
44+ project_slug = WeblateAPI .unique_slug ("func-qbk" )
45+ component_slug = "qbk-fixture"
46+ weblate_api .create_project ("Functional QBK" , project_slug )
47+ # Docfile multipart upload (no empty local VCS template paths).
48+ created = weblate_api .create_component_from_docfile (
49+ project_slug ,
50+ name = "QBK Fixture" ,
51+ slug = component_slug ,
52+ file_format = "quickbook" ,
53+ docfile_path = QBK_FIXTURE ,
54+ filemask = "doc/*.qbk" ,
55+ language_regex = f"^{ TEST_LANG_CODE } $" ,
56+ )
57+ component_slug = str (created .get ("slug" , component_slug ))
58+ weblate_api .ensure_translation (project_slug , component_slug , TEST_LANG_CODE )
59+ return CreatedProjectComponent (
60+ project_slug = project_slug ,
61+ component_slug = component_slug ,
62+ )
63+
64+
3465# ---------------------------------------------------------------------------
3566# P1: QuickBook round-trip via Weblate REST API
3667# ---------------------------------------------------------------------------
3970class TestQuickBookRoundTrip :
4071 """Upload QBK, translate a unit, download translated file."""
4172
42- project_slug : str = ""
43- component_slug : str = ""
44- unit_url : str = ""
45-
46- def test_create_project_and_component (self , weblate_api : WeblateAPI ) -> None :
47- project_slug = WeblateAPI .unique_slug ("func-qbk" )
48- component_slug = "qbk-fixture"
49- weblate_api .create_project ("Functional QBK" , project_slug )
50- # Docfile multipart upload (no empty local VCS template paths).
51- created = weblate_api .create_component_from_docfile (
52- project_slug ,
53- name = "QBK Fixture" ,
54- slug = component_slug ,
55- file_format = "quickbook" ,
56- docfile_path = QBK_FIXTURE ,
57- filemask = "doc/*.qbk" ,
58- language_regex = f"^{ TEST_LANG_CODE } $" ,
59- )
60- component_slug = str (created .get ("slug" , component_slug ))
61- weblate_api .ensure_translation (project_slug , component_slug , TEST_LANG_CODE )
62-
63- type(self ).project_slug = project_slug
64- type(self ).component_slug = component_slug
65-
66- def test_units_extracted (self , weblate_api : WeblateAPI ) -> None :
67- assert self .project_slug and self .component_slug
73+ def test_units_extracted (
74+ self ,
75+ weblate_api : WeblateAPI ,
76+ created_project_component : CreatedProjectComponent ,
77+ ) -> None :
6878 units = weblate_api .list_units (
69- self .project_slug , self .component_slug , TEST_LANG_CODE
79+ created_project_component .project_slug ,
80+ created_project_component .component_slug ,
81+ TEST_LANG_CODE ,
7082 )
7183 assert len (units ) > 0
7284 sources = [
@@ -75,27 +87,36 @@ def test_units_extracted(self, weblate_api: WeblateAPI) -> None:
7587 ]
7688 assert any (KNOWN_SOURCE_STRING in s for s in sources ), sources [:5 ]
7789
78- def test_submit_translation (self , weblate_api : WeblateAPI ) -> None :
79- assert self .project_slug and self .component_slug
90+ def test_submit_translation (
91+ self ,
92+ weblate_api : WeblateAPI ,
93+ created_project_component : CreatedProjectComponent ,
94+ ) -> None :
8095 units = weblate_api .list_units (
81- self .project_slug , self .component_slug , TEST_LANG_CODE
96+ created_project_component .project_slug ,
97+ created_project_component .component_slug ,
98+ TEST_LANG_CODE ,
8299 )
83100 match = next (
84101 (u for u in units if KNOWN_SOURCE_STRING in str (u .get ("source" , "" ))),
85102 None ,
86103 )
87104 assert match is not None
88105 unit_url = WeblateAPI .unit_api_url (match )
89- type(self ).unit_url = unit_url
90106 weblate_api .submit_translation (unit_url , ZH_HANS_TRANSLATION )
91107
92- def test_download_translated_qbk (self , weblate_api : WeblateAPI ) -> None :
93- assert self .project_slug and self .component_slug
108+ def test_download_translated_qbk (
109+ self ,
110+ weblate_api : WeblateAPI ,
111+ created_project_component : CreatedProjectComponent ,
112+ ) -> None :
94113 raw = weblate_api .download_file (
95- self .project_slug , self .component_slug , TEST_LANG_CODE
114+ created_project_component .project_slug ,
115+ created_project_component .component_slug ,
116+ TEST_LANG_CODE ,
96117 )
97118 text = raw .decode ("utf-8" , errors = "replace" )
98- assert ZH_HANS_TRANSLATION in text or KNOWN_SOURCE_STRING in text
119+ assert ZH_HANS_TRANSLATION in text , "translated QBK content was not found"
99120
100121
101122# ---------------------------------------------------------------------------
@@ -229,37 +250,55 @@ def test_idempotency(self, exec_python, test_repo: EphemeralGitHubRepo) -> None:
229250# ---------------------------------------------------------------------------
230251
231252
253+ @dataclass (frozen = True )
254+ class AddOrUpdateTask :
255+ task_id : str
256+ http_code : int
257+ response : dict
258+
259+
260+ @pytest .fixture (scope = "class" )
261+ def add_or_update_task (
262+ api_token : str , test_repo : EphemeralGitHubRepo
263+ ) -> AddOrUpdateTask :
264+ """Accepted add-or-update request and Celery task id."""
265+ owner = test_repo .resolve_owner ()
266+ body = {
267+ "organization" : owner ,
268+ "version" : TEST_VERSION ,
269+ "add_or_update" : {TEST_LANG_CODE : [test_repo .repo_name ]},
270+ }
271+ code , data = http_json (
272+ "POST" ,
273+ "/boost-endpoint/add-or-update/" ,
274+ token = api_token ,
275+ body = body ,
276+ )
277+ assert code == 202 , f"expected 202: { code } { data } "
278+ assert isinstance (data , dict )
279+ assert data .get ("status" ) == "accepted"
280+ assert data .get ("task_id" )
281+ return AddOrUpdateTask (
282+ task_id = str (data ["task_id" ]),
283+ http_code = code ,
284+ response = data ,
285+ )
286+
287+
232288class TestAddOrUpdateCeleryFlow :
233289 """POST /boost-endpoint/add-or-update/ and poll Celery completion."""
234290
235291 def test_add_or_update_returns_202 (
236- self , api_token : str , test_repo : EphemeralGitHubRepo
292+ self , add_or_update_task : AddOrUpdateTask
237293 ) -> None :
238- owner = test_repo .resolve_owner ()
239- body = {
240- "organization" : owner ,
241- "version" : TEST_VERSION ,
242- "add_or_update" : {TEST_LANG_CODE : [test_repo .repo_name ]},
243- }
244- code , data = http_json (
245- "POST" ,
246- "/boost-endpoint/add-or-update/" ,
247- token = api_token ,
248- body = body ,
249- )
250- assert code == 202 , f"expected 202: { code } { data } "
251- assert isinstance (data , dict )
252- assert data .get ("status" ) == "accepted"
253- assert data .get ("task_id" )
254- type(self ).task_id = str (data ["task_id" ])
294+ assert add_or_update_task .http_code == 202
295+ assert add_or_update_task .response .get ("status" ) == "accepted"
296+ assert add_or_update_task .task_id
255297
256298 def test_add_or_update_task_completes (
257- self , weblate_api : WeblateAPI , test_repo : EphemeralGitHubRepo
299+ self , weblate_api : WeblateAPI , add_or_update_task : AddOrUpdateTask
258300 ) -> None :
259- task_id = getattr (self , "task_id" , None )
260- if not task_id :
261- pytest .skip ("depends on test_add_or_update_returns_202" )
262- result = weblate_api .poll_celery_task (task_id , timeout = 300.0 )
301+ result = weblate_api .poll_celery_task (add_or_update_task .task_id , timeout = 300.0 )
263302 assert isinstance (result , dict )
264303 lang_result = result .get (TEST_LANG_CODE )
265304 assert lang_result is not None
0 commit comments