test_sqlite.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import logging
  2. from flexmock import flexmock
  3. from borgmatic.hooks import sqlite as module
  4. def test_use_streaming_true_for_any_databases():
  5. assert module.use_streaming(
  6. databases=[flexmock(), flexmock()], config=flexmock(), log_prefix=flexmock()
  7. )
  8. def test_use_streaming_false_for_no_databases():
  9. assert not module.use_streaming(databases=[], config=flexmock(), log_prefix=flexmock())
  10. def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
  11. databases = [{'path': '/path/to/database', 'name': 'database'}]
  12. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  13. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  14. '/run/borgmatic/database'
  15. )
  16. flexmock(module.os.path).should_receive('exists').and_return(True)
  17. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  18. flexmock(module).should_receive('execute_command').never()
  19. assert (
  20. module.dump_data_sources(
  21. databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
  22. )
  23. == []
  24. )
  25. def test_dump_data_sources_dumps_each_database():
  26. databases = [
  27. {'path': '/path/to/database1', 'name': 'database1'},
  28. {'path': '/path/to/database2', 'name': 'database2'},
  29. ]
  30. processes = [flexmock(), flexmock()]
  31. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  32. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  33. '/run/borgmatic/database'
  34. )
  35. flexmock(module.os.path).should_receive('exists').and_return(False)
  36. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  37. flexmock(module).should_receive('execute_command').and_return(processes[0]).and_return(
  38. processes[1]
  39. )
  40. assert (
  41. module.dump_data_sources(
  42. databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
  43. )
  44. == processes
  45. )
  46. def test_dump_data_sources_with_path_injection_attack_gets_escaped():
  47. databases = [
  48. {'path': '/path/to/database1; naughty-command', 'name': 'database1'},
  49. ]
  50. processes = [flexmock()]
  51. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  52. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  53. '/run/borgmatic/database'
  54. )
  55. flexmock(module.os.path).should_receive('exists').and_return(False)
  56. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  57. flexmock(module).should_receive('execute_command').with_args(
  58. (
  59. 'sqlite3',
  60. "'/path/to/database1; naughty-command'",
  61. '.dump',
  62. '>',
  63. '/run/borgmatic/database',
  64. ),
  65. shell=True,
  66. run_to_completion=False,
  67. ).and_return(processes[0])
  68. assert (
  69. module.dump_data_sources(
  70. databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
  71. )
  72. == processes
  73. )
  74. def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
  75. databases = [
  76. {'path': '/path/to/database1', 'name': 'database1'},
  77. ]
  78. processes = [flexmock()]
  79. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  80. flexmock(module.logger).should_receive('warning').once()
  81. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  82. '/run/borgmatic'
  83. )
  84. flexmock(module.os.path).should_receive('exists').and_return(False)
  85. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  86. flexmock(module).should_receive('execute_command').and_return(processes[0])
  87. assert (
  88. module.dump_data_sources(
  89. databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
  90. )
  91. == processes
  92. )
  93. def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
  94. databases = [
  95. {'path': '/path/to/database1', 'name': 'all'},
  96. ]
  97. processes = [flexmock()]
  98. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  99. flexmock(module.logger).should_receive(
  100. 'warning'
  101. ).twice() # once for the name=all, once for the non-existent path
  102. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  103. '/run/borgmatic/database'
  104. )
  105. flexmock(module.os.path).should_receive('exists').and_return(False)
  106. flexmock(module.dump).should_receive('create_named_pipe_for_dump')
  107. flexmock(module).should_receive('execute_command').and_return(processes[0])
  108. assert (
  109. module.dump_data_sources(
  110. databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
  111. )
  112. == processes
  113. )
  114. def test_dump_data_sources_does_not_dump_if_dry_run():
  115. databases = [{'path': '/path/to/database', 'name': 'database'}]
  116. flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
  117. flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
  118. '/run/borgmatic'
  119. )
  120. flexmock(module.os.path).should_receive('exists').and_return(False)
  121. flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
  122. flexmock(module).should_receive('execute_command').never()
  123. assert (
  124. module.dump_data_sources(
  125. databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
  126. )
  127. == []
  128. )
  129. def test_restore_data_source_dump_restores_database():
  130. hook_config = [{'path': '/path/to/database', 'name': 'database'}, {'name': 'other'}]
  131. extract_process = flexmock(stdout=flexmock())
  132. flexmock(module).should_receive('execute_command_with_processes').with_args(
  133. (
  134. 'sqlite3',
  135. '/path/to/database',
  136. ),
  137. processes=[extract_process],
  138. output_log_level=logging.DEBUG,
  139. input_file=extract_process.stdout,
  140. ).once()
  141. flexmock(module.os).should_receive('remove').once()
  142. module.restore_data_source_dump(
  143. hook_config,
  144. {},
  145. 'test.yaml',
  146. data_source=hook_config[0],
  147. dry_run=False,
  148. extract_process=extract_process,
  149. connection_params={'restore_path': None},
  150. )
  151. def test_restore_data_source_dump_with_connection_params_uses_connection_params_for_restore():
  152. hook_config = [
  153. {'path': '/path/to/database', 'name': 'database', 'restore_path': 'config/path/to/database'}
  154. ]
  155. extract_process = flexmock(stdout=flexmock())
  156. flexmock(module).should_receive('execute_command_with_processes').with_args(
  157. (
  158. 'sqlite3',
  159. 'cli/path/to/database',
  160. ),
  161. processes=[extract_process],
  162. output_log_level=logging.DEBUG,
  163. input_file=extract_process.stdout,
  164. ).once()
  165. flexmock(module.os).should_receive('remove').once()
  166. module.restore_data_source_dump(
  167. hook_config,
  168. {},
  169. 'test.yaml',
  170. data_source={'name': 'database'},
  171. dry_run=False,
  172. extract_process=extract_process,
  173. connection_params={'restore_path': 'cli/path/to/database'},
  174. )
  175. def test_restore_data_source_dump_without_connection_params_uses_restore_params_in_config_for_restore():
  176. hook_config = [
  177. {'path': '/path/to/database', 'name': 'database', 'restore_path': 'config/path/to/database'}
  178. ]
  179. extract_process = flexmock(stdout=flexmock())
  180. flexmock(module).should_receive('execute_command_with_processes').with_args(
  181. (
  182. 'sqlite3',
  183. 'config/path/to/database',
  184. ),
  185. processes=[extract_process],
  186. output_log_level=logging.DEBUG,
  187. input_file=extract_process.stdout,
  188. ).once()
  189. flexmock(module.os).should_receive('remove').once()
  190. module.restore_data_source_dump(
  191. hook_config,
  192. {},
  193. 'test.yaml',
  194. data_source=hook_config[0],
  195. dry_run=False,
  196. extract_process=extract_process,
  197. connection_params={'restore_path': None},
  198. )
  199. def test_restore_data_source_dump_does_not_restore_database_if_dry_run():
  200. hook_config = [{'path': '/path/to/database', 'name': 'database'}]
  201. extract_process = flexmock(stdout=flexmock())
  202. flexmock(module).should_receive('execute_command_with_processes').never()
  203. flexmock(module.os).should_receive('remove').never()
  204. module.restore_data_source_dump(
  205. hook_config,
  206. {},
  207. 'test.yaml',
  208. data_source={'name': 'database'},
  209. dry_run=True,
  210. extract_process=extract_process,
  211. connection_params={'restore_path': None},
  212. )