ro 4.4.0 → 5.0.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 +30 -18
 - data/a.yml +60 -0
 - data/bin/ro +10 -0
 - data/lib/ro/_lib.rb +1 -1
 - data/lib/ro/asset.rb +46 -5
 - 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/ro.gemspec +207 -16
 - 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/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 +111 -18
 - /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
 
| 
         @@ -0,0 +1,127 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Unit tests for Ro::Collection with new structure support
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative '../test_helper'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class CollectionTest < RoTestCase
         
     | 
| 
      
 7 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 8 
     | 
    
         
            +
                @root = Ro::Root.new(new_structure_path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                @collection = Ro::Collection.new(new_structure_path / 'posts')
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              # T011: Test Collection#metadata_files
         
     | 
| 
      
 13 
     | 
    
         
            +
              def test_metadata_files_returns_yml_and_json_files
         
     | 
| 
      
 14 
     | 
    
         
            +
                files = @collection.metadata_files
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                assert_not_nil files, "metadata_files should not be nil"
         
     | 
| 
      
 17 
     | 
    
         
            +
                assert files.is_a?(Array), "metadata_files should return an Array"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                # Should find .yml and .json files
         
     | 
| 
      
 20 
     | 
    
         
            +
                yml_files = files.select { |f| f.to_s.end_with?('.yml') }
         
     | 
| 
      
 21 
     | 
    
         
            +
                assert yml_files.any?, "Should find at least one .yml file"
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # Should be Pathname or Ro::Path objects
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert files.all? { |f| f.is_a?(Pathname) || f.is_a?(Ro::Path) }, "All files should be Pathname or Ro::Path objects"
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def test_metadata_files_excludes_directories
         
     | 
| 
      
 28 
     | 
    
         
            +
                files = @collection.metadata_files
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                # Should only return files, not directories
         
     | 
| 
      
 31 
     | 
    
         
            +
                assert files.all?(&:file?), "metadata_files should only return files, not directories"
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              def test_metadata_files_sorted
         
     | 
| 
      
 35 
     | 
    
         
            +
                files = @collection.metadata_files
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                # Should be sorted
         
     | 
| 
      
 38 
     | 
    
         
            +
                assert_equal files.sort.map(&:to_s), files.map(&:to_s), "metadata_files should be sorted"
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              # T012: Test Collection#each with new structure
         
     | 
| 
      
 42 
     | 
    
         
            +
              def test_each_iterates_nodes_from_metadata_files
         
     | 
| 
      
 43 
     | 
    
         
            +
                nodes = []
         
     | 
| 
      
 44 
     | 
    
         
            +
                @collection.each do |node|
         
     | 
| 
      
 45 
     | 
    
         
            +
                  nodes << node
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                assert nodes.any?, "Collection should have at least one node"
         
     | 
| 
      
 49 
     | 
    
         
            +
                assert nodes.all? { |n| n.is_a?(Ro::Node) }, "All items should be Node instances"
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def test_each_creates_nodes_from_metadata_files
         
     | 
| 
      
 53 
     | 
    
         
            +
                node_ids = []
         
     | 
| 
      
 54 
     | 
    
         
            +
                @collection.each do |node|
         
     | 
| 
      
 55 
     | 
    
         
            +
                  node_ids << node.id
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                # Should find nodes based on metadata files
         
     | 
| 
      
 59 
     | 
    
         
            +
                assert node_ids.include?('sample-post'), "Should find sample-post node"
         
     | 
| 
      
 60 
     | 
    
         
            +
                assert node_ids.include?('metadata-only'), "Should find metadata-only node"
         
     | 
| 
      
 61 
     | 
    
         
            +
                assert node_ids.include?('nested-test'), "Should find nested-test node"
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
              def test_each_without_block_returns_enumerator
         
     | 
| 
      
 65 
     | 
    
         
            +
                enum = @collection.each
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                assert enum.is_a?(Enumerator), "each without block should return Enumerator"
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                nodes = enum.to_a
         
     | 
| 
      
 70 
     | 
    
         
            +
                assert nodes.any?, "Enumerator should yield nodes"
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
            end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            # Run the tests
         
     | 
| 
      
 75 
     | 
    
         
            +
            if __FILE__ == $0
         
     | 
| 
      
 76 
     | 
    
         
            +
              test = CollectionTest.new
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              puts "Running Collection unit tests..."
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              begin
         
     | 
| 
      
 81 
     | 
    
         
            +
                test.setup
         
     | 
| 
      
 82 
     | 
    
         
            +
                test.test_metadata_files_returns_yml_and_json_files
         
     | 
| 
      
 83 
     | 
    
         
            +
                puts "✓ test_metadata_files_returns_yml_and_json_files"
         
     | 
| 
      
 84 
     | 
    
         
            +
              rescue => e
         
     | 
| 
      
 85 
     | 
    
         
            +
                puts "✗ test_metadata_files_returns_yml_and_json_files: #{e.message}"
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
              begin
         
     | 
| 
      
 89 
     | 
    
         
            +
                test.setup
         
     | 
| 
      
 90 
     | 
    
         
            +
                test.test_metadata_files_excludes_directories
         
     | 
| 
      
 91 
     | 
    
         
            +
                puts "✓ test_metadata_files_excludes_directories"
         
     | 
| 
      
 92 
     | 
    
         
            +
              rescue => e
         
     | 
| 
      
 93 
     | 
    
         
            +
                puts "✗ test_metadata_files_excludes_directories: #{e.message}"
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              begin
         
     | 
| 
      
 97 
     | 
    
         
            +
                test.setup
         
     | 
| 
      
 98 
     | 
    
         
            +
                test.test_metadata_files_sorted
         
     | 
| 
      
 99 
     | 
    
         
            +
                puts "✓ test_metadata_files_sorted"
         
     | 
| 
      
 100 
     | 
    
         
            +
              rescue => e
         
     | 
| 
      
 101 
     | 
    
         
            +
                puts "✗ test_metadata_files_sorted: #{e.message}"
         
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
              begin
         
     | 
| 
      
 105 
     | 
    
         
            +
                test.setup
         
     | 
| 
      
 106 
     | 
    
         
            +
                test.test_each_iterates_nodes_from_metadata_files
         
     | 
| 
      
 107 
     | 
    
         
            +
                puts "✓ test_each_iterates_nodes_from_metadata_files"
         
     | 
| 
      
 108 
     | 
    
         
            +
              rescue => e
         
     | 
| 
      
 109 
     | 
    
         
            +
                puts "✗ test_each_iterates_nodes_from_metadata_files: #{e.message}"
         
     | 
| 
      
 110 
     | 
    
         
            +
              end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
              begin
         
     | 
| 
      
 113 
     | 
    
         
            +
                test.setup
         
     | 
| 
      
 114 
     | 
    
         
            +
                test.test_each_creates_nodes_from_metadata_files
         
     | 
| 
      
 115 
     | 
    
         
            +
                puts "✓ test_each_creates_nodes_from_metadata_files"
         
     | 
| 
      
 116 
     | 
    
         
            +
              rescue => e
         
     | 
| 
      
 117 
     | 
    
         
            +
                puts "✗ test_each_creates_nodes_from_metadata_files: #{e.message}"
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
              begin
         
     | 
| 
      
 121 
     | 
    
         
            +
                test.setup
         
     | 
| 
      
 122 
     | 
    
         
            +
                test.test_each_without_block_returns_enumerator
         
     | 
| 
      
 123 
     | 
    
         
            +
                puts "✓ test_each_without_block_returns_enumerator"
         
     | 
| 
      
 124 
     | 
    
         
            +
              rescue => e
         
     | 
| 
      
 125 
     | 
    
         
            +
                puts "✗ test_each_without_block_returns_enumerator: #{e.message}"
         
     | 
| 
      
 126 
     | 
    
         
            +
              end
         
     | 
| 
      
 127 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,209 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Unit tests for Ro::Migrator with asset structure migration
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative '../test_helper'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class MigratorTest < RoTestCase
         
     | 
| 
      
 7 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 8 
     | 
    
         
            +
                @old_root = Ro::Root.new(old_structure_path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                @temp_dir = create_temp_dir('migration_test')
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                # Copy old structure fixtures to temp for migration testing
         
     | 
| 
      
 12 
     | 
    
         
            +
                FileUtils.cp_r(old_structure_path.to_s + '/.', @temp_dir.to_s)
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def teardown
         
     | 
| 
      
 16 
     | 
    
         
            +
                FileUtils.rm_rf(@temp_dir) if @temp_dir && @temp_dir.exist?
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              # T042: Test Migrator#initialize
         
     | 
| 
      
 20 
     | 
    
         
            +
              def test_initialize_with_root_path
         
     | 
| 
      
 21 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                assert_not_nil migrator, "Migrator should be created"
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert_equal @temp_dir.to_s, migrator.root_path.to_s, "Migrator should store root path"
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def test_initialize_with_options
         
     | 
| 
      
 28 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir, dry_run: true, backup: true, verbose: true)
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                assert migrator.dry_run?, "Migrator should be in dry-run mode"
         
     | 
| 
      
 31 
     | 
    
         
            +
                assert migrator.backup?, "Migrator should have backup enabled"
         
     | 
| 
      
 32 
     | 
    
         
            +
                assert migrator.verbose?, "Migrator should be verbose"
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              # T043: Test Migrator#validate detecting old structure
         
     | 
| 
      
 36 
     | 
    
         
            +
              def test_validate_detects_old_structure
         
     | 
| 
      
 37 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 38 
     | 
    
         
            +
                result = migrator.validate
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                assert result[:has_old_structure], "Should detect old structure"
         
     | 
| 
      
 41 
     | 
    
         
            +
                assert result[:old_nodes].any?, "Should find old structure nodes"
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              # T044: Test Migrator#validate detecting new structure
         
     | 
| 
      
 45 
     | 
    
         
            +
              def test_validate_detects_new_structure
         
     | 
| 
      
 46 
     | 
    
         
            +
                new_dir = create_temp_dir('new_structure_test')
         
     | 
| 
      
 47 
     | 
    
         
            +
                FileUtils.cp_r(new_structure_path.to_s + '/.', new_dir.to_s)
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                migrator = Ro::Migrator.new(new_dir)
         
     | 
| 
      
 50 
     | 
    
         
            +
                result = migrator.validate
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                assert result[:has_new_structure], "Should detect new structure"
         
     | 
| 
      
 53 
     | 
    
         
            +
                assert result[:new_nodes].any?, "Should find new structure nodes"
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                FileUtils.rm_rf(new_dir)
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
              # T045: Test Migrator#preview generating migration plan
         
     | 
| 
      
 59 
     | 
    
         
            +
              def test_preview_generates_migration_plan
         
     | 
| 
      
 60 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 61 
     | 
    
         
            +
                plan = migrator.preview
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                assert plan.is_a?(Array), "Preview should return array of migration steps"
         
     | 
| 
      
 64 
     | 
    
         
            +
                assert plan.any?, "Preview should have migration steps"
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                # Check that plan includes details about what will be migrated
         
     | 
| 
      
 67 
     | 
    
         
            +
                first_step = plan.first
         
     | 
| 
      
 68 
     | 
    
         
            +
                assert first_step.key?(:node_id), "Step should include node_id"
         
     | 
| 
      
 69 
     | 
    
         
            +
                assert first_step.key?(:old_path), "Step should include old_path"
         
     | 
| 
      
 70 
     | 
    
         
            +
                assert first_step.key?(:new_metadata_file), "Step should include new_metadata_file"
         
     | 
| 
      
 71 
     | 
    
         
            +
                assert first_step.key?(:new_asset_dir), "Step should include new_asset_dir"
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
              # T046: Test Migrator#migrate_node for single node
         
     | 
| 
      
 75 
     | 
    
         
            +
              def test_migrate_node_single_node
         
     | 
| 
      
 76 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 77 
     | 
    
         
            +
                posts_dir = @temp_dir / 'posts'
         
     | 
| 
      
 78 
     | 
    
         
            +
                old_node_dir = posts_dir / 'sample-post'
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                result = migrator.migrate_node('posts', 'sample-post')
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                assert result[:success], "Migration should succeed"
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                # Check new structure was created
         
     | 
| 
      
 85 
     | 
    
         
            +
                metadata_file = posts_dir / 'sample-post.yml'
         
     | 
| 
      
 86 
     | 
    
         
            +
                asset_dir = posts_dir / 'sample-post'
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                assert metadata_file.exist?, "Metadata file should exist at collection level"
         
     | 
| 
      
 89 
     | 
    
         
            +
                assert asset_dir.directory?, "Asset directory should exist"
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                # Check old attributes file was removed, but assets/ directory remains
         
     | 
| 
      
 92 
     | 
    
         
            +
                old_attributes_file = asset_dir / 'attributes.yml'
         
     | 
| 
      
 93 
     | 
    
         
            +
                assets_dir = asset_dir / 'assets'
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                assert !old_attributes_file.exist?, "Old attributes.yml should be removed"
         
     | 
| 
      
 96 
     | 
    
         
            +
                assert assets_dir.exist?, "Assets/ directory should still exist (not moved)"
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              # T047: Test Migrator#migrate_collection for entire collection
         
     | 
| 
      
 100 
     | 
    
         
            +
              def test_migrate_collection
         
     | 
| 
      
 101 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                result = migrator.migrate_collection('posts')
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                assert result[:success], "Collection migration should succeed"
         
     | 
| 
      
 106 
     | 
    
         
            +
                assert result[:migrated_count] > 0, "Should migrate at least one node"
         
     | 
| 
      
 107 
     | 
    
         
            +
              end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
              # T048: Test Migrator#migrate for full migration
         
     | 
| 
      
 110 
     | 
    
         
            +
              def test_migrate_full_root
         
     | 
| 
      
 111 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                result = migrator.migrate
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                assert result[:success], "Full migration should succeed"
         
     | 
| 
      
 116 
     | 
    
         
            +
                assert result[:collections_migrated] > 0, "Should migrate at least one collection"
         
     | 
| 
      
 117 
     | 
    
         
            +
                assert result[:nodes_migrated] > 0, "Should migrate at least one node"
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
              # T049: Test Migrator#backup creating backup
         
     | 
| 
      
 121 
     | 
    
         
            +
              def test_backup_creates_backup
         
     | 
| 
      
 122 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir, backup: true)
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                backup_path = migrator.backup
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                assert backup_path.exist?, "Backup should be created"
         
     | 
