scriptorium 0.6.1 → 0.7.2

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 (358) hide show
  1. checksums.yaml +4 -4
  2. data/assets/icons/social/reddit.png +0 -0
  3. data/assets/icons/social/x-logo.png +0 -0
  4. data/assets/imagenotfound.jpg +0 -0
  5. data/bin/sblog +84 -5
  6. data/bin/scriptorium +1 -0
  7. data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +0 -1
  8. data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +0 -29
  9. data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +0 -19
  10. data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +1 -1
  11. data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +1 -1
  12. data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +1 -1
  13. data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +0 -10
  14. data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +1 -4
  15. data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
  16. data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
  17. data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
  18. data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
  19. data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
  20. data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
  21. data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
  22. data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
  23. data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
  24. data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
  25. data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
  26. data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
  27. data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
  28. data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
  29. data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
  30. data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
  31. data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
  32. data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
  33. data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
  34. data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
  35. data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
  36. data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
  37. data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
  38. data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
  39. data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
  40. data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
  41. data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
  42. data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
  43. data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
  44. data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
  45. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
  46. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
  47. data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
  48. data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
  49. data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
  50. data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
  51. data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
  52. data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
  53. data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
  54. data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
  55. data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
  56. data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
  57. data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
  58. data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
  59. data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
  60. data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
  61. data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
  62. data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
  63. data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
  64. data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
  65. data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
  66. data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
  67. data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
  68. data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
  69. data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
  70. data/doc/imported/0018-ruby-puns/post.html +46 -0
  71. data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
  72. data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
  73. data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
  74. data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
  75. data/doc/imported/0020-fffff/metadata.txt +7 -0
  76. data/doc/imported/0020-fffff/post.html +24 -0
  77. data/doc/imported/0020-fffff/source.lt3 +12 -0
  78. data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
  79. data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
  80. data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
  81. data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
  82. data/doc/imported/0023-doctor-sleep/post.html +63 -0
  83. data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
  84. data/doc/imported/0024-just-a-test/metadata.txt +7 -0
  85. data/doc/imported/0024-just-a-test/post.html +24 -0
  86. data/doc/imported/0024-just-a-test/source.lt3 +12 -0
  87. data/doc/imported/import_summary.txt +98 -0
  88. data/doc/livetext-informal-spec.txt +65 -0
  89. data/doc/myuserdoc/ch-0.lt3 +31 -0
  90. data/doc/myuserdoc/ch-1.lt3 +37 -0
  91. data/doc/myuserdoc/ch-10.lt3 +22 -0
  92. data/doc/myuserdoc/ch-2.lt3 +37 -0
  93. data/doc/myuserdoc/ch-3.lt3 +19 -0
  94. data/doc/myuserdoc/ch-4.lt3 +43 -0
  95. data/doc/myuserdoc/ch-5.lt3 +22 -0
  96. data/doc/myuserdoc/ch-6.lt3 +19 -0
  97. data/doc/myuserdoc/ch-7.lt3 +16 -0
  98. data/doc/myuserdoc/ch-8.lt3 +13 -0
  99. data/doc/myuserdoc/ch-9.lt3 +19 -0
  100. data/doc/myuserdoc/tweak.rb +18 -0
  101. data/doc/{userdoc-toc.txt → myuserdoc/userdoc-toc.txt} +27 -27
  102. data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
  103. data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
  104. data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
  105. data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
  106. data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
  107. data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
  108. data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
  109. data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
  110. data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
  111. data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
  112. data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
  113. data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
  114. data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
  115. data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
  116. data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
  117. data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
  118. data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
  119. data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
  120. data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
  121. data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
  122. data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
  123. data/doc/old-posts/0024-just-a-test.lt3 +12 -0
  124. data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
  125. data/doc/old-repo +1 -0
  126. data/doc/reddit_integration.md +2 -2
  127. data/doc/user.lt3 +0 -3
  128. data/lib/scriptorium/api.rb +1811 -78
  129. data/lib/scriptorium/banner_svg.rb +55 -68
  130. data/lib/scriptorium/contract.rb +3 -2
  131. data/lib/scriptorium/exceptions.rb +133 -102
  132. data/lib/scriptorium/helpers.rb +282 -82
  133. data/lib/scriptorium/post.rb +81 -17
  134. data/lib/scriptorium/reddit.rb +1 -1
  135. data/lib/scriptorium/repo.rb +478 -164
  136. data/lib/scriptorium/standard_files.rb +30 -396
  137. data/lib/scriptorium/support/common_js/clipboard.js +35 -0
  138. data/lib/scriptorium/support/common_js/content-loader.js +187 -0
  139. data/lib/scriptorium/support/common_js/navigation.js +52 -0
  140. data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
  141. data/lib/scriptorium/support/config/reddit_template.txt +17 -0
  142. data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/social.txt +1 -0
  143. data/lib/scriptorium/support/highlight/css.txt +2 -0
  144. data/lib/scriptorium/support/highlight/custom.css +119 -0
  145. data/lib/scriptorium/support/highlight/js.txt +1 -0
  146. data/lib/scriptorium/support/post_index/config.txt +15 -0
  147. data/lib/scriptorium/support/post_index/style.css +55 -0
  148. data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
  149. data/{test/scriptorium-TEST-1754622690-146/themes/standard/initial/post.lt3 → lib/scriptorium/support/templates/initial_post.lt3} +5 -5
  150. data/lib/scriptorium/support/templates/post.lt3 +104 -0
  151. data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/header.txt → lib/scriptorium/support/theme/header.lt3} +1 -1
  152. data/lib/scriptorium/theme.rb +83 -70
  153. data/lib/scriptorium/version.rb +2 -2
  154. data/lib/scriptorium/view.rb +194 -149
  155. data/lib/scriptorium.rb +24 -1
  156. data/lib/skeleton.rb +4 -1
  157. data/scriptorium.gemspec +2 -1
  158. data/test/WEB_INTEGRATION_README.md +196 -0
  159. data/test/all +40 -0
  160. data/test/banner_svg/unit.rb +267 -35
  161. data/test/config/deployment.txt +5 -0
  162. data/test/integration/integration_test.rb +7 -7
  163. data/test/integration/preview_flow_test.rb +94 -0
  164. data/test/livetext_plugin_test.rb +453 -182
  165. data/test/manual/banner-tests/test01.html +82 -18
  166. data/test/manual/banner-tests/test02.html +82 -18
  167. data/test/manual/banner-tests/test03.html +82 -18
  168. data/test/manual/banner-tests/test04.html +89 -25
  169. data/test/manual/banner-tests/test05.html +89 -25
  170. data/test/manual/banner-tests/test06.html +89 -25
  171. data/test/manual/banner-tests/test07.html +89 -25
  172. data/test/manual/banner-tests/test08.html +82 -18
  173. data/test/manual/banner-tests/test09.html +82 -18
  174. data/test/manual/banner-tests/test10.html +82 -18
  175. data/test/manual/banner-tests/test11.html +82 -18
  176. data/test/manual/banner-tests/test12.html +82 -18
  177. data/test/manual/banner-tests/test13.html +82 -18
  178. data/test/manual/banner-tests/test14.html +82 -18
  179. data/test/manual/banner-tests/test15.html +82 -18
  180. data/test/manual/banner-tests/test16.html +82 -18
  181. data/test/manual/banner-tests/test17.html +82 -18
  182. data/test/manual/banner-tests/test18.html +90 -26
  183. data/test/manual/banner-tests/test19.html +90 -26
  184. data/test/manual/banner-tests/test20.html +90 -26
  185. data/test/manual/banner-tests/test21.html +90 -26
  186. data/test/manual/banner-tests/test22.html +90 -26
  187. data/test/manual/banner-tests/test23.html +90 -26
  188. data/test/manual/banner-tests/test24.html +90 -26
  189. data/test/manual/banner-tests/test25.html +89 -25
  190. data/test/manual/banner_environment.rb +15 -2
  191. data/test/manual/codemirror_demo.html +773 -0
  192. data/test/manual/create_posts_for_web.rb +114 -0
  193. data/test/manual/preview_manual_test.rb +129 -0
  194. data/test/manual/test_banner_features.rb +14 -14
  195. data/test/manual/test_banner_integration.rb +115 -0
  196. data/test/manual/test_banner_radial.rb +87 -0
  197. data/test/manual/test_syntax_highlighting.rb +60 -40
  198. data/test/support/preview_utils.rb +88 -0
  199. data/test/test_gem_assets.rb +48 -0
  200. data/test/test_helpers.rb +10 -0
  201. data/test/tui_editor_integration_test.rb +15 -15
  202. data/test/tui_integration_test.rb +687 -441
  203. data/test/unit/api.rb +757 -37
  204. data/test/unit/asset_management.rb +195 -221
  205. data/test/unit/backup_test.rb +451 -0
  206. data/test/unit/contract_test.rb +1 -23
  207. data/test/unit/core.rb +415 -61
  208. data/test/unit/deploy_config_test.rb +248 -0
  209. data/test/unit/deploy_test.rb +312 -21
  210. data/test/unit/edit_post_test.rb +168 -0
  211. data/test/unit/gem_asset_management.rb +36 -42
  212. data/test/unit/livetext_basic.rb +23 -35
  213. data/test/unit/livetext_compatibility.rb +7 -14
  214. data/test/unit/parse_cmd_test.rb +260 -0
  215. data/test/unit/{symlink_test.rb → permalink_copy_test.rb} +47 -49
  216. data/test/unit/post.rb +91 -26
  217. data/test/unit/post_index_config_test.rb +258 -0
  218. data/test/unit/post_state_helpers_test.rb +137 -0
  219. data/test/unit/read_commented_file_test.rb +8 -6
  220. data/test/unit/repo.rb +75 -54
  221. data/test/unit/social_test.rb +41 -44
  222. data/test/unit/syntax_highlighting.rb +70 -0
  223. data/test/unit/theme_management_test.rb +91 -0
  224. data/test/unit/view.rb +79 -12
  225. data/test/unit/widgets.rb +8 -8
  226. data/test/web_integration_test.rb +231 -0
  227. data/test/web_test_helper.rb +218 -0
  228. data/test/web_workflow_test.rb +527 -0
  229. data/ui/tui/bin/scriptorium +885 -415
  230. data/ui/web/app/app.rb +1398 -176
  231. data/ui/web/app/assets/livetext_mode.js +244 -0
  232. data/ui/web/app/error_helpers.rb +16 -16
  233. data/ui/web/app/views/advanced_config.erb +8 -2
  234. data/ui/web/app/views/asset_management.erb +56 -0
  235. data/ui/web/app/views/backup_management.erb +238 -0
  236. data/ui/web/app/views/config_widget.erb +232 -0
  237. data/ui/web/app/views/dashboard.erb +64 -72
  238. data/ui/web/app/views/deploy_config.erb +3 -0
  239. data/ui/web/app/views/edit_pages.erb +170 -2
  240. data/ui/web/app/views/edit_post.erb +130 -9
  241. data/ui/web/app/views/edit_theme.erb +73 -0
  242. data/ui/web/app/views/edit_theme_file.erb +74 -0
  243. data/ui/web/app/views/theme_management.erb +130 -0
  244. data/ui/web/app/views/view_dashboard.erb +666 -25
  245. data/ui/web/app/views/widgets.erb +249 -0
  246. data/ui/web/bin/scriptorium-web +35 -24
  247. data/ui/web/tmp/timing.log +17 -0
  248. data/ui/web/tmp/web_server.log +0 -5
  249. metadata +190 -116
  250. data/assets/back-icon.png +0 -0
  251. data/assets/icons/facebook.svg +0 -1
  252. data/assets/icons/github.svg +0 -1
  253. data/assets/icons/instagram.svg +0 -1
  254. data/assets/icons/reddit.svg +0 -1
  255. data/assets/icons/x.svg +0 -1
  256. data/assets/icons/youtube.svg +0 -1
  257. data/bin/scriptorium +0 -1511
  258. data/doc/anti-amnesia/20250727-060000-api-design-tui-planning.md +0 -34
  259. data/doc/anti-amnesia/20250727-061000-runeblog-tui-analysis.md +0 -50
  260. data/doc/anti-amnesia/20250727-154000-livetext-plugin-file-stats.md +0 -73
  261. data/doc/anti-amnesia/20250727-172600-unified-minitest-framework.md +0 -70
  262. data/doc/anti-amnesia/20250727-173000-widget-testing-achievement.md +0 -110
  263. data/doc/anti-amnesia/20250727-180000-post-id-num-refactoring.md +0 -73
  264. data/doc/anti-amnesia/20250728-124421-conversation-summary-concise.md +0 -124
  265. data/doc/anti-amnesia/20250729-190000-scriptorium-tui-testing-complete.md +0 -46
  266. data/doc/anti-amnesia/20250729-200000-scriptorium-tui-testing-edit-file-workflow.md +0 -97
  267. data/doc/anti-amnesia/20250729-211500-dependency-management-system.md +0 -211
  268. data/doc/anti-amnesia/20250729-213000-python-virtual-environment-setup.md +0 -141
  269. data/doc/anti-amnesia/20250729-214500-theme-management-commands.md +0 -211
  270. data/doc/anti-amnesia/20250729-215000-version-update-to-0.6.0.md +0 -134
  271. data/doc/anti-amnesia/20250729-220000-user-guide-complete.md +0 -41
  272. data/doc/anti-amnesia/20250804-213700-publishing-test-fix.md +0 -49
  273. data/doc/anti-amnesia/20250804-214400-additional-test-fixes.md +0 -46
  274. data/doc/anti-amnesia/20250804-220000-asset-function-logic-clarification.md +0 -41
  275. data/doc/anti-amnesia/20250806-202032-asset-function-logic-clarification.md +0 -41
  276. data/doc/anti-amnesia/20250813-082428-syntax-highlighting-and-navigation-improvements.md +0 -256
  277. data/lib/scriptorium/syntax_highlighter.rb +0 -234
  278. data/test/manual/deploy_symlink_demo.rb +0 -142
  279. data/test/manual/symlink_demo.rb +0 -117
  280. data/test/manual/test2.rb +0 -12
  281. data/test/manual/test_banner_from_file.rb +0 -150
  282. data/test/manual/test_banner_in_header.rb +0 -35
  283. data/test/manual/test_code_highlighting.rb +0 -68
  284. data/test/manual/test_complex_header.rb +0 -74
  285. data/test/manual/test_empty_header.rb +0 -32
  286. data/test/manual/test_radial_custom.rb +0 -58
  287. data/test/manual/test_radial_large_radius.rb +0 -52
  288. data/test/manual/test_svg_debug.rb +0 -47
  289. data/test/pages-demo/config/currentview.txt +0 -1
  290. data/test/pages-demo/views/demo/config/common.js +0 -57
  291. data/test/pages-demo/views/demo/config/footer.txt +0 -1
  292. data/test/pages-demo/views/demo/config/global-head.txt +0 -8
  293. data/test/pages-demo/views/demo/config/header.txt +0 -1
  294. data/test/pages-demo/views/demo/config/layout.txt +0 -1
  295. data/test/pages-demo/views/demo/config/left.txt +0 -1
  296. data/test/pages-demo/views/demo/config/main.txt +0 -1
  297. data/test/pages-demo/views/demo/config/right.txt +0 -1
  298. data/test/pages-demo/views/demo/config.txt +0 -3
  299. data/test/pages-demo/views/demo/output/panes/footer.html +0 -1
  300. data/test/pages-demo/views/demo/output/panes/header.html +0 -1
  301. data/test/pages-demo/views/demo/output/panes/left.html +0 -1
  302. data/test/pages-demo/views/demo/output/panes/main.html +0 -1
  303. data/test/pages-demo/views/demo/output/panes/right.html +0 -1
  304. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_css.txt +0 -5
  305. data/test/scriptorium-TEST-1754622690-146/config/bootstrap_js.txt +0 -4
  306. data/test/scriptorium-TEST-1754622690-146/config/common.js +0 -57
  307. data/test/scriptorium-TEST-1754622690-146/config/currentview.txt +0 -1
  308. data/test/scriptorium-TEST-1754622690-146/config/global-head.txt +0 -9
  309. data/test/scriptorium-TEST-1754622690-146/config/last_post_num.txt +0 -1
  310. data/test/scriptorium-TEST-1754622690-146/config/os_helpers.rb +0 -4
  311. data/test/scriptorium-TEST-1754622690-146/config/widgets.txt +0 -3
  312. data/test/scriptorium-TEST-1754622690-146/posts/0001/meta.txt +0 -8
  313. data/test/scriptorium-TEST-1754622690-146/posts/0001/source.lt3 +0 -6
  314. data/test/scriptorium-TEST-1754622690-146/themes/standard/README.txt +0 -1
  315. data/test/scriptorium-TEST-1754622690-146/themes/standard/config.txt +0 -1
  316. data/test/scriptorium-TEST-1754622690-146/themes/standard/layout/gen/text.css +0 -1
  317. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index.lt3 +0 -1
  318. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/index_entry.lt3 +0 -14
  319. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/post.lt3 +0 -13
  320. data/test/scriptorium-TEST-1754622690-146/themes/standard/templates/widget.lt3 +0 -1
  321. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_css.txt +0 -5
  322. data/test/scriptorium-TEST-1754622690-146/views/sample/config/bootstrap_js.txt +0 -4
  323. data/test/scriptorium-TEST-1754622690-146/views/sample/config/common.js +0 -57
  324. data/test/scriptorium-TEST-1754622690-146/views/sample/config/deploy.txt +0 -5
  325. data/test/scriptorium-TEST-1754622690-146/views/sample/config/footer.txt +0 -2
  326. data/test/scriptorium-TEST-1754622690-146/views/sample/config/global-head.txt +0 -9
  327. data/test/scriptorium-TEST-1754622690-146/views/sample/config/header.txt +0 -4
  328. data/test/scriptorium-TEST-1754622690-146/views/sample/config/layout.txt +0 -5
  329. data/test/scriptorium-TEST-1754622690-146/views/sample/config/left.txt +0 -3
  330. data/test/scriptorium-TEST-1754622690-146/views/sample/config/main.txt +0 -5
  331. data/test/scriptorium-TEST-1754622690-146/views/sample/config/right.txt +0 -3
  332. data/test/scriptorium-TEST-1754622690-146/views/sample/config/status.txt +0 -7
  333. data/test/scriptorium-TEST-1754622690-146/views/sample/config.txt +0 -3
  334. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/footer.html +0 -3
  335. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/header.html +0 -3
  336. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/left.html +0 -3
  337. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/main.html +0 -3
  338. data/test/scriptorium-TEST-1754622690-146/views/sample/layout/right.html +0 -3
  339. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/footer.html +0 -1
  340. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/header.html +0 -1
  341. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/left.html +0 -1
  342. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/main.html +0 -1
  343. data/test/scriptorium-TEST-1754622690-146/views/sample/output/panes/right.html +0 -1
  344. data/ui/web/tmp/web_server.pid +0 -1
  345. /data/{test/pages-demo/views/demo/config/bootstrap_css.txt → lib/scriptorium/support/bootstrap/css.txt} +0 -0
  346. /data/{test/pages-demo/views/demo/config/bootstrap_js.txt → lib/scriptorium/support/bootstrap/js.txt} +0 -0
  347. /data/{test/scriptorium-TEST-1754622690-146/views/sample → lib/scriptorium/support}/config/reddit.txt +0 -0
  348. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout → lib/scriptorium/support/templates}/layout.txt +0 -0
  349. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/footer.txt → lib/scriptorium/support/theme/footer.lt3} +0 -0
  350. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/left.txt → lib/scriptorium/support/theme/left.lt3} +0 -0
  351. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/main.txt → lib/scriptorium/support/theme/main.lt3} +0 -0
  352. /data/{test/scriptorium-TEST-1754622690-146/themes/standard/layout/config/right.txt → lib/scriptorium/support/theme/right.lt3} +0 -0
  353. /data/test/manual/banner-tests/{config.txt → svg.txt} +0 -0
  354. /data/test/manual/{test6.rb → test_advanced_widgets.rb} +0 -0
  355. /data/test/manual/{test1.rb → test_basic_posts.rb} +0 -0
  356. /data/test/manual/{test4.rb → test_layout_widgets.rb} +0 -0
  357. /data/test/manual/{test5.rb → test_pagination.rb} +0 -0
  358. /data/test/manual/{test3.rb → test_random_posts.rb} +0 -0
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+
5
+ require_relative '../../lib/scriptorium'
6
+ require_relative '../test_helpers'
7
+
8
+ class TestDeployConfig < Minitest::Test
9
+ include Scriptorium::Exceptions
10
+ include Scriptorium::Helpers
11
+ include TestHelpers
12
+
13
+ def setup
14
+ # Clean up any existing test directory first
15
+ test_dir = "test/scriptorium-TEST"
16
+ FileUtils.rm_rf(test_dir) if Dir.exist?(test_dir)
17
+
18
+ @repo = Scriptorium::Repo.create(test_dir, testmode: true)
19
+ @view = @repo.create_view("testview", "Test View", "Test Subtitle")
20
+ @api = Scriptorium::API.new(testmode: true)
21
+ @api.open_repo(@repo.root)
22
+
23
+ # Set up deployment configuration for tests
24
+ setup_deployment_config
25
+ end
26
+
27
+ def teardown
28
+ # Clean up any test directories that were created
29
+ Dir.glob("test/scriptorium-TEST*").each do |dir|
30
+ system("rm -rf #{dir}")
31
+ end
32
+ end
33
+
34
+ def set_deploy_status(status = "y")
35
+ status_file = @view.dir/:config/"status.txt"
36
+ status_content = read_file(status_file)
37
+ status_content = status_content.gsub(/deploy\s+n/, "deploy #{status}")
38
+ write_file(status_file, status_content)
39
+ end
40
+
41
+ def test_001_deploy_config_availability
42
+ @view = @api.current_view
43
+ set_deploy_status
44
+ # Test that deployment tests can check for configuration availability
45
+ unless @api.testing
46
+ skip "Deployment tests require test mode"
47
+ return
48
+ end
49
+
50
+ # Check if deployment config exists
51
+ config_file = "test/config/deployment.txt"
52
+ unless File.exist?(config_file)
53
+ skip "Deployment not configured - add deployment.txt to test/config/"
54
+ return
55
+ end
56
+
57
+ # If we get here, config exists and we can run deployment tests
58
+ # This test just verifies the configuration is available
59
+ assert File.exist?(config_file), "Deployment config should exist for testing"
60
+
61
+ # Parse the config to make sure it's valid
62
+ config_content = read_file(config_file)
63
+ config = @api.parse_deploy_config(config_content)
64
+
65
+ # Verify required fields are present
66
+ required_fields = %w[user server docroot path]
67
+ required_fields.each do |field|
68
+ assert config.key?(field), "Deployment config missing required field: #{field}"
69
+ assert !config[field].to_s.empty?, "Deployment config field #{field} cannot be empty"
70
+ end
71
+
72
+ # Verify we can build a valid rsync destination
73
+ destination = @api.build_rsync_destination(config)
74
+ assert destination, "Should be able to build rsync destination from valid config"
75
+ assert destination.match(/^.+@.+:.+$/), "Rsync destination should match user@server:path format"
76
+ end
77
+
78
+ def test_002_deploy_config_format_validation
79
+ @view = @api.current_view
80
+ set_deploy_status
81
+ # Test that deployment config follows expected format
82
+ unless @api.testing
83
+ skip "Deployment tests require test mode"
84
+ return
85
+ end
86
+
87
+ config_file = "test/config/deployment.txt"
88
+ unless File.exist?(config_file)
89
+ skip "Deployment not configured - add deployment.txt to test/config/"
90
+ return
91
+ end
92
+
93
+ # Test various config formats
94
+ config_content = read_file(config_file)
95
+ config = @api.parse_deploy_config(config_content)
96
+
97
+ # Test that user field doesn't contain @ (should be separate from server)
98
+ user = config["user"]
99
+ assert !user.include?("@"), "User field should not contain @ symbol"
100
+
101
+ # Test that server field is a valid hostname/IP
102
+ server = config["server"]
103
+ assert server, "Server field should be present"
104
+ assert server.length > 0, "Server field should not be empty"
105
+
106
+ # Test that docroot field is a valid Unix path
107
+ docroot = config["docroot"]
108
+ assert docroot, "Docroot field should be present"
109
+ assert docroot.start_with?("/"), "Docroot should be absolute (start with /)"
110
+
111
+ # Test that path field is present (can be relative)
112
+ path = config["path"]
113
+ assert path, "Path field should be present"
114
+ assert !path.empty?, "Path field should not be empty"
115
+ end
116
+
117
+ def test_003_real_deployment_workflow
118
+ @view = @api.current_view
119
+ set_deploy_status
120
+ # Skip if not in test mode or no deployment config
121
+ unless @api.testing
122
+ skip "Deployment tests require test mode"
123
+ return
124
+ end
125
+
126
+ config_file = "test/config/deployment.txt"
127
+ unless File.exist?(config_file)
128
+ skip "Deployment not configured - add deployment.txt to test/config/"
129
+ return
130
+ end
131
+
132
+ # Create some test content
133
+ @api.create_post("Test Post 1", "Test content here", blurb: "This is a test post for deployment")
134
+ @api.create_post("Test Post 2", "More test content", blurb: "Another test post")
135
+ # Generate the view
136
+ @api.generate_view(@view.name)
137
+ # Verify output was created
138
+ output_dir = @view.dir/:output
139
+ assert Dir.exist?(output_dir), "Output directory should exist"
140
+ assert File.exist?(output_dir/"index.html"), "Index should be generated"
141
+ assert Dir.exist?(output_dir/:posts), "Posts directory should exist"
142
+ # Test deployment (this will actually try to deploy)
143
+ deploy_config = @api.parse_deploy_config(read_file(config_file))
144
+ assert deploy_config, "Should be able to parse deployment config"
145
+ # Verify we can build deployment destination
146
+ destination = @api.build_rsync_destination(deploy_config)
147
+ assert destination, "Should be able to build rsync destination"
148
+ # Test actual deployment
149
+ result = @api.deploy(@view.name)
150
+ assert result, "Deployment should succeed"
151
+ end
152
+
153
+ def test_004_deployment_with_assets
154
+ @view = @api.current_view
155
+ set_deploy_status
156
+ # Skip if not configured
157
+ unless @api.testing && File.exist?("test/config/deployment.txt")
158
+ skip "Deployment not configured"
159
+ return
160
+ end
161
+
162
+ # Create post with assets
163
+ post = @api.create_post("Asset Test Post", "Content with assets", blurb: "Testing asset deployment")
164
+
165
+ # Add some test assets
166
+ assets_dir = post.dir/:assets
167
+ make_dir(assets_dir)
168
+ write_file(assets_dir/"test-image.jpg", "fake image data")
169
+ write_file(assets_dir/"test.css", "body { color: red; }")
170
+
171
+ # Generate and deploy
172
+ @api.generate_view(@view.name)
173
+ result = @api.deploy(@view.name)
174
+
175
+ assert result, "Deployment with assets should succeed"
176
+ end
177
+
178
+ def test_005_deployment_verification
179
+ @view = @api.current_view
180
+ set_deploy_status
181
+ # Skip if not configured
182
+ unless @api.testing && File.exist?("test/config/deployment.txt")
183
+ skip "Deployment not configured"
184
+ return
185
+ end
186
+
187
+ # Create and deploy content
188
+ @api.create_post("Verification Post", "Content to verify", blurb: "Testing deployment verification")
189
+ @api.generate_view(@view.name)
190
+ result = @api.deploy(@view.name)
191
+
192
+ # Verify deployment succeeded
193
+ assert result, "Deployment should succeed"
194
+
195
+ # Verify deployed content via HTTP
196
+ # Get deployment config for domain construction
197
+ config = @api.parse_deploy_config(read_file("test/config/deployment.txt"))
198
+ domain = config["proto"] + "://" + config["server"]
199
+ base_url = "#{domain}/#{config["path"]}"
200
+
201
+ # Test main page
202
+ main_page = `curl -s #{base_url}/`
203
+ assert main_page.include?("Test View"), "Main page should contain view title"
204
+ assert main_page.include?("Test Subtitle"), "Main page should contain view subtitle"
205
+ assert main_page.include?("Verification Post"), "Main page should contain deployed post title"
206
+ # assert main_page.include?("Testing deployment verification"), "Main page should contain post blurb" # May not be on main page
207
+
208
+ # Test post page - posts are numbered, so try 0001
209
+ post_url = "#{base_url}/posts/0001-verification-post.html"
210
+ post_page = `curl -s #{post_url} 2>/dev/null`
211
+ assert post_page.include?("Verification Post"), "Post page should contain post title"
212
+ assert post_page.include?("Content to verify"), "Post page should contain post body"
213
+
214
+ # Test clean permalink URL (should work now with copying instead of symlinking)
215
+ clean_post_url = "#{base_url}/permalink/verification-post.html"
216
+ clean_post_page = `curl -s #{clean_post_url} 2>/dev/null`
217
+ assert clean_post_page.include?("Verification Post"), "Clean permalink should contain post title"
218
+ assert clean_post_page.include?("Content to verify"), "Clean permalink should contain post body"
219
+
220
+ # Test basic structure elements that are likely to be present
221
+ # assert post_page.include?("<html"), "Post page should have HTML structure" # May be fragment
222
+ # assert post_page.include?("<head"), "Post page should have head section" # May be fragment
223
+ # assert post_page.include?("<body"), "Post page should have body section" # May be fragment
224
+
225
+ # Test navigation elements on main page
226
+ # assert main_page.include?("permalink"), "Main page should have permalink links" # May not be present
227
+ # assert main_page.include?("copy link"), "Main page should have copy link buttons" # May not be present
228
+
229
+ # Test basic functionality that should be present
230
+ assert main_page.include?("bootstrap"), "Main page should include Bootstrap CSS/JS"
231
+ assert main_page.length > 100, "Main page should have substantial content"
232
+ assert post_page.length > 50, "Post page should have content"
233
+ end
234
+
235
+ private
236
+
237
+ def setup_deployment_config
238
+ # Copy deployment config to the view if it exists
239
+ config_source = "test/config/deployment.txt"
240
+ if File.exist?(config_source)
241
+ deploy_file = @view.dir/:config/"deploy.txt"
242
+ write_file(deploy_file, read_file(config_source))
243
+ else
244
+ raise "Deployment config source not found: #{config_source}"
245
+ end
246
+ end
247
+
248
+ end
@@ -11,12 +11,23 @@ class TestDeploy < Minitest::Test
11
11
  include TestHelpers
