deimos-ruby 2.6.0.pre.beta1 → 2.6.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/CHANGELOG.md +8 -2
- data/README.md +45 -20
- data/deimos-ruby.gemspec +0 -1
- data/docs/CONFIGURATION.md +5 -11
- data/lib/deimos/active_record_consume/batch_consumption.rb +128 -31
- data/lib/deimos/active_record_consume/batch_record.rb +3 -0
- data/lib/deimos/active_record_consume/message_consumption.rb +2 -2
- data/lib/deimos/active_record_consumer.rb +6 -1
- data/lib/deimos/active_record_producer.rb +1 -1
- data/lib/deimos/config/configuration.rb +11 -34
- data/lib/deimos/exceptions.rb +17 -0
- data/lib/deimos/ext/consumer_route.rb +3 -1
- data/lib/deimos/ext/producer_middleware.rb +2 -2
- data/lib/deimos/logging.rb +0 -9
- data/lib/deimos/producer.rb +5 -5
- data/lib/deimos/schema_backends/avro_base.rb +36 -7
- data/lib/deimos/schema_class/base.rb +62 -0
- data/lib/deimos/schema_class/enum.rb +49 -0
- data/lib/deimos/schema_class/record.rb +100 -0
- data/lib/deimos/transcoder.rb +6 -6
- data/lib/deimos/utils/deadlock_retry.rb +16 -2
- data/lib/deimos/utils/schema_class.rb +47 -14
- data/lib/deimos/version.rb +1 -1
- data/lib/deimos.rb +4 -3
- data/lib/generators/deimos/schema_class/templates/schema_class.rb.tt +8 -0
- data/lib/generators/deimos/schema_class/templates/schema_enum.rb.tt +13 -0
- data/lib/generators/deimos/schema_class/templates/schema_record.rb.tt +102 -0
- data/lib/generators/deimos/schema_class_generator.rb +369 -0
- data/lib/tasks/deimos.rake +4 -16
- data/regenerate_test_schema_classes.rb +3 -12
- data/spec/active_record_batch_consumer_association_spec.rb +1 -1
- data/spec/active_record_batch_consumer_spec.rb +286 -1
- data/spec/active_record_consume/batch_consumption_spec.rb +1 -1
- data/spec/active_record_consumer_spec.rb +3 -3
- data/spec/active_record_producer_spec.rb +1 -1
- data/spec/batch_consumer_spec.rb +2 -2
- data/spec/consumer_spec.rb +5 -5
- data/spec/generators/schema_class/my_schema_spec.rb +16 -0
- data/spec/generators/schema_class/my_schema_with_circular_reference_spec.rb +98 -0
- data/spec/generators/schema_class/my_schema_with_complex_types_spec.rb +237 -0
- data/spec/generators/schema_class_generator_spec.rb +283 -0
- data/spec/schemas/my_namespace/generated.rb +4 -4
- data/spec/schemas/my_namespace/my_long_namespace_schema.rb +2 -2
- data/spec/schemas/my_namespace/my_nested_schema.rb +3 -3
- data/spec/schemas/my_namespace/my_schema.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_compound_key.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_id_key.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_key.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_with_boolean.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_with_circular_reference.rb +3 -3
- data/spec/schemas/my_namespace/my_schema_with_complex_type.rb +6 -6
- data/spec/schemas/my_namespace/my_schema_with_date_time.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_with_id.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_with_title.rb +2 -2
- data/spec/schemas/my_namespace/my_schema_with_union_type.rb +6 -6
- data/spec/schemas/my_namespace/my_schema_with_unique_id.rb +2 -2
- data/spec/schemas/my_namespace/my_updated_schema.rb +1 -1
- data/spec/schemas/my_namespace/request/create_topic.rb +2 -2
- data/spec/schemas/my_namespace/request/index.rb +2 -2
- data/spec/schemas/my_namespace/request/update_request.rb +2 -2
- data/spec/schemas/my_namespace/response/create_topic.rb +2 -2
- data/spec/schemas/my_namespace/response/index.rb +2 -2
- data/spec/schemas/my_namespace/response/update_response.rb +2 -2
- data/spec/schemas/my_namespace/wibble.rb +2 -2
- data/spec/schemas/my_namespace/widget.rb +2 -2
- data/spec/schemas/my_namespace/widget_the_second.rb +2 -2
- data/spec/schemas/my_namespace/widget_the_third.rb +2 -2
- data/spec/snapshots/consumers-no-nest.snap +1740 -0
- data/spec/snapshots/consumers.snap +1720 -0
- data/spec/snapshots/consumers_and_producers-no-nest.snap +1740 -0
- data/spec/snapshots/consumers_and_producers.snap +1720 -0
- data/spec/snapshots/consumers_circular-no-nest.snap +1740 -0
- data/spec/snapshots/consumers_circular.snap +1720 -0
- data/spec/snapshots/consumers_complex_types-no-nest.snap +1740 -0
- data/spec/snapshots/consumers_complex_types.snap +1720 -0
- data/spec/snapshots/consumers_nested-no-nest.snap +1740 -0
- data/spec/snapshots/consumers_nested.snap +1720 -0
- data/spec/snapshots/namespace_folders.snap +1800 -0
- data/spec/snapshots/namespace_map.snap +1800 -0
- data/spec/snapshots/producers_with_key-no-nest.snap +1740 -0
- data/spec/snapshots/producers_with_key.snap +1720 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/utils/db_poller_spec.rb +1 -1
- data/spec/utils/deadlock_retry_spec.rb +57 -0
- metadata +27 -18
- data/lib/deimos/schema_class.rb +0 -72
- data/spec/schema_class_spec.rb +0 -65
|
@@ -86,7 +86,7 @@ module ActiveRecordBatchConsumerTest
|
|
|
86
86
|
before(:each) do
|
|
87
87
|
Deimos.configure do |config|
|
|
88
88
|
config.schema.use_schema_classes = use_schema_classes
|
|
89
|
-
config.
|
|
89
|
+
config.schema.use_full_namespace = true
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -460,6 +460,291 @@ module ActiveRecordBatchConsumerTest
|
|
|
460
460
|
end
|
|
461
461
|
end
|
|
462
462
|
|
|
463
|
+
describe 'individual fallback when a batch operation fails' do
|
|
464
|
+
# `test_id: ''` passes Avro validation (it's a string) but fails the model's
|
|
465
|
+
# `validates :test_id, presence: true`, so `MassUpdater` raises before it issues any SQL.
|
|
466
|
+
# That's the poison-message case: one record that can never be persisted.
|
|
467
|
+
let(:poison_payload) { { test_id: '', some_int: 3 } }
|
|
468
|
+
|
|
469
|
+
let(:consumer_class) do
|
|
470
|
+
Class.new(described_class) do
|
|
471
|
+
record_class Widget
|
|
472
|
+
compacted false
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
before(:each) do
|
|
477
|
+
register_consumer(consumer_class, 'MySchema',
|
|
478
|
+
key_config: { plain: true },
|
|
479
|
+
configs: { reraise_errors: true })
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
it 'should not raise when the batch succeeds' do
|
|
483
|
+
publish_batch(
|
|
484
|
+
[
|
|
485
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
486
|
+
{ key: 2, payload: { test_id: 'def', some_int: 2 } }
|
|
487
|
+
]
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
expect(all_widgets).
|
|
491
|
+
to contain_exactly(have_attributes(id: 1, test_id: 'abc'),
|
|
492
|
+
have_attributes(id: 2, test_id: 'def'))
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
context 'when one record in the batch cannot be persisted' do
|
|
496
|
+
|
|
497
|
+
it 'should save every record except the bad one and report the failed key' do
|
|
498
|
+
expect {
|
|
499
|
+
publish_batch(
|
|
500
|
+
[
|
|
501
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
502
|
+
{ key: 2, payload: poison_payload },
|
|
503
|
+
{ key: 3, payload: { test_id: 'ghi', some_int: 3 } }
|
|
504
|
+
]
|
|
505
|
+
)
|
|
506
|
+
}.to raise_error(Deimos::BatchFallbackError, /Failed keys: "2"/)
|
|
507
|
+
|
|
508
|
+
expect(all_widgets).
|
|
509
|
+
to contain_exactly(have_attributes(id: 1, test_id: 'abc', some_int: 1),
|
|
510
|
+
have_attributes(id: 3, test_id: 'ghi', some_int: 3))
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
it 'should collect the failures of every bad record in the batch' do
|
|
514
|
+
expect {
|
|
515
|
+
publish_batch(
|
|
516
|
+
[
|
|
517
|
+
{ key: 1, payload: poison_payload },
|
|
518
|
+
{ key: 2, payload: { test_id: 'def', some_int: 2 } },
|
|
519
|
+
{ key: 3, payload: poison_payload }
|
|
520
|
+
]
|
|
521
|
+
)
|
|
522
|
+
}.to raise_error(Deimos::BatchFallbackError) { |error|
|
|
523
|
+
expect(error.failures.map { |message, _| message.key }).to contain_exactly('1', '3')
|
|
524
|
+
expect(error.failures.map(&:last)).to all(be_a(ActiveRecord::RecordInvalid))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
expect(all_widgets).to contain_exactly(have_attributes(id: 2, test_id: 'def'))
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
it 'should raise the original error when no record could be saved on its own' do
|
|
531
|
+
# Isolating salvaged nothing, so the failure was never about one bad message. The
|
|
532
|
+
# original error is more useful than a BatchFallbackError listing every key.
|
|
533
|
+
expect {
|
|
534
|
+
publish_batch(
|
|
535
|
+
[
|
|
536
|
+
{ key: 1, payload: poison_payload },
|
|
537
|
+
{ key: 2, payload: poison_payload }
|
|
538
|
+
]
|
|
539
|
+
)
|
|
540
|
+
}.to raise_error(ActiveRecord::RecordInvalid)
|
|
541
|
+
|
|
542
|
+
expect(all_widgets).to be_empty
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
it 'should emit an instrumentation event naming the topic and operation' do
|
|
546
|
+
events = []
|
|
547
|
+
Karafka.monitor.subscribe('deimos.batch_consumption.initial_failure') do |event|
|
|
548
|
+
events << event.payload
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
expect {
|
|
552
|
+
publish_batch(
|
|
553
|
+
[
|
|
554
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
555
|
+
{ key: 2, payload: poison_payload }
|
|
556
|
+
]
|
|
557
|
+
)
|
|
558
|
+
}.to raise_error(Deimos::BatchFallbackError)
|
|
559
|
+
|
|
560
|
+
expect(events.size).to eq(1)
|
|
561
|
+
expect(events.first).to include(consumer: consumer_class,
|
|
562
|
+
topic: 'my-topic',
|
|
563
|
+
operation: :upsert_records,
|
|
564
|
+
count: 2)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
it 'should only re-run the database write, not the message-level processing' do
|
|
568
|
+
# Retrying by breaking the batch into writes of one must not re-run anything that
|
|
569
|
+
# already ran for the batch as a whole. pre_process is called once for the group, and
|
|
570
|
+
# valid_records is announced once with every record that made it, rather than once per
|
|
571
|
+
# record.
|
|
572
|
+
pre_processed = []
|
|
573
|
+
allow_any_instance_of(consumer_class).to receive(:pre_process) do |_, messages|
|
|
574
|
+
pre_processed << messages.map(&:key)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
valid_records = []
|
|
578
|
+
Karafka.monitor.subscribe('deimos.batch_consumption.valid_records') do |event|
|
|
579
|
+
valid_records << event.payload[:records]
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
expect {
|
|
583
|
+
publish_batch(
|
|
584
|
+
[
|
|
585
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
586
|
+
{ key: 2, payload: poison_payload },
|
|
587
|
+
{ key: 3, payload: { test_id: 'ghi', some_int: 3 } }
|
|
588
|
+
]
|
|
589
|
+
)
|
|
590
|
+
}.to raise_error(Deimos::BatchFallbackError)
|
|
591
|
+
|
|
592
|
+
expect(pre_processed).to eq([%w(1 2 3)])
|
|
593
|
+
expect(valid_records.size).to eq(1)
|
|
594
|
+
expect(valid_records.first.map(&:test_id)).to contain_exactly('abc', 'ghi')
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
it 'should not retry individually when the batch failed on a deadlock' do
|
|
598
|
+
allow(Deimos::Utils::DeadlockRetry).to receive(:sleep)
|
|
599
|
+
allow(Widget).to receive(:import!).
|
|
600
|
+
and_raise(ActiveRecord::Deadlocked.new('Lock wait timeout exceeded'))
|
|
601
|
+
|
|
602
|
+
expect {
|
|
603
|
+
publish_batch(
|
|
604
|
+
[
|
|
605
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
606
|
+
{ key: 2, payload: { test_id: 'def', some_int: 2 } }
|
|
607
|
+
]
|
|
608
|
+
)
|
|
609
|
+
}.to raise_error(ActiveRecord::Deadlocked)
|
|
610
|
+
|
|
611
|
+
# Only DeadlockRetry's own 3 attempts - no per-message retries on top.
|
|
612
|
+
expect(Widget).to have_received(:import!).exactly(3).times
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
context 'with a single-message batch' do
|
|
617
|
+
it 'should raise the original error rather than wrapping it' do
|
|
618
|
+
expect {
|
|
619
|
+
publish_batch([{ key: 1, payload: poison_payload }])
|
|
620
|
+
}.to raise_error(ActiveRecord::RecordInvalid)
|
|
621
|
+
|
|
622
|
+
expect(all_widgets).to be_empty
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
context 'when batch_message_fallback is turned off' do
|
|
627
|
+
before(:each) do
|
|
628
|
+
register_consumer(consumer_class, 'MySchema',
|
|
629
|
+
key_config: { plain: true },
|
|
630
|
+
configs: { reraise_errors: true,
|
|
631
|
+
batch_message_fallback: false })
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
it 'should lose the whole batch to a single bad record' do
|
|
635
|
+
expect {
|
|
636
|
+
publish_batch(
|
|
637
|
+
[
|
|
638
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
639
|
+
{ key: 2, payload: poison_payload },
|
|
640
|
+
{ key: 3, payload: { test_id: 'ghi', some_int: 3 } }
|
|
641
|
+
]
|
|
642
|
+
)
|
|
643
|
+
}.to raise_error(ActiveRecord::RecordInvalid)
|
|
644
|
+
|
|
645
|
+
expect(all_widgets).to be_empty
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
it 'should not attempt any individual writes' do
|
|
649
|
+
allow(Widget).to receive(:import!).and_call_original
|
|
650
|
+
|
|
651
|
+
expect {
|
|
652
|
+
publish_batch(
|
|
653
|
+
[
|
|
654
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
655
|
+
{ key: 2, payload: poison_payload }
|
|
656
|
+
]
|
|
657
|
+
)
|
|
658
|
+
}.to raise_error(ActiveRecord::RecordInvalid)
|
|
659
|
+
|
|
660
|
+
# The batch write raises during validation, before any import is issued.
|
|
661
|
+
expect(Widget).not_to have_received(:import!)
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
context 'with keys repeated across BatchSlicer slices' do
|
|
666
|
+
it 'should still process the later slices after an earlier one fails' do
|
|
667
|
+
# Key 1 appears twice, so BatchSlicer splits the batch into two slices:
|
|
668
|
+
# [key 1 (poison), key 2] and [key 1 (valid)]. The valid update for key 1 sits in the
|
|
669
|
+
# second slice, so it only lands if a failure in the first slice doesn't abort the rest.
|
|
670
|
+
expect {
|
|
671
|
+
publish_batch(
|
|
672
|
+
[
|
|
673
|
+
{ key: 1, payload: poison_payload },
|
|
674
|
+
{ key: 1, payload: { test_id: 'later', some_int: 2 } },
|
|
675
|
+
{ key: 2, payload: { test_id: 'ok', some_int: 3 } }
|
|
676
|
+
]
|
|
677
|
+
)
|
|
678
|
+
}.to raise_error(Deimos::BatchFallbackError, /Failed keys: "1"/)
|
|
679
|
+
|
|
680
|
+
expect(all_widgets).
|
|
681
|
+
to contain_exactly(have_attributes(id: 1, test_id: 'later', some_int: 2),
|
|
682
|
+
have_attributes(id: 2, test_id: 'ok', some_int: 3))
|
|
683
|
+
end
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
context 'with max_db_batch_size' do
|
|
687
|
+
let(:consumer_class) do
|
|
688
|
+
Class.new(described_class) do
|
|
689
|
+
record_class Widget
|
|
690
|
+
compacted false
|
|
691
|
+
max_db_batch_size 2
|
|
692
|
+
end
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
it 'should still process the later groups after an earlier one fails' do
|
|
696
|
+
expect {
|
|
697
|
+
publish_batch(
|
|
698
|
+
[
|
|
699
|
+
{ key: 1, payload: { test_id: 'abc', some_int: 1 } },
|
|
700
|
+
{ key: 2, payload: poison_payload },
|
|
701
|
+
{ key: 3, payload: { test_id: 'ghi', some_int: 3 } },
|
|
702
|
+
{ key: 4, payload: { test_id: 'jkl', some_int: 4 } }
|
|
703
|
+
]
|
|
704
|
+
)
|
|
705
|
+
}.to raise_error(Deimos::BatchFallbackError, /Failed keys: "2"/)
|
|
706
|
+
|
|
707
|
+
expect(all_widgets).
|
|
708
|
+
to contain_exactly(have_attributes(id: 1, test_id: 'abc'),
|
|
709
|
+
have_attributes(id: 3, test_id: 'ghi'),
|
|
710
|
+
have_attributes(id: 4, test_id: 'jkl'))
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
context 'when removing records fails' do
|
|
715
|
+
let(:consumer_class) do
|
|
716
|
+
Class.new(described_class) do
|
|
717
|
+
record_class Widget
|
|
718
|
+
compacted false
|
|
719
|
+
|
|
720
|
+
def remove_records(messages)
|
|
721
|
+
raise 'cannot delete widget 2' if messages.any? { |m| m.key.to_s == '2' }
|
|
722
|
+
|
|
723
|
+
super
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
it 'should delete the records it can and report the one it cannot' do
|
|
729
|
+
Widget.create!(id: 1, test_id: 'abc', some_int: 1)
|
|
730
|
+
Widget.create!(id: 2, test_id: 'def', some_int: 2)
|
|
731
|
+
Widget.create!(id: 3, test_id: 'ghi', some_int: 3)
|
|
732
|
+
|
|
733
|
+
expect {
|
|
734
|
+
publish_batch(
|
|
735
|
+
[
|
|
736
|
+
{ key: 1, payload: nil },
|
|
737
|
+
{ key: 2, payload: nil },
|
|
738
|
+
{ key: 3, payload: nil }
|
|
739
|
+
]
|
|
740
|
+
)
|
|
741
|
+
}.to raise_error(Deimos::BatchFallbackError, /Failed keys: "2"/)
|
|
742
|
+
|
|
743
|
+
expect(all_widgets).to contain_exactly(have_attributes(id: 2, test_id: 'def'))
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
463
748
|
describe 'skipping records' do
|
|
464
749
|
before(:each) do
|
|
465
750
|
register_consumer(consumer_class,
|
|
@@ -119,7 +119,7 @@ RSpec.describe Deimos::ActiveRecordConsume::BatchConsumption do
|
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
it 'for schema classes' do
|
|
122
|
-
klass = Class.new(
|
|
122
|
+
klass = Class.new(Deimos::SchemaClass::Record) do
|
|
123
123
|
attr_accessor :some_name
|
|
124
124
|
|
|
125
125
|
# @override
|
|
@@ -59,7 +59,7 @@ module ActiveRecordConsumerTest
|
|
|
59
59
|
|
|
60
60
|
Time.zone = 'Eastern Time (US & Canada)'
|
|
61
61
|
|
|
62
|
-
schema_class = Class.new(
|
|
62
|
+
schema_class = Class.new(Deimos::SchemaClass::Record) do
|
|
63
63
|
def schema
|
|
64
64
|
'MySchema'
|
|
65
65
|
end
|
|
@@ -88,7 +88,7 @@ module ActiveRecordConsumerTest
|
|
|
88
88
|
end
|
|
89
89
|
stub_const('Schemas::MySchema', schema_class)
|
|
90
90
|
|
|
91
|
-
schema_datetime_class = Class.new(
|
|
91
|
+
schema_datetime_class = Class.new(Deimos::SchemaClass::Record) do
|
|
92
92
|
def schema
|
|
93
93
|
'MySchemaWithDateTimes'
|
|
94
94
|
end
|
|
@@ -164,7 +164,7 @@ module ActiveRecordConsumerTest
|
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
Deimos.configure do |config|
|
|
167
|
-
config.
|
|
167
|
+
config.schema.use_full_namespace = true
|
|
168
168
|
end
|
|
169
169
|
end
|
|
170
170
|
|
data/spec/batch_consumer_spec.rb
CHANGED
|
@@ -51,14 +51,14 @@ module ConsumerTest
|
|
|
51
51
|
|
|
52
52
|
let(:schema_class_batch) do
|
|
53
53
|
batch.map do |p|
|
|
54
|
-
|
|
54
|
+
Deimos::Utils::SchemaClass.instance(p, 'MySchema', 'com.my-namespace')
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
let(:use_schema_classes) { true }
|
|
58
58
|
|
|
59
59
|
before(:each) do
|
|
60
60
|
Deimos.configure do |config|
|
|
61
|
-
config.
|
|
61
|
+
config.schema.use_full_namespace = true
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
data/spec/consumer_spec.rb
CHANGED
|
@@ -48,8 +48,8 @@ module ConsumerTest
|
|
|
48
48
|
|
|
49
49
|
before(:each) do
|
|
50
50
|
Deimos.configure do |config|
|
|
51
|
-
config.
|
|
52
|
-
config.
|
|
51
|
+
config.schema.use_full_namespace = true
|
|
52
|
+
config.schema.schema_namespace_map = {
|
|
53
53
|
'com' => 'Schemas',
|
|
54
54
|
'com.my-namespace.my-suborg' => %w(Schemas MyNamespace)
|
|
55
55
|
}
|
|
@@ -160,7 +160,7 @@ module ConsumerTest
|
|
|
160
160
|
before(:each) do
|
|
161
161
|
set_karafka_config(:use_schema_classes, true)
|
|
162
162
|
Deimos.configure do |config|
|
|
163
|
-
config.
|
|
163
|
+
config.schema.use_full_namespace = true
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
|
|
@@ -205,8 +205,8 @@ module ConsumerTest
|
|
|
205
205
|
before(:each) do
|
|
206
206
|
set_karafka_config(:use_schema_classes, true)
|
|
207
207
|
Deimos.configure do |config|
|
|
208
|
-
config.
|
|
209
|
-
config.
|
|
208
|
+
config.schema.use_full_namespace = true
|
|
209
|
+
config.schema.schema_namespace_map = {
|
|
210
210
|
'com' => 'Schemas',
|
|
211
211
|
'com.my-namespace.my-suborg' => %w(Schemas MyNamespace)
|
|
212
212
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
RSpec.describe Schemas::MyNamespace::MySchema do
|
|
2
|
+
let(:key) { Schemas::MyNamespace::MySchemaKey.new(test_id: 123) }
|
|
3
|
+
|
|
4
|
+
it 'should produce a tombstone with a hash' do
|
|
5
|
+
result = described_class.tombstone({ test_id: 123 })
|
|
6
|
+
expect(result.payload_key).to eq(key)
|
|
7
|
+
expect(result.to_h).to eq({ payload_key: { 'test_id' => 123 } })
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should work with a record' do
|
|
11
|
+
key = Schemas::MyNamespace::MySchemaKey.new(test_id: 123)
|
|
12
|
+
result = described_class.tombstone(key)
|
|
13
|
+
expect(result.payload_key).to eq(key)
|
|
14
|
+
expect(result.to_h).to eq({ payload_key: { 'test_id' => 123 } })
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# For testing the generated class.
|
|
4
|
+
RSpec.describe Schemas::MyNamespace::MySchemaWithCircularReference do
|
|
5
|
+
let(:payload_hash) do
|
|
6
|
+
{
|
|
7
|
+
properties: {
|
|
8
|
+
a_boolean: {
|
|
9
|
+
'property' => true
|
|
10
|
+
},
|
|
11
|
+
an_integer: {
|
|
12
|
+
'property' => 1
|
|
13
|
+
},
|
|
14
|
+
a_float: {
|
|
15
|
+
'property' => 4.5
|
|
16
|
+
},
|
|
17
|
+
a_string: {
|
|
18
|
+
'property' => 'string'
|
|
19
|
+
},
|
|
20
|
+
an_array: {
|
|
21
|
+
'property' => [1, 2, 3]
|
|
22
|
+
},
|
|
23
|
+
an_hash: {
|
|
24
|
+
'property' => {
|
|
25
|
+
'a_key' => 'a_value'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe 'class initialization' do
|
|
33
|
+
it 'should initialize the class from keyword arguments' do
|
|
34
|
+
klass = described_class.new(properties: payload_hash[:properties])
|
|
35
|
+
expect(klass).to be_instance_of(described_class)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'should initialize the class from a hash with symbols as keys' do
|
|
39
|
+
klass = described_class.new(**payload_hash)
|
|
40
|
+
expect(klass).to be_instance_of(described_class)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'should initialize the class when missing attributes' do
|
|
44
|
+
payload_hash.delete(:properties)
|
|
45
|
+
klass = described_class.new(**payload_hash)
|
|
46
|
+
expect(klass).to be_instance_of(described_class)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe 'base class methods' do
|
|
52
|
+
let(:klass) do
|
|
53
|
+
described_class.new(**payload_hash)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'should return the name of the schema and namespace' do
|
|
57
|
+
expect(klass.schema).to eq('MySchemaWithCircularReference')
|
|
58
|
+
expect(klass.namespace).to eq('com.my-namespace')
|
|
59
|
+
expect(klass.full_schema).to eq('com.my-namespace.MySchemaWithCircularReference')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'should return a json version of the payload' do
|
|
63
|
+
described_class.new(**payload_hash)
|
|
64
|
+
payload_h = {
|
|
65
|
+
'properties' => {
|
|
66
|
+
a_boolean: {
|
|
67
|
+
'property' =>true
|
|
68
|
+
},
|
|
69
|
+
an_integer: {
|
|
70
|
+
'property' =>1
|
|
71
|
+
},
|
|
72
|
+
a_float: {
|
|
73
|
+
'property' =>4.5
|
|
74
|
+
},
|
|
75
|
+
a_string: {
|
|
76
|
+
'property' =>'string'
|
|
77
|
+
},
|
|
78
|
+
an_array: {
|
|
79
|
+
'property' =>[1, 2, 3]
|
|
80
|
+
},
|
|
81
|
+
an_hash: {
|
|
82
|
+
'property' =>{
|
|
83
|
+
'a_key' => 'a_value'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
expect(klass.as_json).to eq(payload_h)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'should return a JSON string of the payload' do
|
|
93
|
+
s = '{"properties":{"a_boolean":{"property":true},"an_integer":{"property":1},"a_float":{"property":4.5},"a_str' \
|
|
94
|
+
'ing":{"property":"string"},"an_array":{"property":[1,2,3]},"an_hash":{"property":{"a_key":"a_value"}}}}'
|
|
95
|
+
expect(klass.to_json).to eq(s)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|