| 
      
 127 
     | 
    
         
            +
                assert backup_path.to_s.include?('.backup.'), "Backup should have .backup. in name"
         
     | 
| 
      
 128 
     | 
    
         
            +
              end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
              # T050: Test Migrator#rollback restoring from backup
         
     | 
| 
      
 131 
     | 
    
         
            +
              def test_rollback_restores_from_backup
         
     | 
| 
      
 132 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir, backup: true)
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                # Create backup, migrate, then rollback
         
     | 
| 
      
 135 
     | 
    
         
            +
                backup_path = migrator.backup
         
     | 
| 
      
 136 
     | 
    
         
            +
                migrator.migrate
         
     | 
| 
      
 137 
     | 
    
         
            +
                result = migrator.rollback
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                assert result[:success], "Rollback should succeed"
         
     | 
| 
      
 140 
     | 
    
         
            +
                assert result[:restored_from].to_s == backup_path.to_s, "Should restore from backup"
         
     | 
| 
      
 141 
     | 
    
         
            +
              end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
              # T051: Test Migrator finds nodes without attributes.yml
         
     | 
| 
      
 144 
     | 
    
         
            +
              def test_validate_finds_nodes_without_attributes
         
     | 
| 
      
 145 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 146 
     | 
    
         
            +
                validation = migrator.validate
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                # Should find both sample-post (with attributes) and assets-only (without)
         
     | 