12
12
 
13
13
  def setup
14
- @repo = Scriptorium::Repo.create("test/scriptorium-TEST", testmode: true)
14
+ # Clean up any existing test directory first
15
+ test_dir = "test/scriptorium-TEST"
16
+ FileUtils.rm_rf(test_dir) if Dir.exist?(test_dir)
17
+
18
+ @repo = Scriptorium::Repo.create(test_dir, testmode: true)
15
19
  @view = @repo.create_view("testview", "Test View", "Test Subtitle")
20
+ @api = Scriptorium::API.new(testmode: true)
21
+ @api.open_repo(@repo.root)
16
22
  end
17
23
 
18
24
  def teardown
19
- system("rm -rf test/scriptorium-TEST")
25
+ # Clean up any test directories that were created
26
+ Dir.glob("test/scriptorium-TEST*").each do |dir|
27
+ system("rm -rf #{dir}")
28
+ end
29
+ # Clean up any root directory created during deployment testing
30
+ # FileUtils.rm_rf("root") if Dir.exist?("root")
20
31
  end
21
32
 
22
33
  def test_001_deploy_config_file_creation
@@ -118,18 +129,18 @@ class TestDeploy < Minitest::Test
118
129
  # Create the target file
119
130
  write_file(target_file, "<html><body>Test post</body></html>")
