tuner-testkit 4.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tuner-testkit might be problematic. Click here for more details.

Files changed (179) hide show
  1. tuner_testkit-4.0.0/LICENSE +21 -0
  2. tuner_testkit-4.0.0/PKG-INFO +176 -0
  3. tuner_testkit-4.0.0/README.md +118 -0
  4. tuner_testkit-4.0.0/pyproject.toml +109 -0
  5. tuner_testkit-4.0.0/setup.cfg +4 -0
  6. tuner_testkit-4.0.0/tuner_testkit/__init__.py +5 -0
  7. tuner_testkit-4.0.0/tuner_testkit/action_words/__init__.py +48 -0
  8. tuner_testkit-4.0.0/tuner_testkit/action_words/__main__.py +111 -0
  9. tuner_testkit-4.0.0/tuner_testkit/action_words/_internal/__init__.py +5 -0
  10. tuner_testkit-4.0.0/tuner_testkit/action_words/_internal/api.py +56 -0
  11. tuner_testkit-4.0.0/tuner_testkit/action_words/_internal/db.py +48 -0
  12. tuner_testkit-4.0.0/tuner_testkit/action_words/_internal/generators.py +37 -0
  13. tuner_testkit-4.0.0/tuner_testkit/action_words/_internal/ids.py +78 -0
  14. tuner_testkit-4.0.0/tuner_testkit/action_words/_internal/wait.py +27 -0
  15. tuner_testkit-4.0.0/tuner_testkit/action_words/base.py +131 -0
  16. tuner_testkit-4.0.0/tuner_testkit/action_words/context.py +105 -0
  17. tuner_testkit-4.0.0/tuner_testkit/action_words/plane.py +83 -0
  18. tuner_testkit-4.0.0/tuner_testkit/action_words/registry.py +96 -0
  19. tuner_testkit-4.0.0/tuner_testkit/api_mock/__init__.py +68 -0
  20. tuner_testkit-4.0.0/tuner_testkit/api_mock/app.py +419 -0
  21. tuner_testkit-4.0.0/tuner_testkit/api_mock/errors.py +17 -0
  22. tuner_testkit-4.0.0/tuner_testkit/api_mock/router.py +100 -0
  23. tuner_testkit-4.0.0/tuner_testkit/api_mock/spec.py +180 -0
  24. tuner_testkit-4.0.0/tuner_testkit/api_mock/store.py +214 -0
  25. tuner_testkit-4.0.0/tuner_testkit/api_objects/__init__.py +1 -0
  26. tuner_testkit-4.0.0/tuner_testkit/api_objects/auth.py +227 -0
  27. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/__init__.py +35 -0
  28. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/capture.py +271 -0
  29. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/codegen.py +381 -0
  30. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/freeze.py +312 -0
  31. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/mocks.py +149 -0
  32. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/normalize.py +256 -0
  33. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/pipeline.py +149 -0
  34. tuner_testkit-4.0.0/tuner_testkit/api_objects/recording/sanitize.py +99 -0
  35. tuner_testkit-4.0.0/tuner_testkit/api_test/__init__.py +18 -0
  36. tuner_testkit-4.0.0/tuner_testkit/api_test/client.py +274 -0
  37. tuner_testkit-4.0.0/tuner_testkit/api_test/errors.py +26 -0
  38. tuner_testkit-4.0.0/tuner_testkit/api_test/jsonpath.py +88 -0
  39. tuner_testkit-4.0.0/tuner_testkit/api_test/model.py +350 -0
  40. tuner_testkit-4.0.0/tuner_testkit/apps/__init__.py +1 -0
  41. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/__init__.py +1 -0
  42. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/changelog.py +77 -0
  43. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/plane.py +25 -0
  44. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/plane_app.py +296 -0
  45. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/plane_asset.py +147 -0
  46. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/tests/test_plane_asset.py +124 -0
  47. tuner_testkit-4.0.0/tuner_testkit/apps/_shared/tests/test_plane_collect.py +45 -0
  48. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/__init__.py +11 -0
  49. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/__main__.py +6 -0
  50. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/addon.py +67 -0
  51. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/cli.py +201 -0
  52. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/playwright_tap.py +113 -0
  53. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/tests/__init__.py +0 -0
  54. tuner_testkit-4.0.0/tuner_testkit/apps/api_recorder/tests/test_addon.py +117 -0
  55. tuner_testkit-4.0.0/tuner_testkit/apps/dna/__init__.py +1 -0
  56. tuner_testkit-4.0.0/tuner_testkit/apps/dna/__main__.py +4 -0
  57. tuner_testkit-4.0.0/tuner_testkit/apps/dna/build.py +80 -0
  58. tuner_testkit-4.0.0/tuner_testkit/apps/dna/cli.py +147 -0
  59. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/agents/bdd-asset-pipeline.md +102 -0
  60. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/hooks/sync_registry.py +45 -0
  61. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/hooks.json +11 -0
  62. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/apps-authoring.mdc +49 -0
  63. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/apps-handover.mdc +29 -0
  64. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/assets-knowledge.mdc +44 -0
  65. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/bdd-asset-layering.mdc +65 -0
  66. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/bdd-pipeline-gates.mdc +53 -0
  67. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/bdd-stage-layout.mdc +53 -0
  68. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/commit-convention.mdc +21 -0
  69. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/index-hygiene.mdc +33 -0
  70. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/packages-db.mdc +131 -0
  71. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/packages-fake.mdc +59 -0
  72. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/packages-logging.mdc +60 -0
  73. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/rules/packages-page-test.mdc +92 -0
  74. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/create-action-word/SKILL.md +84 -0
  75. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/create-app/SKILL.md +64 -0
  76. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/dump-ddl/SKILL.md +110 -0
  77. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/feature-authoring/SKILL.md +52 -0
  78. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/feature-review-lint/SKILL.md +37 -0
  79. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/freeze-api-objects/SKILL.md +75 -0
  80. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/maintain-behave-steps/SKILL.md +57 -0
  81. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/maintain-index/SKILL.md +47 -0
  82. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/maintain-page-objects/SKILL.md +93 -0
  83. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/release-template/SKILL.md +51 -0
  84. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/reuse-analysis/SKILL.md +51 -0
  85. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/.cursor/skills/run-feature-playwright-mcp/SKILL.md +64 -0
  86. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/AGENTS.md +79 -0
  87. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/MANIFEST.txt +13 -0
  88. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/action-words-syntax.md +172 -0
  89. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/api-objects-syntax.md +240 -0
  90. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/apps-authoring-syntax.md +75 -0
  91. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/assets-knowledge-syntax.md +70 -0
  92. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/bdd-run-gherkin-in-playwrightMCP.md +159 -0
  93. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/behave-gerkin-syntax.md +282 -0
  94. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/behave-step-definitions.md +199 -0
  95. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/commit-convention.md +83 -0
  96. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/docs/spec/page-objects-syntax.md +282 -0
  97. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/tools/git-hooks/README.md +28 -0
  98. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/tools/git-hooks/commit-msg +20 -0
  99. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/tools/git-hooks/pre-push +22 -0
  100. tuner_testkit-4.0.0/tuner_testkit/apps/dna/dna_payload/tools/git-hooks/validate_commit_msg.py +178 -0
  101. tuner_testkit-4.0.0/tuner_testkit/apps/dna/paths.py +15 -0
  102. tuner_testkit-4.0.0/tuner_testkit/apps/dna/source.py +84 -0
  103. tuner_testkit-4.0.0/tuner_testkit/apps/dna/tests/test_cli.py +21 -0
  104. tuner_testkit-4.0.0/tuner_testkit/apps/dump_ddl.py +1333 -0
  105. tuner_testkit-4.0.0/tuner_testkit/apps/index_ai/__init__.py +8 -0
  106. tuner_testkit-4.0.0/tuner_testkit/apps/index_ai/__main__.py +42 -0
  107. tuner_testkit-4.0.0/tuner_testkit/apps/index_ai/registry.py +237 -0
  108. tuner_testkit-4.0.0/tuner_testkit/apps/index_platform/__init__.py +9 -0
  109. tuner_testkit-4.0.0/tuner_testkit/apps/index_platform/__main__.py +29 -0
  110. tuner_testkit-4.0.0/tuner_testkit/apps/index_platform/build.py +63 -0
  111. tuner_testkit-4.0.0/tuner_testkit/apps/index_platform/catalog.py +228 -0
  112. tuner_testkit-4.0.0/tuner_testkit/apps/index_platform/plane.py +29 -0
  113. tuner_testkit-4.0.0/tuner_testkit/apps/index_platform/tests/test_catalog.py +29 -0
  114. tuner_testkit-4.0.0/tuner_testkit/apps/init_repo/__init__.py +11 -0
  115. tuner_testkit-4.0.0/tuner_testkit/apps/init_repo/__main__.py +87 -0
  116. tuner_testkit-4.0.0/tuner_testkit/apps/init_repo/manifest.py +133 -0
  117. tuner_testkit-4.0.0/tuner_testkit/apps/init_repo/scaffold.py +149 -0
  118. tuner_testkit-4.0.0/tuner_testkit/apps/mock_server/__init__.py +1 -0
  119. tuner_testkit-4.0.0/tuner_testkit/apps/mock_server/__main__.py +6 -0
  120. tuner_testkit-4.0.0/tuner_testkit/apps/mock_server/cli.py +213 -0
  121. tuner_testkit-4.0.0/tuner_testkit/apps/mock_server/plane.py +52 -0
  122. tuner_testkit-4.0.0/tuner_testkit/apps/mock_server/seed.py +215 -0
  123. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/__init__.py +7 -0
  124. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/__main__.py +6 -0
  125. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/capture.py +215 -0
  126. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/cli.py +134 -0
  127. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/codegen.py +239 -0
  128. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/freeze.py +390 -0
  129. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/inject.py +235 -0
  130. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/session.py +253 -0
  131. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/tests/__init__.py +0 -0
  132. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/tests/test_capture.py +171 -0
  133. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/tests/test_cli.py +53 -0
  134. tuner_testkit-4.0.0/tuner_testkit/apps/page_recorder/tests/test_freeze.py +192 -0
  135. tuner_testkit-4.0.0/tuner_testkit/apps/recorder/__init__.py +11 -0
  136. tuner_testkit-4.0.0/tuner_testkit/apps/recorder/__main__.py +6 -0
  137. tuner_testkit-4.0.0/tuner_testkit/apps/recorder/cli.py +233 -0
  138. tuner_testkit-4.0.0/tuner_testkit/apps/recorder/tests/__init__.py +0 -0
  139. tuner_testkit-4.0.0/tuner_testkit/apps/recorder/tests/test_cli.py +169 -0
  140. tuner_testkit-4.0.0/tuner_testkit/config.py +333 -0
  141. tuner_testkit-4.0.0/tuner_testkit/db/__init__.py +34 -0
  142. tuner_testkit-4.0.0/tuner_testkit/db/client.py +247 -0
  143. tuner_testkit-4.0.0/tuner_testkit/db/connection.py +140 -0
  144. tuner_testkit-4.0.0/tuner_testkit/excel/__init__.py +37 -0
  145. tuner_testkit-4.0.0/tuner_testkit/excel/workbook.py +225 -0
  146. tuner_testkit-4.0.0/tuner_testkit/fake/__init__.py +220 -0
  147. tuner_testkit-4.0.0/tuner_testkit/fake/__main__.py +4 -0
  148. tuner_testkit-4.0.0/tuner_testkit/fake/catalog.py +195 -0
  149. tuner_testkit-4.0.0/tuner_testkit/fake/china.py +139 -0
  150. tuner_testkit-4.0.0/tuner_testkit/fake/cli.py +162 -0
  151. tuner_testkit-4.0.0/tuner_testkit/fake/core.py +340 -0
  152. tuner_testkit-4.0.0/tuner_testkit/fake/medical.py +237 -0
  153. tuner_testkit-4.0.0/tuner_testkit/fake/wrappers.py +95 -0
  154. tuner_testkit-4.0.0/tuner_testkit/logging/__init__.py +50 -0
  155. tuner_testkit-4.0.0/tuner_testkit/logging/context.py +130 -0
  156. tuner_testkit-4.0.0/tuner_testkit/logging/core.py +202 -0
  157. tuner_testkit-4.0.0/tuner_testkit/logging/helpers.py +152 -0
  158. tuner_testkit-4.0.0/tuner_testkit/logging/hooks.py +196 -0
  159. tuner_testkit-4.0.0/tuner_testkit/logging/scenario_file.py +98 -0
  160. tuner_testkit-4.0.0/tuner_testkit/page_test/__init__.py +182 -0
  161. tuner_testkit-4.0.0/tuner_testkit/page_test/__main__.py +216 -0
  162. tuner_testkit-4.0.0/tuner_testkit/page_test/base.py +208 -0
  163. tuner_testkit-4.0.0/tuner_testkit/page_test/driver.py +724 -0
  164. tuner_testkit-4.0.0/tuner_testkit/page_test/errors.py +51 -0
  165. tuner_testkit-4.0.0/tuner_testkit/page_test/harvest.py +526 -0
  166. tuner_testkit-4.0.0/tuner_testkit/page_test/health.py +369 -0
  167. tuner_testkit-4.0.0/tuner_testkit/page_test/locator.py +575 -0
  168. tuner_testkit-4.0.0/tuner_testkit/page_test/model.py +522 -0
  169. tuner_testkit-4.0.0/tuner_testkit/page_test/registry.py +237 -0
  170. tuner_testkit-4.0.0/tuner_testkit/page_test/session.py +192 -0
  171. tuner_testkit-4.0.0/tuner_testkit/page_test/steps.py +517 -0
  172. tuner_testkit-4.0.0/tuner_testkit/page_test/testing.py +375 -0
  173. tuner_testkit-4.0.0/tuner_testkit/project.py +180 -0
  174. tuner_testkit-4.0.0/tuner_testkit.egg-info/PKG-INFO +176 -0
  175. tuner_testkit-4.0.0/tuner_testkit.egg-info/SOURCES.txt +150 -0
  176. tuner_testkit-4.0.0/tuner_testkit.egg-info/dependency_links.txt +1 -0
  177. tuner_testkit-4.0.0/tuner_testkit.egg-info/entry_points.txt +14 -0
  178. tuner_testkit-4.0.0/tuner_testkit.egg-info/requires.txt +58 -0
  179. tuner_testkit-4.0.0/tuner_testkit.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tuner-testkit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: tuner-testkit
