package_spec.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Package, type: :model do
  4. # cleanup package files
  5. after :all do # rubocop:disable RSpec/BeforeAfterAll
  6. %w[example.rb app/controllers/test_controller.rb].each do |file|
  7. next if !Rails.root.join(file).exist?
  8. Rails.root.join(file).delete
  9. end
  10. end
  11. def get_package_structure(name, files, version = '1.0.1')
  12. <<-JSON
  13. {
  14. "name": "#{name}",
  15. "version": "#{version}",
  16. "vendor": "Zammad Foundation",
  17. "license": "ABC",
  18. "url": "https://zammad.org/",
  19. "description": [
  20. {
  21. "language": "en",
  22. "text": "some description"
  23. }
  24. ],
  25. "files": #{files}
  26. }
  27. JSON
  28. end
  29. let(:package_zpm_files_json) do
  30. <<-JSON
  31. [
  32. {
  33. "permission": "644",
  34. "location": "example.rb",
  35. "content": "YWJjw6TDtsO8w58="
  36. },
  37. {
  38. "permission": "644",
  39. "location": "app/controllers/test_controller.rb",
  40. "content": "YWJjw6TDtsO8w58="
  41. }
  42. ]
  43. JSON
  44. end
  45. let(:package_name) { 'UnitTestSample' }
  46. let(:package_zpm_json) { get_package_structure(package_name, package_zpm_files_json) }
  47. let(:old_package_zpm_json) { get_package_structure(package_name, package_zpm_files_json, '1.0.0') }
  48. let(:new_package_zpm_json) { get_package_structure(package_name, package_zpm_files_json, '1.0.2') }
  49. context 'when performing different package actions' do
  50. context 'when installing a package' do
  51. it 'does install package' do
  52. expect { described_class.install(string: package_zpm_json) }
  53. .to change(described_class, :count)
  54. .and change(Store, :count)
  55. end
  56. end
  57. context 'when reinstalling a package' do
  58. before do
  59. described_class.install(string: package_zpm_json)
  60. end
  61. it 'does not reinstall package' do
  62. expect { described_class.reinstall(package_name) }
  63. .to not_change(described_class, :count)
  64. .and not_change(Store, :count)
  65. end
  66. end
  67. context 'when installing a package again' do
  68. before do
  69. described_class.install(string: package_zpm_json)
  70. end
  71. it 'does not install package' do
  72. expect { described_class.install(string: package_zpm_json) }
  73. .to raise_error(RuntimeError)
  74. .and not_change(described_class, :count)
  75. .and not_change(Store, :count)
  76. end
  77. end
  78. context 'when installing a package with a lower version' do
  79. before do
  80. described_class.install(string: package_zpm_json)
  81. end
  82. it 'does not install package' do
  83. expect { described_class.install(string: old_package_zpm_json) }
  84. .to raise_error(RuntimeError)
  85. .and not_change(described_class, :count)
  86. .and not_change(Store, :count)
  87. end
  88. end
  89. context 'when upgrading a package' do
  90. before do
  91. described_class.install(string: package_zpm_json)
  92. end
  93. it 'does install package' do
  94. expect { described_class.install(string: new_package_zpm_json) }
  95. .to not_raise_error
  96. .and not_change(described_class, :count)
  97. .and change(Store, :count)
  98. end
  99. end
  100. context 'when installing + uninstalling a package' do
  101. before do
  102. described_class.install(string: package_zpm_json)
  103. end
  104. it 'does install + uninstall the package' do
  105. expect { described_class.uninstall(string: package_zpm_json) }
  106. .to not_raise_error
  107. .and change(described_class, :count)
  108. .and not_change(Store, :count)
  109. end
  110. end
  111. context 'when auto installing' do
  112. before do
  113. FileUtils.mkdir_p(Rails.root.join('auto_install'))
  114. location = Rails.root.join('auto_install/unittest.zpm')
  115. file = File.new(location, 'wb')
  116. file.write(package_zpm_json)
  117. file.close
  118. end
  119. after do
  120. Rails.root.join('auto_install/unittest.zpm').delete
  121. end
  122. it 'does install package' do
  123. expect { described_class.auto_install }
  124. .to change(described_class, :count)
  125. .and change(Store, :count)
  126. end
  127. end
  128. context 'when verify package install' do
  129. context 'when verify is ok' do
  130. it 'returns no verify issues' do
  131. package = described_class.install(string: package_zpm_json)
  132. expect(package.verify).to be_nil
  133. end
  134. end
  135. context 'when verify is not ok' do
  136. it 'returns verify issues' do
  137. package = described_class.install(string: package_zpm_json)
  138. Rails.root.join('example.rb').delete
  139. expect(package.verify).not_to be_nil
  140. end
  141. end
  142. end
  143. end
  144. context 'with different file locations' do
  145. context 'with correct file locations' do
  146. it 'installation should work' do
  147. expect(described_class.install(string: package_zpm_json)).to be_truthy
  148. end
  149. end
  150. shared_examples 'check not allowed file location' do |file_location|
  151. let(:package_zpm_files_json) do
  152. <<-JSON
  153. [
  154. {
  155. "permission": "644",
  156. "location": "example.rb",
  157. "content": "YWJjw6TDtsO8w58="
  158. },
  159. {
  160. "permission": "644",
  161. "location": "#{file_location}",
  162. "content": "YWJjw6TDtsO8w58="
  163. }
  164. ]
  165. JSON
  166. end
  167. it 'installation should raise a error and package/store should not be present, because of not allowed file location' do
  168. expect { described_class.install(string: package_zpm_json) }
  169. .to raise_error(RuntimeError)
  170. .and not_change(described_class, :count)
  171. .and not_change(Store, :count)
  172. end
  173. end
  174. context "with not allowed file location part: '..'" do
  175. include_examples 'check not allowed file location', '../../../../../tmp/test_controller.rb'
  176. end
  177. context "with not allowed file location part: '%2e%2e'" do
  178. include_examples 'check not allowed file location', '%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/tmp/test_controller.rb'
  179. end
  180. end
  181. describe 'Multiple uninstalling packages will course missing backup files #4577' do
  182. let(:core_file) { Rails.root.join('app/models/ticket.rb') }
  183. let(:orig_content) { File.read(core_file) }
  184. let(:package_zpm_files_json) do
  185. <<-JSON
  186. [
  187. {
  188. "permission": "644",
  189. "location": "app/models/ticket.rb",
  190. "content": "YWJjw6TDtsO8w58="
  191. }
  192. ]
  193. JSON
  194. end
  195. before do
  196. orig_content
  197. end
  198. def expect_install_package
  199. described_class.install(string: package_zpm_json)
  200. expect(File.exist?(core_file)).to be(true)
  201. expect(File.read(core_file)).to eq('abcäöüß')
  202. expect(File.exist?("#{core_file}.save")).to be(true)
  203. expect(File.read("#{core_file}.save")).to eq(orig_content)
  204. expect(described_class.last.state).to eq('installed')
  205. end
  206. def expect_uninstall_package_files
  207. described_class.uninstall(string: package_zpm_json, migration_not_down: true, reinstall: true)
  208. expect(File.exist?(core_file)).to be(true)
  209. expect(File.read(core_file)).to eq(orig_content)
  210. expect(File.exist?("#{core_file}.save")).to be(false)
  211. expect(described_class.last.state).to eq('uninstalled')
  212. end
  213. def expect_reinstall_package
  214. described_class.reinstall(package_name)
  215. expect(File.exist?(core_file)).to be(true)
  216. expect(File.read(core_file)).to eq('abcäöüß')
  217. expect(File.exist?("#{core_file}.save")).to be(true)
  218. expect(File.read("#{core_file}.save")).to eq(orig_content)
  219. expect(described_class.last.state).to eq('installed')
  220. end
  221. def expect_uninstall_package
  222. described_class.uninstall(string: package_zpm_json)
  223. expect(File.exist?(core_file)).to be(true)
  224. expect(File.read(core_file)).to eq(orig_content)
  225. expect(File.exist?("#{core_file}.save")).to be(false)
  226. expect(File.read(core_file)).to eq(orig_content)
  227. end
  228. it 'does support the classic package migration path but with multiple uninstalls' do
  229. expect_install_package
  230. expect_uninstall_package_files
  231. expect_uninstall_package_files
  232. expect_reinstall_package
  233. expect_uninstall_package
  234. end
  235. it 'does have a proper package state after multiple reinstalls' do
  236. expect_install_package
  237. expect_reinstall_package
  238. expect_reinstall_package
  239. expect_uninstall_package
  240. end
  241. end
  242. describe 'Vendor url in installed package is the zammad instance url #4753' do
  243. it 'does have a url for the package' do
  244. described_class.install(string: package_zpm_json)
  245. expect(described_class.last.url).to eq('https://zammad.org/')
  246. end
  247. end
  248. describe 'Package: Missing backup files for files with the same content #5012' do
  249. let(:package_v1_files) do
  250. <<-JSON
  251. [
  252. {
  253. "permission": "644",
  254. "location": "lib/version.rb",
  255. "content": "#{Base64.strict_encode64(File.read('lib/version.rb')).strip}"
  256. }
  257. ]
  258. JSON
  259. end
  260. let(:package_v2_files) do
  261. <<-JSON
  262. []
  263. JSON
  264. end
  265. let(:package_v1) { get_package_structure(package_name, package_v1_files, '1.0.0') }
  266. let(:package_v2) { get_package_structure(package_name, package_v2_files, '1.0.1') }
  267. it 'does not lose core files when patched by package and released in future updates of zammad' do
  268. described_class.install(string: package_v1)
  269. described_class.install(string: package_v2)
  270. expect(File.exist?('lib/version.rb')).to be(true)
  271. end
  272. end
  273. describe 'Package: File conflict with packages which include the same file location #5014' do
  274. let(:package_1) { get_package_structure('PackageA', package_zpm_files_json, '1.0.0') }
  275. let(:package_2) { get_package_structure('PackageB', package_zpm_files_json, '1.0.0') }
  276. it 'does not allow to patch the same file twice via package' do
  277. described_class.install(string: package_1)
  278. expect { described_class.install(string: package_2) }.to raise_error("Can't create file, because file 'example.rb' is already provided by package 'PackageA'!")
  279. end
  280. end
  281. end