scriptorium 0.0.3 → 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.
- checksums.yaml +4 -4
- data/README.lt3 +324 -0
- data/README.md +3155 -1
- data/assets/.DS_Store +0 -0
- data/assets/README.md +44 -0
- data/assets/icons/social/reddit.png +0 -0
- data/assets/icons/social/x-logo.png +0 -0
- data/assets/icons/ui/.DS_Store +0 -0
- data/assets/icons/ui/back.png +0 -0
- data/assets/icons/ui/copy.png +0 -0
- data/assets/icons/ui/down.png +0 -0
- data/assets/icons/ui/end.png +0 -0
- data/assets/icons/ui/exit.png +0 -0
- data/assets/icons/ui/foo +10 -0
- data/assets/icons/ui/home.png +0 -0
- data/assets/icons/ui/left.png +0 -0
- data/assets/icons/ui/next.png +0 -0
- data/assets/icons/ui/right.png +0 -0
- data/assets/icons/ui/start.png +0 -0
- data/assets/icons/ui/up.png +0 -0
- data/assets/imagenotfound.jpg +0 -0
- data/assets/samples/placeholder.svg +9 -0
- data/assets/themes/standard/favicon.svg +6 -0
- data/bin/sblog +84 -5
- data/bin/scriptorium +1 -0
- data/doc/README.txt +6 -0
- data/doc/anti-amnesia/20250727-054000-scriptorium-overview.md +94 -0
- data/doc/anti-amnesia/20250727-123000-anti-amnesia-conventions.md +2 -0
- data/doc/anti-amnesia/20250727-172600-cursor-rbenv-ruby-version-mystery.md +45 -0
- data/doc/anti-amnesia/20250727-172900-ai-cognitive-assessment-capabilities.md +40 -0
- data/doc/anti-amnesia/20250728-124243-aaa-syntax-clarification.md +46 -0
- data/doc/anti-amnesia/20250729-210000-reddit-autopost-integration-complete.md +158 -0
- data/doc/anti-amnesia/20250804-190500-cognitive-loop-bug.md +35 -0
- data/doc/anti-amnesia/20250804-190700-anti-amnesia-timestamping-fix.md +27 -0
- data/doc/anti-amnesia/20250807-213025.md +116 -0
- data/doc/anti-amnesia/20250901-211714-codemirror-integration-and-web-tests.md +172 -0
- data/doc/anti-amnesia/20250902-002402-backup-restore-system.md +126 -0
- data/doc/anti-amnesia/20250907-203339-backup-metadata-implementation.md +66 -0
- data/doc/banner_svg_config.md +114 -0
- data/doc/contrib.lt3 +8 -0
- data/doc/dependencies.md +281 -0
- data/doc/hacker.lt3 +5 -0
- data/doc/imported/0001-elixir-conf-2014/metadata.txt +7 -0
- data/doc/imported/0001-elixir-conf-2014/post.html +37 -0
- data/doc/imported/0001-elixir-conf-2014/source.lt3 +22 -0
- data/doc/imported/0002-programmers-and-word-processing/metadata.txt +7 -0
- data/doc/imported/0002-programmers-and-word-processing/post.html +192 -0
- data/doc/imported/0002-programmers-and-word-processing/source.lt3 +146 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/metadata.txt +7 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/post.html +60 -0
- data/doc/imported/0003-how-to-turn-your-brain-sideways/source.lt3 +40 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/metadata.txt +7 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/post.html +42 -0
- data/doc/imported/0004-upcoming-lone-star-ruby-conference/source.lt3 +24 -0
- data/doc/imported/0005-elixir-conf-2015-announced/metadata.txt +7 -0
- data/doc/imported/0005-elixir-conf-2015-announced/post.html +30 -0
- data/doc/imported/0005-elixir-conf-2015-announced/source.lt3 +16 -0
- data/doc/imported/0006-ruby-for-dinosaurs/metadata.txt +7 -0
- data/doc/imported/0006-ruby-for-dinosaurs/post.html +43 -0
- data/doc/imported/0006-ruby-for-dinosaurs/source.lt3 +27 -0
- data/doc/imported/0007-phoenix-isnt-rails/metadata.txt +7 -0
- data/doc/imported/0007-phoenix-isnt-rails/post.html +116 -0
- data/doc/imported/0007-phoenix-isnt-rails/source.lt3 +87 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/metadata.txt +7 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/post.html +129 -0
- data/doc/imported/0008-concerning-the-term-monkeypatching/source.lt3 +92 -0
- data/doc/imported/0009-announcement-coming-soon/metadata.txt +7 -0
- data/doc/imported/0009-announcement-coming-soon/post.html +33 -0
- data/doc/imported/0009-announcement-coming-soon/source.lt3 +19 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/metadata.txt +7 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/post.html +175 -0
- data/doc/imported/0010-immutable-data-ditching-the-wax-tablet/source.lt3 +139 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/metadata.txt +7 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/post.html +139 -0
- data/doc/imported/0011-computer-science-as-a-lost-art/source.lt3 +104 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/metadata.txt +7 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/post.html +42 -0
- data/doc/imported/0012-ruby-day-in-turin-italy/source.lt3 +24 -0
- data/doc/imported/0013-rubyday-was-a-success/metadata.txt +7 -0
- data/doc/imported/0013-rubyday-was-a-success/post.html +44 -0
- data/doc/imported/0013-rubyday-was-a-success/source.lt3 +27 -0
- data/doc/imported/0014-working-on-the-blogging-software/metadata.txt +7 -0
- data/doc/imported/0014-working-on-the-blogging-software/post.html +63 -0
- data/doc/imported/0014-working-on-the-blogging-software/source.lt3 +41 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/metadata.txt +7 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/post.html +172 -0
- data/doc/imported/0015-ok-its-not-really-a-lost-art/source.lt3 +134 -0
- data/doc/imported/0016-an-in-operator-for-ruby/metadata.txt +7 -0
- data/doc/imported/0016-an-in-operator-for-ruby/post.html +155 -0
- data/doc/imported/0016-an-in-operator-for-ruby/source.lt3 +106 -0
- data/doc/imported/0017-the-forgotten-mathematician/metadata.txt +7 -0
- data/doc/imported/0017-the-forgotten-mathematician/post.html +161 -0
- data/doc/imported/0017-the-forgotten-mathematician/source.lt3 +119 -0
- data/doc/imported/0018-ruby-puns/metadata.txt +7 -0
- data/doc/imported/0018-ruby-puns/post.html +46 -0
- data/doc/imported/0018-ruby-puns/source.lt3 +28 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/metadata.txt +7 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/post.html +138 -0
- data/doc/imported/0019-custom-exceptions-via-metaprogramming/source.lt3 +101 -0
- data/doc/imported/0020-fffff/metadata.txt +7 -0
- data/doc/imported/0020-fffff/post.html +24 -0
- data/doc/imported/0020-fffff/source.lt3 +12 -0
- data/doc/imported/0021-trying-ror-yet-again/metadata.txt +7 -0
- data/doc/imported/0021-trying-ror-yet-again/post.html +26 -0
- data/doc/imported/0021-trying-ror-yet-again/source.lt3 +12 -0
- data/doc/imported/0023-doctor-sleep/metadata.txt +7 -0
- data/doc/imported/0023-doctor-sleep/post.html +63 -0
- data/doc/imported/0023-doctor-sleep/source.lt3 +44 -0
- data/doc/imported/0024-just-a-test/metadata.txt +7 -0
- data/doc/imported/0024-just-a-test/post.html +24 -0
- data/doc/imported/0024-just-a-test/source.lt3 +12 -0
- data/doc/imported/import_summary.txt +98 -0
- data/doc/livetext-informal-spec.txt +65 -0
- data/doc/myuserdoc/ch-0.lt3 +31 -0
- data/doc/myuserdoc/ch-1.lt3 +37 -0
- data/doc/myuserdoc/ch-10.lt3 +22 -0
- data/doc/myuserdoc/ch-2.lt3 +37 -0
- data/doc/myuserdoc/ch-3.lt3 +19 -0
- data/doc/myuserdoc/ch-4.lt3 +43 -0
- data/doc/myuserdoc/ch-5.lt3 +22 -0
- data/doc/myuserdoc/ch-6.lt3 +19 -0
- data/doc/myuserdoc/ch-7.lt3 +16 -0
- data/doc/myuserdoc/ch-8.lt3 +13 -0
- data/doc/myuserdoc/ch-9.lt3 +19 -0
- data/doc/myuserdoc/tweak.rb +18 -0
- data/doc/myuserdoc/userdoc-toc.txt +88 -0
- data/doc/old-posts/0001-elixir-conf-2014.lt3 +24 -0
- data/doc/old-posts/0002-programmers-and-word-processing.lt3 +150 -0
- data/doc/old-posts/0003-how-to-turn-your-brain-sideways.lt3 +43 -0
- data/doc/old-posts/0004-upcoming-lone-star-ruby-conference.lt3 +26 -0
- data/doc/old-posts/0005-elixir-conf-2015-announced.lt3 +17 -0
- data/doc/old-posts/0006-ruby-for-dinosaurs.lt3 +30 -0
- data/doc/old-posts/0007-phoenix-isnt-rails.lt3 +90 -0
- data/doc/old-posts/0008-concerning-the-term-monkeypatching.lt3 +105 -0
- data/doc/old-posts/0009-announcement-coming-soon.lt3 +20 -0
- data/doc/old-posts/0010-immutable-data-ditching-the-wax-tablet.lt3 +142 -0
- data/doc/old-posts/0011-computer-science-as-a-lost-art.lt3 +117 -0
- data/doc/old-posts/0012-ruby-day-in-turin-italy.lt3 +26 -0
- data/doc/old-posts/0013-rubyday-was-a-success.lt3 +28 -0
- data/doc/old-posts/0014-working-on-the-blogging-software.lt3 +42 -0
- data/doc/old-posts/0015-ok-its-not-really-a-lost-art.lt3 +137 -0
- data/doc/old-posts/0016-an-in-operator-for-ruby.lt3 +142 -0
- data/doc/old-posts/0017-the-forgotten-mathematician.lt3 +129 -0
- data/doc/old-posts/0018-ruby-puns.lt3 +31 -0
- data/doc/old-posts/0019-custom-exceptions-via-metaprogramming.lt3 +116 -0
- data/doc/old-posts/0021-trying-ror-yet-again.lt3 +35 -0
- data/doc/old-posts/0023-doctor-sleep.lt3 +43 -0
- data/doc/old-posts/0024-just-a-test.lt3 +12 -0
- data/doc/old-posts/0025-trying-another-post.lt3 +12 -0
- data/doc/old-repo +1 -0
- data/doc/reddit_credentials_template.json +8 -0
- data/doc/reddit_integration.md +207 -0
- data/doc/user.lt3 +35 -0
- data/doc/user_guide_section_1.md +137 -0
- data/doc/user_guide_section_10.md +515 -0
- data/doc/user_guide_section_11.md +708 -0
- data/doc/user_guide_section_2.md +233 -0
- data/doc/user_guide_section_3.md +5 -0
- data/doc/user_guide_section_4.md +221 -0
- data/doc/user_guide_section_5.md +243 -0
- data/doc/user_guide_section_6.md +147 -0
- data/doc/user_guide_section_7.md +311 -0
- data/doc/user_guide_section_8.md +224 -0
- data/doc/user_guide_section_9.md +375 -0
- data/lib/rouge/lexers/livetext.rb +74 -0
- data/lib/scriptorium/api.rb +2373 -0
- data/lib/scriptorium/banner_svg.rb +729 -0
- data/lib/scriptorium/contract.rb +34 -0
- data/lib/scriptorium/exceptions.rb +201 -1
- data/lib/scriptorium/helpers.rb +675 -0
- data/lib/scriptorium/post.rb +259 -0
- data/lib/scriptorium/reddit.rb +83 -0
- data/lib/scriptorium/repo.rb +938 -0
- data/lib/scriptorium/standard_files.rb +149 -0
- data/lib/scriptorium/support/bootstrap/css.txt +5 -0
- data/lib/scriptorium/support/bootstrap/js.txt +4 -0
- data/lib/scriptorium/support/common_js/clipboard.js +35 -0
- data/lib/scriptorium/support/common_js/content-loader.js +187 -0
- data/lib/scriptorium/support/common_js/navigation.js +52 -0
- data/lib/scriptorium/support/common_js/syntax-highlighting.js +27 -0
- data/lib/scriptorium/support/config/reddit.txt +10 -0
- data/lib/scriptorium/support/config/reddit_template.txt +17 -0
- data/lib/scriptorium/support/config/social.txt +8 -0
- data/lib/scriptorium/support/highlight/css.txt +2 -0
- data/lib/scriptorium/support/highlight/custom.css +119 -0
- data/lib/scriptorium/support/highlight/js.txt +1 -0
- data/lib/scriptorium/support/post_index/config.txt +15 -0
- data/lib/scriptorium/support/post_index/style.css +55 -0
- data/lib/scriptorium/support/templates/index_entry.lt3 +16 -0
- data/lib/scriptorium/support/templates/initial_post.lt3 +12 -0
- data/lib/scriptorium/support/templates/layout.txt +5 -0
- data/lib/scriptorium/support/templates/post.lt3 +104 -0
- data/lib/scriptorium/support/theme/footer.lt3 +2 -0
- data/lib/scriptorium/support/theme/header.lt3 +4 -0
- data/lib/scriptorium/support/theme/left.lt3 +3 -0
- data/lib/scriptorium/support/theme/main.lt3 +5 -0
- data/lib/scriptorium/support/theme/right.lt3 +3 -0
- data/lib/scriptorium/theme.rb +192 -0
- data/lib/scriptorium/version.rb +1 -1
- data/lib/scriptorium/view.rb +1021 -0
- data/lib/scriptorium/widgets/featured_posts.rb +149 -0
- data/lib/scriptorium/widgets/links.rb +112 -0
- data/lib/scriptorium/widgets/pages.rb +133 -0
- data/lib/scriptorium/widgets/widget.rb +133 -0
- data/lib/scriptorium.rb +38 -34
- data/lib/skeleton.rb +10 -1
- data/scriptorium.gemspec +17 -5
- data/test/README.md +69 -0
- data/test/WEB_INTEGRATION_README.md +196 -0
- data/test/all +83 -0
- data/test/api_demo.rb +99 -0
- data/test/assets/imagenotfound.jpg +0 -0
- data/test/assets/images/.DS_Store +0 -0
- data/test/assets/images/README.md +27 -0
- data/test/assets/images/odd_aspect.png +0 -0
- data/test/assets/images/perfect.png +0 -0
- data/test/assets/images/small.png +0 -0
- data/test/assets/images/tall.png +0 -0
- data/test/assets/images/very_tall.png +0 -0
- data/test/assets/images/very_wide.png +0 -0
- data/test/assets/images/wide.png +0 -0
- data/test/assets/testbanner.jpg +0 -0
- data/test/banner_svg/simple_helpers.rb +13 -0
- data/test/banner_svg/unit.rb +1000 -0
- data/test/config/deployment.txt +5 -0
- data/test/ed_test.rb +204 -0
- data/test/integration/cursor_banner_combinations.rb +193 -0
- data/test/integration/cursor_banner_features.rb +374 -0
- data/test/integration/integration_test.rb +326 -0
- data/test/integration/preview_flow_test.rb +94 -0
- data/test/livetext_plugin_test.rb +500 -0
- data/test/manual/asset_mgmt.rb +67 -0
- data/test/manual/banner-tests/index.html +45 -0
- data/test/manual/banner-tests/svg.txt +3 -0
- data/test/manual/banner-tests/test01.html +122 -0
- data/test/manual/banner-tests/test02.html +122 -0
- data/test/manual/banner-tests/test03.html +122 -0
- data/test/manual/banner-tests/test04.html +129 -0
- data/test/manual/banner-tests/test05.html +129 -0
- data/test/manual/banner-tests/test06.html +129 -0
- data/test/manual/banner-tests/test07.html +129 -0
- data/test/manual/banner-tests/test08.html +123 -0
- data/test/manual/banner-tests/test09.html +123 -0
- data/test/manual/banner-tests/test10.html +123 -0
- data/test/manual/banner-tests/test11.html +123 -0
- data/test/manual/banner-tests/test12.html +123 -0
- data/test/manual/banner-tests/test13.html +123 -0
- data/test/manual/banner-tests/test14.html +123 -0
- data/test/manual/banner-tests/test15.html +122 -0
- data/test/manual/banner-tests/test16.html +122 -0
- data/test/manual/banner-tests/test17.html +122 -0
- data/test/manual/banner-tests/test18.html +132 -0
- data/test/manual/banner-tests/test19.html +132 -0
- data/test/manual/banner-tests/test20.html +132 -0
- data/test/manual/banner-tests/test21.html +132 -0
- data/test/manual/banner-tests/test22.html +132 -0
- data/test/manual/banner-tests/test23.html +132 -0
- data/test/manual/banner-tests/test24.html +132 -0
- data/test/manual/banner-tests/test25.html +131 -0
- data/test/manual/banner_environment.rb +205 -0
- data/test/manual/codemirror_demo.html +773 -0
- data/test/manual/create_posts_for_web.rb +114 -0
- data/test/manual/environment.rb +67 -0
- data/test/manual/make_banner.rb +153 -0
- data/test/manual/preview_manual_test.rb +129 -0
- data/test/manual/sample_banner_config.txt +12 -0
- data/test/manual/test_advanced_widgets.rb +73 -0
- data/test/manual/test_banner_combinations.rb +120 -0
- data/test/manual/test_banner_features.rb +306 -0
- data/test/manual/test_banner_integration.rb +115 -0
- data/test/manual/test_banner_radial.rb +87 -0
- data/test/manual/test_basic_posts.rb +47 -0
- data/test/manual/test_layout_widgets.rb +40 -0
- data/test/manual/test_pagination.rb +24 -0
- data/test/manual/test_random_posts.rb +38 -0
- data/test/manual/test_syntax_highlighting.rb +167 -0
- data/test/rubytext/rubytext_comprehensive_test.rb +307 -0
- data/test/rubytext/rubytext_demo_test.rb +42 -0
- data/test/rubytext/rubytext_testing_guide.md +277 -0
- data/test/run_automated_tests.rb +45 -0
- data/test/staging/.DS_Store +0 -0
- data/test/support/preview_utils.rb +88 -0
- data/test/syntax_highlighting_test.lt3 +124 -0
- data/test/test_gem_assets.rb +48 -0
- data/test/test_helpers.rb +240 -0
- data/test/tui_editor_integration_test.rb +296 -0
- data/test/tui_integration_test.rb +883 -0
- data/test/unit/api.rb +1776 -0
- data/test/unit/asset_management.rb +219 -0
- data/test/unit/backup_test.rb +451 -0
- data/test/unit/clipboard_test.rb +60 -0
- data/test/unit/contract_test.rb +69 -0
- data/test/unit/core.rb +1211 -0
- data/test/unit/deploy_config_test.rb +248 -0
- data/test/unit/deploy_test.rb +478 -0
- data/test/unit/edit_post_test.rb +168 -0
- data/test/unit/gem_asset_management.rb +183 -0
- data/test/unit/livetext_basic.rb +57 -0
- data/test/unit/livetext_compatibility.rb +82 -0
- data/test/unit/parse_cmd_test.rb +260 -0
- data/test/unit/permalink_copy_test.rb +211 -0
- data/test/unit/post.rb +309 -0
- data/test/unit/post_index_config_test.rb +258 -0
- data/test/unit/post_state_helpers_test.rb +137 -0
- data/test/unit/read_commented_file_test.rb +278 -0
- data/test/unit/reddit_test.rb +235 -0
- data/test/unit/repo.rb +569 -0
- data/test/unit/social_test.rb +366 -0
- data/test/unit/syntax_highlighting.rb +70 -0
- data/test/unit/theme_management_test.rb +91 -0
- data/test/unit/view.rb +498 -0
- data/test/unit/widgets.rb +669 -0
- data/test/web_integration_test.rb +231 -0
- data/test/web_test_helper.rb +218 -0
- data/test/web_workflow_test.rb +527 -0
- data/test/wizard_test.rb +123 -0
- data/ui/README.md +67 -0
- data/ui/common/lib/ui_common.rb +8 -0
- data/ui/rubytext/README.md +191 -0
- data/ui/rubytext/bin/scriptorium-rubytext +402 -0
- data/ui/rubytext/lib/rubytext_ui.rb +300 -0
- data/ui/tui/bin/scriptorium +1890 -0
- data/ui/tui/test/tui_test.rb +23 -0
- data/ui/web/app/app.rb +2600 -0
- data/ui/web/app/assets/livetext_mode.js +244 -0
- data/ui/web/app/error_helpers.rb +150 -0
- data/ui/web/app/views/advanced_config.erb +196 -0
- data/ui/web/app/views/asset_management.erb +645 -0
- data/ui/web/app/views/backup_management.erb +238 -0
- data/ui/web/app/views/banner_config.erb +200 -0
- data/ui/web/app/views/config_widget.erb +232 -0
- data/ui/web/app/views/configure_view.erb +401 -0
- data/ui/web/app/views/dashboard.erb +154 -0
- data/ui/web/app/views/deploy_config.erb +149 -0
- data/ui/web/app/views/edit_pages.erb +363 -0
- data/ui/web/app/views/edit_post.erb +175 -0
- data/ui/web/app/views/edit_theme.erb +73 -0
- data/ui/web/app/views/edit_theme_file.erb +74 -0
- data/ui/web/app/views/error_page.erb +29 -0
- data/ui/web/app/views/header_config.erb +155 -0
- data/ui/web/app/views/layout_config.erb +147 -0
- data/ui/web/app/views/navbar_config.erb +411 -0
- data/ui/web/app/views/theme_management.erb +130 -0
- data/ui/web/app/views/view_dashboard.erb +779 -0
- data/ui/web/app/views/widgets.erb +249 -0
- data/ui/web/bin/scriptorium-web +164 -0
- data/ui/web/test/web_basic_test.rb +38 -0
- data/ui/web/test_navbar.txt +7 -0
- data/ui/web/tmp/timing.log +17 -0
- data/ui/web/tmp/web_server.log +0 -0
- metadata +434 -8
- data/lib/scriptorium/engine.rb +0 -22
- data/test/engine/unit.rb +0 -44
@@ -0,0 +1,219 @@
|
|
1
|
+
# Test file for asset management functionality
|
2
|
+
# Tests the $$asset and $$image Livetext functions
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require_relative '../../lib/scriptorium'
|
6
|
+
require_relative '../test_helpers'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
class TestAssetManagement < Minitest::Test
|
10
|
+
include TestHelpers
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@repo_name = "test/scriptorium-TEST"
|
14
|
+
@view_name = "asset_test_view"
|
15
|
+
|
16
|
+
# Create test repo and API
|
17
|
+
@api = Scriptorium::API.new
|
18
|
+
@api.create_repo(@repo_name)
|
19
|
+
@api.open_repo(@repo_name)
|
20
|
+
|
21
|
+
# Create a view for testing
|
22
|
+
@api.create_view(@view_name, "Asset Test View")
|
23
|
+
|
24
|
+
# Create test posts
|
25
|
+
@api.create_post("Test Post 1", "This is the first test post for asset testing.")
|
26
|
+
@api.create_post("Test Post 2", "This is the second test post for asset testing.")
|
27
|
+
|
28
|
+
# Generate the view to create output directory structure
|
29
|
+
@api.generate_view(@view_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def teardown
|
33
|
+
FileUtils.rm_rf("test/scriptorium-TEST") # Commented out for debugging
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_001_asset_function_basic_functionality
|
37
|
+
# Test that asset() function returns correct paths for existing assets
|
38
|
+
# Create a global asset
|
39
|
+
global_file = Tempfile.new(['global-image', '.jpg'])
|
40
|
+
global_file.write("global image content")
|
41
|
+
global_file.close
|
42
|
+
@api.upload_asset(global_file.path, filename: "global-image.jpg")
|
43
|
+
|
44
|
+
# Update existing post 0001 with asset reference
|
45
|
+
source_file = "test/scriptorium-TEST/posts/0001/source.lt3"
|
46
|
+
File.write(source_file, "Global asset: $$asset[global-image.jpg]")
|
47
|
+
|
48
|
+
# Generate the view
|
49
|
+
@api.generate_view(@view_name)
|
50
|
+
|
51
|
+
# Check that the asset path is correct in generated HTML
|
52
|
+
html_file = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0001-test-post-1.html"
|
53
|
+
html_content = File.read(html_file)
|
54
|
+
|
55
|
+
assert_includes_concise_string html_content, "Global asset: ../assets/global-image.jpg", "Asset function should return correct path"
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_002_asset_function_missing_asset
|
59
|
+
# Test that asset() function returns fallback for missing assets
|
60
|
+
# Update existing post 0002 with missing asset reference
|
61
|
+
source_file = "test/scriptorium-TEST/posts/0002/source.lt3"
|
62
|
+
File.write(source_file, "Missing asset: $$asset[missing-image.jpg]")
|
63
|
+
|
64
|
+
# Generate the view
|
65
|
+
@api.generate_view(@view_name)
|
66
|
+
|
67
|
+
# Check that the fallback path is used
|
68
|
+
html_file = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0002-test-post-2.html"
|
69
|
+
html_content = File.read(html_file)
|
70
|
+
|
71
|
+
assert_includes_concise_string html_content, "Missing asset: ../assets/imagenotfound.jpg", "Asset function should return fallback for missing assets"
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_003_image_function_basic_functionality
|
75
|
+
# Test that image() function generates correct HTML
|
76
|
+
# Create a global asset
|
77
|
+
global_file = Tempfile.new(['global-image', '.jpg'])
|
78
|
+
global_file.write("global image content")
|
79
|
+
global_file.close
|
80
|
+
@api.upload_asset(global_file.path, filename: "global-image.jpg")
|
81
|
+
|
82
|
+
# Update existing post 0001 with image reference
|
83
|
+
source_file = "test/scriptorium-TEST/posts/0001/source.lt3"
|
84
|
+
File.write(source_file, "Image:\n.image global-image.jpg")
|
85
|
+
|
86
|
+
# Generate the view
|
87
|
+
@api.generate_view(@view_name)
|
88
|
+
|
89
|
+
# Check that the image tag is correct
|
90
|
+
html_file = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0001-test-post-1.html"
|
91
|
+
html_content = File.read(html_file)
|
92
|
+
|
93
|
+
assert_includes_concise_string html_content, "<img src=../assets/global-image.jpg alt='No alt text'></img>", "Image function should generate correct HTML"
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_004_image_function_with_real_asset
|
97
|
+
# Test that image() function works with actual test assets
|
98
|
+
# Upload a real test asset
|
99
|
+
@api.upload_asset("test/assets/testbanner.jpg", filename: "testbanner.jpg")
|
100
|
+
|
101
|
+
# Update existing post 0002 with image reference
|
102
|
+
source_file = "test/scriptorium-TEST/posts/0002/source.lt3"
|
103
|
+
File.write(source_file, "Real asset:\n.image testbanner.jpg")
|
104
|
+
|
105
|
+
# Generate the view
|
106
|
+
@api.generate_view(@view_name)
|
107
|
+
|
108
|
+
# Check that the image tag is correct
|
109
|
+
html_file = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0002-test-post-2.html"
|
110
|
+
html_content = File.read(html_file)
|
111
|
+
|
112
|
+
assert_includes_concise_string html_content, "<img src=../assets/testbanner.jpg alt='No alt text'></img>", "Image function should work with real assets"
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_005_global_asset_copied_to_view
|
116
|
+
# Test that global assets are automatically copied to view assets during generation
|
117
|
+
# First, create a global asset file directly in the repo
|
118
|
+
global_asset_path = "test/scriptorium-TEST/assets/global-copy-test.jpg"
|
119
|
+
FileUtils.cp("test/assets/testbanner.jpg", global_asset_path)
|
120
|
+
|
121
|
+
# Update existing post 0001 to reference the global asset
|
122
|
+
source_file = "test/scriptorium-TEST/posts/0001/source.lt3"
|
123
|
+
File.write(source_file, "Global asset copy test:\n.image global-copy-test.jpg")
|
124
|
+
|
125
|
+
# Assert initial state before generation
|
126
|
+
view_assets_dir = "test/scriptorium-TEST/views/#{@view_name}/assets"
|
127
|
+
|
128
|
+
# The global asset we just created should NOT be in view assets yet
|
129
|
+
view_asset_path = "#{view_assets_dir}/global-copy-test.jpg"
|
130
|
+
refute File.exist?(view_asset_path), "Global asset should not be in view assets before generation"
|
131
|
+
|
132
|
+
# Generate the view (this should copy the global asset to the view)
|
133
|
+
@api.generate_view(@view_name)
|
134
|
+
|
135
|
+
# Check that the global asset was copied to the view assets directory
|
136
|
+
view_asset_path = "test/scriptorium-TEST/views/#{@view_name}/assets/global-copy-test.jpg"
|
137
|
+
assert File.exist?(view_asset_path), "Global asset should be copied to view assets directory"
|
138
|
+
|
139
|
+
# Check that the image tag references the correct path
|
140
|
+
html_file = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0001-test-post-1.html"
|
141
|
+
html_content = File.read(html_file)
|
142
|
+
assert_includes_concise_string html_content, "<img src=../assets/global-copy-test.jpg alt='No alt text'></img>", "Image should reference the copied asset"
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_006_asset_priority_hierarchy
|
146
|
+
# Test that post assets take precedence over view assets with same filename
|
147
|
+
# 1. Create a file with specific contents and upload to view assets
|
148
|
+
view_file = Tempfile.new(['priority-test', '.txt'])
|
149
|
+
view_file.write("File 1, view asset")
|
150
|
+
view_file.close
|
151
|
+
@api.upload_asset(view_file.path, 'view', @view_name, filename: "priority-test.txt")
|
152
|
+
|
153
|
+
# 2. Create another file with SAME NAME but different contents and upload to post 2 assets
|
154
|
+
post_file = Tempfile.new(['priority-test', '.txt'])
|
155
|
+
post_file.write("File 2, post asset")
|
156
|
+
post_file.close
|
157
|
+
@api.upload_asset(post_file.path, 'post', 2, filename: "priority-test.txt")
|
158
|
+
|
159
|
+
# 3. Update both posts to reference the asset
|
160
|
+
source_file_1 = "test/scriptorium-TEST/posts/0001/source.lt3"
|
161
|
+
File.write(source_file_1, "Asset priority test:\n$$asset[priority-test.txt]")
|
162
|
+
|
163
|
+
source_file_2 = "test/scriptorium-TEST/posts/0002/source.lt3"
|
164
|
+
File.write(source_file_2, "Asset priority test:\n$$asset[priority-test.txt]")
|
165
|
+
|
166
|
+
# 4. Generate the view
|
167
|
+
@api.generate_view(@view_name)
|
168
|
+
|
169
|
+
# 5. Check that post 1 refers to the view asset (since it has no post asset)
|
170
|
+
html_file_1 = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0001-test-post-1.html"
|
171
|
+
html_content_1 = File.read(html_file_1)
|
172
|
+
assert_includes_concise_string html_content_1, "../assets/priority-test.txt", "Post 1 should reference the view asset"
|
173
|
+
|
174
|
+
# 6. Check that post 2 refers to its own post asset (post assets take precedence)
|
175
|
+
html_file_2 = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0002-test-post-2.html"
|
176
|
+
html_content_2 = File.read(html_file_2)
|
177
|
+
assert_includes_concise_string html_content_2, "../assets/posts/0002/priority-test.txt", "Post 2 should reference its post asset"
|
178
|
+
|
179
|
+
# 7. Verify the actual files exist in the correct locations
|
180
|
+
view_asset_path = "test/scriptorium-TEST/views/#{@view_name}/assets/priority-test.txt"
|
181
|
+
post_asset_path = "test/scriptorium-TEST/posts/0002/assets/priority-test.txt"
|
182
|
+
|
183
|
+
assert File.exist?(view_asset_path), "View asset should exist"
|
184
|
+
assert File.exist?(post_asset_path), "Post asset should exist"
|
185
|
+
|
186
|
+
# 8. Check that the files have the correct contents
|
187
|
+
view_content = File.read(view_asset_path)
|
188
|
+
post_content = File.read(post_asset_path)
|
189
|
+
|
190
|
+
assert_includes view_content, "File 1, view asset", "View asset should have correct content"
|
191
|
+
assert_includes post_content, "File 2, post asset", "Post asset should have correct content"
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_007_asset_paths_in_post_context
|
195
|
+
# Test that asset paths work correctly in post page context (subdirectory)
|
196
|
+
|
197
|
+
# 1. Create a test asset
|
198
|
+
test_file = Tempfile.new(['path-test', '.txt'])
|
199
|
+
test_file.write("Path test asset")
|
200
|
+
test_file.close
|
201
|
+
@api.upload_asset(test_file.path, 'post', 1, filename: "path-test.txt")
|
202
|
+
|
203
|
+
# 2. Update post 1 to reference the asset
|
204
|
+
source_file_1 = "test/scriptorium-TEST/posts/0001/source.lt3"
|
205
|
+
File.write(source_file_1, "Asset path test:\n$$asset[path-test.txt]")
|
206
|
+
|
207
|
+
# 3. Generate the view
|
208
|
+
@api.generate_view(@view_name)
|
209
|
+
|
210
|
+
# 4. Check post page - should have ../assets/posts/0001/path-test.txt
|
211
|
+
post_file = "test/scriptorium-TEST/views/#{@view_name}/output/posts/0001-test-post-1.html"
|
212
|
+
post_content = File.read(post_file)
|
213
|
+
assert_includes_concise_string post_content, "../assets/posts/0001/path-test.txt", "Post page should reference assets with ../ prefix"
|
214
|
+
|
215
|
+
# 5. Verify the asset file exists in the output directory
|
216
|
+
asset_file = "test/scriptorium-TEST/views/#{@view_name}/output/assets/posts/0001/path-test.txt"
|
217
|
+
assert File.exist?(asset_file), "Asset should be copied to output directory"
|
218
|
+
end
|
219
|
+
end
|
@@ -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
|