120
131
 
121
- # Create the symlink
122
- File.symlink("0001-test-post.html", symlink_file)
132
+ # Create the copy
133
+ FileUtils.cp(target_file, symlink_file)
123
134
 
124
- # Verify symlink exists
125
- assert File.exist?(symlink_file), "Symlink should exist"
126
- assert File.symlink?(symlink_file), "Should be a symlink"
135
+ # Verify copy exists
136
+ assert File.exist?(symlink_file), "Copy should exist"
137
+ assert !File.symlink?(symlink_file), "Should not be a symlink"
127
138
 
128
- # Test that rsync command includes symlink preservation
129
- expected_cmd = "rsync -r -z -l #{output_dir}/ user@server:/path/"
130
- actual_cmd = "rsync -r -z -l #{output_dir}/ user@server:/path/"
139
+ # Test that rsync command works with regular files (no symlink preservation needed)
140
+ expected_cmd = "rsync -r -z #{output_dir}/ user@server:/path/"
141
+ actual_cmd = "rsync -r -z #{output_dir}/ user@server:/path/"
131
142
 
132
- assert_equal expected_cmd, actual_cmd, "Rsync command should preserve symlinks"
143
+ assert_equal expected_cmd, actual_cmd, "Rsync command should work with regular files"
133
144
  end
