lutaml-store 0.1.1 → 0.2.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/.rubocop_todo.yml +79 -275
- data/Gemfile.lock +11 -4
- data/TODO.impl/1-lutaml-hal-migration.md +54 -18
- data/lib/lutaml/store/attribute_updater.rb +12 -3
- data/lib/lutaml/store/basic_store.rb +4 -0
- data/lib/lutaml/store/cache_store.rb +1 -1
- data/lib/lutaml/store/database_store.rb +1 -1
- data/lib/lutaml/store/format/yamls.rb +2 -2
- data/lib/lutaml/store/http_cache.rb +4 -1
- data/lib/lutaml/store/model_registry.rb +4 -1
- data/lib/lutaml/store/package_definition.rb +62 -0
- data/lib/lutaml/store/package_store.rb +149 -0
- data/lib/lutaml/store/package_transport.rb +450 -0
- data/lib/lutaml/store/version.rb +1 -1
- data/lib/lutaml/store.rb +3 -0
- data/lutaml-store.gemspec +1 -0
- data/spec/lutaml/store/cache_store_spec.rb +2 -2
- data/spec/lutaml/store/file_io_spec.rb +6 -5
- data/spec/lutaml/store/format/yamls_spec.rb +80 -0
- data/spec/lutaml/store/format_round_trip_spec.rb +2 -2
- data/spec/lutaml/store/package_definition_spec.rb +89 -0
- data/spec/lutaml/store/package_store_spec.rb +153 -0
- data/spec/lutaml/store/package_transport/directory_transport_spec.rb +139 -0
- data/spec/lutaml/store/package_transport/zip_transport_spec.rb +85 -0
- data/spec/support/simple_test_model.rb +15 -0
- data/spec/support/yamls_test_model.rb +35 -0
- metadata +25 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "support/simple_test_model"
|
|
5
|
+
require "support/yamls_test_model"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Lutaml::Store::PackageDefinition do
|
|
8
|
+
subject(:definition) do
|
|
9
|
+
described_class.new(
|
|
10
|
+
name: :test,
|
|
11
|
+
metadata_model: SimpleTestModel,
|
|
12
|
+
metadata_file: "meta.yaml"
|
|
13
|
+
) do |pkg|
|
|
14
|
+
pkg.model(model: YamlsTestModel, dir: "items",
|
|
15
|
+
layout: :separate, key: :id, default_format: :yamls)
|
|
16
|
+
pkg.model(model: SimpleTestModel, file: "config.yaml",
|
|
17
|
+
key: :id, default_format: :yaml)
|
|
18
|
+
pkg.asset("images", type: :directory)
|
|
19
|
+
pkg.asset("readme.txt", type: :file)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "stores name" do
|
|
24
|
+
expect(definition.name).to eq(:test)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "stores metadata model and file" do
|
|
28
|
+
expect(definition.metadata_model).to eq(SimpleTestModel)
|
|
29
|
+
expect(definition.metadata_file).to eq("meta.yaml")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "stores model entries" do
|
|
33
|
+
expect(definition.model_entries.length).to eq(2)
|
|
34
|
+
|
|
35
|
+
yamls_entry = definition.model_entries.first
|
|
36
|
+
expect(yamls_entry.model).to eq(YamlsTestModel)
|
|
37
|
+
expect(yamls_entry.dir).to eq("items")
|
|
38
|
+
expect(yamls_entry.layout).to eq(:separate)
|
|
39
|
+
expect(yamls_entry.key).to eq(:id)
|
|
40
|
+
expect(yamls_entry.default_format).to eq(:yamls)
|
|
41
|
+
|
|
42
|
+
simple_entry = definition.model_entries.last
|
|
43
|
+
expect(simple_entry.model).to eq(SimpleTestModel)
|
|
44
|
+
expect(simple_entry.file).to eq("config.yaml")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "stores asset entries" do
|
|
48
|
+
expect(definition.asset_entries.length).to eq(2)
|
|
49
|
+
expect(definition.asset_entries[0].path).to eq("images")
|
|
50
|
+
expect(definition.asset_entries[0].type).to eq(:directory)
|
|
51
|
+
expect(definition.asset_entries[1].path).to eq("readme.txt")
|
|
52
|
+
expect(definition.asset_entries[1].type).to eq(:file)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "dir/file mutual exclusivity" do
|
|
56
|
+
it "raises when both dir and file are specified" do
|
|
57
|
+
expect do
|
|
58
|
+
definition.model(model: SimpleTestModel, dir: "d", file: "f.yaml", key: :id)
|
|
59
|
+
end.to raise_error(ArgumentError, /Specify dir: or file:, not both/)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "#entry_for" do
|
|
64
|
+
it "finds entry by model class" do
|
|
65
|
+
entry = definition.entry_for(YamlsTestModel)
|
|
66
|
+
expect(entry).not_to be_nil
|
|
67
|
+
expect(entry.dir).to eq("items")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "returns nil for unknown model" do
|
|
71
|
+
expect(definition.entry_for(String)).to be_nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "#model_classes" do
|
|
76
|
+
it "returns registered model classes" do
|
|
77
|
+
expect(definition.model_classes).to contain_exactly(YamlsTestModel, SimpleTestModel)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe "#database_store_models" do
|
|
82
|
+
it "includes model entries only (metadata is stored separately)" do
|
|
83
|
+
configs = definition.database_store_models
|
|
84
|
+
expect(configs.length).to eq(2) # 2 model entries, no metadata
|
|
85
|
+
models = configs.map { |c| c[:model] }
|
|
86
|
+
expect(models).to contain_exactly(YamlsTestModel, SimpleTestModel)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "support/simple_test_model"
|
|
5
|
+
require "support/yamls_test_model"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Lutaml::Store::PackageStore do
|
|
8
|
+
let(:definition) do
|
|
9
|
+
Lutaml::Store::PackageDefinition.new(name: :test) do |pkg|
|
|
10
|
+
pkg.model(model: SimpleTestModel, dir: "items",
|
|
11
|
+
layout: :separate, key: :id, default_format: :yaml)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
let(:store) { described_class.new(definition) }
|
|
16
|
+
|
|
17
|
+
describe "#add_model / #models_for" do
|
|
18
|
+
it "stores and retrieves model instances" do
|
|
19
|
+
model = SimpleTestModel.new(id: "test-1", name: "Test")
|
|
20
|
+
store.add_model(model)
|
|
21
|
+
expect(store.models_for(SimpleTestModel)).to include(model)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "stores multiple instances" do
|
|
25
|
+
3.times { |i| store.add_model(SimpleTestModel.new(id: "test-#{i}")) }
|
|
26
|
+
expect(store.model_count(SimpleTestModel)).to eq(3)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#fetch_model" do
|
|
31
|
+
it "retrieves by key" do
|
|
32
|
+
model = SimpleTestModel.new(id: "abc", name: "ABC")
|
|
33
|
+
store.add_model(model)
|
|
34
|
+
expect(store.fetch_model(SimpleTestModel, "abc").name).to eq("ABC")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns nil for missing key" do
|
|
38
|
+
expect(store.fetch_model(SimpleTestModel, "missing")).to be_nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#model_exists?" do
|
|
43
|
+
it "returns true for existing key" do
|
|
44
|
+
store.add_model(SimpleTestModel.new(id: "exists"))
|
|
45
|
+
expect(store.model_exists?(SimpleTestModel, "exists")).to be true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "returns false for missing key" do
|
|
49
|
+
expect(store.model_exists?(SimpleTestModel, "missing")).to be false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "#remove_model" do
|
|
54
|
+
it "removes by key" do
|
|
55
|
+
store.add_model(SimpleTestModel.new(id: "remove-me"))
|
|
56
|
+
store.remove_model(SimpleTestModel, "remove-me")
|
|
57
|
+
expect(store.model_exists?(SimpleTestModel, "remove-me")).to be false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "#metadata=" do
|
|
62
|
+
let(:def_with_metadata) do
|
|
63
|
+
Lutaml::Store::PackageDefinition.new(
|
|
64
|
+
name: :test, metadata_model: SimpleTestModel,
|
|
65
|
+
metadata_file: "meta.yaml", metadata_key: :id
|
|
66
|
+
) do |pkg|
|
|
67
|
+
pkg.model(model: SimpleTestModel, dir: "items",
|
|
68
|
+
layout: :separate, key: :id, default_format: :yaml)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "stores metadata" do
|
|
73
|
+
store = described_class.new(def_with_metadata)
|
|
74
|
+
store.metadata = SimpleTestModel.new(id: "meta", name: "Metadata")
|
|
75
|
+
expect(store.metadata.name).to eq("Metadata")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe "#add_asset / #asset" do
|
|
80
|
+
it "stores and retrieves raw content" do
|
|
81
|
+
store.add_asset("images/logo.png", "PNG_DATA")
|
|
82
|
+
expect(store.asset("images/logo.png")).to eq("PNG_DATA")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "lists asset paths" do
|
|
86
|
+
store.add_asset("a.txt", "A")
|
|
87
|
+
store.add_asset("b.txt", "B")
|
|
88
|
+
expect(store.asset_paths).to contain_exactly("a.txt", "b.txt")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "#remove_asset" do
|
|
93
|
+
it "removes an asset" do
|
|
94
|
+
store.add_asset("remove.txt", "data")
|
|
95
|
+
store.remove_asset("remove.txt")
|
|
96
|
+
expect(store.asset("remove.txt")).to be_nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe "#clear_all" do
|
|
101
|
+
it "removes everything" do
|
|
102
|
+
store.add_model(SimpleTestModel.new(id: "x"))
|
|
103
|
+
store.add_asset("file.txt", "data")
|
|
104
|
+
store.clear_all
|
|
105
|
+
expect(store.model_count(SimpleTestModel)).to eq(0)
|
|
106
|
+
expect(store.asset_paths).to be_empty
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "#stats" do
|
|
111
|
+
it "reports model counts and asset count" do
|
|
112
|
+
store.add_model(SimpleTestModel.new(id: "1"))
|
|
113
|
+
store.add_asset("f.txt", "x")
|
|
114
|
+
stats = store.stats
|
|
115
|
+
expect(stats[:package]).to eq(:test)
|
|
116
|
+
expect(stats[:models][SimpleTestModel.name]).to eq(1)
|
|
117
|
+
expect(stats[:assets]).to eq(1)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "#resolve_formats" do
|
|
122
|
+
let(:def_with_two) do
|
|
123
|
+
Lutaml::Store::PackageDefinition.new(name: :test) do |pkg|
|
|
124
|
+
pkg.model(model: SimpleTestModel, dir: "a", key: :id, default_format: :yaml)
|
|
125
|
+
pkg.model(model: SimpleTestModel, dir: "b", key: :id, default_format: :yamls)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
let(:two_model_store) { described_class.new(def_with_two) }
|
|
130
|
+
|
|
131
|
+
it "global format overrides all models" do
|
|
132
|
+
formats = two_model_store.send(:resolve_formats, :json, {})
|
|
133
|
+
expect(formats[SimpleTestModel]).to eq(:json)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "per-model format overrides specific models" do
|
|
137
|
+
formats = two_model_store.send(:resolve_formats, nil,
|
|
138
|
+
{ SimpleTestModel => :marshal })
|
|
139
|
+
expect(formats[SimpleTestModel]).to eq(:marshal)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "both: global + per-model merge" do
|
|
143
|
+
formats = two_model_store.send(:resolve_formats, :json,
|
|
144
|
+
{ SimpleTestModel => :marshal })
|
|
145
|
+
expect(formats[SimpleTestModel]).to eq(:marshal)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "nil: use defaults (empty hash)" do
|
|
149
|
+
formats = two_model_store.send(:resolve_formats, nil, {})
|
|
150
|
+
expect(formats).to be_empty
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "support/simple_test_model"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Lutaml::Store::PackageTransport::DirectoryTransport do
|
|
7
|
+
let(:transport) { described_class.new }
|
|
8
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
9
|
+
after { FileUtils.rm_rf(tmpdir) }
|
|
10
|
+
|
|
11
|
+
let(:definition) do
|
|
12
|
+
Lutaml::Store::PackageDefinition.new(
|
|
13
|
+
name: :test, metadata_model: SimpleTestModel,
|
|
14
|
+
metadata_file: "meta.yaml", metadata_key: :id
|
|
15
|
+
) do |pkg|
|
|
16
|
+
pkg.model(model: SimpleTestModel, dir: "items",
|
|
17
|
+
layout: :separate, key: :id, default_format: :yaml)
|
|
18
|
+
pkg.asset("data.bin", type: :file)
|
|
19
|
+
pkg.asset("images", type: :directory)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "round-trip: write then read" do
|
|
24
|
+
it "preserves models, metadata, and assets" do
|
|
25
|
+
store = Lutaml::Store::PackageStore.new(definition)
|
|
26
|
+
store.add_model(SimpleTestModel.new(id: "item-1", name: "First"))
|
|
27
|
+
store.add_model(SimpleTestModel.new(id: "item-2", name: "Second"))
|
|
28
|
+
store.metadata = SimpleTestModel.new(id: "meta", name: "Test Package")
|
|
29
|
+
store.add_asset("data.bin", "BINARY_CONTENT")
|
|
30
|
+
store.add_asset("images/logo.png", "PNG_BYTES")
|
|
31
|
+
|
|
32
|
+
output_dir = File.join(tmpdir, "output")
|
|
33
|
+
store.save(output_dir, transport: :directory)
|
|
34
|
+
|
|
35
|
+
# Verify file structure
|
|
36
|
+
expect(File.exist?(File.join(output_dir, "meta.yaml"))).to be true
|
|
37
|
+
expect(File.exist?(File.join(output_dir, "items", "item-1.yaml"))).to be true
|
|
38
|
+
expect(File.exist?(File.join(output_dir, "items", "item-2.yaml"))).to be true
|
|
39
|
+
expect(File.exist?(File.join(output_dir, "data.bin"))).to be true
|
|
40
|
+
expect(File.exist?(File.join(output_dir, "images", "logo.png"))).to be true
|
|
41
|
+
|
|
42
|
+
# Load back
|
|
43
|
+
loaded = Lutaml::Store::PackageStore.load(definition, output_dir,
|
|
44
|
+
transport: :directory)
|
|
45
|
+
expect(loaded.model_count(SimpleTestModel)).to eq(2)
|
|
46
|
+
expect(loaded.metadata.name).to eq("Test Package")
|
|
47
|
+
expect(loaded.asset("data.bin")).to eq("BINARY_CONTENT")
|
|
48
|
+
expect(loaded.asset("images/logo.png")).to eq("PNG_BYTES")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe "missing optional entries" do
|
|
53
|
+
it "loads successfully with empty directory" do
|
|
54
|
+
empty_dir = File.join(tmpdir, "empty")
|
|
55
|
+
FileUtils.mkdir_p(File.join(empty_dir, "items"))
|
|
56
|
+
loaded = Lutaml::Store::PackageStore.load(definition, empty_dir,
|
|
57
|
+
transport: :directory)
|
|
58
|
+
expect(loaded.model_count(SimpleTestModel)).to eq(0)
|
|
59
|
+
expect(loaded.metadata).to be_nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe "format override" do
|
|
64
|
+
it "writes models in JSON format when overridden" do
|
|
65
|
+
store = Lutaml::Store::PackageStore.new(definition)
|
|
66
|
+
store.add_model(SimpleTestModel.new(id: "item-1", name: "First"))
|
|
67
|
+
|
|
68
|
+
output_dir = File.join(tmpdir, "json")
|
|
69
|
+
store.save(output_dir, transport: :directory,
|
|
70
|
+
formats: { SimpleTestModel => :json })
|
|
71
|
+
|
|
72
|
+
files = Dir.glob(File.join(output_dir, "items", "*"))
|
|
73
|
+
expect(files.length).to eq(1)
|
|
74
|
+
expect(File.extname(files.first)).to eq(".json")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "loads models saved in JSON format" do
|
|
78
|
+
store = Lutaml::Store::PackageStore.new(definition)
|
|
79
|
+
store.add_model(SimpleTestModel.new(id: "item-1", name: "First"))
|
|
80
|
+
|
|
81
|
+
output_dir = File.join(tmpdir, "json")
|
|
82
|
+
store.save(output_dir, transport: :directory,
|
|
83
|
+
formats: { SimpleTestModel => :json })
|
|
84
|
+
|
|
85
|
+
loaded = Lutaml::Store::PackageStore.load(definition, output_dir,
|
|
86
|
+
transport: :directory,
|
|
87
|
+
format: :json)
|
|
88
|
+
expect(loaded.model_count(SimpleTestModel)).to eq(1)
|
|
89
|
+
expect(loaded.fetch_model(SimpleTestModel, "item-1").name).to eq("First")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "corrupt file handling" do
|
|
94
|
+
it "warns and continues when a model file is corrupt" do
|
|
95
|
+
dir = File.join(tmpdir, "corrupt")
|
|
96
|
+
FileUtils.mkdir_p(File.join(dir, "items"))
|
|
97
|
+
File.write(File.join(dir, "items", "good.yaml"), "---\nid: good\nname: OK\n")
|
|
98
|
+
File.write(File.join(dir, "items", "bad.yaml"), "{{{{not yaml")
|
|
99
|
+
|
|
100
|
+
expect do
|
|
101
|
+
Lutaml::Store::PackageStore.load(definition, dir, transport: :directory)
|
|
102
|
+
end.to output(/failed to load/).to_stderr
|
|
103
|
+
|
|
104
|
+
loaded = Lutaml::Store::PackageStore.load(definition, dir,
|
|
105
|
+
transport: :directory)
|
|
106
|
+
expect(loaded.model_count(SimpleTestModel)).to eq(1)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe "dir: nil (root-level models)" do
|
|
111
|
+
let(:root_definition) do
|
|
112
|
+
Lutaml::Store::PackageDefinition.new(name: :root_test) do |pkg|
|
|
113
|
+
pkg.model(model: SimpleTestModel, dir: nil,
|
|
114
|
+
layout: :separate, key: :id, default_format: :yaml)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "writes models to package root" do
|
|
119
|
+
store = Lutaml::Store::PackageStore.new(root_definition)
|
|
120
|
+
store.add_model(SimpleTestModel.new(id: "root-1", name: "Root"))
|
|
121
|
+
|
|
122
|
+
output_dir = File.join(tmpdir, "root")
|
|
123
|
+
store.save(output_dir, transport: :directory)
|
|
124
|
+
|
|
125
|
+
expect(File.exist?(File.join(output_dir, "root-1.yaml"))).to be true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "reads models from package root" do
|
|
129
|
+
dir = File.join(tmpdir, "root_read")
|
|
130
|
+
FileUtils.mkdir_p(dir)
|
|
131
|
+
File.write(File.join(dir, "abc.yaml"), "---\nid: abc\nname: Found\n")
|
|
132
|
+
|
|
133
|
+
loaded = Lutaml::Store::PackageStore.load(root_definition, dir,
|
|
134
|
+
transport: :directory)
|
|
135
|
+
expect(loaded.model_count(SimpleTestModel)).to eq(1)
|
|
136
|
+
expect(loaded.fetch_model(SimpleTestModel, "abc").name).to eq("Found")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require "support/simple_test_model"
|
|
5
|
+
require "zip"
|
|
6
|
+
|
|
7
|
+
RSpec.describe Lutaml::Store::PackageTransport::ZipTransport do
|
|
8
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
9
|
+
after { FileUtils.rm_rf(tmpdir) }
|
|
10
|
+
|
|
11
|
+
let(:definition) do
|
|
12
|
+
Lutaml::Store::PackageDefinition.new(
|
|
13
|
+
name: :test, metadata_model: SimpleTestModel, metadata_file: "meta.yaml",
|
|
14
|
+
metadata_key: :id
|
|
15
|
+
) do |pkg|
|
|
16
|
+
pkg.model(model: SimpleTestModel, dir: "items",
|
|
17
|
+
layout: :separate, key: :id, default_format: :yaml)
|
|
18
|
+
pkg.asset("data.bin", type: :file)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "round-trip through ZIP" do
|
|
23
|
+
it "preserves models, metadata, and assets" do
|
|
24
|
+
store = Lutaml::Store::PackageStore.new(definition)
|
|
25
|
+
store.add_model(SimpleTestModel.new(id: "item-1", name: "First"))
|
|
26
|
+
store.metadata = SimpleTestModel.new(id: "meta", name: "Package")
|
|
27
|
+
store.add_asset("data.bin", "BINARY")
|
|
28
|
+
|
|
29
|
+
zip_path = File.join(tmpdir, "test.zip")
|
|
30
|
+
store.save(zip_path, transport: :zip)
|
|
31
|
+
|
|
32
|
+
expect(File.exist?(zip_path)).to be true
|
|
33
|
+
|
|
34
|
+
# Verify ZIP contents
|
|
35
|
+
Zip::File.open(zip_path) do |zf|
|
|
36
|
+
expect(zf.find_entry("meta.yaml")).not_to be_nil
|
|
37
|
+
expect(zf.find_entry("items/item-1.yaml")).not_to be_nil
|
|
38
|
+
expect(zf.find_entry("data.bin")).not_to be_nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Load from ZIP
|
|
42
|
+
loaded = Lutaml::Store::PackageStore.load(definition, zip_path,
|
|
43
|
+
transport: :zip)
|
|
44
|
+
expect(loaded.model_count(SimpleTestModel)).to eq(1)
|
|
45
|
+
expect(loaded.metadata.name).to eq("Package")
|
|
46
|
+
expect(loaded.asset("data.bin")).to eq("BINARY")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "directory → ZIP → directory round-trip" do
|
|
51
|
+
it "preserves data through format conversion" do
|
|
52
|
+
dir = File.join(tmpdir, "source")
|
|
53
|
+
dir_store = Lutaml::Store::PackageStore.new(definition)
|
|
54
|
+
dir_store.add_model(SimpleTestModel.new(id: "abc", name: "Test"))
|
|
55
|
+
dir_store.metadata = SimpleTestModel.new(id: "meta", name: "Dir Package")
|
|
56
|
+
dir_store.save(dir, transport: :directory)
|
|
57
|
+
|
|
58
|
+
loaded_from_dir = Lutaml::Store::PackageStore.load(definition, dir,
|
|
59
|
+
transport: :directory)
|
|
60
|
+
zip_path = File.join(tmpdir, "converted.zip")
|
|
61
|
+
loaded_from_dir.save(zip_path, transport: :zip)
|
|
62
|
+
|
|
63
|
+
loaded_from_zip = Lutaml::Store::PackageStore.load(definition, zip_path,
|
|
64
|
+
transport: :zip)
|
|
65
|
+
expect(loaded_from_zip.model_count(SimpleTestModel)).to eq(1)
|
|
66
|
+
expect(loaded_from_zip.fetch_model(SimpleTestModel, "abc").name).to eq("Test")
|
|
67
|
+
expect(loaded_from_zip.metadata.name).to eq("Dir Package")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "format override in ZIP" do
|
|
72
|
+
it "writes models in JSON format" do
|
|
73
|
+
store = Lutaml::Store::PackageStore.new(definition)
|
|
74
|
+
store.add_model(SimpleTestModel.new(id: "item-1", name: "First"))
|
|
75
|
+
|
|
76
|
+
zip_path = File.join(tmpdir, "json.zip")
|
|
77
|
+
store.save(zip_path, transport: :zip,
|
|
78
|
+
formats: { SimpleTestModel => :json })
|
|
79
|
+
|
|
80
|
+
Zip::File.open(zip_path) do |zf|
|
|
81
|
+
expect(zf.find_entry("items/item-1.json")).not_to be_nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Simple model with key_value DSL for testing package components
|
|
4
|
+
# without depending on glossarist or lutaml-jsonschema.
|
|
5
|
+
class SimpleTestModel < Lutaml::Model::Serializable
|
|
6
|
+
attribute :id, :string
|
|
7
|
+
attribute :name, :string
|
|
8
|
+
attribute :value, :string
|
|
9
|
+
|
|
10
|
+
key_value do
|
|
11
|
+
map "id", to: :id
|
|
12
|
+
map "name", to: :name
|
|
13
|
+
map "value", to: :value
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Minimal model with yamls DSL for testing Format::Yamls.
|
|
4
|
+
# Structure mirrors ConceptDocument: header + collection of parts.
|
|
5
|
+
class YamlsTestHeader < Lutaml::Model::Serializable
|
|
6
|
+
attribute :id, :string
|
|
7
|
+
attribute :name, :string
|
|
8
|
+
|
|
9
|
+
yaml do
|
|
10
|
+
map "id", to: :id
|
|
11
|
+
map "name", to: :name
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class YamlsTestPart < Lutaml::Model::Serializable
|
|
16
|
+
attribute :label, :string
|
|
17
|
+
attribute :value, :string
|
|
18
|
+
|
|
19
|
+
yaml do
|
|
20
|
+
map "label", to: :label
|
|
21
|
+
map "value", to: :value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class YamlsTestModel < Lutaml::Model::Serializable
|
|
26
|
+
attribute :header, YamlsTestHeader
|
|
27
|
+
attribute :parts, YamlsTestPart, collection: true
|
|
28
|
+
|
|
29
|
+
yamls do
|
|
30
|
+
sequence do
|
|
31
|
+
map_document 0, to: :header, type: YamlsTestHeader
|
|
32
|
+
map_document 1.., to: :parts, type: YamlsTestPart, collection: true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lutaml-store
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ronald Tse
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: 0.8.15
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubyzip
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.3'
|
|
26
40
|
description: |
|
|
27
41
|
Provides a unified store interface for Lutaml::Model objects with model registry,
|
|
28
42
|
polymorphic support, composite relationships, and multiple storage backends
|
|
@@ -110,6 +124,9 @@ files:
|
|
|
110
124
|
- lib/lutaml/store/model_registry.rb
|
|
111
125
|
- lib/lutaml/store/model_serializer.rb
|
|
112
126
|
- lib/lutaml/store/monitor.rb
|
|
127
|
+
- lib/lutaml/store/package_definition.rb
|
|
128
|
+
- lib/lutaml/store/package_store.rb
|
|
129
|
+
- lib/lutaml/store/package_transport.rb
|
|
113
130
|
- lib/lutaml/store/storage_key.rb
|
|
114
131
|
- lib/lutaml/store/version.rb
|
|
115
132
|
- lutaml-store.gemspec
|
|
@@ -126,6 +143,7 @@ files:
|
|
|
126
143
|
- spec/lutaml/store/custom_serializer_spec.rb
|
|
127
144
|
- spec/lutaml/store/database_store_spec.rb
|
|
128
145
|
- spec/lutaml/store/file_io_spec.rb
|
|
146
|
+
- spec/lutaml/store/format/yamls_spec.rb
|
|
129
147
|
- spec/lutaml/store/format_round_trip_spec.rb
|
|
130
148
|
- spec/lutaml/store/format_spec.rb
|
|
131
149
|
- spec/lutaml/store/http_cache_entry_spec.rb
|
|
@@ -138,9 +156,15 @@ files:
|
|
|
138
156
|
- spec/lutaml/store/load_save_spec.rb
|
|
139
157
|
- spec/lutaml/store/lutaml_model_integration_spec.rb
|
|
140
158
|
- spec/lutaml/store/model_serializer_spec.rb
|
|
159
|
+
- spec/lutaml/store/package_definition_spec.rb
|
|
160
|
+
- spec/lutaml/store/package_store_spec.rb
|
|
161
|
+
- spec/lutaml/store/package_transport/directory_transport_spec.rb
|
|
162
|
+
- spec/lutaml/store/package_transport/zip_transport_spec.rb
|
|
141
163
|
- spec/lutaml/store/store_spec.rb
|
|
142
164
|
- spec/lutaml/store_spec.rb
|
|
143
165
|
- spec/spec_helper.rb
|
|
166
|
+
- spec/support/simple_test_model.rb
|
|
167
|
+
- spec/support/yamls_test_model.rb
|
|
144
168
|
homepage: https://github.com/lutaml/lutaml-store
|
|
145
169
|
licenses:
|
|
146
170
|
- BSD-2-Clause
|