require 'spec_helper' require_relative '../../../lib/locomotive/steam/adapters/filesystem.rb' describe Locomotive::Steam::ContentEntryRepository do let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) } let(:type) { build_content_type('Articles', label_field_name: :title, localized_names: [:title], fields: _fields, fields_by_name: { title: instance_double('Field', name: :title, type: :string) }, fields_with_default: []) } let(:entries) { [{ content_type_id: 1, _position: 0, _label: 'Update #1', title: { fr: 'Mise a jour #1' }, text: { en: 'added some free stuff', fr: 'phrase FR' }, date: '2009/05/12', category: 'General' }] } let(:locale) { :en } let(:site) { instance_double('Site', _id: 1, default_locale: :en, locales: %i(en fr)) } let(:adapter) { Locomotive::Steam::FilesystemAdapter.new(nil) } let(:content_type_repository) { instance_double('ContentTypeRepository') } let(:repository) { described_class.new(adapter, site, locale, content_type_repository) } before do allow(adapter).to receive(:collection).and_return(entries) adapter.cache = NoCacheStore.new end describe '#all' do let(:conditions) { nil } subject { repository.with(type).all(conditions) } it { expect(subject.size).to eq 1 } describe 'first element' do subject { repository.with(type).all(conditions).first } it { expect(subject.class).to eq Locomotive::Steam::ContentEntry } it { expect(subject._label.translations).to eq('en' => 'Update #1', 'fr' => 'Mise a jour #1') } it { expect(subject._slug.translations).to eq('en' => 'update-number-1', 'fr' => 'mise-a-jour-number-1') } it { expect(subject.title.translations).to eq('en' => 'Update #1', 'fr' => 'Mise a jour #1') } it { expect(subject.content_type).to eq type } end end describe '#build' do let(:attributes) { { title: 'Hello world' } } subject { repository.with(type).build(attributes) } it { expect(subject.title[:en]).to eq 'Hello world' } it { expect(subject.content_type).to eq type } end describe '#exists?' do let(:conditions) { {} } subject { repository.with(type).exists?(conditions) } it { expect(subject).to eq true } context 'more specific conditions' do let(:conditions) { { '_slug' => 'update-number-1' } } it { expect(subject).to eq true } end context 'conditions which do match any entries' do let(:conditions) { { '_slug' => 'foo' } } it { expect(subject).to eq false } end end describe '#by_slug' do let(:slug) { nil } subject { repository.with(type).by_slug(slug) } it { is_expected.to eq nil } context 'existing slug' do let(:slug) { 'update-number-1' } it { expect(subject.title.translations).to eq('en' => 'Update #1', 'fr' => 'Mise a jour #1') } end end describe '#value_for' do let(:entry) { nil } let(:conditions) { {} } let(:name) { :title } subject { repository.with(type).value_for(entry, name, conditions) } it { is_expected.to eq nil } context 'existing entry' do let(:entry) { instance_double('Entry', title: 'Hello world') } it { is_expected.to eq 'Hello world' } context 'unknown field' do let(:name) { :authors } it { is_expected.to eq nil } end end context 'with a has_many field' do let(:type) { build_content_type('Articles', label_field_name: :title, localized_names: [:title], fields: _fields, fields_by_name: { articles: instance_double('Field', type: :has_many) }, fields_with_default: []) } let(:proxy_repository) { repository.dup } let(:entry) { instance_double('Entry', articles: proxy_repository) } let(:name) { :articles } let(:conditions) { { published: true } } it 'does not modify the local conditions of the initial proxy repository' do expect(subject.local_conditions).to eq(content_type_id: 1, published: true) expect(proxy_repository.local_conditions).to eq(content_type_id: 1) end end end describe '#next or #previous' do let(:direction) { 'asc' } let(:type) { build_content_type('Articles', order_by: { _position: direction }, label_field_name: :title, localized_names: [:title], fields: _fields, fields_by_name: { title: instance_double('Field', name: :title, type: :string) }, fields_with_default: []) } let(:entry) { nil } let(:entries) do [ { content_type_id: 1, _position: 0, _label: 'Update #1', title: { fr: 'Mise a jour #1' }, text: { en: 'added some free stuff', fr: 'phrase FR' }, date: '2009/05/12', category: 'General' }, { content_type_id: 1, _position: 1, _label: 'Update #2', title: { fr: 'Mise a jour #2' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' }, { content_type_id: 1, _position: 2, _label: 'Update #3', title: { fr: 'Mise a jour #3' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' }, { content_type_id: 1, _position: 3, _label: 'Update #4', title: { fr: 'Mise a jour #4' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' } ] end describe '#next' do subject { repository.next(entry) } it { is_expected.to eq nil } context 'being last' do let(:entry) { instance_double('Entry', content_type: type, _position: 3) } it { is_expected.to eq nil } end context 'being middle' do let(:entry) { instance_double('Entry', content_type: type, _position: 0) } it { expect(subject._position).to eq 1 } describe 'another example' do let(:entry) { instance_double('Entry', content_type: type, _position: 1) } it { expect(subject._position).to eq 2 } end context 'changing direction' do let(:direction) { 'desc' } let(:entry) { instance_double('Entry', content_type: type, _position: 2) } it { expect(subject._position).to eq 1 } end end end describe '#previous' do subject { repository.previous(entry) } it { is_expected.to eq nil } context 'being first' do let(:entry) { instance_double('Entry', content_type: type, _position: 0) } it { is_expected.to eq nil } end context 'being middle' do let(:entry) { instance_double('Entry', content_type: type, _position: 1) } it { expect(subject._position).to eq 0 } describe 'another example' do let(:entry) { instance_double('Entry', content_type: type, _position: 2) } it { expect(subject._position).to eq 1 } end context 'changing direction' do let(:direction) { 'desc' } let(:entry) { instance_double('Entry', content_type: type, _position: 2) } it { expect(subject._position).to eq 3 } end end end end describe '#group_by_select_option' do let(:type) { nil } let(:name) { nil } subject { repository.with(type).group_by_select_option(name) } it { is_expected.to eq({}) } context 'select field' do let(:fields) do { title: instance_double('TitleField', name: :title, type: :string), category: instance_double('SelectField', name: :category, type: :select, select_options: { en: ['cooking', 'bread'], fr: ['cuisine', 'pain'] }) } end let(:type) { build_content_type('Articles', order_by: '_position asc', label_field_name: :title, localized_names: [:title, :category], fields: _fields, fields_by_name: fields, fields_with_default: []) } let(:name) { :category } let(:options) { [ instance_double('SelectOption1', name: 'cooking'), instance_double('SelectOption2', name: 'wine'), instance_double('SelectOption3', name: 'bread') ] } let(:entries) do [ { content_type_id: 1, _position: 0, _label: 'Recipe #1', category_id: 'cooking' }, { content_type_id: 1, _position: 1, _label: 'Recipe #2', category_id: 'bread' }, { content_type_id: 1, _position: 2, _label: 'Recipe #3', category_id: 'bread' }, { content_type_id: 1, _position: 3, _label: 'Recipe #4', category_id: 'unknown' } ] end before { allow(content_type_repository).to receive(:select_options).and_return(options) %w(cooking wine bread).each_with_index do |name, i| allow(fields[:category].select_options).to receive(:find).with(name).and_return(options.at(i)) end allow(fields[:category].select_options).to receive(:find).with('unknown').and_return(nil) } it { expect(subject.size).to eq 4 } it { expect(subject.map { |h| h[:name] }).to eq ['cooking', 'wine', 'bread', nil] } it { expect(subject.map { |h| h[:entries].size }).to eq [1, 0, 2, 1] } end end describe 'belongs_to' do let(:field) { instance_double('Field', name: :author, type: :belongs_to, association_options: { target_id: 2 }) } let(:type) { build_content_type('Articles', label_field_name: :title, association_fields: [field], fields_with_default: []) } let(:entries) { [{ content_type_id: 1, title: 'Hello world', author_id: 'john-doe' }] } let(:other_type) { build_content_type('Authors', _id: 2, label_field_name: :name, fields: _fields, fields_by_name: { name: instance_double('Field', name: :name, type: :string) }, fields_with_default: []) } let(:other_entries) { [{ content_type_id: 2, _id: 'john-doe', name: 'John Doe' }] } let(:type_repository) { instance_double('ArticleBelongsToRepository', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) } before do allow(type).to receive(:fields).and_return(type_repository) allow(content_type_repository).to receive(:find).with(2).and_return(other_type) end subject { repository.with(type).by_slug('hello-world') } it { expect(subject.author.class).to eq Locomotive::Steam::Models::BelongsToAssociation } it 'calls the new repository to fetch the target entity' do author = subject.author allow(adapter).to receive(:collection).and_return(other_entries) expect(author.name).to eq 'John Doe' end end describe 'has_many' do let(:field) { instance_double('Field', name: :articles, type: :has_many, association_options: { target_id: 2, inverse_of: :author, order_by: 'position_in_author' }) } let(:type) { build_content_type('Authors', label_field_name: :name, association_fields: [field], fields_with_default: []) } let(:entries) { [{ content_type_id: 1, _id: 'john-doe', name: 'John Doe' }] } let(:other_type) { build_content_type('Articles', _id: 2, label_field_name: :title, fields: _fields, fields_by_name: { name: instance_double('Field', name: :title, type: :string) }, fields_with_default: []) } let(:other_entries) { [ { content_type_id: 2, _id: 'hello-world', title: 'Hello world', author_id: 'john-doe', position_in_author: 2 }, { content_type_id: 2, _id: 'lorem-ipsum', title: 'Lorem ipsum', author_id: 'john-doe', position_in_author: 1 }, { content_type_id: 2, _id: 'lost', title: 'Lost', author_id: 'jane-doe' }, ] } let(:type_repository) { instance_double('AuthorRepository', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) } before do allow(type).to receive(:fields).and_return(type_repository) allow(content_type_repository).to receive(:find).with(2).and_return(other_type) end subject { repository.with(type).by_slug('john-doe') } it { expect(subject.articles.class).to eq Locomotive::Steam::Models::HasManyAssociation } it 'calls the new repository to fetch the target entities' do articles = subject.articles allow(adapter).to receive(:collection).and_return(other_entries) expect(articles.all.map(&:title)).to eq ['Lorem ipsum', 'Hello world'] end end describe 'many_to_many' do let(:field) { instance_double('Field', name: :articles, type: :many_to_many, association_options: { target_id: 2, inverse_of: :authors }) } let(:type) { build_content_type('Authors', label_field_name: :name, association_fields: [field], fields: _fields, fields_with_default: []) } let(:entries) { [{ content_type_id: 1, _id: 1, name: 'John Doe', article_ids: ['hello-world', 'lorem-ipsum'] }] } let(:other_type) { build_content_type('Articles', _id: 2, label_field_name: :title, fields: _fields, fields_by_name: { name: instance_double('Field', name: :title, type: :string) }, fields_with_default: []) } let(:other_entries) { [ { content_type_id: 2, _id: 'hello-world', title: 'Hello world', author_id: 'john-doe', position_in_author: 2 }, { content_type_id: 2, _id: 'lorem-ipsum', title: 'Lorem ipsum', author_id: 'john-doe', position_in_author: 1 }, { content_type_id: 2, _id: 'lost', title: 'Lost', author_id: 'jane-doe' }, ] } let(:type_repository) { instance_double('AuthorRepository', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) } before do allow(type).to receive(:fields).and_return(type_repository) allow(content_type_repository).to receive(:find).with(2).and_return(other_type) end subject { repository.with(type).by_slug('john-doe') } it { expect(subject.articles.class).to eq Locomotive::Steam::Models::ManyToManyAssociation } it 'calls the new repository to fetch the target entities' do articles = subject.articles allow(adapter).to receive(:collection).and_return(other_entries) expect(articles.all.map(&:title)).to eq ['Hello world', 'Lorem ipsum'] end end describe '#conditions_without_order_by' do let(:conditions) { {} } subject { repository.with(type).send(:conditions_without_order_by, conditions) } it { is_expected.to eq([{ _visible: true, content_type_id: 1 }, nil]) } context 'select fields' do let(:value) { 'CMS' } let(:option) { instance_double('Option', _id: 42)} let(:options) { instance_double('OptionRepository', by_name: option) } let(:field) { instance_double('SelectField', name: 'category', persisted_name: 'category_id', select_options: options) } let(:_fields) { instance_double('Fields', selects: [field], belongs_to: [], many_to_many: [], dates_and_date_times: []) } let(:conditions) { { 'category' => value } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'category_id' => 42 }, nil]) } end context 'date fields' do let(:value) { '2009/09/10' } let(:field) { instance_double('DateField', name: 'launched_at', persisted_name: 'launched_at', type: :date) } let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: [field]) } let(:conditions) { { 'launched_at' => value } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'launched_at' => Date.parse('2009/09/10') }, nil]) } end context 'date time fields' do before { Time.zone = 'Paris' } let(:value) { '2007/06/29 21:15:00' } let(:field) { instance_double('DateField', name: 'launched_at', persisted_name: 'launched_at', type: :date_time) } let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: [field]) } let(:conditions) { { 'launched_at' => value } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'launched_at' => Time.zone.parse('2007/06/29 21:15:00').to_datetime }, nil]) } end context 'belongs_to fields' do let(:value) { 42 } let(:field) { instance_double('BelongsToField', name: 'person', persisted_name: 'person_id') } let(:_fields) { instance_double('Fields', selects: [], belongs_to: [field], many_to_many: [], dates_and_date_times: []) } let(:conditions) { { 'person' => value } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id' => 42 }, nil]) } context 'the target value is a content entry' do let(:value) { instance_double('TargetContentEntry', _id: 1) } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id' => 1 }, nil]) } end context 'the target is a hash' do let(:value) { { '_id' => 42 } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id' => 42 }, nil]) } end context 'the target value is an arry of content entry' do let(:value) { [instance_double('TargetContentEntry', _id: 1), instance_double('TargetContentEntry', _id: 2)] } let(:conditions) { { 'person.in' => value } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id.in' => [1, 2] }, nil]) } end context 'testing a nil value (field.ne => nil)' do let(:conditions) { { 'person.ne' => nil } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id.ne' => nil }, nil]) } end end context 'many_to_many fields' do let(:value) { 42 } let(:field) { instance_double('ManyToManyField', name: 'tags', persisted_name: 'tag_ids') } let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [field], dates_and_date_times: []) } let(:conditions) { { 'tags.in' => value } } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'tag_ids.in' => [42] }, nil]) } context 'the target value is a content entry' do let(:value) { [instance_double('TargetContentEntry', _id: 1), 42] } it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'tag_ids.in' => [1, 42] }, nil]) } end end end def build_content_type(name, attributes = {}) instance_double(name, { _id: 1, slug: name.to_s.downcase, order_by: nil, localized_names: [], association_fields: [], fields_by_name: {} }.merge(attributes)) end end