3
+ Version: 4.0.0
4
+ Summary: Shared test runtime, recorders, and DNA overlay for SUT test repos
5
+ Author: tuner-testkit contributors
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: pydantic>=2
11
+ Provides-Extra: db
12
+ Requires-Dist: pymysql>=1.2.0; extra == "db"
13
+ Requires-Dist: pymssql>=2.3.0; extra == "db"
14
+ Requires-Dist: psycopg[binary]>=3.2; extra == "db"
15
+ Requires-Dist: sqlalchemy>=2.0; extra == "db"
16
+ Provides-Extra: api
17
+ Requires-Dist: requests>=2.33.1; extra == "api"
18
+ Provides-Extra: web-ui
19
+ Requires-Dist: playwright>=1.54.0; extra == "web-ui"
20
+ Provides-Extra: phone-ui
21
+ Provides-Extra: excel
22
+ Requires-Dist: openpyxl>=3.1.0; extra == "excel"
23
+ Provides-Extra: fake
24
+ Requires-Dist: faker>=37.0.0; extra == "fake"
25
+ Provides-Extra: mock
26
+ Requires-Dist: fastapi>=0.115.0; extra == "mock"
27
+ Requires-Dist: uvicorn[standard]>=0.32.0; extra == "mock"
28
+ Requires-Dist: httpx>=0.28.0; extra == "mock"
29
+ Provides-Extra: recorder
30
+ Requires-Dist: playwright>=1.54.0; extra == "recorder"
31
+ Requires-Dist: mitmproxy>=11.1.3; extra == "recorder"
32
+ Provides-Extra: test
33
+ Requires-Dist: pytest>=9.0.3; extra == "test"
34
+ Requires-Dist: openpyxl>=3.1.0; extra == "test"
35
+ Requires-Dist: httpx>=0.28.0; extra == "test"
36
+ Provides-Extra: bdd
37
+ Requires-Dist: behave>=1.2.6; extra == "bdd"
38
+ Requires-Dist: playwright>=1.54.0; extra == "bdd"
39
+ Requires-Dist: behave-html-pretty-formatter>=1.16.1; extra == "bdd"
40
+ Provides-Extra: dev
41
+ Requires-Dist: pymysql>=1.2.0; extra == "dev"
42
+ Requires-Dist: pymssql>=2.3.0; extra == "dev"
43
+ Requires-Dist: psycopg[binary]>=3.2; extra == "dev"
44
+ Requires-Dist: sqlalchemy>=2.0; extra == "dev"
45
+ Requires-Dist: requests>=2.33.1; extra == "dev"
46
+ Requires-Dist: pydantic>=2; extra == "dev"
47
+ Requires-Dist: faker>=37.0.0; extra == "dev"
48
+ Requires-Dist: openpyxl>=3.1.0; extra == "dev"
49
+ Requires-Dist: fastapi>=0.115.0; extra == "dev"
50
+ Requires-Dist: uvicorn[standard]>=0.32.0; extra == "dev"
51
+ Requires-Dist: httpx>=0.28.0; extra == "dev"
52
+ Requires-Dist: pytest>=9.0.3; extra == "dev"
53
+ Requires-Dist: behave>=1.2.6; extra == "dev"
54
+ Requires-Dist: playwright>=1.54.0; extra == "dev"
55
+ Requires-Dist: behave-html-pretty-formatter>=1.16.1; extra == "dev"
56
+ Requires-Dist: mitmproxy>=11.1.3; extra == "dev"
57
+ Dynamic: license-file
58
+
59
+ # tuner-testkit
60
+
61
+ 发行名 **`tuner-testkit`**,导入名 **`tuner_testkit`**。给被测系统(SUT)测试仓提供公共运行库与 CLI(db / API / Web UI / 录制 / mock),以及 Cursor/git **DNA**(rules、skills、hooks)。
62
+
63
+ 业务资产(page/api objects、action words、features)留在**各项目仓**的 `packages/` 与 `tests/`,不进本包。平台发新版本后,下游用 `uv` 按需升级,不必再整树拷贝运行库。
64
+
65
+ ---
66
+
67
+ ## 标准作业流程
68
+
69
+ ### 1. 安装 `tuner-testkit`
70
+
71
+ 还没有任何项目仓时,先把 CLI 装到工具环境:
72
+
73
+ ```bash
74
+ uv tool install "tuner-testkit[db,api]"
75
+ ```
76
+
77
+ 需要 headed 录制或 Playwright 时加上对应 extra(见下方 extras)。已有项目仓则在该仓根目录:
78
+
79
+ ```bash
80
+ uv add "tuner-testkit[db,api]>=4.0.0"
81
+ uv sync
82
+ ```
83
+
84
+ 入口命令是 `tuner-init`、`tuner-dna`、`tuner-recorder` 等(`tuner-*`),**没有** `tuner-testkit` 这个 script。
85
+
86
+ ### 2. 初始化测试仓
87
+
88
+ ```bash
89
+ tuner-init scaffold ../my-sut-tests
90
+ cd ../my-sut-tests
91
+ uv sync
92
+ ```
93
+
94
+ `scaffold` 会铺六层目录、写入依赖 `tuner-testkit` 的 `pyproject.toml`,并执行一次 `tuner-dna sync`(把 `.cursor/`、`docs/spec/`、`tools/git-hooks/` 放进**项目树**)。不要用 `tuner-testkit init repo`。
95
+
96
+ Web UI / 合录按需再加 extra:
97
+
98
+ ```bash
99
+ uv add "tuner-testkit[web-ui]"
100
+ uv add "tuner-testkit[recorder]"
101
+ ```
102
+
103
+ ### 3. 开始写业务
104
+
105
+ 在**项目仓**里写,不要改 site-packages 里的 kit:
106
+
107
+ | 你写什么 | 放哪里 |
108
+ | --- | --- |
109
+ | 页面 / 接口资产 | `packages/page_objects/`、`packages/api_objects/` |
110
+ | 业务动作 | `packages/action_words/` |
111
+ | BDD / pytest | `tests/features/`、`tests/pytest/` |
112
+ | 知识(DDL、用例) | `assets/` |
113
+ | 本仓私有工具 | `apps/`(`python -m apps.<name>`) |
114
+
115
+ 代码里 `from tuner_testkit.db import …` / `from tuner_testkit.page_test import …`。密钥只放本机 `config/env_local.py` 或环境变量(`TUNER_ENV`、`TUNER_DB_<ALIAS>_*`),**不要提交**。
116
+
117
+ 录制产物同样写到项目仓 `packages/`,不会写进 venv。
118
+
119
+ ### 4. 升级已发布的 kit
120
+
121
+ 平台把新版本推上 PyPI 之后,在项目仓改依赖并同步 DNA——**两步都要做**:
122
+
123
+ ```bash
124
+ # 改版本:直接改 pyproject.toml,或
125
+ uv add "tuner-testkit[db,api]==4.1.0"
126
+ uv sync
127
+
128
+ # Cursor 不会读 site-packages 里的 rule/skill,必须再铺进本仓并提交
129
+ tuner-dna sync
130
+ git add -A && git commit
131
+ ```
132
+
133
+ 只跑 `uv add` / `uv sync` 只会更新运行库与 CLI;AI 约束和 git hooks 仍停留在旧 DNA。检查是否与当前安装的 kit 一致:
134
+
135
+ ```bash
136
+ tuner-dna check
137
+ ```
138
+
139
+ 一个 wheel 不能「只升 web-ui、钉死旧 db」。`[phone-ui]` 为预留空 extra,4.0 未实现。
140
+
141
+ ---
142
+
143
+ ## extras
144
+
145
+ | extra | 内容 |
146
+ | --- | --- |
147
+ | (默认 wheel) | `config` / `logging`、`tuner-init`、`tuner-dna`、`tuner-dump-ddl` 等 |
148
+ | `[db]` | MySQL / SQL Server / PostgreSQL |
149
+ | `[api]` | `api_test`(HTTP 客户端) |
150
+ | `[web-ui]` | `page_test` + Playwright |
151
+ | `[phone-ui]` | 预留,无实现 |
152
+ | `[recorder]` | 合录 / 仅 UI / mitmproxy 代理 |
153
+ | `[mock]` | 按冻结的 api_objects 回放 |
154
+ | `[excel]` / `[fake]` | 表格 / 假数据 |
155
+ | `[bdd]` / `[test]` / `[dev]` | behave / pytest / 开发全集 |
156
+
157
+ CLI 与 `python -m tuner_testkit.…` 等价,例如 `tuner-recorder`、`tuner-page-recorder`、`tuner-api-recorder`、`tuner-mock-server`。
158
+
159
+ ---
160
+
161
+ ## 安全
162
+
163
+ 不得把 token、cookie、Authorization、密码、session 写入仓库(含 capture 与 api_objects)。凭证只允许运行时注入。
164
+
165
+ ---
166
+
167
+ ## 本仓库(平台源码)
168
+
169
+ 克隆后在仓库根目录开发 kit 本身:
170
+
171
+ ```powershell
172
+ uv venv
173
+ uv sync --extra dev
174
+ ```
175
+
176
+ 下游迁移清单见 [`docs/changelog/TEMPLATE-4.0.0.md`](docs/changelog/TEMPLATE-4.0.0.md)。平台能力地图见 [`INDEX.md`](INDEX.md)。
@@ -0,0 +1,118 @@
1
+ # tuner-testkit
2
+
3
+ 发行名 **`tuner-testkit`**,导入名 **`tuner_testkit`**。给被测系统(SUT)测试仓提供公共运行库与 CLI(db / API / Web UI / 录制 / mock),以及 Cursor/git **DNA**(rules、skills、hooks)。
4
+
5
+ 业务资产(page/api objects、action words、features)留在**各项目仓**的 `packages/` 与 `tests/`,不进本包。平台发新版本后,下游用 `uv` 按需升级,不必再整树拷贝运行库。
6
+
7
+ ---
8
+
9
+ ## 标准作业流程
10
+
11
+ ### 1. 安装 `tuner-testkit`
12
+
13
+ 还没有任何项目仓时,先把 CLI 装到工具环境:
14
+
15
+ ```bash
16
+ uv tool install "tuner-testkit[db,api]"
17
+ ```
18
+
19
+ 需要 headed 录制或 Playwright 时加上对应 extra(见下方 extras)。已有项目仓则在该仓根目录:
20
+
21
+ ```bash
22
+ uv add "tuner-testkit[db,api]>=4.0.0"
23
+ uv sync
24
+ ```
25
+
26
+ 入口命令是 `tuner-init`、`tuner-dna`、`tuner-recorder` 等(`tuner-*`),**没有** `tuner-testkit` 这个 script。
27
+
28
+ ### 2. 初始化测试仓
29
+
30
+ ```bash
31
+ tuner-init scaffold ../my-sut-tests
32
+ cd ../my-sut-tests
33
+ uv sync
34
+ ```
35
+
36
+ `scaffold` 会铺六层目录、写入依赖 `tuner-testkit` 的 `pyproject.toml`,并执行一次 `tuner-dna sync`(把 `.cursor/`、`docs/spec/`、`tools/git-hooks/` 放进**项目树**)。不要用 `tuner-testkit init repo`。
37
+
38
+ Web UI / 合录按需再加 extra:
39
+
40
+ ```bash
41
+ uv add "tuner-testkit[web-ui]"
42
+ uv add "tuner-testkit[recorder]"
43
+ ```
44
+
45
+ ### 3. 开始写业务
46
+
47
+ 在**项目仓**里写,不要改 site-packages 里的 kit:
48
+
49
+ | 你写什么 | 放哪里 |
50
+ | --- | --- |
51
+ | 页面 / 接口资产 | `packages/page_objects/`、`packages/api_objects/` |
52
+ | 业务动作 | `packages/action_words/` |
53
+ | BDD / pytest | `tests/features/`、`tests/pytest/` |
54
+ | 知识(DDL、用例) | `assets/` |
55
+ | 本仓私有工具 | `apps/`(`python -m apps.<name>`) |
56
+
57
+ 代码里 `from tuner_testkit.db import …` / `from tuner_testkit.page_test import …`。密钥只放本机 `config/env_local.py` 或环境变量(`TUNER_ENV`、`TUNER_DB_<ALIAS>_*`),**不要提交**。
58
+
59
+ 录制产物同样写到项目仓 `packages/`,不会写进 venv。
60
+
61
+ ### 4. 升级已发布的 kit
62
+
63
+ 平台把新版本推上 PyPI 之后,在项目仓改依赖并同步 DNA——**两步都要做**:
64
+
65
+ ```bash
66
+ # 改版本:直接改 pyproject.toml,或
67
+ uv add "tuner-testkit[db,api]==4.1.0"
68
+ uv sync
69
+
70
+ # Cursor 不会读 site-packages 里的 rule/skill,必须再铺进本仓并提交
71
+ tuner-dna sync
72
+ git add -A && git commit
73
+ ```
74
+
75
+ 只跑 `uv add` / `uv sync` 只会更新运行库与 CLI;AI 约束和 git hooks 仍停留在旧 DNA。检查是否与当前安装的 kit 一致:
76
+
77
+ ```bash
78
+ tuner-dna check
79
+ ```
80
+
81
+ 一个 wheel 不能「只升 web-ui、钉死旧 db」。`[phone-ui]` 为预留空 extra,4.0 未实现。
82
+
83
+ ---
84
+
85
+ ## extras
86
+
87
+ | extra | 内容 |
88
+ | --- | --- |
89
+ | (默认 wheel) | `config` / `logging`、`tuner-init`、`tuner-dna`、`tuner-dump-ddl` 等 |
90
+ | `[db]` | MySQL / SQL Server / PostgreSQL |
91
+ | `[api]` | `api_test`(HTTP 客户端) |
92
+ | `[web-ui]` | `page_test` + Playwright |
93
+ | `[phone-ui]` | 预留,无实现 |
94
+ | `[recorder]` | 合录 / 仅 UI / mitmproxy 代理 |
95
+ | `[mock]` | 按冻结的 api_objects 回放 |
96
+ | `[excel]` / `[fake]` | 表格 / 假数据 |
97
+ | `[bdd]` / `[test]` / `[dev]` | behave / pytest / 开发全集 |
98
+
99
+ CLI 与 `python -m tuner_testkit.…` 等价,例如 `tuner-recorder`、`tuner-page-recorder`、`tuner-api-recorder`、`tuner-mock-server`。
100
+
101
+ ---
102
+
103
+ ## 安全
104
+
105
+ 不得把 token、cookie、Authorization、密码、session 写入仓库(含 capture 与 api_objects)。凭证只允许运行时注入。
106
+
107
+ ---
108
+
109
+ ## 本仓库(平台源码)
110
+
111
+ 克隆后在仓库根目录开发 kit 本身:
112
+
113
+ ```powershell
114
+ uv venv
115
+ uv sync --extra dev
116
+ ```
117
+
118
+ 下游迁移清单见 [`docs/changelog/TEMPLATE-4.0.0.md`](docs/changelog/TEMPLATE-4.0.0.md)。平台能力地图见 [`INDEX.md`](INDEX.md)。
@@ -0,0 +1,109 @@
1
+ [project]
2
+ name = "tuner-testkit"
3
+ version = "4.0.0"
4
+ description = "Shared test runtime, recorders, and DNA overlay for SUT test repos"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{name = "tuner-testkit contributors"}]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "pydantic>=2",
12
+ ]
13
+
14
+ [project.optional-dependencies]
15
+ db = [
16
+ "pymysql>=1.2.0",
17
+ "pymssql>=2.3.0",
18
+ "psycopg[binary]>=3.2",
19
+ "sqlalchemy>=2.0",
20
+ ]
21
+ api = [
22
+ "requests>=2.33.1",
23
+ ]
24
+ web-ui = [
25
+ "playwright>=1.54.0",
26
+ ]
27
+ # Reserved extra name for a future mobile stack (not implemented in 4.0).
28
+ phone-ui = []
29
+ excel = [
30
+ "openpyxl>=3.1.0",
31
+ ]
32
+ fake = [
33
+ "faker>=37.0.0",
34
+ ]
35
+ mock = [
36
+ "fastapi>=0.115.0",
37
+ "uvicorn[standard]>=0.32.0",
38
+ "httpx>=0.28.0",
39
+ ]
40
+ recorder = [
41
+ "playwright>=1.54.0",
42
+ "mitmproxy>=11.1.3",
43
+ ]
44
+ test = [
45
+ "pytest>=9.0.3",
46
+ "openpyxl>=3.1.0",
47
+ "httpx>=0.28.0",
48
+ ]
49
+ bdd = [
50
+ "behave>=1.2.6",
51
+ "playwright>=1.54.0",
52
+ "behave-html-pretty-formatter>=1.16.1",
53
+ ]
54
+ dev = [
55
+ "pymysql>=1.2.0",
56
+ "pymssql>=2.3.0",
57
+ "psycopg[binary]>=3.2",
58
+ "sqlalchemy>=2.0",
59
+ "requests>=2.33.1",
60
+ "pydantic>=2",
61
+ "faker>=37.0.0",
62
+ "openpyxl>=3.1.0",
63
+ "fastapi>=0.115.0",
64
+ "uvicorn[standard]>=0.32.0",
65
+ "httpx>=0.28.0",
66
+ "pytest>=9.0.3",
67
+ "behave>=1.2.6",
68
+ "playwright>=1.54.0",
69
+ "behave-html-pretty-formatter>=1.16.1",
70
+ "mitmproxy>=11.1.3",
71
+ ]
72
+
73
+ [project.scripts]
74
+ tuner-config = "tuner_testkit.config:main"
75
+ tuner-recorder = "tuner_testkit.apps.recorder.cli:main"
76
+ tuner-page-recorder = "tuner_testkit.apps.page_recorder.cli:main"
77
+ tuner-api-recorder = "tuner_testkit.apps.api_recorder.cli:main"
78
+ tuner-dump-ddl = "tuner_testkit.apps.dump_ddl:main"
79
+ tuner-mock-server = "tuner_testkit.apps.mock_server.cli:main"
80
+ tuner-dna = "tuner_testkit.apps.dna.cli:main"
81
+ tuner-init = "tuner_testkit.apps.init_repo.__main__:main"
82
+ tuner-index-ai = "tuner_testkit.apps.index_ai.__main__:main"
83
+ tuner-index-platform = "tuner_testkit.apps.index_platform.__main__:main"
84
+ tuner-fake = "tuner_testkit.fake.cli:main"
85
+ tuner-page-test = "tuner_testkit.page_test.__main__:main"
86
+ tuner-action-words = "tuner_testkit.action_words.__main__:main"
87
+
88
+ [build-system]
89
+ requires = ["setuptools>=77", "wheel"]
90
+ build-backend = "setuptools.build_meta"
91
+
92
+ [tool.setuptools.packages.find]
93
+ include = ["tuner_testkit*"]
94
+
95
+ [tool.setuptools.package-data]
96
+ "tuner_testkit.apps.dna" = ["dna_payload/**/*"]
97
+
98
+ [tool.setuptools.cmdclass]
99
+ build_py = "tuner_testkit.apps.dna.build.BuildPyWithDna"
100
+ sdist = "tuner_testkit.apps.dna.build.SdistWithDna"
101
+
102
+ [tool.tuner-testkit]
103
+ page_objects = "packages/page_objects"
104
+ api_objects = "packages/api_objects"
105
+ mocks = "data/mocks"
106
+
107
+ [tool.pytest.ini_options]
108
+ pythonpath = ["."]
109
+ testpaths = ["packages/tests", "tuner_testkit"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """tuner-testkit — shared test runtime (db, api_test, page_test, recorders, DNA)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "4.0.0"
@@ -0,0 +1,48 @@
1
+ """Action Words — 统一管理自动化测试资产的最小业务动作层。
2
+
3
+ 设计目标(见 docs/spec/action-words-syntax.md):
4
+
5
+ - 每个 action word 是一个独立可运行的业务动作(DB 造数 / DB 断言 /
6
+ 接口请求 / 接口断言 / 页面操作 / 页面断言),入参经 pydantic 强类型
7
+ 声明并带样例,可 CLI 一键执行,也可被 behave / pytest 步骤直接调用;
8
+ - 接口类 word 只做冻结 ``packages/api_objects`` 资产的引用与编排,
9
+ 不重复定义请求契约;
10
+ - DB 类 word 统一走 ``tuner_testkit.db.DbClient``(经类元数据 ``datasource``
11
+ 声明目标库),造数结果按表登记 ``cleanup``,供测试夹具精确清理。
12
+
13
+ CLI::
14
+
15
+ uv run python -m tuner_testkit.action_words list
16
+ uv run python -m tuner_testkit.action_words describe <word_id>
17
+ uv run python -m tuner_testkit.action_words run <word_id> --params '{...}'
18
+ uv run python -m tuner_testkit.action_words catalog --out report/action_words_catalog.json
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from tuner_testkit.action_words.base import (
23
+ ActionCategory,
24
+ ActionResult,
25
+ ActionWord,
26
+ TableRows,
27
+ )
28
+ from tuner_testkit.action_words.context import ActionContext
29
+ from tuner_testkit.action_words.registry import (
30
+ discover,
31
+ export_catalog,
32
+ get,
33
+ list_all,
34
+ register,
35
+ )
36
+
37
+ __all__ = [
38
+ "ActionCategory",
39
+ "ActionContext",
40
+ "ActionResult",
41
+ "ActionWord",
42
+ "TableRows",
43
+ "discover",
44
+ "export_catalog",
45
+ "get",
46
+ "list_all",
47
+ "register",
48
+ ]
@@ -0,0 +1,111 @@
1
+ """Action Words CLI — 列出 / 查看 / 一键运行 / 导出目录。
2
+
3
+ 用法::
4
+
5
+ uv run python -m tuner_testkit.action_words list
6
+ uv run python -m tuner_testkit.action_words describe db_seed.create_example
7
+ uv run python -m tuner_testkit.action_words run db_seed.create_example --params "{...}"
8
+ uv run python -m tuner_testkit.action_words run db_seed.create_example --params-file p.json
9
+ uv run python -m tuner_testkit.action_words run db_seed.create_example --example
10
+ uv run python -m tuner_testkit.action_words catalog --out report/action_words_catalog.json
11
+
12
+ ``run`` 未提供 ``--params`` 时使用 ``{}``(即全部默认值);``--example``
13
+ 使用 word 自带的 ``example_params`` 样例运行。
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ from tuner_testkit.action_words import ActionContext, export_catalog, get, list_all
23
+
24
+
25
+ def _print_json(data: object) -> None:
26
+ print(json.dumps(data, ensure_ascii=False, indent=2, default=str))
27
+
28
+
29
+ def _cmd_list(_args: argparse.Namespace) -> int:
30
+ rows = [
31
+ {"word_id": cls.word_id, "name": cls.name, "category": str(cls.category)}
32
+ for cls in list_all()
33
+ ]
34
+ _print_json(rows)
35
+ return 0
36
+
37
+
38
+ def _cmd_describe(args: argparse.Namespace) -> int:
39
+ _print_json(get(args.word_id).describe())
40
+ return 0
41
+
42
+
43
+ def _cmd_run(args: argparse.Namespace) -> int:
44
+ cls = get(args.word_id)
45
+ if args.example:
46
+ raw = dict(cls.example_params)
47
+ elif args.params_file:
48
+ raw = json.loads(Path(args.params_file).read_text(encoding="utf-8"))
49
+ else:
50
+ raw = json.loads(args.params or "{}")
51
+
52
+ with ActionContext(username=args.username, password=args.password) as ctx:
53
+ try:
54
+ result = cls(ctx).run_from_dict(raw)
55
+ except AssertionError as exc:
56
+ _print_json({"ok": False, "error": str(exc)})
57
+ return 1
58
+ _print_json(result.model_dump(mode="json"))
59
+ return 0 if result.ok else 1
60
+
61
+
62
+ def _cmd_catalog(args: argparse.Namespace) -> int:
63
+ catalog = export_catalog()
64
+ if args.out:
65
+ out = Path(args.out)
66
+ out.parent.mkdir(parents=True, exist_ok=True)
67
+ out.write_text(
68
+ json.dumps(catalog, ensure_ascii=False, indent=2, default=str),
69
+ encoding="utf-8",
70
+ )
71
+ print(f"catalog written: {out} ({len(catalog)} words)")
72
+ else:
73
+ _print_json(catalog)
74
+ return 0
75
+
76
+
77
+ def main(argv: list[str] | None = None) -> int:
78
+ if hasattr(sys.stdout, "reconfigure"):
79
+ sys.stdout.reconfigure(encoding="utf-8")
80
+
81
+ parser = argparse.ArgumentParser(prog="python -m tuner_testkit.action_words")
82
+ sub = parser.add_subparsers(dest="command", required=True)
83
+
84
+ sub.add_parser("list", help="列出全部已注册 action words")
85
+
86
+ p_desc = sub.add_parser("describe", help="查看某个 word 的元数据 / 入参 schema / 样例")
87
+ p_desc.add_argument("word_id")
88
+
89
+ p_run = sub.add_parser("run", help="一键运行某个 word")
90
+ p_run.add_argument("word_id")
91
+ p_run.add_argument("--params", help="JSON 入参字符串(默认 {})")
92
+ p_run.add_argument("--params-file", help="JSON 入参文件路径")
93
+ p_run.add_argument("--example", action="store_true", help="使用 example_params 样例运行")
94
+ p_run.add_argument("--username", help="接口登录用户名(覆盖环境变量/配置)")
95
+ p_run.add_argument("--password", help="接口登录密码(覆盖环境变量/配置)")
96
+
97
+ p_cat = sub.add_parser("catalog", help="导出全量目录 JSON(平台化数据源)")
98
+ p_cat.add_argument("--out", help="输出文件路径;缺省打印到 stdout")
99
+
100
+ args = parser.parse_args(argv)
101
+ handlers = {
102
+ "list": _cmd_list,
103
+ "describe": _cmd_describe,
104
+ "run": _cmd_run,
105
+ "catalog": _cmd_catalog,
106
+ }
107
+ return handlers[args.command](args)
108
+
109
+
110
+ if __name__ == "__main__":
111
+ raise SystemExit(main())
@@ -0,0 +1,5 @@
1
+ """action_words 内部共用工具(id 生成 / 随机业务值 / 批量插入 / API 辅助 / 轮询)。
2
+
3
+ 仅供 packages.action_words 内部使用;外部调用方请走各 action word 的
4
+ ``run`` / ``run_from_dict`` 入口。
5
+ """
@@ -0,0 +1,56 @@
1
+ """API 辅助 — 冻结 api_objects 的动态加载与登录换 token。
2
+
3
+ 登录资产模块由环境变量 ``TEST_LOGIN_API_MODULE`` /
4
+ ``TEST_LOGIN_API_ATTR`` 配置(默认未设置时明确报错),避免模板硬编码
5
+ 业务路由目录。含连字符的包路径(如 ``packages.api_objects.prod-api.login``)
6
+ 同样经 :func:`load_api` 按名加载。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import importlib
11
+ import os
12
+ from typing import Any
13
+
14
+
15
+ def load_api(module_path: str, attr: str) -> Any:
16
+ """按模块路径与属性名加载一个冻结的 APIModel 资产。"""
17
+ return getattr(importlib.import_module(module_path), attr)
18
+
19
+
20
+ def login_token(username: str, password: str) -> str:
21
+ """调用冻结的 login 接口资产换取 bearer token。
22
+
23
+ 配置(环境变量):
24
+ - ``TEST_LOGIN_API_MODULE`` — 如 ``tuner_testkit.api_objects.auth_api.login``
25
+ - ``TEST_LOGIN_API_ATTR`` — 如 ``login_post_v1``(默认 ``login_post_v1``)
26
+ """
27
+ module = (os.getenv("TEST_LOGIN_API_MODULE") or "").strip()
28
+ attr = (os.getenv("TEST_LOGIN_API_ATTR") or "login_post_v1").strip()
29
+ if not module:
30
+ raise RuntimeError(
31
+ "未配置登录 API Object:请设置环境变量 TEST_LOGIN_API_MODULE "
32
+ "(及可选 TEST_LOGIN_API_ATTR),指向冻结的 login 资产;"
33
+ "或向 ActionContext 显式传入 bearer_token。"
34
+ )
35
+ login_model = load_api(module, attr)
36
+ resp = login_model.set_json({"username": username, "password": password}).execute()
37
+ token = _extract_token(resp)
38
+ if not token:
39
+ keys = sorted(resp.json.keys()) if isinstance(resp.json, dict) else type(resp.json)
40
+ raise RuntimeError(f"登录响应中未找到 token(响应顶层键: {keys})")
41
+ return token
42
+
43
+
44
+ def _extract_token(resp: Any) -> str | None:
45
+ payload = resp.json if isinstance(resp.json, dict) else {}
46
+ for candidate in (payload.get("token"), payload.get("access_token")):
47
+ if candidate:
48
+ return str(candidate)
49
+ data = payload.get("data")
50
+ if isinstance(data, dict):
51
+ for key in ("token", "access_token", "accessToken"):
52
+ if data.get(key):
53
+ return str(data[key])
54
+ if isinstance(data, str) and data:
55
+ return data
56
+ return None