| 
      
 149 
     | 
    
         
            +
                assert validation[:old_nodes].size >= 2, "Should find at least 2 nodes"
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                assets_only_node = validation[:old_nodes].find { |n| n[:node_id] == 'assets-only' }
         
     | 
| 
      
 152 
     | 
    
         
            +
                assert_not_nil assets_only_node, "Should find assets-only node"
         
     | 
| 
      
 153 
     | 
    
         
            +
                assert !assets_only_node[:has_attributes], "assets-only should not have attributes"
         
     | 
| 
      
 154 
     | 
    
         
            +
              end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
              # T052: Test migration creates empty metadata for nodes without attributes
         
     | 
| 
      
 157 
     | 
    
         
            +
              def test_migrate_node_without_attributes
         
     | 
| 
      
 158 
     | 
    
         
            +
                migrator = Ro::Migrator.new(@temp_dir)
         
     | 
| 
      
 159 
     | 
    
         
            +
                posts_dir = @temp_dir / 'posts'
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                result = migrator.migrate_node('posts', 'assets-only')
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                assert result[:success], "Migration should succeed"
         
     | 
| 
      
 164 
     | 
    
         
            +
                assert !result[:had_attributes], "Node should not have had attributes"
         
     | 
| 
      
 165 
     | 
    
         
            +
             
     | 
