ro 4.4.0 → 5.1.0
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/Gemfile.lock +42 -16
 - data/MIGRATION.md +320 -0
 - data/README.md +31 -19
 - data/a.yml +60 -0
 - data/bin/ro +10 -0
 - data/lib/ro/_lib.rb +1 -1
 - data/lib/ro/asset.rb +48 -6
 - data/lib/ro/collection.rb +51 -13
 - data/lib/ro/migrator.rb +285 -0
 - data/lib/ro/node.rb +53 -13
 - data/lib/ro/root.rb +75 -1
 - data/lib/ro/script/migrate.rb +204 -0
 - data/lib/ro/script/server.rb +1 -1
 - data/lib/ro.rb +1 -0
 - data/public/api/ro/index-1.json +82 -148
 - data/public/api/ro/index.json +82 -148
 - data/public/api/ro/nerd/fastest-possible-embeddings/index.json +7 -8
 - data/public/api/ro/nerd/ima/index.json +3 -4
 - data/public/api/ro/nerd/index/index.json +5 -6
 - data/public/api/ro/nerd/index-1.json +15 -18
 - data/public/api/ro/nerd/index.json +15 -18
 - data/public/api/ro/pages/contact/index.json +4 -5
 - data/public/api/ro/pages/cv/index.json +3 -4
 - data/public/api/ro/pages/disco/index.json +9 -10
 - data/public/api/ro/pages/index/index.json +2 -3
 - data/public/api/ro/pages/index-1.json +25 -82
 - data/public/api/ro/pages/index.json +25 -82
 - data/public/api/ro/pages/jess/index.json +4 -5
 - data/public/api/ro/pages/now/index.json +3 -4
 - data/public/api/ro/posts/almost-died-in-an-ice-cave/index.json +21 -22
 - data/public/api/ro/posts/facebook-and-global-extremism/index.json +8 -9
 - data/public/api/ro/posts/index-1.json +42 -48
 - data/public/api/ro/posts/index.json +42 -48
 - data/public/api/ro/posts/lemmings-considered-harmful/index.json +3 -4
 - data/public/api/ro/posts/lost-in-the-desert/index.json +3 -4
 - data/public/api/ro/posts/mission/index.json +3 -4
 - data/public/api/ro/posts/return-your-laptop/index.json +4 -5
 - data/ro.gemspec +247 -18
 - data/specs/001-simplify-asset-structure/IMPLEMENTATION_SUMMARY.md +212 -0
 - data/specs/001-simplify-asset-structure/checklists/requirements.md +36 -0
 - data/specs/001-simplify-asset-structure/contracts/collection_api.md +407 -0
 - data/specs/001-simplify-asset-structure/contracts/migrator_api.md +461 -0
 - data/specs/001-simplify-asset-structure/contracts/node_api.md +294 -0
 - data/specs/001-simplify-asset-structure/data-model.md +381 -0
 - data/specs/001-simplify-asset-structure/plan.md +90 -0
 - data/specs/001-simplify-asset-structure/quickstart.md +575 -0
 - data/specs/001-simplify-asset-structure/research.md +333 -0
 - data/specs/001-simplify-asset-structure/spec.md +127 -0
 - data/specs/001-simplify-asset-structure/tasks.md +349 -0
 - data/test/fixtures/new_structure/mixed/test-json.json +5 -0
 - data/test/fixtures/new_structure/mixed/test-yaml.yml +3 -0
 - data/test/fixtures/new_structure/posts/metadata-only.yml +7 -0
 - data/test/fixtures/new_structure/posts/nested-test/assets/subdirectory/image.png +2 -0
 - data/test/fixtures/new_structure/posts/nested-test.yml +7 -0
 - data/test/fixtures/new_structure/posts/sample-post/assets/body.md +5 -0
 - data/test/fixtures/new_structure/posts/sample-post/assets/image.jpg +2 -0
 - data/test/fixtures/new_structure/posts/sample-post.yml +7 -0
 - data/test/fixtures/old_structure/posts/assets-only/assets/test.txt +1 -0
 - data/test/fixtures/old_structure/posts/sample-post/assets/body.md +5 -0
 - data/test/fixtures/old_structure/posts/sample-post/assets/image.jpg +2 -0
 - data/test/fixtures/old_structure/posts/sample-post/attributes.yml +2 -0
 - data/test/integration/ro_integration_test.rb +165 -0
 - data/test/test_helper.rb +149 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/attributes.yml +7 -0
 - data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post.yml +7 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/body.md +5 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post.yml +7 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/migration_test_1760940939/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/migration_test_1760941048/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/assets-only/assets/test.txt +1 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/assets/body.md +5 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/assets/image.jpg +2 -0
 - data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/sample-post/attributes.yml +2 -0
 - data/test/tmp/new_structure_test_1760746452/mixed/test-json.json +5 -0
 - data/test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml +3 -0
 - data/test/tmp/new_structure_test_1760746452/posts/metadata-only.yml +7 -0
 - data/test/tmp/new_structure_test_1760746452/posts/nested-test/subdirectory/image.png +2 -0
 - data/test/tmp/new_structure_test_1760746452/posts/nested-test.yml +7 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post/body.md +5 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg +2 -0
 - data/test/tmp/new_structure_test_1760746452/posts/sample-post.yml +7 -0
 - data/test/unit/asset_test.rb +90 -0
 - data/test/unit/collection_test.rb +127 -0
 - data/test/unit/migrator_test.rb +209 -0
 - data/test/unit/node_test.rb +138 -0
 - metadata +127 -19
 - data/public/api/ro/pages/about/index.json +0 -60
 - /data/public/ro/nerd/{fastest-possible-embeddings/attributes.yml → fastest-possible-embeddings.yml} +0 -0
 - /data/public/ro/nerd/{ima/attributes.yml → ima.yml} +0 -0
 - /data/public/ro/nerd/{index/attributes.yml → index.yml} +0 -0
 - /data/public/ro/pages/{contact/attributes.yml → contact.yml} +0 -0
 - /data/public/ro/pages/{cv/attributes.yml → cv.yml} +0 -0
 - /data/public/ro/pages/{disco/attributes.yml → disco.yml} +0 -0
 - /data/public/ro/pages/{index/attributes.yml → index.yml} +0 -0
 - /data/public/ro/pages/{jess/attributes.yml → jess.yml} +0 -0
 - /data/public/ro/pages/{now/attributes.yml → now.yml} +0 -0
 - /data/public/ro/posts/{almost-died-in-an-ice-cave/attributes.yml → almost-died-in-an-ice-cave.yml} +0 -0
 - /data/public/ro/posts/{facebook-and-global-extremism/attributes.yml → facebook-and-global-extremism.yml} +0 -0
 - /data/public/ro/posts/{lemmings-considered-harmful/attributes.yml → lemmings-considered-harmful.yml} +0 -0
 - /data/public/ro/posts/{lost-in-the-desert/attributes.yml → lost-in-the-desert.yml} +0 -0
 - /data/public/ro/posts/{mission/attributes.yml → mission.yml} +0 -0
 - /data/public/ro/posts/{return-your-laptop/attributes.yml → return-your-laptop.yml} +0 -0
 
    
        data/test/test_helper.rb
    ADDED
    
    | 
         @@ -0,0 +1,149 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Test Helper for ro gem v5.0 tests
         
     | 
