test_sqlite.py 7.7 KB

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