| 
      
 166 
     | 
    
         
            +
                # Check metadata file was created
         
     | 
| 
      
 167 
     | 
    
         
            +
                metadata_file = posts_dir / 'assets-only.yml'
         
     | 
| 
      
 168 
     | 
    
         
            +
                assert metadata_file.exist?, "Empty metadata file should be created"
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                # Check it contains empty hash
         
     | 
| 
      
 171 
     | 
    
         
            +
                content = YAML.load_file(metadata_file.to_s)
         
     | 
| 
      
 172 
     | 
    
         
            +
                assert content.is_a?(Hash), "Metadata should be a hash"
         
     | 
| 
      
 173 
     | 
    
         
            +
              end
         
     | 
| 
      
 174 
     | 
    
         
            +
            end
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
            # Run the tests
         
     | 
| 
      
 177 
     | 
    
         
            +
            if __FILE__ == $0
         
     | 
| 
      
 178 
     | 
    
         
            +
              test = MigratorTest.new
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
              puts "Running Migrator unit tests..."
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
              tests = [
         
     | 
| 
      
 183 
     | 
    
         
            +
                :test_initialize_with_root_path,
         
     | 
| 
      
 184 
     | 
    
         
            +
                :test_initialize_with_options,
         
     | 
| 
      
 185 
     | 
    
         
            +
                :test_validate_detects_old_structure,
         
     | 
| 
      
 186 
     | 
    
         
            +
                :test_validate_detects_new_structure,
         
     | 
| 
      
 187 
     | 
    
         
            +
                :test_preview_generates_migration_plan,
         
     | 
| 
      
 188 
     | 
    
         
            +
                :test_migrate_node_single_node,
         
     | 
| 
      
 189 
     | 
    
         
            +
                :test_migrate_collection,
         
     | 
| 
      
 190 
     | 
    
         
            +
                :test_migrate_full_root,
         
     | 
| 
      
 191 
     | 
    
         
            +
                :test_backup_creates_backup,
         
     | 
| 
      
 192 
     | 
    
         
            +
                :test_rollback_restores_from_backup,
         
     | 
| 
      
 193 
     | 
    
         
            +
                :test_validate_finds_nodes_without_attributes,
         
     | 
| 
      
 194 
     | 
    
         
            +
                :test_migrate_node_without_attributes
         
     | 
| 
      
 195 
     | 
    
         
            +
              ]
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
              tests.each do |test_method|
         
     | 