134
145
 
135
146
  def test_008_deploy_symlink_target_verification
@@ -144,18 +155,17 @@ class TestDeploy < Minitest::Test
144
155
  # Create the target file
145
156
  write_file(target_file, "<html><body>Another test post</body></html>")
146
157
 
147
- # Create the symlink
148
- File.symlink("0001-another-post.html", symlink_file)
149
-
150
- # Verify symlink points to existing target
151
- assert File.exist?(symlink_file), "Symlink should exist"
152
- assert File.symlink?(symlink_file), "Should be a symlink"
158
+ # Create the copy
159
+ FileUtils.cp(target_file, symlink_file)
153
160
 
154
- symlink_target = File.readlink(symlink_file)
155
- target_path = output_dir/:permalink/symlink_target
161
+ # Verify copy exists and has same content
162
+ assert File.exist?(symlink_file), "Copy should exist"
163
+ assert !File.symlink?(symlink_file), "Should not be a symlink"
156
164
 
157
- assert File.exist?(target_path), "Symlink target should exist"
158
- assert_equal "0001-another-post.html", symlink_target, "Symlink should point to correct target"
165
+ # Verify copy has same content as target
166
+ copy_content = File.read(symlink_file)
167
+ target_content = File.read(target_file)
168
+ assert_equal target_content, copy_content, "Copy should have same content as target"
159
169
  end
