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,451 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require_relative '../../lib/scriptorium'
7
+ require_relative '../test_helpers'
8
+
9
+ class BackupTestFixed < Minitest::Test
10
+ include Scriptorium::Exceptions
11
+ include Scriptorium::Helpers
12
+
13
+ def setup
14
+ @test_dir = "test/scriptorium-TEST"
15
+ # Clean up any existing test directory first
16
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
17
+ @api = Scriptorium::API.new(testmode: true)
18
+ @api.create_repo(@test_dir)
19
+ end
20
+
21
+ def teardown
22
+ FileUtils.rm_rf(@test_dir) if Dir.exist?(@test_dir)
23
+ # Clean up backup directory too
24
+ if @api
25
+ backup_dir = @api.get_backup_directory
26
+ FileUtils.rm_rf(backup_dir) if Dir.exist?(backup_dir)
27
+ end
28
+ @api = nil
29
+ end
30
+
31
+ private def count_posts_in_compressed_backup(backup_dir)
32
+ tar_gz_path = backup_dir/"data.tar.gz"
33
+ return 0 unless File.exist?(tar_gz_path)
34
+
35
+ # Use tar -tf to list files and count posts directories
36
+ output = `tar -tf '#{tar_gz_path}' 2>/dev/null`
37
+ return 0 unless $?.success?
38
+
39
+ # Count unique post directories (tar output has ./ prefix)
40
+ post_dirs = output.lines.select { |line| line.strip.match(/^\.\/posts\/\d+\//) }
41
+ post_dirs.map { |line| line.strip.split('/')[2] }.uniq.length
42
+ end
43
+
44
+ def test_001_create_full_backup
45
+ # Create some test content
46
+ @api.create_view("test-view", "Test View", "A test view")
47
+ @api.create_post("Test Post", "This is a test post", views: "test-view")
48
+
49
+ # Create a full backup
50
+ backup_name = @api.create_backup(type: :full, label: "test-backup")
51
+
52
+ # Verify backup was created
53
+ assert_match(/^\d{8}-\d{6}-full$/, backup_name)
54
+
55
+ backup_dir = @api.get_backup_directory/"data"/backup_name
56
+ assert Dir.exist?(backup_dir), "Backup directory should exist"
57
+
58
+ # Verify compressed backup structure
59
+ assert File.exist?(backup_dir/"data.tar.gz"), "Compressed backup data should exist"
60
+
61
+ # Verify backup info file exists (uncompressed)
62
+ assert File.exist?(backup_dir/"backup-info.txt"), "Backup info file should exist"
63
+
64
+ # Verify manifest was created
65
+ manifest_file = @api.get_backup_directory/"manifest.txt"
66
+ assert File.exist?(manifest_file), "Backup manifest should exist"
67
+
68
+ manifest_content = File.read(manifest_file)
69
+ assert_includes manifest_content, backup_name, "Backup should be in manifest"
70
+ assert_includes manifest_content, "test-backup", "Label should be in manifest"
71
+
72
+ # Verify backup info file was created
73
+ backup_info_file = backup_dir/"backup-info.txt"
74
+ assert File.exist?(backup_info_file), "Backup info file should exist"
75
+
76
+ info_content = File.read(backup_info_file)
77
+ assert_includes info_content, "scriptorium_version:", "Should contain scriptorium version"
78
+ assert_includes info_content, "livetext_version:", "Should contain livetext version"
79
+ assert_includes info_content, "ruby_version:", "Should contain ruby version"
80
+ assert_includes info_content, "backup_type: full", "Should contain backup type"
81
+ end
82
+
83
+ def test_002_create_incremental_backup
84
+ # Create some test content
85
+ @api.create_view("test-view", "Test View", "A test view")
86
+ @api.create_post("Test Post", "This is a test post", views: "test-view")
87
+
88
+ # Create an incremental backup
89
+ backup_name = @api.create_backup(type: :incremental, label: "incremental-test")
90
+
91
+ # Verify backup was created
92
+ assert_match(/^\d{8}-\d{6}-incr$/, backup_name)
93
+
94
+ backup_dir = @api.get_backup_directory/"data"/backup_name
95
+ assert Dir.exist?(backup_dir), "Backup directory should exist"
96
+
97
+ # Verify compressed backup structure
98
+ assert File.exist?(backup_dir/"data.tar.gz"), "Compressed backup data should exist"
99
+
100
+ # Verify manifest was created
101
+ manifest_file = @api.get_backup_directory/"manifest.txt"
102
+ assert File.exist?(manifest_file), "Backup manifest should exist"
103
+
104
+ manifest_content = File.read(manifest_file)
105
+ assert_includes manifest_content, backup_name, "Backup should be in manifest"
106
+ assert_includes manifest_content, "incremental-test", "Label should be in manifest"
107
+
108
+ # Verify backup info file was created
109
+ backup_info_file = backup_dir/"backup-info.txt"
110
+ assert File.exist?(backup_info_file), "Backup info file should exist"
111
+
112
+ info_content = File.read(backup_info_file)
113
+ assert_includes info_content, "scriptorium_version:", "Should contain scriptorium version"
114
+ assert_includes info_content, "livetext_version:", "Should contain livetext version"
115
+ assert_includes info_content, "ruby_version:", "Should contain ruby version"
116
+ assert_includes info_content, "backup_type: incremental", "Should contain backup type"
117
+ end
118
+
119
+ def test_003_list_backups
120
+ # Create multiple backups
121
+ backup1 = @api.create_backup(type: :full, label: "first")
122
+ sleep(1) # Ensure different timestamps
123
+ backup2 = @api.create_backup(type: :incremental, label: "second")
124
+
125
+ # List backups
126
+ backups = @api.list_backups
127
+
128
+ assert_equal 2, backups.length, "Should have 2 backups"
129
+
130
+ # Should be sorted by creation time, newest first
131
+ assert_equal backup2, backups[0][:name], "Newest backup should be first"
132
+ assert_equal backup1, backups[1][:name], "Older backup should be second"
133
+
134
+ # Verify backup info
135
+ backup_info = backups[0]
136
+ assert_equal backup2, backup_info[:name]
137
+ assert_equal :incremental, backup_info[:type]
138
+ assert_equal "second", backup_info[:description]
139
+ assert backup_info[:timestamp].is_a?(Time)
140
+ assert backup_info[:size].is_a?(Integer)
141
+ assert backup_info[:file_count].is_a?(Integer)
142
+ end
143
+
144
+ def test_004_restore_backup_safe_strategy
145
+ # Create initial content
146
+ @api.create_view("test-view", "Test View", "A test view")
147
+ @api.create_post("Original Post", "Original content", views: "test-view")
148
+
149
+ # Create a backup
150
+ backup_name = @api.create_backup(type: :full, label: "before-changes")
151
+
152
+ # Modify content
153
+ @api.create_post("New Post", "New content", views: "test-view")
154
+
155
+ # Restore from backup using safe strategy (default)
156
+ result = @api.restore_backup(backup_name, strategy: :safe)
157
+ assert result.is_a?(Hash), "Restore should return a hash"
158
+ assert_equal backup_name, result[:restored], "Should return the restored backup name"
159
+ assert result[:pre_restore], "Should have created a pre-restore backup"
160
+
161
+ # Verify content was restored
162
+ posts = @api.posts("test-view")
163
+ assert_equal 1, posts.length, "Should have only 1 post after restore"
164
+ assert_equal "Original Post", posts[0].title, "Should have original post"
165
+
166
+ # Verify pre-restore backup was created
167
+ backups = @api.list_backups
168
+ pre_restore = backups.find { |b| b[:description]&.include?("pre-restore") }
169
+ assert pre_restore, "Should have created a pre-restore backup"
170
+ end
171
+
172
+ def test_004b_restore_backup_destroy_strategy
173
+ # Create initial content
174
+ @api.create_view("test-view", "Test View", "A test view")
175
+ @api.create_post("Original Post", "Original content", views: "test-view")
176
+
177
+ # Create a backup
178
+ backup_name = @api.create_backup(type: :full, label: "before-changes")
179
+
180
+ # Modify content
181
+ @api.create_post("New Post", "New content", views: "test-view")
182
+
183
+ # Restore from backup using destroy strategy
184
+ result = @api.restore_backup(backup_name, strategy: :destroy)
185
+ assert result.is_a?(Hash), "Restore should return a hash"
186
+ assert_equal backup_name, result[:restored], "Should return the restored backup name"
187
+ assert_equal :destroy, result[:strategy], "Should indicate destroy strategy"
188
+
189
+ # Verify content was restored
190
+ posts = @api.posts("test-view")
191
+ assert_equal 1, posts.length, "Should have only 1 post after restore"
192
+ assert_equal "Original Post", posts[0].title, "Should have original post"
193
+ end
194
+
195
+ def test_004c_restore_backup_merge_strategy
196
+ # Create initial content
197
+ @api.create_view("test-view", "Test View", "A test view")
198
+ @api.create_post("Original Post", "Original content", views: "test-view")
199
+
200
+ # Create a backup
201
+ backup_name = @api.create_backup(type: :full, label: "before-changes")
202
+
203
+ # Modify content
204
+ @api.create_post("New Post", "New content", views: "test-view")
205
+
206
+ # Restore from backup using merge strategy
207
+ result = @api.restore_backup(backup_name, strategy: :merge)
208
+ assert result.is_a?(Hash), "Restore should return a hash"
209
+ assert_equal backup_name, result[:restored], "Should return the restored backup name"
210
+ assert_equal :merge, result[:strategy], "Should indicate merge strategy"
211
+
212
+ # Verify content was restored (merge should keep existing files)
213
+ posts = @api.posts("test-view")
214
+ assert_equal 2, posts.length, "Should have 2 posts after merge restore"
215
+ post_titles = posts.map(&:title).sort
216
+ assert_equal ["New Post", "Original Post"], post_titles, "Should have both posts"
217
+ end
218
+
219
+ def test_005_delete_backup
220
+ # Create a backup
221
+ backup_name = @api.create_backup(type: :full, label: "to-delete")
222
+
223
+ # Verify it exists
224
+ backups = @api.list_backups
225
+ assert_equal 1, backups.length, "Should have 1 backup"
226
+
227
+ # Delete the backup
228
+ result = @api.delete_backup(backup_name)
229
+ assert result, "Delete should succeed"
230
+
231
+ # Verify it's gone
232
+ backups = @api.list_backups
233
+ assert_equal 0, backups.length, "Should have 0 backups after delete"
234
+
235
+ # Verify directory is gone
236
+ backup_dir = @api.get_backup_directory/"data"/backup_name
237
+ assert !Dir.exist?(backup_dir), "Backup directory should be deleted"
238
+ end
239
+
240
+ def test_006_incremental_backup_tracks_changes
241
+ # Create initial content and full backup first
242
+ @api.create_view("test-view", "Test View", "A test view")
243
+ @api.create_post("Post 1", "Content 1", views: "test-view")
244
+
245
+
246
+ backup1 = @api.create_backup(type: :full, label: "initial")
247
+
248
+ # Now add new content and create incremental backup
249
+ @api.create_post("Post 2", "Content 2", views: "test-view")
250
+ backup2 = @api.create_backup(type: :incremental, label: "after-changes")
251
+
252
+ # Verify backups contain expected content
253
+ backup1_dir = @api.get_backup_directory/"data"/backup1
254
+ backup2_dir = @api.get_backup_directory/"data"/backup2
255
+
256
+ # Full backup should have 1 post (check compressed content)
257
+ backup1_posts = count_posts_in_compressed_backup(backup1_dir)
258
+ assert_equal 1, backup1_posts, "Full backup should have 1 post"
259
+
260
+ # Incremental backup should have 2 posts (both posts, since post creation modifies existing files)
261
+ backup2_posts = count_posts_in_compressed_backup(backup2_dir)
262
+ assert_equal 2, backup2_posts, "Incremental backup should have 2 posts (both posts, since post creation modifies existing files)"
263
+
264
+ # Verify the incremental backup contains the new post
265
+ # Extract and check compressed content
266
+ temp_extract_dir = backup2_dir/"temp_extract"
267
+ FileUtils.mkdir_p(temp_extract_dir)
268
+
269
+ begin
270
+ # Extract tar.gz to temporary directory
271
+ system("tar -xzf '#{backup2_dir}/data.tar.gz' -C '#{temp_extract_dir}'")
272
+ assert $?.success?, "Should successfully extract compressed backup"
273
+
274
+ # Check for post directories
275
+ backup2_post_dirs = Dir.glob("#{temp_extract_dir}/posts/*")
276
+ assert_equal 2, backup2_post_dirs.length, "Should have exactly two post directories"
277
+
278
+ # The incremental backup should contain both posts
279
+ # Check that both post directories exist and contain the expected content
280
+ post_dirs = backup2_post_dirs.sort
281
+
282
+ # Check first post (0001)
283
+ source_file_1 = "#{post_dirs[0]}/source.lt3"
284
+ assert File.exist?(source_file_1), "Source file should exist for post 1"
285
+ content_1 = File.read(source_file_1)
286
+ assert_includes content_1, "Post 1", "Should contain Post 1"
287
+
288
+ # Check second post (0002)
289
+ source_file_2 = "#{post_dirs[1]}/source.lt3"
290
+ assert File.exist?(source_file_2), "Source file should exist for post 2"
291
+ content_2 = File.read(source_file_2)
292
+ assert_includes content_2, "Post 2", "Should contain Post 2"
293
+ ensure
294
+ # Clean up temporary directory
295
+ FileUtils.rm_rf(temp_extract_dir) if Dir.exist?(temp_extract_dir)
296
+ end
297
+ end
298
+
299
+ def test_007_backup_validation
300
+ # Temporarily enable contracts for this test
301
+ original_dbc = ENV['DBC_DISABLED']
302
+ ENV['DBC_DISABLED'] = nil
303
+
304
+ begin
305
+ # Test invalid backup type - this should fail the assume check
306
+ assert_raises(RuntimeError) do
307
+ @api.create_backup(type: :invalid)
308
+ end
309
+
310
+ # Test with nil repo - this should fail the assume check
311
+ api_no_repo = Scriptorium::API.new(testmode: true)
312
+ assert_raises(RuntimeError) do
313
+ api_no_repo.create_backup(type: :full)
314
+ end
315
+ ensure
316
+ # Restore original contract setting
317
+ ENV['DBC_DISABLED'] = original_dbc
318
+ end
319
+ end
320
+
321
+ def test_008_restore_nonexistent_backup
322
+ # Try to restore a backup that doesn't exist
323
+ assert_raises(BackupNotFound) do
324
+ @api.restore_backup("nonexistent-backup")
325
+ end
326
+ end
327
+
328
+ def test_009_delete_nonexistent_backup
329
+ # Try to delete a backup that doesn't exist
330
+ assert_raises(BackupNotFound) do
331
+ @api.delete_backup("nonexistent-backup")
332
+ end
333
+ end
334
+
335
+ def test_010_restore_incremental_backup_with_dependencies
336
+ # Create initial content
337
+ @api.create_view("test-view", "Test View", "A test view")
338
+ @api.create_post("Post 1", "Content 1", views: "test-view")
339
+
340
+ # Create full backup
341
+ full_backup = @api.create_backup(type: :full, label: "initial")
342
+ sleep(1) # Ensure different timestamps
343
+
344
+ # Add more content
345
+ @api.create_post("Post 2", "Content 2", views: "test-view")
346
+
347
+ # Create incremental backup
348
+ incr_backup = @api.create_backup(type: :incremental, label: "after-post-2")
349
+ sleep(1) # Ensure different timestamps
350
+
351
+ # Add more content
352
+ @api.create_post("Post 3", "Content 3", views: "test-view")
353
+
354
+ # Restore from incremental backup using safe strategy
355
+ result = @api.restore_backup(incr_backup, strategy: :safe)
356
+ assert result.is_a?(Hash), "Restore should return a hash"
357
+ assert_equal incr_backup, result[:restored], "Should return the restored backup name"
358
+ assert result[:pre_restore], "Should have created a pre-restore backup"
359
+
360
+ # Verify content was restored correctly (should have Post 1 and Post 2)
361
+ posts = @api.posts("test-view")
362
+ assert_equal 2, posts.length, "Should have 2 posts after restore"
363
+ post_titles = posts.map(&:title).sort
364
+ assert_equal ["Post 1", "Post 2"], post_titles, "Should have correct posts"
365
+ end
366
+
367
+ def test_011_restore_invalid_strategy
368
+ # Create a backup first
369
+ @api.create_view("test-view", "Test View", "A test view")
370
+ @api.create_post("Test Post", "Test content", views: "test-view")
371
+ backup_name = @api.create_backup(type: :full, label: "test")
372
+
373
+ # Try to restore with invalid strategy
374
+ assert_raises(ArgumentError) do
375
+ @api.restore_backup(backup_name, strategy: :invalid)
376
+ end
377
+ end
378
+
379
+ def test_012_restore_default_strategy_is_safe
380
+ # Create initial content
381
+ @api.create_view("test-view", "Test View", "A test view")
382
+ @api.create_post("Original Post", "Original content", views: "test-view")
383
+
384
+ # Create a backup
385
+ backup_name = @api.create_backup(type: :full, label: "before-changes")
386
+
387
+ # Modify content
388
+ @api.create_post("New Post", "New content", views: "test-view")
389
+
390
+ # Restore from backup without specifying strategy (should default to :safe)
391
+ result = @api.restore_backup(backup_name)
392
+ assert result.is_a?(Hash), "Restore should return a hash"
393
+ assert_equal backup_name, result[:restored], "Should return the restored backup name"
394
+ assert result[:pre_restore], "Should have created a pre-restore backup (default is safe)"
395
+
396
+ # Verify content was restored
397
+ posts = @api.posts("test-view")
398
+ assert_equal 1, posts.length, "Should have only 1 post after restore"
399
+ assert_equal "Original Post", posts[0].title, "Should have original post"
400
+ end
401
+
402
+ def test_013_restore_with_pre_restore_backup_timestamp_handling
403
+ # Create initial content
404
+ @api.create_view("test-view", "Test View", "A test view")
405
+ @api.create_post("Original Post", "Original content", views: "test-view")
406
+
407
+ # Create a backup
408
+ backup_name = @api.create_backup(type: :full, label: "before-changes")
409
+
410
+ # Modify content
411
+ @api.create_post("New Post", "New content", views: "test-view")
412
+
413
+ # Restore from backup using safe strategy
414
+ result = @api.restore_backup(backup_name, strategy: :safe)
415
+
416
+ # Verify that the pre-restore backup was created and is not used as the base for restore
417
+ backups = @api.list_backups
418
+ pre_restore = backups.find { |b| b[:description]&.include?("pre-restore") }
419
+ assert pre_restore, "Should have created a pre-restore backup"
420
+
421
+ # The pre-restore backup should not be the same as the original backup
422
+ refute_equal backup_name, pre_restore[:name], "Pre-restore backup should be different from original"
423
+
424
+ # Verify content was restored correctly
425
+ posts = @api.posts("test-view")
426
+ assert_equal 1, posts.length, "Should have only 1 post after restore"
427
+ assert_equal "Original Post", posts[0].title, "Should have original post"
428
+ end
429
+
430
+ def test_014_merge_strategy_preserves_existing_files
431
+ # Create initial content
432
+ @api.create_view("test-view", "Test View", "A test view")
433
+ @api.create_post("Original Post", "Original content", views: "test-view")
434
+
435
+ # Create a backup
436
+ backup_name = @api.create_backup(type: :full, label: "before-changes")
437
+
438
+ # Add content that should be preserved
439
+ @api.create_post("Keep This Post", "This should be kept", views: "test-view")
440
+
441
+ # Restore from backup using merge strategy
442
+ result = @api.restore_backup(backup_name, strategy: :merge)
443
+
444
+ # Verify both posts exist (merge should preserve existing files)
445
+ posts = @api.posts("test-view")
446
+ assert_equal 2, posts.length, "Should have 2 posts after merge restore"
447
+ post_titles = posts.map(&:title).sort
448
+ assert_equal ["Keep This Post", "Original Post"], post_titles, "Should have both posts"
449
+ end
450
+
451
+ end
@@ -65,27 +65,5 @@ class TestContract < Minitest::Test
65
65
  end
66
66
  end
67
67
 
68
- def test_post_contracts
69
- # Enable DBC
70
- ENV['DBC_DISABLED'] = nil
71
-
72
- begin
73
- post = @repo.create_post(title: "Test Post", body: "Test body")
74
-
75
- # Test save_metadata contracts
76
- post.meta["post.title"] = "Updated Title"
77
- post.save_metadata # Should not raise
78
-
79
- # Test deleted= contracts
80
- post.deleted = true # Should not raise
81
- assert post.deleted == true
82
-
83
- post.deleted = false # Should not raise
84
- assert post.deleted == false
85
-
86
- ensure
87
- # Restore disabled state
88
- ENV['DBC_DISABLED'] = 'true'
89
- end
90
- end
68
+
91
69
  end