mcp-dbutils 1.0.0__tar.gz → 1.0.1__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.
Files changed (211) hide show
  1. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/CHANGELOG.md +7 -0
  2. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/PKG-INFO +1 -1
  3. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/pyproject.toml +1 -1
  4. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/base.py +7 -12
  5. mcp_dbutils-1.0.1/tests/unit/test_base_extended.py +504 -0
  6. mcp_dbutils-1.0.1/tests/unit/test_init.py +141 -0
  7. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_write_permissions.py +12 -11
  8. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.coveragerc +0 -0
  9. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/bug_report_ar.md +0 -0
  10. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/bug_report_en.md +0 -0
  11. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/bug_report_es.md +0 -0
  12. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/bug_report_fr.md +0 -0
  13. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/bug_report_ru.md +0 -0
  14. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/bug_report_zh.md +0 -0
  15. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/documentation_improvement_ar.md +0 -0
  17. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/documentation_improvement_en.md +0 -0
  18. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/documentation_improvement_es.md +0 -0
  19. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/documentation_improvement_fr.md +0 -0
  20. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/documentation_improvement_ru.md +0 -0
  21. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/documentation_improvement_zh.md +0 -0
  22. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/feature_request_ar.md +0 -0
  23. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/feature_request_en.md +0 -0
  24. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/feature_request_es.md +0 -0
  25. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/feature_request_fr.md +0 -0
  26. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/feature_request_ru.md +0 -0
  27. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/feature_request_zh.md +0 -0
  28. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/performance_issue_ar.md +0 -0
  29. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/performance_issue_en.md +0 -0
  30. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/performance_issue_es.md +0 -0
  31. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/performance_issue_fr.md +0 -0
  32. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/performance_issue_ru.md +0 -0
  33. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/performance_issue_zh.md +0 -0
  34. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/security_vulnerability_ar.md +0 -0
  35. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/security_vulnerability_en.md +0 -0
  36. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/security_vulnerability_es.md +0 -0
  37. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/security_vulnerability_fr.md +0 -0
  38. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/security_vulnerability_ru.md +0 -0
  39. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/ISSUE_TEMPLATE/security_vulnerability_zh.md +0 -0
  40. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/workflows/code-style.yml +0 -0
  41. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/workflows/issue-translator.yml +0 -0
  42. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/workflows/quality-assurance.yml +0 -0
  43. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.github/workflows/release.yml +0 -0
  44. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.gitignore +0 -0
  45. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.pre-commit-config.yaml +0 -0
  46. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/.releaserc.json +0 -0
  47. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/Dockerfile +0 -0
  48. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/LICENSE +0 -0
  49. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/README.md +0 -0
  50. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/README_AR.md +0 -0
  51. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/README_EN.md +0 -0
  52. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/README_ES.md +0 -0
  53. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/README_FR.md +0 -0
  54. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/README_RU.md +0 -0
  55. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/config.yaml.example +0 -0
  56. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/coverage.xml +0 -0
  57. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/configuration.md +0 -0
  58. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/examples/README.md +0 -0
  59. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/examples/advanced-llm-interactions.md +0 -0
  60. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/examples/mysql-examples.md +0 -0
  61. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/examples/postgresql-examples.md +0 -0
  62. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/examples/sqlite-examples.md +0 -0
  63. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/installation-platform-specific.md +0 -0
  64. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/installation.md +0 -0
  65. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/technical/architecture.md +0 -0
  66. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/technical/development.md +0 -0
  67. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/technical/security.md +0 -0
  68. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/technical/sonarcloud-integration.md +0 -0
  69. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/technical/testing.md +0 -0
  70. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ar/usage.md +0 -0
  71. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/document-consistency-check.md +0 -0
  72. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/document-version-history.md +0 -0
  73. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/configuration.md +0 -0
  74. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/examples/advanced-llm-interactions.md +0 -0
  75. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/examples/mysql-examples.md +0 -0
  76. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/examples/postgresql-examples.md +0 -0
  77. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/examples/sqlite-examples.md +0 -0
  78. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/installation-platform-specific.md +0 -0
  79. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/installation.md +0 -0
  80. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/technical/architecture.md +0 -0
  81. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/technical/development.md +0 -0
  82. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/technical/security.md +0 -0
  83. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/technical/sonarcloud-integration.md +0 -0
  84. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/technical/testing.md +0 -0
  85. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/en/usage.md +0 -0
  86. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/configuration.md +0 -0
  87. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/examples/README.md +0 -0
  88. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/examples/advanced-llm-interactions.md +0 -0
  89. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/examples/mysql-examples.md +0 -0
  90. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/examples/postgresql-examples.md +0 -0
  91. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/examples/sqlite-examples.md +0 -0
  92. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/installation-platform-specific.md +0 -0
  93. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/installation.md +0 -0
  94. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/technical/architecture.md +0 -0
  95. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/technical/development.md +0 -0
  96. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/technical/security.md +0 -0
  97. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/technical/sonarcloud-integration.md +0 -0
  98. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/technical/testing.md +0 -0
  99. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/es/usage.md +0 -0
  100. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/configuration.md +0 -0
  101. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/examples/README.md +0 -0
  102. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/examples/advanced-llm-interactions.md +0 -0
  103. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/examples/mysql-examples.md +0 -0
  104. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/examples/postgresql-examples.md +0 -0
  105. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/examples/sqlite-examples.md +0 -0
  106. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/installation-platform-specific.md +0 -0
  107. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/installation.md +0 -0
  108. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/technical/architecture.md +0 -0
  109. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/technical/development.md +0 -0
  110. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/technical/security.md +0 -0
  111. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/technical/sonarcloud-integration.md +0 -0
  112. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/technical/testing.md +0 -0
  113. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/fr/usage.md +0 -0
  114. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/index.md +0 -0
  115. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/configuration.md +0 -0
  116. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/examples/README.md +0 -0
  117. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/examples/advanced-llm-interactions.md +0 -0
  118. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/examples/mysql-examples.md +0 -0
  119. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/examples/postgresql-examples.md +0 -0
  120. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/examples/sqlite-examples.md +0 -0
  121. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/installation-platform-specific.md +0 -0
  122. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/installation.md +0 -0
  123. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/technical/architecture.md +0 -0
  124. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/technical/development.md +0 -0
  125. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/technical/security.md +0 -0
  126. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/technical/sonarcloud-integration.md +0 -0
  127. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/technical/testing.md +0 -0
  128. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/ru/usage.md +0 -0
  129. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/write-operations.md +0 -0
  130. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/configuration-write-operations.md +0 -0
  131. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/configuration.md +0 -0
  132. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/examples/advanced-llm-interactions.md +0 -0
  133. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/examples/mysql-examples.md +0 -0
  134. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/examples/postgresql-examples.md +0 -0
  135. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/examples/sqlite-examples.md +0 -0
  136. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/installation-platform-specific.md +0 -0
  137. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/installation.md +0 -0
  138. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/security-best-practices.md +0 -0
  139. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/architecture.md +0 -0
  140. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/audit-logging.md +0 -0
  141. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/development.md +0 -0
  142. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/security.md +0 -0
  143. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/sonarcloud-integration.md +0 -0
  144. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/testing.md +0 -0
  145. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/technical/write-operations-design.md +0 -0
  146. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/usage-write-operations.md +0 -0
  147. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/docs/zh/usage.md +0 -0
  148. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/examples/config.yaml +0 -0
  149. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/check_docs_consistency.py +0 -0
  150. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/check_zh_docs.py +0 -0
  151. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/fix_en_nav_links.py +0 -0
  152. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/fix_imports.sh +0 -0
  153. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/fix_remaining_issues.sh +0 -0
  154. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/fix_zh_nav_links.py +0 -0
  155. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/run_sonar_analysis.sh +0 -0
  156. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/scripts/sonar-ai-fix.fish +0 -0
  157. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/smithery.yaml +0 -0
  158. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/sonar-project.properties +0 -0
  159. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/__init__.py +0 -0
  160. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/audit.py +0 -0
  161. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/config.py +0 -0
  162. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/log.py +0 -0
  163. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/mysql/__init__.py +0 -0
  164. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/mysql/config.py +0 -0
  165. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/mysql/handler.py +0 -0
  166. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/mysql/server.py +0 -0
  167. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/postgres/__init__.py +0 -0
  168. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/postgres/config.py +0 -0
  169. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/postgres/handler.py +0 -0
  170. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/postgres/server.py +0 -0
  171. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/sqlite/__init__.py +0 -0
  172. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/sqlite/config.py +0 -0
  173. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/sqlite/handler.py +0 -0
  174. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/sqlite/server.py +0 -0
  175. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/src/mcp_dbutils/stats.py +0 -0
  176. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/conftest.py +0 -0
  177. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/__init__.py +0 -0
  178. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/conftest.py +0 -0
  179. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/fixtures.py +0 -0
  180. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_list_connections.py +0 -0
  181. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_logging.py +0 -0
  182. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_monitoring.py +0 -0
  183. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_monitoring_enhanced.py +0 -0
  184. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_mysql.py +0 -0
  185. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_mysql_config.py +0 -0
  186. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_mysql_config_helpers.py +0 -0
  187. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_mysql_handler_extended.py +0 -0
  188. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_postgres.py +0 -0
  189. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_postgres_config.py +0 -0
  190. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_prompts.py +0 -0
  191. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_sqlite.py +0 -0
  192. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_sqlite_config.py +0 -0
  193. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_sqlite_handler_extended.py +0 -0
  194. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_tools.py +0 -0
  195. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/integration/test_tools_advanced.py +0 -0
  196. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/test_write_operations.py +0 -0
  197. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_audit.py +0 -0
  198. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_base.py +0 -0
  199. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_base_handlers.py +0 -0
  200. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_base_helpers.py +0 -0
  201. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_base_server.py +0 -0
  202. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_base_write_operations.py +0 -0
  203. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_log.py +0 -0
  204. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_mysql_handler.py +0 -0
  205. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_mysql_server.py +0 -0
  206. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_postgres_handler.py +0 -0
  207. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_postgres_server.py +0 -0
  208. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_sql_parsing.py +0 -0
  209. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_sqlite_handler.py +0 -0
  210. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_sqlite_server.py +0 -0
  211. {mcp_dbutils-1.0.0 → mcp_dbutils-1.0.1}/tests/unit/test_stats.py +0 -0