160
170
 
161
171
  def test_009_domain_extraction_from_deploy_config
@@ -184,4 +194,285 @@ class TestDeploy < Minitest::Test
184
194
 
185
195
  assert_equal "https://example.com/last-deployed.txt", expected_url, "Verification URL should be correctly formatted"
186
196
  end
197
+
198
+ # New API deployment method tests
199
+
200
+ def test_011_can_deploy_status_check
201
+ # Test can_deploy? with various status configurations
202
+
203
+ # Test with deploy status 'n' (should fail)
204
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
205
+ write_file(status_file, "deploy n")
206
+
207
+ refute @api.can_deploy?("testview"), "Should not deploy when status is 'n'"
208
+
209
+ # Test with deploy status 'y' (should pass status check)
210
+ write_file(status_file, "deploy y")
211
+
212
+ # This will still fail because deploy.txt doesn't exist, but status check passes
213
+ refute @api.can_deploy?("testview"), "Should fail when deploy.txt is missing"
214
+ end
215
+
216
+ def test_012_can_deploy_config_validation
217
+ # Test can_deploy? with various deploy.txt configurations
218
+
219
+ # Set deploy status to 'y'
220
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
221
+ write_file(status_file, "deploy y")
222
+
223
+ # Test with missing deploy.txt
224
+ refute @api.can_deploy?("testview"), "Should fail when deploy.txt is missing"
225
+
226
+ # Test with incomplete deploy.txt
227
+ deploy_file = @repo.root/"views"/"testview"/"config"/"deploy.txt"
228
+ write_file(deploy_file, "user root\nserver example.com")
229
+
230
+ refute @api.can_deploy?("testview"), "Should fail when required fields are missing"
231
+
232
+ # Test with complete deploy.txt
233
+ write_file(deploy_file, "user root\nserver.example.com\ndocroot /var/www/html\npath testview")
234
+
235
+ # This will fail SSH test, but we'll skip that for now
236
+ # The method should at least pass the config validation
237
+ begin
238
+ result = @api.can_deploy?("testview")
239
+ # If SSH test passes, result should be true
240
+ # If SSH test fails, result should be false
241
+ assert result == false || result == true, "Should return boolean result"
242
+ rescue => e
243
+ # SSH test might fail, which is expected in test environment
244
+ skip "SSH test failed (expected in test environment): #{e.message}"
245
+ end
246
+ end
247
+
248
+ def test_013_deploy_dry_run_mode
249
+ # Test deploy method in dry-run mode
250
+
251
+ # Set up deployment configuration
252
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
253
+ write_file(status_file, "deploy y")
254
+
255
+ deploy_file = @repo.root/"views"/"testview"/"config"/"deploy.txt"
256
+ write_file(deploy_file, "user root\nserver.example.com\ndocroot /var/www/html\npath testview")
257
+
258
+ # Create some output content
259
+ output_dir = @repo.root/"views"/"testview"/"output"
260
+ make_dir(output_dir)
261
+ write_file(output_dir/"index.html", "<html><body>Test content</body></html>")
262
+
263
+ # Test that the deploy method can be called (deployment test will fail, but that's expected)
264
+ begin
265
+ result = @api.deploy("testview", dry_run: true)
266
+ assert result, "Dry-run should succeed"
267
+ rescue DeploymentNotReady => e
268
+ # Deployment not ready, which is expected in test environment
269
+ skip "Deployment not ready (expected in test environment): #{e.message}"
270
+ rescue => e
271
+ raise e
272
+ end
273
+ end
274
+
275
+ def test_014_deploy_missing_configuration
276
+ # Test deploy method with missing configuration
277
+
278
+ # Test with no status file
279
+ assert_raises(DeploymentNotReady, "Should fail when status is not ready") do
280
+ @api.deploy("testview")
281
+ end
282
+
283
+ # Test with deploy status 'n'
284
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
285
+ write_file(status_file, "deploy n")
286
+
287
+ assert_raises(DeploymentNotReady, "Should fail when status is not ready") do
288
+ @api.deploy("testview")
289
+ end
290
+ end
291
+
292
+ def test_015_deploy_invalid_configuration
293
+ # Test deploy method with invalid configuration
294
+
295
+ # Set deploy status to 'y'
296
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
297
+ write_file(status_file, "deploy y")
298
+
299
+ # Test with missing deploy.txt
300
+ assert_raises(DeploymentNotReady, "Should fail when deploy.txt is missing") do
301
+ @api.deploy("testview")
302
+ end
303
+
304
+ # Test with incomplete deploy.txt
305
+ deploy_file = @repo.root/"views"/"testview"/"config"/"deploy.txt"
306
+ write_file(deploy_file, "user root\nserver example.com")
307
+
308
+ assert_raises(DeploymentNotReady, "Should fail when required fields are missing") do
309
+ @api.deploy("testview")
310
+ end
311
+ end
312
+
313
+ def test_016_deploy_rsync_command_construction
314
+ # Test that deploy method constructs correct rsync command
315
+
316
+ # Set up deployment configuration
317
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
318
+ write_file(status_file, "deploy y")
319
+
320
+ deploy_file = @repo.root/"views"/"testview"/"config"/"deploy.txt"
321
+ write_file(deploy_file, "user root\nserver example.com\ndocroot /var/www/html\npath testview")
322
+
323
+ # Create output content
324
+ output_dir = @repo.root/"views"/"testview"/"output"
325
+ make_dir(output_dir)
326
+ write_file(output_dir/"index.html", "<html><body>Test content</body></html>")
327
+
328
+ # Test dry-run to see the command that would be executed
329
+ begin
330
+ result = @api.deploy("testview", dry_run: true)
331
+ assert result, "Dry-run should succeed"
332
+ rescue DeploymentNotReady => e
333
+ # Deployment not ready, which is expected in test environment
334
+ skip "Deployment not ready (expected in test environment): #{e.message}"
335
+ rescue => e
336
+ raise e
337
+ end
338
+
339
+ # The dry-run should output the rsync command to stdout
340
+ # We can't easily capture this in the test, but we can verify the method runs
341
+ # The actual command format is: "rsync -r -z -l #{output_dir}/ #{remote_path}/"
342
+ end
343
+
344
+
345
+
346
+ def test_018_deploy_ssh_keys_test
347
+ # Test SSH key validation (will likely be skipped in test environment)
348
+
349
+ # Set up deployment configuration
350
+ status_file = @repo.root/"views"/"testview"/"config"/"status.txt"
351
+ write_file(status_file, "deploy y")
352
+
353
+ deploy_file = @repo.root/"views"/"testview"/"config"/"deploy.txt"
354
+ write_file(deploy_file, "user root\nserver example.com\ndocroot /var/www/html\npath testview")
355
+
356
+ # Test SSH key validation
357
+ begin
358
+ result = @api.can_deploy?("testview")
359
+ # This might pass or fail depending on SSH configuration
360
+ assert result == false || result == true, "Should return boolean result"
361
+ rescue => e
362
+ # SSH test might fail, which is expected in test environment
363
+ skip "SSH test failed (expected in test environment): #{e.message}"
364
+ end
365
+ end
366
+
367
+ def test_020_deploy_config_parsing
368
+ # Test various deployment config formats
369
+ test_cases = [
370
+ # Valid space-separated format
371
+ {
372
+ input: "user root\nserver example.com\npath sample",
373
+ expected: {"user" => "root", "server" => "example.com", "path" => "sample"},
374
+ description: "valid space-separated format"
375
+ },
376
+ # Config with comments
377
+ {
378
+ input: "# This is a comment\nuser root\nserver example.com\npath sample",
379
+ expected: {"user" => "root", "server" => "example.com", "path" => "sample"},
380
+ description: "config with comments"
381
+ },
382
+ # Missing required fields
383
+ {
384
+ input: "user root\nserver example.com",
385
+ expected: {"user" => "root", "server" => "example.com"},
386
+ description: "missing path field"
387
+ },
388
+ # Empty config
389
+ {
390
+ input: "",
391
+ expected: {},
392
+ description: "empty config"
393
+ },
394
+ # Config with junk lines
395
+ {
396
+ input: "user root\njunk line here\nserver example.com\npath sample",
397
+ expected: {"user" => "root", "junk" => "line here", "server" => "example.com", "path" => "sample"},
398
+ description: "config with junk lines"
399
+ },
400
+ # Config with extra whitespace
401
+ {
402
+ input: " user root \n server example.com \n path sample ",
403
+ expected: {"user" => "root", "server" => "example.com", "path" => "sample"},
404
+ description: "config with extra whitespace"
405
+ }
406
+ ]
407
+
408
+ test_cases.each do |test_case|
409
+ result = @api.parse_deploy_config(test_case[:input])
410
+ assert_equal test_case[:expected], result,
411
+ "Failed for #{test_case[:description]}: expected '#{test_case[:expected]}', got '#{result}'"
412
+ end
413
+ end
414
+
415
+ def test_020b_build_rsync_destination
416
+ # Test building rsync destinations from config hashes
417
+ test_cases = [
418
+ {
419
+ config: {"user" => "root", "server" => "example.com", "path" => "sample"},
420
+ expected: "root@example.com:sample",
421
+ description: "complete config"
422
+ },
423
+ {
424
+ config: {"user" => "root", "server" => "example.com"},
425
+ expected: nil,
426
+ description: "missing path"
427
+ },
428
+ {
429
+ config: {},
430
+ expected: nil,
431
+ description: "empty config"
432
+ }
433
+ ]
434
+
435
+ test_cases.each do |test_case|
436
+ result = @api.build_rsync_destination(test_case[:config])
437
+ if test_case[:expected].nil?
438
+ assert_nil result, "Failed for #{test_case[:description]}: expected nil, got '#{result}'"
439
+ else
440
+ assert_equal test_case[:expected], result,
441
+ "Failed for #{test_case[:description]}: expected '#{test_case[:expected]}', got '#{result}'"
442
+ end
443
+ end
444
+ end
445
+
446
+
447
+
448
+ def test_022_deploy_config_edge_cases
449
+ # Test edge cases that could cause issues
450
+ edge_cases = [
451
+ # Config with only junk
452
+ {
453
+ input: "this is not a config\nneither is this\nor this",
454
+ expected: {"this" => "is not a config", "neither" => "is this", "or" => "this"},
455
+ description: "only junk lines"
456
+ },
457
+ # Config with empty lines
458
+ {
459
+ input: "user root\n\nserver example.com\n\npath sample\n",
460
+ expected: {"user" => "root", "server" => "example.com", "path" => "sample"},
461
+ description: "config with empty lines"
462
+ },
463
+ # Config with special characters
464
+ {
465
+ input: "user root\nserver example.com\npath /var/www/html",
466
+ expected: {"user" => "root", "server" => "example.com", "path" => "/var/www/html"},
467
+ description: "path with special characters"
468
+ }
469
+ ]
470
+
471
+ edge_cases.each do |test_case|
472
+ result = @api.parse_deploy_config(test_case[:input])
473
+ assert_equal test_case[:expected], result,
474
+ "Failed for #{test_case[:description]}: expected '#{test_case[:expected]}', got '#{result}'"
475
+ end
476
+ end
477
+
187
478
  end