| 
      
 2 
     | 
    
         
            +
            #
         
     | 
| 
      
 3 
     | 
    
         
            +
            # Common setup, assertions, and utilities for unit and integration tests
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'yaml'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            # Load the ro gem
         
     | 
| 
      
 11 
     | 
    
         
            +
            $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
         
     | 
| 
      
 12 
     | 
    
         
            +
            require 'ro'
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            module TestHelper
         
     | 
| 
      
 15 
     | 
    
         
            +
              # Get path to test fixtures directory
         
     | 
| 
      
 16 
     | 
    
         
            +
              def fixtures_path
         
     | 
| 
      
 17 
     | 
    
         
            +
                Pathname.new(File.expand_path('../fixtures', __FILE__))
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              # Get path to old structure fixtures
         
     | 
| 
      
 21 
     | 
    
         
            +
              def old_structure_path
         
     | 
| 
      
 22 
     | 
    
         
            +
                fixtures_path / 'old_structure'
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              # Get path to new structure fixtures
         
     | 
| 
      
 26 
     | 
    
         
            +
              def new_structure_path
         
     | 
| 
      
 27 
     | 
    
         
            +
                fixtures_path / 'new_structure'
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              # Create a temporary test directory
         
     | 
| 
      
 31 
     | 
    
         
            +
              def create_temp_dir(name = 'test')
         
     | 