@@ -1,3 +1,10 @@
1
+ ## [1.0.1](https://github.com/donghao1393/mcp-dbutils/compare/v1.0.0...v1.0.1) (2025-05-04)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * 提高代码覆盖率以解决SonarCloud检查失败问题 ([#90](https://github.com/donghao1393/mcp-dbutils/issues/90)) ([adbc9f2](https://github.com/donghao1393/mcp-dbutils/commit/adbc9f2e6fb54ed446d96775c65edf67611637ed))
7
+
1
8
  # [1.0.0](https://github.com/donghao1393/mcp-dbutils/compare/v0.23.1...v1.0.0) (2025-05-04)
2
9
 
3
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-dbutils
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: MCP Database Utilities Service
5
5
  Author: Dong Hao
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-dbutils"
3
- version = "1.0.0"
3
+ version = "1.0.1"
4
4
  description = "MCP Database Utilities Service"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -188,8 +188,8 @@ class ConnectionHandler(ABC):
188
188
  if "row" in result and "affected" in result:
189
189
  # 从结果字符串中提取受影响的行数
190
190
  import re
191
- # 使用更安全的正则表达式,避免回溯问题
192
- match = re.search(r"(\d+) rows?", result)
191
+ # 限制数字长度,避免DoS风险
192
+ match = re.search(r"(\d{1,10}) rows?", result)
193
193
  if match:
194
194
  affected_rows = int(match.group(1))
195
195
  except Exception:
@@ -601,7 +601,7 @@ class ConnectionServer:
601
601
  # Default fallback
602
602
  return "unknown_table"
603
603
 
604
- async def _check_write_permission(self, connection: str, table_name: str, operation_type: str) -> bool:
604
+ async def _check_write_permission(self, connection: str, table_name: str, operation_type: str) -> None:
605
605
  """检查写操作权限
606
606
 
607
607
  Args:
@@ -609,9 +609,6 @@ class ConnectionServer:
609
609
  table_name: 表名
610
610
  operation_type: 操作类型 (INSERT, UPDATE, DELETE)
611
611
 
612
- Returns:
613
- bool: 是否有权限执行写操作
614
-
615
612
  Raises:
616
613
  ConfigurationError: 如果连接不可写或没有表级权限
617
614
  """
@@ -626,7 +623,7 @@ class ConnectionServer:
626
623
  write_permissions = db_config.get("write_permissions", {})
627
624
  if not write_permissions:
628
625
  # 没有细粒度权限控制,默认允许所有写操作
629
- return True
626
+ return
630
627
 
631
628
  # 检查表级权限
632
629
  tables = write_permissions.get("tables", {})
@@ -634,7 +631,7 @@ class ConnectionServer:
634
631
  # 没有表级权限配置,检查默认策略
635
632
  default_policy = write_permissions.get("default_policy", "read_only")
636
633
  if default_policy == "allow_all":
637
- return True
634
+ return
638
635
  else:
639
636
  # 默认只读
