kube_schema 1.3.4 → 1.3.6

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.
@@ -180,3 +180,326 @@ module Kube
180
180
  end
181
181
  end
182
182
  end
183
+
184
+ if __FILE__ == $0
185
+ require "bundler/setup"
186
+ require "rspec/autorun"
187
+ require "kube/schema"
188
+ require "tmpdir"
189
+
190
+ RSpec.describe Kube::Schema::Manifest do
191
+ let(:resource_a) { Kube::Schema["Deployment"].new }
192
+ let(:resource_b) { Kube::Schema["Service"].new }
193
+ let(:resource_c) { Kube::Schema["Namespace"].new }
194
+
195
+ describe "#initialize" do
196
+ it "creates an empty manifest with no arguments" do
197
+ manifest = described_class.new
198
+ expect(manifest.count).to eq(0)
199
+ end
200
+
201
+ it "accepts resources as arguments" do
202
+ manifest = described_class.new(resource_a, resource_b)
203
+ expect(manifest.count).to eq(2)
204
+ end
205
+
206
+ it "accepts a filename keyword argument" do
207
+ manifest = described_class.new(filename: "/tmp/test.yaml")
208
+ expect(manifest.filename).to eq("/tmp/test.yaml")
209
+ end
210
+
211
+ it "flattens manifests passed as arguments" do
212
+ inner = described_class.new(resource_a, resource_b)
213
+ outer = described_class.new(inner, resource_c)
214
+ expect(outer.count).to eq(3)
215
+ end
216
+ end
217
+
218
+ describe "#<<" do
219
+ subject(:manifest) { described_class.new }
220
+
221
+ it "appends a Resource" do
222
+ manifest << resource_a
223
+ expect(manifest.count).to eq(1)
224
+ expect(manifest.first).to eq(resource_a)
225
+ end
226
+
227
+ it "returns self for chaining" do
228
+ result = manifest << resource_a
229
+ expect(result).to be(manifest)
230
+ end
231
+
232
+ it "flattens a Manifest (cannot be nested)" do
233
+ other = described_class.new(resource_a, resource_b)
234
+ manifest << other
235
+ expect(manifest.count).to eq(2)
236
+ expect(manifest.to_a).to contain_exactly(resource_a, resource_b)
237
+ end
238
+
239
+ it "flattens an Array of Resources" do
240
+ manifest << [resource_a, resource_b]
241
+ expect(manifest.count).to eq(2)
242
+ end
243
+
244
+ it "raises ArgumentError for a Hash" do
245
+ expect { manifest << { "kind" => "Pod" } }.to raise_error(ArgumentError, /Expected a Kube::Schema::Resource/)
246
+ end
247
+
248
+ it "raises ArgumentError for a String" do
249
+ expect { manifest << "not a resource" }.to raise_error(ArgumentError)
250
+ end
251
+
252
+ it "supports chaining multiple appends" do
253
+ manifest << resource_a << resource_b << resource_c
254
+ expect(manifest.count).to eq(3)
255
+ end
256
+ end
257
+
258
+ describe "Enumerable" do
259
+ subject(:manifest) { described_class.new(resource_a, resource_b, resource_c) }
260
+
261
+ it "includes Enumerable" do
262
+ expect(described_class).to include(Enumerable)
263
+ end
264
+
265
+ it "yields resources in insertion order via #each" do
266
+ resources = []
267
+ manifest.each { |r| resources << r }
268
+ expect(resources).to eq([resource_a, resource_b, resource_c])
269
+ end
270
+
271
+ it "supports #map" do
272
+ kinds = manifest.map { |r| r.to_h[:kind] }
273
+ expect(kinds).to eq(["Deployment", "Service", "Namespace"])
274
+ end
275
+
276
+ it "supports #select" do
277
+ services = manifest.select { |r| r.to_h[:kind] == "Service" }
278
+ expect(services.length).to eq(1)
279
+ end
280
+
281
+ it "supports #first and #last" do
282
+ expect(manifest.first).to eq(resource_a)
283
+ # Enumerable doesn't provide #last by default, but #to_a does
284
+ expect(manifest.to_a.last).to eq(resource_c)
285
+ end
286
+ end
287
+
288
+ describe "#size / #length" do
289
+ it "returns the number of resources" do
290
+ manifest = described_class.new(resource_a, resource_b)
291
+ expect(manifest.size).to eq(2)
292
+ expect(manifest.length).to eq(2)
293
+ end
294
+
295
+ it "returns 0 for an empty manifest" do
296
+ manifest = described_class.new
297
+ expect(manifest.size).to eq(0)
298
+ end
299
+ end
300
+
301
+ describe "#to_a" do
302
+ it "returns a copy of the internal resources array" do
303
+ manifest = described_class.new(resource_a, resource_b)
304
+ arr = manifest.to_a
305
+ expect(arr).to eq([resource_a, resource_b])
306
+
307
+ # Verify it's a copy, not the internal array
308
+ arr << resource_c
309
+ expect(manifest.count).to eq(2)
310
+ end
311
+ end
312
+
313
+ describe "#to_yaml" do
314
+ it "returns multi-document YAML" do
315
+ manifest = described_class.new(resource_a, resource_b)
316
+ yaml = manifest.to_yaml
317
+
318
+ expect(yaml).to include("---")
319
+ expect(yaml).to include("kind: Deployment")
320
+ expect(yaml).to include("kind: Service")
321
+ end
322
+
323
+ it "uses string keys (not symbol keys) in output" do
324
+ manifest = described_class.new(resource_a)
325
+ yaml = manifest.to_yaml
326
+
327
+ # Should NOT contain Ruby symbol syntax like `:kind:`
328
+ expect(yaml).not_to match(/:\w+:/)
329
+ expect(yaml).to include("kind: Deployment")
330
+ expect(yaml).to include("apiVersion: apps/v1")
331
+ end
332
+
333
+ it "returns empty string for an empty manifest" do
334
+ manifest = described_class.new
335
+ expect(manifest.to_yaml).to eq("")
336
+ end
337
+
338
+ it "produces parseable YAML that round-trips" do
339
+ manifest = described_class.new(resource_a, resource_b)
340
+ yaml_output = manifest.to_yaml
341
+ docs = if YAML.respond_to?(:safe_load_stream)
342
+ YAML.safe_load_stream(yaml_output)
343
+ else
344
+ YAML.load_stream(yaml_output)
345
+ end
346
+ expect(docs.length).to eq(2)
347
+ expect(docs[0]["kind"]).to eq("Deployment")
348
+ expect(docs[1]["kind"]).to eq("Service")
349
+ end
350
+ end
351
+
352
+ describe ".parse" do
353
+ it "parses a single-document YAML string" do
354
+ yaml = { "kind" => "Pod", "apiVersion" => "v1", "metadata" => { "name" => "test" } }.to_yaml
355
+ manifest = described_class.parse(yaml)
356
+
357
+ expect(manifest.count).to eq(1)
358
+ expect(manifest.first).to be_a(Kube::Schema::Resource)
359
+ expect(manifest.first.kind).to eq("Pod")
360
+ end
361
+
362
+ it "parses a multi-document YAML string" do
363
+ yaml = [
364
+ { "kind" => "Deployment", "apiVersion" => "apps/v1" },
365
+ { "kind" => "Service", "apiVersion" => "v1" }
366
+ ].map(&:to_yaml).join("")
367
+
368
+ manifest = described_class.parse(yaml)
369
+ expect(manifest.count).to eq(2)
370
+ end
371
+
372
+ it "returns typed Resource subclasses" do
373
+ yaml = { "kind" => "Deployment", "apiVersion" => "apps/v1", "metadata" => { "name" => "web" } }.to_yaml
374
+ manifest = described_class.parse(yaml)
375
+
376
+ resource = manifest.first
377
+ expect(resource.class.defaults).to eq({ "apiVersion" => "apps/v1", "kind" => "Deployment" })
378
+ expect(resource.kind).to eq("Deployment")
379
+ end
380
+
381
+ it "skips nil documents (empty YAML docs)" do
382
+ yaml = "---\nkind: Pod\napiVersion: v1\n---\n---\nkind: Service\napiVersion: v1\n"
383
+ manifest = described_class.parse(yaml)
384
+ expect(manifest.count).to eq(2)
385
+ end
386
+
387
+ it "raises for unknown kinds" do
388
+ yaml = { "kind" => "UnknownCRD", "apiVersion" => "custom.io/v1" }.to_yaml
389
+ expect { described_class.parse(yaml) }.to raise_error(RuntimeError)
390
+ end
391
+
392
+ it "does not set a filename" do
393
+ yaml = { "kind" => "Pod", "apiVersion" => "v1" }.to_yaml
394
+ manifest = described_class.parse(yaml)
395
+ expect(manifest.filename).to be_nil
396
+ end
397
+
398
+ it "returns an empty manifest for empty YAML" do
399
+ manifest = described_class.parse("---\n")
400
+ expect(manifest.count).to eq(0)
401
+ end
402
+ end
403
+
404
+ describe ".open" do
405
+ let(:tmpdir) { Dir.mktmpdir("manifest_test") }
406
+ let(:yaml_path) { File.join(tmpdir, "resources.yaml") }
407
+
408
+ after { FileUtils.rm_rf(tmpdir) }
409
+
410
+ it "reads a single-document YAML file" do
411
+ File.write(yaml_path, { "kind" => "Pod", "apiVersion" => "v1" }.to_yaml)
412
+
413
+ manifest = described_class.open(yaml_path)
414
+ expect(manifest.count).to eq(1)
415
+ expect(manifest.first).to be_a(Kube::Schema::Resource)
416
+ end
417
+
418
+ it "reads a multi-document YAML file" do
419
+ content = [
420
+ { "kind" => "Deployment", "apiVersion" => "apps/v1" },
421
+ { "kind" => "Service", "apiVersion" => "v1" }
422
+ ].map(&:to_yaml).join("")
423
+
424
+ File.write(yaml_path, content)
425
+
426
+ manifest = described_class.open(yaml_path)
427
+ expect(manifest.count).to eq(2)
428
+ end
429
+
430
+ it "sets the filename" do
431
+ File.write(yaml_path, { "kind" => "Pod" }.to_yaml)
432
+
433
+ manifest = described_class.open(yaml_path)
434
+ expect(manifest.filename).to eq(yaml_path)
435
+ end
436
+
437
+ it "skips nil documents (empty YAML docs)" do
438
+ File.write(yaml_path, "---\nkind: Pod\n---\n---\nkind: Service\n")
439
+
440
+ manifest = described_class.open(yaml_path)
441
+ expect(manifest.count).to eq(2)
442
+ end
443
+ end
444
+
445
+ describe "#write" do
446
+ let(:tmpdir) { Dir.mktmpdir("manifest_test") }
447
+
448
+ after { FileUtils.rm_rf(tmpdir) }
449
+
450
+ it "writes to the given path" do
451
+ path = File.join(tmpdir, "output.yaml")
452
+ manifest = described_class.new(resource_a)
453
+
454
+ manifest.write(path)
455
+ expect(File.exist?(path)).to be true
456
+
457
+ content = File.read(path)
458
+ expect(content).to include("kind: Deployment")
459
+ end
460
+
461
+ it "writes to the stored filename when no path is given" do
462
+ path = File.join(tmpdir, "stored.yaml")
463
+ manifest = described_class.new(resource_a, filename: path)
464
+
465
+ manifest.write
466
+ expect(File.exist?(path)).to be true
467
+ end
468
+
469
+ it "updates the filename after writing to a new path" do
470
+ path = File.join(tmpdir, "new.yaml")
471
+ manifest = described_class.new(resource_a)
472
+
473
+ manifest.write(path)
474
+ expect(manifest.filename).to eq(path)
475
+ end
476
+
477
+ it "returns the path written to" do
478
+ path = File.join(tmpdir, "result.yaml")
479
+ manifest = described_class.new(resource_a)
480
+
481
+ result = manifest.write(path)
482
+ expect(result).to eq(path)
483
+ end
484
+
485
+ it "raises ArgumentError when no path is available" do
486
+ manifest = described_class.new(resource_a)
487
+ expect { manifest.write }.to raise_error(ArgumentError, /No filename set/)
488
+ end
489
+
490
+ it "round-trips through write and open" do
491
+ path = File.join(tmpdir, "roundtrip.yaml")
492
+ original = described_class.new(resource_a, resource_b)
493
+
494
+ original.write(path)
495
+ loaded = described_class.open(path)
496
+
497
+ expect(loaded.count).to eq(original.count)
498
+
499
+ content = File.read(path)
500
+ expect(content).to include("kind: Deployment")
501
+ expect(content).to include("kind: Service")
502
+ end
503
+ end
504
+ end
505
+ end