| 
      
 32 
     | 
    
         
            +
                dir = Pathname.new(File.expand_path("../tmp/#{name}_#{Time.now.to_i}", __FILE__))
         
     | 
| 
      
 33 
     | 
    
         
            +
                FileUtils.mkdir_p(dir)
         
     | 
| 
      
 34 
     | 
    
         
            +
                dir
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              # Clean up temporary directory
         
     | 
| 
      
 38 
     | 
    
         
            +
              def cleanup_temp_dir(dir)
         
     | 
| 
      
 39 
     | 
    
         
            +
                FileUtils.rm_rf(dir) if dir && dir.exist?
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              # Assert that a path exists
         
     | 
| 
      
 43 
     | 
    
         
            +
              def assert_path_exists(path, message = nil)
         
     | 
| 
      
 44 
     | 
    
         
            +
                assert Pathname.new(path).exist?, message || "Expected path to exist: #{path}"
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              # Assert that a file contains specific content
         
     | 
| 
      
 48 
     | 
    
         
            +
              def assert_file_contains(path, content, message = nil)
         
     | 
| 
      
 49 
     | 
    
         
            +
                actual = File.read(path)
         
     | 
| 
      
 50 
     | 
    
         
            +
                assert actual.include?(content), message || "Expected file #{path} to contain: #{content}"
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              # Assert that a YAML file has a specific key/value
         
     | 
| 
      
 54 
     | 
    
         
            +
              def assert_yaml_has(path, key, value = nil, message = nil)
         
     | 
| 
      
 55 
     | 
    
         
            +
                data = YAML.load_file(path)
         
     | 
| 
      
 56 
     | 
    
         
            +
                assert data.key?(key.to_s) || data.key?(key.to_sym), message || "Expected YAML to have key: #{key}"
         
     | 
| 
      
 57 
     | 
    
         
            +
                if value
         
     | 
| 
      
 58 
     | 
    
         
            +
                  actual_value = data[key.to_s] || data[key.to_sym]
         
     | 
| 
      
 59 
     | 
    
         
            +
                  assert_equal value, actual_value, message || "Expected #{key} to equal #{value}, got #{actual_value}"
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
              # Create a test node in old structure format
         
     | 
| 
      
 64 
     | 
    
         
            +
              def create_old_structure_node(collection_path, node_id, attributes = {}, assets = [])
         
     | 
| 
      
 65 
     | 
    
         
            +
                node_dir = collection_path / node_id
         
     | 
| 
      
 66 
     | 
    
         
            +
                FileUtils.mkdir_p(node_dir)
         
     | 
| 
      
 67 
     | 
    
         
            +
                FileUtils.mkdir_p(node_dir / 'assets')
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                # Write attributes.yml
         
     | 
| 
      
 70 
     | 
    
         
            +
                File.write(node_dir / 'attributes.yml', attributes.to_yaml)
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                # Create asset files
         
     | 
| 
      
 73 
     | 
    
         
            +
                assets.each do |asset_file|
         
     | 
| 
      
 74 
     | 
    
         
            +
                  asset_path = node_dir / 'assets' / asset_file
         
     | 
| 
      
 75 
     | 
    
         
            +
                  FileUtils.mkdir_p(asset_path.dirname)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  File.write(asset_path, "test content for #{asset_file}")
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                node_dir
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              # Create a test node in new structure format
         
     | 