640
637
  raise ConfigurationError(WRITE_OPERATION_NOT_ALLOWED_ERROR.format(
@@ -646,7 +643,7 @@ class ConnectionServer:
646
643
  table_config = tables[table_name]
647
644
  operations = table_config.get("operations", ["INSERT", "UPDATE", "DELETE"])
648
645
  if operation_type in operations:
649
- return True
646
+ return
650
647
  else:
651
648
  raise ConfigurationError(WRITE_OPERATION_NOT_ALLOWED_ERROR.format(
652
649
  operation=operation_type, table=table_name
@@ -655,15 +652,13 @@ class ConnectionServer:
655
652
  # 表未明确配置,检查默认策略
656
653
  default_policy = write_permissions.get("default_policy", "read_only")
657
654
  if default_policy == "allow_all":
658
- return True
655
+ return
659
656
  else:
660
657
  # 默认只读
661
658
  raise ConfigurationError(WRITE_OPERATION_NOT_ALLOWED_ERROR.format(
662
659
  operation=operation_type, table=table_name
663
660
  ))
664
661
 
665
- return False
666
-
667
662
  def _create_handler_for_type(
668
663
  self, db_type: str, connection: str
669
664
  ) -> ConnectionHandler:
@@ -0,0 +1,504 @@
1
+ """Extended tests for base module"""
2
+ import os
3
+ import tempfile
4
+ from unittest.mock import AsyncMock, MagicMock, patch
5
+
6
+ import mcp.types as types
7
+ import pytest
8
+ import yaml
9
+
10
+ from mcp_dbutils.base import (
11
+ CONNECTION_NOT_WRITABLE_ERROR,
12
+ UNSUPPORTED_WRITE_OPERATION_ERROR,
13
+ WRITE_CONFIRMATION_REQUIRED_ERROR,
14
+ WRITE_OPERATION_NOT_ALLOWED_ERROR,
15
+ ConfigurationError,
16
+ ConnectionHandler,
17
+ ConnectionServer,
18
+ )
19
+
20
+
21
+ class TestConnectionServerExtended:
22
+ """Extended tests for ConnectionServer class"""
23
+
24
+ @pytest.fixture
25
+ def config_file(self):
26
+ """Create a temporary config file for testing"""
27
+ with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".yaml") as f:
28
+ config = {
29
+ "connections": {
30
+ "test_conn": {
31
+ "type": "sqlite",
32
+ "database": ":memory:",
33
+ "writable": True
34
+ },
35
+ "test_conn_readonly": {
36
+ "type": "sqlite",
37
+ "database": ":memory:",
38
+ "writable": False
39
+ },
40
+ "test_conn_with_permissions": {
41
+ "type": "sqlite",
42
+ "database": ":memory:",
43
+ "writable": True,
44
+ "write_permissions": {
45
+ "tables": {
46
+ "users": {
47
+ "operations": ["INSERT", "UPDATE"]
48
+ },
49
+ "posts": {
50
+ "operations": ["INSERT", "UPDATE", "DELETE"]
51
+ }
52
+ },
53
+ "default_policy": "read_only"
54
+ }
55
+ },
56
+ "test_conn_allow_all": {
57
+ "type": "sqlite",
58
+ "database": ":memory:",
59
+ "writable": True,
60
+ "write_permissions": {
61
+ "default_policy": "allow_all"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ yaml.dump(config, f)
67
+ f.flush()
68
+ yield f.name
69
+ os.unlink(f.name)
70
+
71
+ @pytest.fixture
72
+ def server(self, config_file):
73
+ """Create a ConnectionServer instance for testing"""
74
+ server = ConnectionServer(config_file)
75
+ server.send_log = MagicMock()
76
+ return server
77
+
78
+ @pytest.mark.asyncio
79
+ async def test_check_write_permission_writable(self, server):
80
+ """Test _check_write_permission with writable connection"""
81
+ # Connection is writable and has no specific permissions
82
+ # Method should return None (no exception) if permission is granted
83
+ await server._check_write_permission("test_conn", "users", "INSERT")
84
+ await server._check_write_permission("test_conn", "users", "UPDATE")
85
+ await server._check_write_permission("test_conn", "users", "DELETE")
86
+
87
+ @pytest.mark.asyncio
88
+ async def test_check_write_permission_readonly(self, server):
89
+ """Test _check_write_permission with readonly connection"""
90
+ # Connection is not writable
91
+ with pytest.raises(ConfigurationError, match=CONNECTION_NOT_WRITABLE_ERROR):
92
+ await server._check_write_permission("test_conn_readonly", "users", "INSERT")
93
+
94
+ @pytest.mark.asyncio
95
+ async def test_check_write_permission_with_table_permissions(self, server):
96
+ """Test _check_write_permission with table-specific permissions"""
97
+ # Table 'users' allows INSERT and UPDATE but not DELETE
98
+ await server._check_write_permission("test_conn_with_permissions", "users", "INSERT")
99
+ await server._check_write_permission("test_conn_with_permissions", "users", "UPDATE")
100
+
101
+ with pytest.raises(ConfigurationError) as excinfo:
102
+ await server._check_write_permission("test_conn_with_permissions", "users", "DELETE")
103
+ assert "No permission to perform DELETE operation on table users" in str(excinfo.value)
104
+
105
+ # Table 'posts' allows all operations
106
+ await server._check_write_permission("test_conn_with_permissions", "posts", "INSERT")
107
+ await server._check_write_permission("test_conn_with_permissions", "posts", "UPDATE")
108
+ await server._check_write_permission("test_conn_with_permissions", "posts", "DELETE")
109
+
110
+ # Table 'unknown_table' is not explicitly configured, default policy is read_only
111
+ with pytest.raises(ConfigurationError) as excinfo:
112
+ await server._check_write_permission("test_conn_with_permissions", "unknown_table", "INSERT")
113
+ assert "No permission to perform INSERT operation on table unknown_table" in str(excinfo.value)
114
+
115
+ @pytest.mark.asyncio
116
+ async def test_check_write_permission_allow_all(self, server):
117
+ """Test _check_write_permission with allow_all default policy"""
118
+ # Default policy is allow_all
119
+ await server._check_write_permission("test_conn_allow_all", "any_table", "INSERT")
120
+ await server._check_write_permission("test_conn_allow_all", "any_table", "UPDATE")
121
+ await server._check_write_permission("test_conn_allow_all", "any_table", "DELETE")
122
+
123
+ @pytest.mark.asyncio
124
+ async def test_handle_execute_write_success(self, server):
125
+ """Test _handle_execute_write with successful operation"""
126
+ # Mock the handler
127
+ mock_handler = AsyncMock()
128
+ mock_handler.execute_write_query.return_value = "Write operation executed successfully. 1 row affected."
129
+
130
+ # Mock the get_handler method to return an async context manager
131
+ class AsyncContextManagerMock:
132
+ async def __aenter__(self):
133
+ return mock_handler
134
+
135
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
136
+ return None
137
+
138
+ server.get_handler = MagicMock(return_value=AsyncContextManagerMock())
139
+
140
+ # Mock the _check_write_permission method
141
+ server._check_write_permission = AsyncMock()
142
+
143
+ result = await server._handle_execute_write(
144
+ connection="test_conn",
145
+ sql="INSERT INTO users (name) VALUES ('test')",
146
+ confirmation="CONFIRM_WRITE"
147
+ )
148
+
149
+ # Verify handler was called
150
+ server.get_handler.assert_called_once_with("test_conn")
151
+ server._check_write_permission.assert_called_once_with("test_conn", "USERS", "INSERT")
152
+ mock_handler.execute_write_query.assert_called_once_with("INSERT INTO users (name) VALUES ('test')")
153
+
154
+ # Verify result
155
+ assert isinstance(result, list)
156
+ assert len(result) == 1
157
+ assert result[0].type == "text"
158
+ assert "Write operation executed successfully" in result[0].text
159
+
160
+ @pytest.mark.asyncio
161
+ async def test_handle_execute_write_no_confirmation(self, server):
162
+ """Test _handle_execute_write without confirmation"""
163
+ with pytest.raises(ConfigurationError, match=WRITE_CONFIRMATION_REQUIRED_ERROR):
164
+ await server._handle_execute_write(
165
+ connection="test_conn",
166
+ sql="INSERT INTO users (name) VALUES ('test')",
167
+ confirmation=""
168
+ )
169
+
170
+ @pytest.mark.asyncio
171
+ async def test_handle_execute_write_invalid_confirmation(self, server):
172
+ """Test _handle_execute_write with invalid confirmation"""
173
+ with pytest.raises(ConfigurationError, match=WRITE_CONFIRMATION_REQUIRED_ERROR):
174
+ await server._handle_execute_write(
175
+ connection="test_conn",
176
+ sql="INSERT INTO users (name) VALUES ('test')",
177
+ confirmation="WRONG_CONFIRMATION"
178
+ )
179
+
180
+ @pytest.mark.asyncio
181
+ async def test_handle_execute_write_unsupported_operation(self, server):
182
+ """Test _handle_execute_write with unsupported operation"""
183
+ with pytest.raises(ConfigurationError, match=UNSUPPORTED_WRITE_OPERATION_ERROR.format(operation="SELECT")):
184
+ await server._handle_execute_write(
185
+ connection="test_conn",
186
+ sql="SELECT * FROM users",
187
+ confirmation="CONFIRM_WRITE"
188
+ )
189
+
190
+ @pytest.mark.asyncio
191
+ async def test_handle_execute_write_no_permission(self, server):
192
+ """Test _handle_execute_write without permission"""
193
+ # Mock the _check_write_permission method to raise error
194
+ server._check_write_permission = AsyncMock(
195
+ side_effect=ConfigurationError(WRITE_OPERATION_NOT_ALLOWED_ERROR.format(
196
+ operation="DELETE", table="users"
197
+ ))
198
+ )
199
+
200
+ with pytest.raises(ConfigurationError, match="No permission to perform DELETE operation on table users"):
201
+ await server._handle_execute_write(
202
+ connection="test_conn_with_permissions",
203
+ sql="DELETE FROM users WHERE id = 1",
204
+ confirmation="CONFIRM_WRITE"
205
+ )
206
+
207
+ @pytest.mark.asyncio
208
+ async def test_handle_execute_write_execution_error(self, server):
209
+ """Test _handle_execute_write with execution error"""
210
+ # Mock the handler
211
+ mock_handler = AsyncMock()
212
+ mock_handler.execute_write_query.side_effect = Exception("Execution error")
213
+
214
+ # Mock the get_handler method to return an async context manager
215
+ class AsyncContextManagerMock:
216
+ async def __aenter__(self):
217
+ return mock_handler
218
+
219
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
220
+ return None
221
+
222
+ server.get_handler = MagicMock(return_value=AsyncContextManagerMock())
223
+
224
+ # Mock the _check_write_permission method
225
+ server._check_write_permission = AsyncMock()
226
+
227
+ with pytest.raises(Exception, match="Execution error"):
228
+ await server._handle_execute_write(
229
+ connection="test_conn",
230
+ sql="INSERT INTO users (name) VALUES ('test')",
231
+ confirmation="CONFIRM_WRITE"
232
+ )
233
+
234
+ # No need to verify result as we're expecting an exception
235
+
236
+ def test_get_sql_type(self, server):
237
+ """Test _get_sql_type method"""
238
+ # Test SELECT statement
239
+ assert server._get_sql_type("SELECT * FROM users") == "SELECT"
240
+ assert server._get_sql_type(" SELECT * FROM users") == "SELECT"
241
+ assert server._get_sql_type("select * from users") == "SELECT"
242
+
243
+ # Test INSERT statement
244
+ assert server._get_sql_type("INSERT INTO users VALUES (1, 'test')") == "INSERT"
245
+ assert server._get_sql_type("insert into users values (1, 'test')") == "INSERT"
246
+
247
+ # Test UPDATE statement
248
+ assert server._get_sql_type("UPDATE users SET name = 'test' WHERE id = 1") == "UPDATE"
249
+ assert server._get_sql_type("update users set name = 'test' where id = 1") == "UPDATE"
250
+
251
+ # Test DELETE statement
252
+ assert server._get_sql_type("DELETE FROM users WHERE id = 1") == "DELETE"
253
+ assert server._get_sql_type("delete from users where id = 1") == "DELETE"
254
+
255
+ # Test CREATE statement
256
+ assert server._get_sql_type("CREATE TABLE users (id INT, name TEXT)") == "CREATE"
257
+ assert server._get_sql_type("create table users (id int, name text)") == "CREATE"
258
+
259
+ # Test ALTER statement
260
+ assert server._get_sql_type("ALTER TABLE users ADD COLUMN email TEXT") == "ALTER"
261
+ assert server._get_sql_type("alter table users add column email text") == "ALTER"
262
+
263
+ # Test DROP statement
264
+ assert server._get_sql_type("DROP TABLE users") == "DROP"
265
+ assert server._get_sql_type("drop table users") == "DROP"
266
+
267
+ # Test TRUNCATE statement
268
+ assert server._get_sql_type("TRUNCATE TABLE users") == "TRUNCATE"
269
+ assert server._get_sql_type("truncate table users") == "TRUNCATE"
270
+
271
+ # Test transaction statements
272
+ assert server._get_sql_type("BEGIN TRANSACTION") == "TRANSACTION_START"
273
+ assert server._get_sql_type("START TRANSACTION") == "TRANSACTION_START"
274
+ assert server._get_sql_type("COMMIT") == "TRANSACTION_COMMIT"
275
+ assert server._get_sql_type("ROLLBACK") == "TRANSACTION_ROLLBACK"
276
+
277
+ # Test unknown statement
278
+ assert server._get_sql_type("UNKNOWN STATEMENT") == "UNKNOWN"
279
+ assert server._get_sql_type("") == "UNKNOWN"
280
+
281
+ def test_extract_table_name(self, server):
282
+ """Test _extract_table_name method"""
283
+ # Test INSERT statement
284
+ assert server._extract_table_name("INSERT INTO users VALUES (1, 'test')").lower() == "users"
285
+ assert server._extract_table_name("INSERT INTO public.users VALUES (1, 'test')").lower() == "public.users"
286
+
287
+ # Test UPDATE statement
288
+ assert server._extract_table_name("UPDATE users SET name = 'test' WHERE id = 1").lower() == "users"
289
+ assert server._extract_table_name("UPDATE public.users SET name = 'test' WHERE id = 1").lower() == "public.users"
290
+
291
+ # Test DELETE statement
292
+ assert server._extract_table_name("DELETE FROM users WHERE id = 1").lower() == "users"
293
+ assert server._extract_table_name("DELETE FROM public.users WHERE id = 1").lower() == "public.users"
294
+
295
+ # Test with quoted table name
296
+ assert server._extract_table_name('INSERT INTO "users" VALUES (1, \'test\')').lower() == "users"
297
+ assert server._extract_table_name("INSERT INTO `users` VALUES (1, 'test')").lower() == "users"
298
+ assert server._extract_table_name("INSERT INTO [users] VALUES (1, 'test')").lower() == "users"
299
+
300
+ # Test unknown statement
301
+ assert server._extract_table_name("UNKNOWN STATEMENT") == "unknown_table"
302
+
303
+
304
+ class TestConnectionHandlerExtended:
305
+ """Extended tests for ConnectionHandler class"""
306
+
307
+ class MockHandler(ConnectionHandler):
308
+ """Mock implementation of ConnectionHandler for testing"""
309
+
310
+ @property
311
+ def db_type(self) -> str:
312
+ return "mock"
313
+
314
+ async def get_tables(self) -> list[types.Resource]:
315
+ return []
316
+
317
+ async def get_schema(self, table_name: str) -> str:
318
+ return "{}"
319
+
320
+ async def _execute_query(self, sql: str) -> str:
321
+ return "Query executed"
322
+
323
+ async def _execute_write_query(self, sql: str) -> str:
324
+ return "Write operation executed successfully. 1 row affected."
325
+
326
+ async def get_table_description(self, table_name: str) -> str:
327
+ return f"Description of {table_name}"
328
+
329
+ async def get_table_ddl(self, table_name: str) -> str:
330
+ return f"DDL for {table_name}"
331
+
332
+ async def get_table_indexes(self, table_name: str) -> str:
333
+ return f"Indexes for {table_name}"
334
+
335
+ async def get_table_stats(self, table_name: str) -> str:
336
+ return f"Stats for {table_name}"
337
+
338
+ async def get_table_constraints(self, table_name: str) -> str:
339
+ return f"Constraints for {table_name}"
340
+
341
+ async def explain_query(self, sql: str) -> str:
342
+ return f"Explanation for {sql}"
343
+
344
+ async def test_connection(self) -> bool:
345
+ return True
346
+
347
+ async def cleanup(self):
348
+ pass
349
+
350
+ @pytest.fixture
351
+ def config_file(self):
352
+ """Create a temporary config file for testing"""
353
+ with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".yaml") as f:
354
+ config = {
355
+ "connections": {
356
+ "test_conn": {
357
+ "type": "mock",
358
+ "database": ":memory:"
359
+ }
360
+ }
361
+ }
362
+ yaml.dump(config, f)
363
+ f.flush()
364
+ yield f.name
365
+ os.unlink(f.name)
366
+
367
+ @pytest.fixture
368
+ def handler(self, config_file):
369
+ """Create a MockHandler instance for testing"""
370
+ handler = self.MockHandler(config_file, "test_conn")
371
+ handler.send_log = MagicMock()
372
+ handler._session = MagicMock()
373
+ return handler
374
+
375
+ @pytest.mark.asyncio
376
+ async def test_execute_query(self, handler):
377
+ """Test execute_query method"""
378
+ # Mock the _execute_query method
379
+ handler._execute_query = AsyncMock(return_value="Query result")
380
+
381
+ result = await handler.execute_query("SELECT * FROM users")
382
+
383
+ # Verify _execute_query was called
384
+ handler._execute_query.assert_called_once_with("SELECT * FROM users")
385
+
386
+ # Verify result
387
+ assert result == "Query result"
388
+
389
+ # Verify stats were updated
390
+ assert handler.stats.query_count == 1
391
+ assert len(handler.stats.query_durations) == 1
392
+
393
+ @pytest.mark.asyncio
394
+ async def test_execute_query_error(self, handler):
395
+ """Test execute_query method with error"""
396
+ # Mock the _execute_query method to raise error
397
+ handler._execute_query = AsyncMock(side_effect=Exception("Query error"))
398
+
399
+ with pytest.raises(Exception, match="Query error"):
400
+ await handler.execute_query("SELECT * FROM users")
401
+
402
+ # Verify stats were updated
403
+ assert handler.stats.query_count == 1
404
+ assert handler.stats.error_count == 1
405
+ assert "Exception" in handler.stats.error_types
406
+
407
+ @pytest.mark.asyncio
408
+ async def test_execute_write_query(self, handler):
409
+ """Test execute_write_query method"""
410
+ # Mock the _execute_write_query method
411
+ handler._execute_write_query = AsyncMock(return_value="Write operation executed successfully. 1 row affected.")
412
+
413
+ result = await handler.execute_write_query("INSERT INTO users (name) VALUES ('test')")
414
+
415
+ # Verify _execute_write_query was called
416
+ handler._execute_write_query.assert_called_once_with("INSERT INTO users (name) VALUES ('test')")
417
+
418
+ # Verify result
419
+ assert result == "Write operation executed successfully. 1 row affected."
420
+
421
+ # Verify stats were updated
422
+ assert handler.stats.query_count == 1
423
+ assert len(handler.stats.query_durations) == 1
424
+
425
+ @pytest.mark.asyncio
426
+ async def test_execute_write_query_error(self, handler):
427
+ """Test execute_write_query method with error"""
428
+ # Mock the _execute_write_query method to raise error
429
+ handler._execute_write_query = AsyncMock(side_effect=Exception("Write error"))
430
+
431
+ with pytest.raises(Exception, match="Write error"):
432
+ await handler.execute_write_query("INSERT INTO users (name) VALUES ('test')")
433
+
434
+ # Verify stats were updated
435
+ assert handler.stats.query_count == 1
436
+ assert handler.stats.error_count == 1
437
+ assert "Exception" in handler.stats.error_types
438
+
439
+ @pytest.mark.asyncio
440
+ async def test_execute_write_query_unsupported_operation(self, handler):
441
+ """Test execute_write_query method with unsupported operation"""
442
+ with pytest.raises(ValueError, match=UNSUPPORTED_WRITE_OPERATION_ERROR.format(operation="SELECT")):
443
+ await handler.execute_write_query("SELECT * FROM users")
444
+
445
+ @pytest.mark.asyncio
446
+ async def test_execute_tool_query(self, handler):
447
+ """Test execute_tool_query method"""
448
+ # Mock the tool methods
449
+ handler.get_table_description = AsyncMock(return_value="Table description")
450
+ handler.get_table_ddl = AsyncMock(return_value="Table DDL")
451
+ handler.get_table_indexes = AsyncMock(return_value="Table indexes")
452
+ handler.get_table_stats = AsyncMock(return_value="Table stats")
453
+ handler.get_table_constraints = AsyncMock(return_value="Table constraints")
454
+ handler.explain_query = AsyncMock(return_value="Query explanation")
455
+
456
+ # Test each tool
457
+ result1 = await handler.execute_tool_query("dbutils-describe-table", table_name="users")
458
+ assert result1 == "[mock]\nTable description"
459
+ handler.get_table_description.assert_called_once_with("users")
460
+
461
+ result2 = await handler.execute_tool_query("dbutils-get-ddl", table_name="users")
462
+ assert result2 == "[mock]\nTable DDL"
463
+ handler.get_table_ddl.assert_called_once_with("users")
464
+
465
+ result3 = await handler.execute_tool_query("dbutils-list-indexes", table_name="users")
466
+ assert result3 == "[mock]\nTable indexes"
467
+ handler.get_table_indexes.assert_called_once_with("users")
468
+
469
+ result4 = await handler.execute_tool_query("dbutils-get-stats", table_name="users")
470
+ assert result4 == "[mock]\nTable stats"
471
+ handler.get_table_stats.assert_called_once_with("users")
472
+
473
+ result5 = await handler.execute_tool_query("dbutils-list-constraints", table_name="users")
474
+ assert result5 == "[mock]\nTable constraints"
475
+ handler.get_table_constraints.assert_called_once_with("users")
476
+
477
+ result6 = await handler.execute_tool_query("dbutils-explain-query", sql="SELECT * FROM users")
478
+ assert result6 == "[mock]\nQuery explanation"
479
+ handler.explain_query.assert_called_once_with("SELECT * FROM users")
480
+
481
+ @pytest.mark.asyncio
482
+ async def test_execute_tool_query_unknown_tool(self, handler):
483
+ """Test execute_tool_query method with unknown tool"""
484
+ with pytest.raises(ValueError, match="Unknown tool"):
485
+ await handler.execute_tool_query("unknown-tool")
486
+
487
+ @pytest.mark.asyncio
488
+ async def test_execute_tool_query_missing_sql(self, handler):
489
+ """Test execute_tool_query method with missing SQL"""
490
+ with pytest.raises(ValueError):
491
+ await handler.execute_tool_query("dbutils-explain-query")
492
+
493
+ @pytest.mark.asyncio
494
+ async def test_execute_tool_query_error(self, handler):
495
+ """Test execute_tool_query method with error"""
496
+ # Mock the tool method to raise error
497
+ handler.get_table_description = AsyncMock(side_effect=Exception("Tool error"))
498
+
499
+ with pytest.raises(Exception, match="Tool error"):
500
+ await handler.execute_tool_query("dbutils-describe-table", table_name="users")
501
+
502
+ # Verify stats were updated
503
+ assert handler.stats.error_count == 1
504
+ assert "Exception" in handler.stats.error_types