| 
      
 198 
     | 
    
         
            +
                begin
         
     | 
| 
      
 199 
     | 
    
         
            +
                  test.setup
         
     | 
| 
      
 200 
     | 
    
         
            +
                  test.send(test_method)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  test.teardown
         
     | 
| 
      
 202 
     | 
    
         
            +
                  puts "✓ #{test_method}"
         
     | 
| 
      
 203 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 204 
     | 
    
         
            +
                  puts "✗ #{test_method}: #{e.message}"
         
     | 
| 
      
 205 
     | 
    
         
            +
                  puts "  #{e.backtrace.first}"
         
     | 
| 
      
 206 
     | 
    
         
            +
                  test.teardown rescue nil
         
     | 
| 
      
 207 
     | 
    
         
            +
                end
         
     | 
| 
      
 208 
     | 
    
         
            +
              end
         
     | 
| 
      
 209 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,138 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Unit tests for Ro::Node with new structure support
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative '../test_helper'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class NodeTest < RoTestCase
         
     | 
| 
      
 7 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 8 
     | 
    
         
            +
                @root = Ro::Root.new(new_structure_path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                @collection = Ro::Collection.new(new_structure_path / 'posts')
         
     | 
| 
      
 10 
     | 
    
         
            +
                @metadata_file = new_structure_path / 'posts' / 'sample-post.yml'
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              # T013: Test Node initialization with metadata_file parameter
         
     | 
| 
      
 14 
     | 
    
         
            +
              def test_initialize_with_metadata_file
         
     | 
| 
      
 15 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                assert_not_nil node, "Node should be created"
         
     | 
| 
      
 18 
     | 
    
         
            +
                assert_equal @collection, node.collection, "Node should store collection reference"
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def test_initialize_stores_metadata_file
         
     | 
| 
      
 22 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                assert_not_nil node.metadata_file, "Node should store metadata_file"
         
     | 
| 
      
 25 
     | 
    
         
            +
                assert_equal @metadata_file.to_s, node.metadata_file.to_s, "metadata_file path should match constructor parameter"
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def test_initialize_raises_error_for_missing_file
         
     | 
| 
      
 29 
     | 
    
         
            +
                missing_file = new_structure_path / 'posts' / 'nonexistent.yml'
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                assert_raises(Errno::ENOENT) do
         
     | 
| 
      
 32 
     | 
    
         
            +
                  Ro::Node.new(@collection, missing_file)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              # T014: Test Node#id derived from metadata filename
         
     | 
| 
      
 37 
     | 
    
         
            +
              def test_id_derived_from_metadata_filename
         
     | 
| 
      
 38 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                assert_equal 'sample-post', node.id, "Node ID should be derived from metadata filename (without extension)"
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              def test_id_strips_yml_extension
         
     | 
| 
      
 44 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                assert !node.id.end_with?('.yml'), "Node ID should not include .yml extension"
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              def test_id_handles_json_extension
         
     | 
| 
      
 50 
     | 
    
         
            +
                json_file = new_structure_path / 'mixed' / 'test-json.json'
         
     | 
| 
      
 51 
     | 
    
         
            +
                collection = Ro::Collection.new(new_structure_path / 'mixed')
         
     | 
| 
      
 52 
     | 
    
         
            +
                node = Ro::Node.new(collection, json_file)
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                assert_equal 'test-json', node.id, "Node ID should be derived from .json filename"
         
     | 
| 
      
 55 
     | 
    
         
            +
                assert !node.id.end_with?('.json'), "Node ID should not include .json extension"
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
              # T015: Test Node#asset_dir returns assets/ subdirectory
         
     | 
| 
      
 59 
     | 
    
         
            +
              def test_asset_dir_returns_assets_subdirectory
         
     | 
| 
      
 60 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 61 
     | 
    
         
            +
                expected_dir = new_structure_path / 'posts' / 'sample-post' / 'assets'
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                assert_equal expected_dir.to_s, node.asset_dir.to_s, "asset_dir should return assets/ subdirectory"
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
              def test_asset_dir_ends_with_assets
         
     | 
| 
      
 67 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                assert node.asset_dir.to_s.end_with?('/assets'), "asset_dir should end with /assets"
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              def test_asset_dir_for_metadata_only_node
         
     | 
| 
      
 73 
     | 
    
         
            +
                metadata_only_file = new_structure_path / 'posts' / 'metadata-only.yml'
         
     | 
| 
      
 74 
     | 
    
         
            +
                node = Ro::Node.new(@collection, metadata_only_file)
         
     | 
| 
      
 75 
     | 
    
         
            +
                expected_dir = new_structure_path / 'posts' / 'metadata-only' / 'assets'
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                # asset_dir should still return the expected path even if directory doesn't exist
         
     | 
| 
      
 78 
     | 
    
         
            +
                assert_equal expected_dir.to_s, node.asset_dir.to_s
         
     | 
| 
      
 79 
     | 
    
         
            +
              end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
              # T016: Test Node#_load_base_attributes loading from external metadata file
         
     | 
| 
      
 82 
     | 
    
         
            +
              def test_load_base_attributes_from_metadata_file
         
     | 
| 
      
 83 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                # Trigger attribute loading (happens in initialize)
         
     | 
| 
      
 86 
     | 
    
         
            +
                attrs = node.attributes
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                assert_not_nil attrs, "Attributes should be loaded"
         
     | 
| 
      
 89 
     | 
    
         
            +
                assert attrs.is_a?(Hash), "Attributes should be a Hash"
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def test_attributes_loaded_from_correct_file
         
     | 
| 
      
 93 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                assert_equal 'Sample Post (New Structure)', node[:title], "Should load title from metadata file"
         
     | 
| 
      
 96 
     | 
    
         
            +
                assert_equal 'Test Author', node[:author], "Should load author from metadata file"
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              def test_attributes_support_symbol_and_string_keys
         
     | 
| 
      
 100 
     | 
    
         
            +
                node = Ro::Node.new(@collection, @metadata_file)
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                # Should work with both symbol and string keys
         
     | 
| 
      
 103 
     | 
    
         
            +
                assert_equal node[:title], node['title'], "Attributes should work with both symbol and string keys"
         
     | 
| 
      
 104 
     | 
    
         
            +
              end
         
     | 
| 
      
 105 
     | 
    
         
            +
            end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
            # Run the tests
         
     | 
| 
      
 108 
     | 
    
         
            +
            if __FILE__ == $0
         
     | 
| 
      
 109 
     | 
    
         
            +
              test = NodeTest.new
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
              puts "Running Node unit tests..."
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              tests = [
         
     | 
| 
      
 114 
     | 
    
         
            +
                :test_initialize_with_metadata_file,
         
     | 
| 
      
 115 
     | 
    
         
            +
                :test_initialize_stores_metadata_file,
         
     | 
| 
      
 116 
     | 
    
         
            +
                :test_initialize_raises_error_for_missing_file,
         
     | 
| 
      
 117 
     | 
    
         
            +
                :test_id_derived_from_metadata_filename,
         
     | 
| 
      
 118 
     | 
    
         
            +
                :test_id_strips_yml_extension,
         
     | 
| 
      
 119 
     | 
    
         
            +
                :test_id_handles_json_extension,
         
     | 
| 
      
 120 
     | 
    
         
            +
                :test_asset_dir_returns_assets_subdirectory,
         
     | 
| 
      
 121 
     | 
    
         
            +
                :test_asset_dir_ends_with_assets,
         
     | 
| 
      
 122 
     | 
    
         
            +
                :test_asset_dir_for_metadata_only_node,
         
     | 
| 
      
 123 
     | 
    
         
            +
                :test_load_base_attributes_from_metadata_file,
         
     | 
| 
      
 124 
     | 
    
         
            +
                :test_attributes_loaded_from_correct_file,
         
     | 
| 
      
 125 
     | 
    
         
            +
                :test_attributes_support_symbol_and_string_keys
         
     | 
| 
      
 126 
     | 
    
         
            +
              ]
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
              tests.each do |test_method|
         
     | 
| 
      
 129 
     | 
    
         
            +
                begin
         
     | 
| 
      
 130 
     | 
    
         
            +
                  test.setup
         
     | 
| 
      
 131 
     | 
    
         
            +
                  test.send(test_method)
         
     | 
| 
      
 132 
     | 
    
         
            +
                  puts "✓ #{test_method}"
         
     | 
| 
      
 133 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 134 
     | 
    
         
            +
                  puts "✗ #{test_method}: #{e.message}"
         
     | 
| 
      
 135 
     | 
    
         
            +
                  puts "  #{e.backtrace.first}"
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
              end
         
     | 
| 
      
 138 
     | 
    
         
            +
            end
         
     |