| 
      
 83 
     | 
    
         
            +
              def create_new_structure_node(collection_path, node_id, attributes = {}, assets = [])
         
     | 
| 
      
 84 
     | 
    
         
            +
                FileUtils.mkdir_p(collection_path)
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                # Write metadata file (identifier.yml)
         
     | 
| 
      
 87 
     | 
    
         
            +
                File.write(collection_path / "#{node_id}.yml", attributes.to_yaml)
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                # Create asset directory and files
         
     | 
| 
      
 90 
     | 
    
         
            +
                if assets.any?
         
     | 
| 
      
 91 
     | 
    
         
            +
                  node_dir = collection_path / node_id
         
     | 
| 
      
 92 
     | 
    
         
            +
                  FileUtils.mkdir_p(node_dir)
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                  assets.each do |asset_file|
         
     | 
| 
      
 95 
     | 
    
         
            +
                    asset_path = node_dir / asset_file
         
     | 
| 
      
 96 
     | 
    
         
            +
                    FileUtils.mkdir_p(asset_path.dirname)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    File.write(asset_path, "test content for #{asset_file}")
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                collection_path / "#{node_id}.yml"
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
            # Base test class
         
     | 
| 
      
 106 
     | 
    
         
            +
            class RoTestCase
         
     | 
| 
      
 107 
     | 
    
         
            +
              include TestHelper
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 110 
     | 
    
         
            +
                # Override in subclasses
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              def teardown
         
     | 
| 
      
 114 
     | 
    
         
            +
                # Override in subclasses
         
     | 
| 
      
 115 
     | 
    
         
            +
              end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
              # Simple assertion methods (compatible with custom test runner)
         
     | 
| 
      
 118 
     | 
    
         
            +
              def assert(condition, message = "Assertion failed")
         
     | 
| 
      
 119 
     | 
    
         
            +
                raise AssertionError, message unless condition
         
     | 
| 
      
 120 
     | 
    
         
            +
              end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
              def assert_equal(expected, actual, message = nil)
         
     | 
| 
      
 123 
     | 
    
         
            +
                msg = message || "Expected #{expected.inspect}, got #{actual.inspect}"
         
     | 
| 
      
 124 
     | 
    
         
            +
                assert expected == actual, msg
         
     | 
| 
      
 125 
     | 
    
         
            +
              end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
              def assert_nil(actual, message = nil)
         
     | 
| 
      
 128 
     | 
    
         
            +
                msg = message || "Expected nil, got #{actual.inspect}"
         
     | 
| 
      
 129 
     | 
    
         
            +
                assert actual.nil?, msg
         
     | 
| 
      
 130 
     | 
    
         
            +
              end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
              def assert_not_nil(actual, message = nil)
         
     | 
| 
      
 133 
     | 
    
         
            +
                msg = message || "Expected non-nil value"
         
     | 
| 
      
 134 
     | 
    
         
            +
                assert !actual.nil?, msg
         
     | 
| 
      
 135 
     | 
    
         
            +
              end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
              def assert_raises(exception_class, message = nil)
         
     | 
| 
      
 138 
     | 
    
         
            +
                begin
         
     | 
| 
      
 139 
     | 
    
         
            +
                  yield
         
     | 
| 
      
 140 
     | 
    
         
            +
                  assert false, message || "Expected #{exception_class} to be raised"
         
     | 
| 
      
 141 
     | 
    
         
            +
                rescue exception_class
         
     | 
| 
      
 142 
     | 
    
         
            +
                  # Expected
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
              end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
              class AssertionError < StandardError; end
         
     | 
| 
      
 147 
     | 
    
         
            +
            end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            puts "Test helper loaded successfully"
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
    
        data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
    
        data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
    
        data/test/tmp/migration_test_1760940939.backup.20251020061539/posts/assets-only/assets/test.txt
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     | 
    
        data/test/tmp/migration_test_1760941048.backup.20251020061728/posts/assets-only/assets/test.txt
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            test asset
         
     |