lutaml-store 0.2.0 → 0.2.1
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 +11 -175
- data/README.adoc +233 -1124
- data/lib/lutaml/store/adapter/base.rb +4 -0
- data/lib/lutaml/store/adapter/memory.rb +8 -0
- data/lib/lutaml/store/cache_store.rb +9 -6
- data/lib/lutaml/store/format.rb +19 -0
- data/lib/lutaml/store/http_cache.rb +3 -13
- data/lib/lutaml/store/model_registration.rb +5 -2
- data/lib/lutaml/store/model_registry.rb +22 -20
- data/lib/lutaml/store/package_store.rb +2 -18
- data/lib/lutaml/store/package_transport/base.rb +48 -0
- data/lib/lutaml/store/package_transport/directory_transport.rb +196 -0
- data/lib/lutaml/store/package_transport/zip_transport.rb +178 -0
- data/lib/lutaml/store/package_transport.rb +11 -438
- data/lib/lutaml/store/version.rb +1 -1
- metadata +12 -77
- data/.github/workflows/main.yml +0 -27
- data/.gitignore +0 -12
- data/CORRECTED_HTTP_CACHE_IMPLEMENTATION.md +0 -209
- data/CORRECTED_HTTP_CACHE_PLAN.md +0 -164
- data/Gemfile +0 -15
- data/Gemfile.lock +0 -227
- data/TODO.impl/0-lutaml-store-self-quality.md +0 -112
- data/TODO.impl/1-lutaml-hal-migration.md +0 -96
- data/TODO.impl/2-glossarist-migration.md +0 -359
- data/TODO.impl/3-lutaml-jsonschema-migration.md +0 -273
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/demo/Gemfile +0 -15
- data/demo/Gemfile.lock +0 -61
- data/demo/README.adoc +0 -301
- data/demo/data/vcards/co/contact_10_thompson.data +0 -1
- data/demo/data/vcards/co/contact_10_thompson.meta +0 -1
- data/demo/data/vcards/co/contact_1_doe.data +0 -1
- data/demo/data/vcards/co/contact_1_doe.meta +0 -1
- data/demo/data/vcards/co/contact_2_smith.data +0 -1
- data/demo/data/vcards/co/contact_2_smith.meta +0 -1
- data/demo/data/vcards/co/contact_3_johnson.data +0 -1
- data/demo/data/vcards/co/contact_3_johnson.meta +0 -1
- data/demo/data/vcards/co/contact_4_garcia.data +0 -1
- data/demo/data/vcards/co/contact_4_garcia.meta +0 -1
- data/demo/data/vcards/co/contact_5_wilson.data +0 -1
- data/demo/data/vcards/co/contact_5_wilson.meta +0 -1
- data/demo/data/vcards/co/contact_6_brown.data +0 -1
- data/demo/data/vcards/co/contact_6_brown.meta +0 -1
- data/demo/data/vcards/co/contact_7_davis.data +0 -1
- data/demo/data/vcards/co/contact_7_davis.meta +0 -1
- data/demo/data/vcards/co/contact_8_anderson.data +0 -1
- data/demo/data/vcards/co/contact_8_anderson.meta +0 -1
- data/demo/data/vcards/co/contact_9_taylor.data +0 -1
- data/demo/data/vcards/co/contact_9_taylor.meta +0 -1
- data/demo/data/vcards.db +0 -0
- data/demo/pottery_class_demo.rb +0 -164
- data/demo/vcard_models.rb +0 -140
- data/demo/vcard_store_demo.rb +0 -526
- data/lutaml-store.gemspec +0 -36
- data/plan.adoc +0 -606
- data/spec/lutaml/store/adapter_interface_spec.rb +0 -89
- data/spec/lutaml/store/anti_pattern_guard_spec.rb +0 -35
- data/spec/lutaml/store/anti_pattern_spec.rb +0 -78
- data/spec/lutaml/store/autoload_spec.rb +0 -34
- data/spec/lutaml/store/cache_store_spec.rb +0 -271
- data/spec/lutaml/store/compression_spec.rb +0 -78
- data/spec/lutaml/store/config_enhanced_spec.rb +0 -158
- data/spec/lutaml/store/corrected_http_cache_integration_spec.rb +0 -336
- data/spec/lutaml/store/custom_serializer_spec.rb +0 -108
- data/spec/lutaml/store/database_store_spec.rb +0 -279
- data/spec/lutaml/store/file_io_spec.rb +0 -220
- data/spec/lutaml/store/format/yamls_spec.rb +0 -80
- data/spec/lutaml/store/format_round_trip_spec.rb +0 -110
- data/spec/lutaml/store/format_spec.rb +0 -70
- data/spec/lutaml/store/http_cache_entry_spec.rb +0 -203
- data/spec/lutaml/store/http_cache_hal_integration_spec.rb +0 -404
- data/spec/lutaml/store/http_cache_spec.rb +0 -422
- data/spec/lutaml/store/http_header_processor_spec.rb +0 -290
- data/spec/lutaml/store/import_spec.rb +0 -90
- data/spec/lutaml/store/integrity_spec.rb +0 -157
- data/spec/lutaml/store/key_collision_serializer_spec.rb +0 -98
- data/spec/lutaml/store/load_save_spec.rb +0 -107
- data/spec/lutaml/store/lutaml_model_integration_spec.rb +0 -291
- data/spec/lutaml/store/model_serializer_spec.rb +0 -140
- data/spec/lutaml/store/package_definition_spec.rb +0 -89
- data/spec/lutaml/store/package_store_spec.rb +0 -153
- data/spec/lutaml/store/package_transport/directory_transport_spec.rb +0 -139
- data/spec/lutaml/store/package_transport/zip_transport_spec.rb +0 -85
- data/spec/lutaml/store/store_spec.rb +0 -182
- data/spec/lutaml/store_spec.rb +0 -21
- data/spec/spec_helper.rb +0 -16
- data/spec/support/simple_test_model.rb +0 -15
- data/spec/support/yamls_test_model.rb +0 -35
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
require "tmpdir"
|
|
5
|
-
|
|
6
|
-
module IntegrationTestModels
|
|
7
|
-
class TestDocument < Lutaml::Model::Serializable
|
|
8
|
-
attribute :id, :string
|
|
9
|
-
attribute :title, :string
|
|
10
|
-
attribute :content, :string
|
|
11
|
-
attribute :created_at, :string
|
|
12
|
-
|
|
13
|
-
key_value do
|
|
14
|
-
map :id, to: :id
|
|
15
|
-
map :title, to: :title
|
|
16
|
-
map :content, to: :content
|
|
17
|
-
map :created_at, to: :created_at
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
class TestAuthor < Lutaml::Model::Serializable
|
|
22
|
-
attribute :name, :string
|
|
23
|
-
attribute :email, :string
|
|
24
|
-
attribute :bio, :string
|
|
25
|
-
|
|
26
|
-
key_value do
|
|
27
|
-
map :name, to: :name
|
|
28
|
-
map :email, to: :email
|
|
29
|
-
map :bio, to: :bio
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
class TestBook < Lutaml::Model::Serializable
|
|
34
|
-
attribute :isbn, :string
|
|
35
|
-
attribute :title, :string
|
|
36
|
-
attribute :author, :string
|
|
37
|
-
attribute :published_year, :integer
|
|
38
|
-
attribute :genre, :string
|
|
39
|
-
|
|
40
|
-
key_value do
|
|
41
|
-
map :isbn, to: :isbn
|
|
42
|
-
map :title, to: :title
|
|
43
|
-
map :author, to: :author
|
|
44
|
-
map :published_year, to: :published_year
|
|
45
|
-
map :genre, to: :genre
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
RSpec.describe "Lutaml::Store with Lutaml::Model::Serializable integration" do
|
|
51
|
-
let(:doc_model) { { model: IntegrationTestModels::TestDocument, key: :id, dir: "documents" } }
|
|
52
|
-
let(:author_model) { { model: IntegrationTestModels::TestAuthor, key: :name, dir: "authors" } }
|
|
53
|
-
let(:book_model) { { model: IntegrationTestModels::TestBook, key: :isbn, dir: "books" } }
|
|
54
|
-
|
|
55
|
-
describe "single model CRUD" do
|
|
56
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [doc_model]) }
|
|
57
|
-
let(:document) do
|
|
58
|
-
IntegrationTestModels::TestDocument.new(
|
|
59
|
-
id: "doc1", title: "Test Document",
|
|
60
|
-
content: "This is a test", created_at: "2024-01-01"
|
|
61
|
-
)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it "saves and fetches a Lutaml::Model::Serializable instance" do
|
|
65
|
-
store.save(document)
|
|
66
|
-
|
|
67
|
-
retrieved = store.fetch(model: IntegrationTestModels::TestDocument, id: "doc1")
|
|
68
|
-
expect(retrieved).to be_a(IntegrationTestModels::TestDocument)
|
|
69
|
-
expect(retrieved.id).to eq("doc1")
|
|
70
|
-
expect(retrieved.title).to eq("Test Document")
|
|
71
|
-
expect(retrieved.content).to eq("This is a test")
|
|
72
|
-
expect(retrieved.created_at).to eq("2024-01-01")
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it "returns nil for non-existent key" do
|
|
76
|
-
expect(store.fetch(model: IntegrationTestModels::TestDocument, id: "missing")).to be_nil
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
it "updates a model with hash attributes" do
|
|
80
|
-
store.save(document)
|
|
81
|
-
|
|
82
|
-
updated = store.update(
|
|
83
|
-
model: IntegrationTestModels::TestDocument,
|
|
84
|
-
id: "doc1",
|
|
85
|
-
attributes: { title: "Updated Title" }
|
|
86
|
-
)
|
|
87
|
-
expect(updated.title).to eq("Updated Title")
|
|
88
|
-
expect(updated.content).to eq("This is a test")
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it "updates a model with block" do
|
|
92
|
-
store.save(document)
|
|
93
|
-
|
|
94
|
-
updated = store.update(model: IntegrationTestModels::TestDocument, id: "doc1") do |model|
|
|
95
|
-
model.content = "New content"
|
|
96
|
-
model
|
|
97
|
-
end
|
|
98
|
-
expect(updated.content).to eq("New content")
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
it "deletes a model" do
|
|
102
|
-
store.save(document)
|
|
103
|
-
expect(store.exists?(model: IntegrationTestModels::TestDocument, id: "doc1")).to be true
|
|
104
|
-
|
|
105
|
-
result = store.destroy(model: IntegrationTestModels::TestDocument, id: "doc1")
|
|
106
|
-
expect(result).to be true
|
|
107
|
-
expect(store.fetch(model: IntegrationTestModels::TestDocument, id: "doc1")).to be_nil
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
it "reports count and exists" do
|
|
111
|
-
expect(store.count(model: IntegrationTestModels::TestDocument)).to eq(0)
|
|
112
|
-
|
|
113
|
-
store.save(document)
|
|
114
|
-
expect(store.count(model: IntegrationTestModels::TestDocument)).to eq(1)
|
|
115
|
-
expect(store.exists?(model: IntegrationTestModels::TestDocument, id: "doc1")).to be true
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
describe "model type validation" do
|
|
120
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [doc_model]) }
|
|
121
|
-
|
|
122
|
-
it "raises when saving an unregistered model" do
|
|
123
|
-
author = IntegrationTestModels::TestAuthor.new(name: "John Doe", email: "john@example.com")
|
|
124
|
-
|
|
125
|
-
expect { store.save(author) }.to raise_error(Lutaml::Store::ModelNotRegisteredError)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
describe "collection operations" do
|
|
130
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [author_model]) }
|
|
131
|
-
let(:authors) do
|
|
132
|
-
[
|
|
133
|
-
IntegrationTestModels::TestAuthor.new(name: "Alice Johnson", email: "alice@example.com", bio: "Fiction writer"),
|
|
134
|
-
IntegrationTestModels::TestAuthor.new(name: "Bob Wilson", email: "bob@example.com", bio: "Technical writer"),
|
|
135
|
-
IntegrationTestModels::TestAuthor.new(name: "Carol Davis", email: "carol@example.com", bio: "Science writer")
|
|
136
|
-
]
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
it "stores multiple models and retrieves all" do
|
|
140
|
-
authors.each { |a| store.save(a) }
|
|
141
|
-
|
|
142
|
-
all_authors = store.all(model: IntegrationTestModels::TestAuthor)
|
|
143
|
-
expect(all_authors.size).to eq(3)
|
|
144
|
-
expect(all_authors.map(&:name).sort).to eq(["Alice Johnson", "Bob Wilson", "Carol Davis"])
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
it "queries with where" do
|
|
148
|
-
authors.each { |a| store.save(a) }
|
|
149
|
-
|
|
150
|
-
fiction = store.where(model: IntegrationTestModels::TestAuthor, bio: "Fiction writer")
|
|
151
|
-
expect(fiction.size).to eq(1)
|
|
152
|
-
expect(fiction.first.name).to eq("Alice Johnson")
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
it "updates a model and persists the change" do
|
|
156
|
-
authors.each { |a| store.save(a) }
|
|
157
|
-
|
|
158
|
-
store.update(model: IntegrationTestModels::TestAuthor, name: "Alice Johnson") do |model|
|
|
159
|
-
model.bio = "Updated bio: Fiction and mystery writer"
|
|
160
|
-
model
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
retrieved = store.fetch(model: IntegrationTestModels::TestAuthor, name: "Alice Johnson")
|
|
164
|
-
expect(retrieved.bio).to eq("Updated bio: Fiction and mystery writer")
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
describe "multi-model store" do
|
|
169
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [doc_model, author_model, book_model]) }
|
|
170
|
-
|
|
171
|
-
it "stores and retrieves different model types independently" do
|
|
172
|
-
document = IntegrationTestModels::TestDocument.new(id: "doc1", title: "Test Doc")
|
|
173
|
-
author = IntegrationTestModels::TestAuthor.new(name: "John Doe", email: "john@example.com")
|
|
174
|
-
book = IntegrationTestModels::TestBook.new(isbn: "978-123", title: "Test Book", author: "John Doe",
|
|
175
|
-
published_year: 2024, genre: "Fiction")
|
|
176
|
-
|
|
177
|
-
store.save(document)
|
|
178
|
-
store.save(author)
|
|
179
|
-
store.save(book)
|
|
180
|
-
|
|
181
|
-
expect(store.count(model: IntegrationTestModels::TestDocument)).to eq(1)
|
|
182
|
-
expect(store.count(model: IntegrationTestModels::TestAuthor)).to eq(1)
|
|
183
|
-
expect(store.count(model: IntegrationTestModels::TestBook)).to eq(1)
|
|
184
|
-
|
|
185
|
-
fetched_doc = store.fetch(model: IntegrationTestModels::TestDocument, id: "doc1")
|
|
186
|
-
fetched_author = store.fetch(model: IntegrationTestModels::TestAuthor, name: "John Doe")
|
|
187
|
-
fetched_book = store.fetch(model: IntegrationTestModels::TestBook, isbn: "978-123")
|
|
188
|
-
|
|
189
|
-
expect(fetched_doc).to be_a(IntegrationTestModels::TestDocument)
|
|
190
|
-
expect(fetched_author).to be_a(IntegrationTestModels::TestAuthor)
|
|
191
|
-
expect(fetched_book).to be_a(IntegrationTestModels::TestBook)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
it "isolates where queries by model type" do
|
|
195
|
-
store.save(IntegrationTestModels::TestDocument.new(id: "d1", title: "Ruby Guide"))
|
|
196
|
-
store.save(IntegrationTestModels::TestBook.new(isbn: "b1", title: "Ruby Guide", author: "Author"))
|
|
197
|
-
|
|
198
|
-
docs = store.where(model: IntegrationTestModels::TestDocument, title: "Ruby Guide")
|
|
199
|
-
books = store.where(model: IntegrationTestModels::TestBook, title: "Ruby Guide")
|
|
200
|
-
|
|
201
|
-
expect(docs.size).to eq(1)
|
|
202
|
-
expect(docs.first).to be_a(IntegrationTestModels::TestDocument)
|
|
203
|
-
expect(books.size).to eq(1)
|
|
204
|
-
expect(books.first).to be_a(IntegrationTestModels::TestBook)
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
describe "file I/O round-trip" do
|
|
209
|
-
let(:tmpdir) { Dir.mktmpdir }
|
|
210
|
-
after { FileUtils.rm_rf(tmpdir) }
|
|
211
|
-
|
|
212
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [book_model]) }
|
|
213
|
-
let(:books) do
|
|
214
|
-
[
|
|
215
|
-
IntegrationTestModels::TestBook.new(isbn: "978-0123456789", title: "Ruby Programming", author: "Jane Smith",
|
|
216
|
-
published_year: 2023, genre: "Programming"),
|
|
217
|
-
IntegrationTestModels::TestBook.new(isbn: "978-9876543210", title: "Go Programming", author: "Bob Jones",
|
|
218
|
-
published_year: 2024, genre: "Programming")
|
|
219
|
-
]
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
it "round-trips through YAML separate layout" do
|
|
223
|
-
store.save_all(books, path: tmpdir, format: :yaml, layout: :separate)
|
|
224
|
-
|
|
225
|
-
fresh_store = Lutaml::Store.new(adapter: :memory, models: [book_model])
|
|
226
|
-
loaded = fresh_store.load_all(IntegrationTestModels::TestBook, path: tmpdir, format: :yaml, layout: :separate)
|
|
227
|
-
|
|
228
|
-
expect(loaded.size).to eq(2)
|
|
229
|
-
expect(loaded.map(&:title).sort).to eq(["Go Programming", "Ruby Programming"])
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
it "round-trips through JSON separate layout" do
|
|
233
|
-
store.save_all(books, path: tmpdir, format: :json, layout: :separate)
|
|
234
|
-
|
|
235
|
-
fresh_store = Lutaml::Store.new(adapter: :memory, models: [book_model])
|
|
236
|
-
loaded = fresh_store.load_all(IntegrationTestModels::TestBook, path: tmpdir, format: :json, layout: :separate)
|
|
237
|
-
|
|
238
|
-
expect(loaded.size).to eq(2)
|
|
239
|
-
expect(loaded.map(&:author).sort).to eq(["Bob Jones", "Jane Smith"])
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
describe "import_all workflow" do
|
|
244
|
-
let(:tmpdir) { Dir.mktmpdir }
|
|
245
|
-
after { FileUtils.rm_rf(tmpdir) }
|
|
246
|
-
|
|
247
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [doc_model]) }
|
|
248
|
-
let(:documents) do
|
|
249
|
-
[
|
|
250
|
-
IntegrationTestModels::TestDocument.new(id: "doc1", title: "First Document", content: "Content 1"),
|
|
251
|
-
IntegrationTestModels::TestDocument.new(id: "doc2", title: "Second Document", content: "Content 2")
|
|
252
|
-
]
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
it "loads from directory and makes models queryable" do
|
|
256
|
-
store.save_all(documents, path: tmpdir, format: :yaml, layout: :separate)
|
|
257
|
-
|
|
258
|
-
fresh_store = Lutaml::Store.new(adapter: :memory, models: [doc_model])
|
|
259
|
-
loaded = fresh_store.import_all(IntegrationTestModels::TestDocument, path: tmpdir, format: :yaml,
|
|
260
|
-
layout: :separate)
|
|
261
|
-
|
|
262
|
-
expect(loaded.size).to eq(2)
|
|
263
|
-
|
|
264
|
-
# Queryable via fetch
|
|
265
|
-
doc1 = fresh_store.fetch(model: IntegrationTestModels::TestDocument, id: "doc1")
|
|
266
|
-
expect(doc1).not_to be_nil
|
|
267
|
-
expect(doc1.title).to eq("First Document")
|
|
268
|
-
|
|
269
|
-
# Queryable via where
|
|
270
|
-
second = fresh_store.where(model: IntegrationTestModels::TestDocument, title: "Second Document")
|
|
271
|
-
expect(second.size).to eq(1)
|
|
272
|
-
expect(second.first.content).to eq("Content 2")
|
|
273
|
-
|
|
274
|
-
# Queryable via count
|
|
275
|
-
expect(fresh_store.count(model: IntegrationTestModels::TestDocument)).to eq(2)
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
describe "statistics" do
|
|
280
|
-
let(:store) { Lutaml::Store.new(adapter: :memory, models: [doc_model]) }
|
|
281
|
-
|
|
282
|
-
it "reports registered models and counts" do
|
|
283
|
-
store.save(IntegrationTestModels::TestDocument.new(id: "doc1", title: "Test"))
|
|
284
|
-
|
|
285
|
-
stats = store.stats
|
|
286
|
-
expect(stats[:models_registered]).to eq(1)
|
|
287
|
-
expect(stats[:total_models]).to eq(1)
|
|
288
|
-
expect(stats[:registered_models]).to include("IntegrationTestModels::TestDocument")
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
end
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
module ModelSerializerTestModels
|
|
6
|
-
class SerialBook < Lutaml::Model::Serializable
|
|
7
|
-
attribute :isbn, :string
|
|
8
|
-
attribute :title, :string
|
|
9
|
-
attribute :author, :string
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
class SerialEbook < SerialBook
|
|
13
|
-
attribute :format, :string
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
RSpec.describe Lutaml::Store::ModelSerializer do
|
|
18
|
-
subject(:serializer) { described_class.new }
|
|
19
|
-
|
|
20
|
-
describe "#serialize" do
|
|
21
|
-
it "serializes a model to a hash with class metadata" do
|
|
22
|
-
book = ModelSerializerTestModels::SerialBook.new(isbn: "978-123", title: "Test Book", author: "Author")
|
|
23
|
-
result = serializer.serialize(book)
|
|
24
|
-
|
|
25
|
-
expect(result).to be_a(Hash)
|
|
26
|
-
expect(result["_class"]).to eq("ModelSerializerTestModels::SerialBook")
|
|
27
|
-
expect(result["isbn"]).to eq("978-123")
|
|
28
|
-
expect(result["title"]).to eq("Test Book")
|
|
29
|
-
expect(result["author"]).to eq("Author")
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it "serializes a subclass with correct class name" do
|
|
33
|
-
ebook = ModelSerializerTestModels::SerialEbook.new(isbn: "978-456", title: "Digital Book", format: "epub")
|
|
34
|
-
result = serializer.serialize(ebook)
|
|
35
|
-
|
|
36
|
-
expect(result["_class"]).to eq("ModelSerializerTestModels::SerialEbook")
|
|
37
|
-
expect(result["format"]).to eq("epub")
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
describe "#deserialize" do
|
|
42
|
-
it "deserializes a hash to the correct model class" do
|
|
43
|
-
data = {
|
|
44
|
-
"_class" => "ModelSerializerTestModels::SerialBook",
|
|
45
|
-
"isbn" => "978-123",
|
|
46
|
-
"title" => "Test Book",
|
|
47
|
-
"author" => "Author"
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
result = serializer.deserialize(data, ModelSerializerTestModels::SerialBook)
|
|
51
|
-
|
|
52
|
-
expect(result).to be_a(ModelSerializerTestModels::SerialBook)
|
|
53
|
-
expect(result.isbn).to eq("978-123")
|
|
54
|
-
expect(result.title).to eq("Test Book")
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
it "deserializes to a subclass when class metadata indicates it" do
|
|
58
|
-
data = {
|
|
59
|
-
"_class" => "ModelSerializerTestModels::SerialEbook",
|
|
60
|
-
"isbn" => "978-456",
|
|
61
|
-
"title" => "Digital",
|
|
62
|
-
"format" => "epub"
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
result = serializer.deserialize(data, ModelSerializerTestModels::SerialBook)
|
|
66
|
-
|
|
67
|
-
expect(result).to be_a(ModelSerializerTestModels::SerialEbook)
|
|
68
|
-
expect(result.format).to eq("epub")
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
it "rejects incompatible polymorphic types" do
|
|
72
|
-
data = {
|
|
73
|
-
"_class" => "ModelSerializerTestModels::SerialBook",
|
|
74
|
-
"isbn" => "978-123"
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
expect do
|
|
78
|
-
serializer.deserialize(data, ModelSerializerTestModels::SerialEbook)
|
|
79
|
-
end.to raise_error(Lutaml::Store::PolymorphicUpdateError, /not compatible/)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it "raises on invalid data (missing _class)" do
|
|
83
|
-
data = { "isbn" => "978-123" }
|
|
84
|
-
|
|
85
|
-
expect do
|
|
86
|
-
serializer.deserialize(data, ModelSerializerTestModels::SerialBook)
|
|
87
|
-
end.to raise_error(Lutaml::Store::CompositeModelError, /Invalid serialized data/)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
it "raises on invalid data (not a Hash)" do
|
|
91
|
-
expect do
|
|
92
|
-
serializer.deserialize("not a hash", ModelSerializerTestModels::SerialBook)
|
|
93
|
-
end.to raise_error(Lutaml::Store::CompositeModelError, /Invalid serialized data/)
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
it "raises on unresolvable class name" do
|
|
97
|
-
data = { "_class" => "NonExistentClass", "isbn" => "123" }
|
|
98
|
-
|
|
99
|
-
expect do
|
|
100
|
-
serializer.deserialize(data, ModelSerializerTestModels::SerialBook)
|
|
101
|
-
end.to raise_error(Lutaml::Store::CompositeModelError, /Cannot resolve class/)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
it "strips internal metadata keys from model data" do
|
|
105
|
-
data = {
|
|
106
|
-
"_class" => "ModelSerializerTestModels::SerialBook",
|
|
107
|
-
"_composite_models" => { "studio" => { "storage_key" => "key1" } },
|
|
108
|
-
"isbn" => "978-123",
|
|
109
|
-
"title" => "Test"
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
result = serializer.deserialize(data, ModelSerializerTestModels::SerialBook)
|
|
113
|
-
|
|
114
|
-
expect(result.isbn).to eq("978-123")
|
|
115
|
-
expect(result.title).to eq("Test")
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
describe "round-trip" do
|
|
120
|
-
it "serializes and deserializes preserving all attributes" do
|
|
121
|
-
book = ModelSerializerTestModels::SerialBook.new(isbn: "978-789", title: "Round Trip", author: "Traveler")
|
|
122
|
-
serialized = serializer.serialize(book)
|
|
123
|
-
deserialized = serializer.deserialize(serialized, ModelSerializerTestModels::SerialBook)
|
|
124
|
-
|
|
125
|
-
expect(deserialized.isbn).to eq("978-789")
|
|
126
|
-
expect(deserialized.title).to eq("Round Trip")
|
|
127
|
-
expect(deserialized.author).to eq("Traveler")
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
it "preserves subclass identity through round-trip" do
|
|
131
|
-
ebook = ModelSerializerTestModels::SerialEbook.new(isbn: "978-000", title: "E-Book", author: "Digital",
|
|
132
|
-
format: "mobi")
|
|
133
|
-
serialized = serializer.serialize(ebook)
|
|
134
|
-
deserialized = serializer.deserialize(serialized, ModelSerializerTestModels::SerialBook)
|
|
135
|
-
|
|
136
|
-
expect(deserialized).to be_a(ModelSerializerTestModels::SerialEbook)
|
|
137
|
-
expect(deserialized.format).to eq("mobi")
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
@@ -1,89 +0,0 @@
|
|
|
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
|
|
@@ -1,153 +0,0 @@
|
|
|
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
|