require 'assert' require 'mr/read_model/fields' require 'much-plugin' require 'mr/factory' require 'mr/json_field' require 'mr/read_model' require 'mr/read_model/data' module MR::ReadModel::Fields class UnitTests < Assert::Context desc "MR::ReadModel::Fields" setup do @read_model_class = Class.new do include MR::ReadModel::Fields def initialize(data); set_read_model_data(data); end end @struct_class = Class.new{ include MR::ReadModelStruct } end subject{ @read_model_class } should have_imeths :fields, :field should have_imeths :json_struct_list, :json_struct_lists should have_imeths :json_struct_obj, :json_struct_objs should "use much-plugin" do assert_includes MuchPlugin, MR::ReadModel::Fields end should "use the mr read model data mixin" do assert_includes MR::ReadModel::Data, subject end should "return a FieldSet using `fields`" do fields = subject.fields assert_instance_of MR::ReadModel::FieldSet, fields assert_same fields, subject.fields end should "add a field to the FieldSet using `field`" do subject.field :test, :string field = subject.fields.find(:test) assert_instance_of MR::ReadModel::Field, field assert_equal 'test', field.name assert_equal :string, field.type end should "define a reader method for the field using `field`" do subject.field :test, :string data = { 'test' => 'something' } read_model = subject.new(data) assert_respond_to :test, read_model assert_equal 'something', read_model.test end should "raise an argument error when `field` is passed an invalid field type" do assert_raises(ArgumentError){ subject.field(:test, :invalid) } end should "know how to add a json struct list" do field_name = Factory.string struct_class_name = Factory.boolean ? @struct_class.to_s : @struct_class subject.json_struct_list(field_name, struct_class_name) assert_equal 1, subject.json_struct_lists.size field = subject.json_struct_lists.first assert_instance_of MR::ReadModel::JsonStructListField, field assert_equal field_name, field.name assert_equal struct_class_name, field.struct_class_name assert_respond_to field.name, subject.new({}) end should "not add duplicate json struct lists" do assert_equal 0, subject.json_struct_lists.size field_name = Factory.string subject.json_struct_list(field_name, @struct_class) assert_equal 1, subject.json_struct_lists.size subject.json_struct_list(field_name, @struct_class) assert_equal 1, subject.json_struct_lists.size end should "know how to add a json struct obj" do field_name = Factory.string struct_class_name = Factory.boolean ? @struct_class.to_s : @struct_class subject.json_struct_obj(field_name, struct_class_name) assert_equal 1, subject.json_struct_objs.size field = subject.json_struct_objs.first assert_instance_of MR::ReadModel::JsonStructObjField, field assert_equal field_name, field.name assert_equal struct_class_name, field.struct_class_name assert_respond_to field.name, subject.new({}) end should "not add duplicate json struct objs" do assert_equal 0, subject.json_struct_objs.size field_name = Factory.string subject.json_struct_obj(field_name, @struct_class) assert_equal 1, subject.json_struct_objs.size subject.json_struct_obj(field_name, @struct_class) assert_equal 1, subject.json_struct_objs.size end end class InstanceTests < UnitTests desc "for a read model instance" setup do @read_model_class.field(:name, :string) @read_model_class.field(:active, :boolean) @read_model_class.field(:description, :string) @read_model_class.json_struct_obj(:struct_obj, @struct_class) @read_model_class.json_struct_list(:struct_list, @struct_class) @struct_class.field(:name, :string) @struct_list_datas = Factory.integer(3).times.map do { 'name' => Factory.string } end @struct_obj_data = { 'name' => Factory.string } @data = { 'name' => 'test', 'active' => true, 'description' => 'desc', 'struct_list' => MR::JsonField.encode(@struct_list_datas), 'struct_obj' => MR::JsonField.encode(@struct_obj_data) } @read_model = @read_model_class.new(@data) end subject{ @read_model } should have_imeths :fields should "know how to read json struct list fields" do results = subject.struct_list assert_instance_of Array, results assert_equal @struct_list_datas.size, results.size results.each_with_index do |result, n| assert_instance_of @struct_class, result exp = @struct_list_datas[n]['name'] assert_equal exp, result.name end assert_same results, subject.struct_list end should "know how to read json struct obj fields" do result = subject.struct_obj assert_instance_of @struct_class, result assert_equal @struct_obj_data['name'], result.name assert_same result, subject.struct_obj end should "allow reading all the fields using `fields`" do exp = { 'name' => subject.name, 'active' => subject.active, 'description' => subject.description, 'struct_list' => subject.struct_list, 'struct_obj' => subject.struct_obj } assert_equal exp, subject.fields end end class FieldSetTests < UnitTests desc "FieldSet" setup do @field_set = MR::ReadModel::FieldSet.new.tap do |s| s.add :name, :string s.add :active, :boolean end end subject{ @field_set } should have_imeths :find, :read_all should have_imeths :add should have_imeths :each should "be enumerable" do assert_includes Enumerable, MR::ReadModel::FieldSet end should "return all of it's fields values using `read_all`" do @data = { 'name' => 'Name', 'active' => 'true' } expected = { 'name' => 'Name', 'active' => true } assert_equal expected, subject.read_all(@data) end should "yield it's fields using `each`" do yielded_fields = [] subject.each{ |f| yielded_fields << f } assert_includes subject.find(:name), yielded_fields assert_includes subject.find(:active), yielded_fields end end class FieldTests < UnitTests desc "Field" setup do @field = MR::ReadModel::Field.new(:test, :boolean) end subject{ @field } should have_readers :name, :type should have_readers :method_name, :ivar_name should have_imeths :read, :define_on should "know it's name and type" do assert_equal 'test', subject.name assert_equal :boolean, subject.type end should "know it's method name and ivar name" do assert_equal 'test', subject.method_name assert_equal '@test', subject.ivar_name end should "read a value and type-cast it from passed-in data using `read`" do assert_equal true, subject.read('test' => 'true') assert_equal false, subject.read('test' => 'false') end should "return `nil` when passed-in data's value is `nil` using `read`" do assert_equal nil, subject.read('test' => nil) end should "define a reader method on an object using `define_on`" do subject.define_on(@read_model_class) read_model = @read_model_class.new('test' => 'true') assert_respond_to subject.method_name, read_model value = read_model.send(subject.method_name) assert_same value, read_model.send(subject.method_name) end should "raise an invalid field type error when built with an invalid field type" do assert_raises(MR::ReadModel::InvalidFieldTypeError) do MR::ReadModel::Field.new(:test, :invalid) end end end class JsonStructFieldSetupTests < UnitTests setup do @read_model_class = TestReadModel @field_name = Factory.string @struct_class = @read_model_class::TestStruct end subject{ @field } class TestReadModel include MR::ReadModel class TestStruct include MR::ReadModelStruct field :name, :string end end end class JsonStructFieldTests < JsonStructFieldSetupTests desc "JsonStructField" setup do @field_class = MR::ReadModel::JsonStructField @field = @field_class.new(@read_model_class, @field_name, @struct_class) end should have_readers :read_model_class, :name, :struct_class_name should have_imeths :struct_class, :reader should "know its struct class" do assert_equal @struct_class, subject.struct_class field = @field_class.new(@read_model_class, @field_name, 'TestStruct') assert_equal @struct_class, field.struct_class field = @field_class.new(@read_model_class, @field_name, '::GlobalTestReadModelStruct') assert_equal GlobalTestReadModelStruct, field.struct_class end should "know how to read json struct data from a read model data" do json_data = if Factory.boolean Factory.integer(3).times.map{ { Factory.string => Factory.string } } else { Factory.string => Factory.string } end read_model_data = { @field_name => MR::JsonField.encode(json_data) } assert_equal json_data, subject.reader(read_model_data) end should "know how to read json struct data when its read model data isn't JSON" do json_data = if Factory.boolean Factory.integer(3).times.map{ { Factory.string => Factory.string } } else { Factory.string => Factory.string } end read_model_data = { @field_name => json_data } assert_equal json_data, subject.reader(read_model_data) end should "return `nil` if the read model data doesn't contain the json struct data" do read_model_data = if Factory.boolean { @field_name => nil } else {} end assert_nil subject.reader(read_model_data) end should "raise a custom message if the json struct data can't be decoded" do read_model_data = { @field_name => Factory.text } original_error_message = Factory.text Assert.stub(MR::JsonField, :decode) do raise MR::JsonField::InvalidJSONError, original_error_message end exception = assert_raises(MR::JsonField::InvalidJSONError) do subject.reader(read_model_data) end exp = "can't decode `#{@field_name}` JSON: #{original_error_message}" assert_equal exp, exception.message end class ::GlobalTestReadModelStruct include MR::ReadModelStruct end end class JsonStructListFieldTests < JsonStructFieldSetupTests desc "JsonStructListField" setup do @field_class = MR::ReadModel::JsonStructListField @field = @field_class.new(@read_model_class, @field_name, @struct_class) end should have_imeths :reader should "know how to read json struct list data from a read model data" do json_datas = Factory.integer(3).times.map{ { 'name' => Factory.string } } read_model_data = { @field_name => MR::JsonField.encode(json_datas) } exp = json_datas.map{ |data| @struct_class.new(data) } assert_equal exp, subject.reader(read_model_data) read_model_data = { @field_name => json_datas } assert_equal exp, subject.reader(read_model_data) end should "return `nil` if the read model data doesn't contain the json struct list data" do read_model_data = if Factory.boolean { @field_name => nil } else {} end assert_nil subject.reader(read_model_data) end end class JsonStructObjFieldTests < JsonStructFieldSetupTests desc "JsonStructObjField" setup do @field_class = MR::ReadModel::JsonStructObjField @field = @field_class.new(@read_model_class, @field_name, @struct_class) end should have_imeths :reader should "know how to read json struct obj data from a read model data" do json_data = { 'name' => Factory.string } read_model_data = { @field_name => MR::JsonField.encode(json_data) } exp = @struct_class.new(json_data) assert_equal exp, subject.reader(read_model_data) read_model_data = { @field_name => json_data } assert_equal exp, subject.reader(read_model_data) end should "return `nil` if the read model data doesn't contain the json struct obj data" do read_model_data = if Factory.boolean { @field_name => nil } else {} end assert_nil subject.reader(read_model_data) end end end