gladwords 1.0.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 +7 -0
- data/.circleci/config.yml +34 -0
- data/.gitignore +4 -0
- data/.projections.json +5 -0
- data/.rspec +1 -0
- data/.rubocop.yml +57 -0
- data/.rubocop_todo.yml +32 -0
- data/.vim/coc-settings.json +12 -0
- data/.vim/install.sh +38 -0
- data/.vscode/launch.json +13 -0
- data/.vscode/settings.json +9 -0
- data/.vscode/tasks.json +21 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +200 -0
- data/LICENSE.txt +21 -0
- data/README.md +71 -0
- data/Rakefile +15 -0
- data/bin/rake +31 -0
- data/bin/rspec +31 -0
- data/bin/solargraph +29 -0
- data/config/environment.rb +3 -0
- data/gladwords.code-workspace +11 -0
- data/gladwords.gemspec +27 -0
- data/lib/ext/rom/inflector.rb +8 -0
- data/lib/gladwords.rb +22 -0
- data/lib/gladwords/associations.rb +7 -0
- data/lib/gladwords/associations/many_to_many.rb +18 -0
- data/lib/gladwords/associations/many_to_one.rb +22 -0
- data/lib/gladwords/associations/one_to_many.rb +19 -0
- data/lib/gladwords/associations/one_to_one.rb +10 -0
- data/lib/gladwords/associations/one_to_one_through.rb +8 -0
- data/lib/gladwords/commands.rb +7 -0
- data/lib/gladwords/commands/core.rb +76 -0
- data/lib/gladwords/commands/create.rb +18 -0
- data/lib/gladwords/commands/delete.rb +22 -0
- data/lib/gladwords/commands/error_wrapper.rb +25 -0
- data/lib/gladwords/commands/update.rb +17 -0
- data/lib/gladwords/errors.rb +7 -0
- data/lib/gladwords/gateway.rb +48 -0
- data/lib/gladwords/inflector.rb +20 -0
- data/lib/gladwords/relation.rb +197 -0
- data/lib/gladwords/relation/association_methods.rb +29 -0
- data/lib/gladwords/relation/joined_relation.rb +52 -0
- data/lib/gladwords/schema.rb +26 -0
- data/lib/gladwords/schema/attributes_inferrer.rb +171 -0
- data/lib/gladwords/schema/dsl.rb +28 -0
- data/lib/gladwords/schema/inferrer.rb +19 -0
- data/lib/gladwords/selector_fields_db.rb +30 -0
- data/lib/gladwords/selector_fields_db/v201806.json +3882 -0
- data/lib/gladwords/selector_fields_db/v201809.json +4026 -0
- data/lib/gladwords/struct.rb +24 -0
- data/lib/gladwords/types.rb +27 -0
- data/lib/gladwords/version.rb +5 -0
- data/rakelib/generate_selector_fields_db.rake +72 -0
- data/spec/integration/commands/create_spec.rb +24 -0
- data/spec/integration/commands/delete_spec.rb +47 -0
- data/spec/integration/commands/update_spec.rb +24 -0
- data/spec/shared/campaigns.rb +56 -0
- data/spec/shared/labels.rb +17 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/adwords_helpers.rb +41 -0
- data/spec/unit/commands/create_spec.rb +85 -0
- data/spec/unit/commands/delete_spec.rb +32 -0
- data/spec/unit/commands/update_spec.rb +96 -0
- data/spec/unit/inflector_spec.rb +11 -0
- data/spec/unit/relation/association_methods_spec.rb +91 -0
- data/spec/unit/relation_spec.rb +187 -0
- data/spec/unit/schema/attributes_inferrer_spec.rb +83 -0
- data/spec/unit/selector_fields_db_spec.rb +29 -0
- data/spec/unit/types_spec.rb +49 -0
- metadata +190 -0
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Gladwords::Commands::Delete do
         | 
| 4 | 
            +
              include_context 'labels'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              subject(:command) do
         | 
| 7 | 
            +
                relation.command(:delete)
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              let(:service) { label_service }
         | 
| 11 | 
            +
              let(:relation) { labels }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              before do
         | 
| 14 | 
            +
                allow(service).to receive(:get).and_return(entries: [{ id: '1' }])
         | 
| 15 | 
            +
                allow(service).to receive(:mutate).and_return(value: [{ id: '1' }])
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              it 'mutates the service with the correct operations' do
         | 
| 19 | 
            +
                expect(service).to receive(:mutate).with(
         | 
| 20 | 
            +
                  [
         | 
| 21 | 
            +
                    {
         | 
| 22 | 
            +
                      operator: 'REMOVE',
         | 
| 23 | 
            +
                      operand: { id: '1' }
         | 
| 24 | 
            +
                    }
         | 
| 25 | 
            +
                  ]
         | 
| 26 | 
            +
                )
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                rel = relation.where(id: 1)
         | 
| 29 | 
            +
                delete_command = rel.command(:delete)
         | 
| 30 | 
            +
                delete_command.call
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'securerandom'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            RSpec.describe Gladwords::Commands::Update do
         | 
| 6 | 
            +
              include_context 'campaigns'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              subject(:command) do
         | 
| 9 | 
            +
                relation.command(:update)
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              let(:service) { campaign_service }
         | 
| 13 | 
            +
              let(:relation) { campaigns }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              context 'when provided a single tuple' do
         | 
| 16 | 
            +
                before do
         | 
| 17 | 
            +
                  allow(service).to receive(:mutate).and_return(
         | 
| 18 | 
            +
                    value: [{ name: 'updated name', id: '1', end_date: '01012017' }]
         | 
| 19 | 
            +
                  )
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                it 'mutates the service with the correct operations' do
         | 
| 23 | 
            +
                  expect(service).to receive(:mutate).with(
         | 
| 24 | 
            +
                    [
         | 
| 25 | 
            +
                      {
         | 
| 26 | 
            +
                        operator: 'SET',
         | 
| 27 | 
            +
                        operand: {
         | 
| 28 | 
            +
                          name: 'updated name',
         | 
| 29 | 
            +
                          id: '1'
         | 
| 30 | 
            +
                        }
         | 
| 31 | 
            +
                      }
         | 
| 32 | 
            +
                    ]
         | 
| 33 | 
            +
                  )
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  subject.call(name: 'updated name', id: '1')
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it 'returns a struct' do
         | 
| 39 | 
            +
                  result = subject.call(name: 'updated name', id: '1')
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  expect(result).to be_a(Gladwords::Struct::Campaign)
         | 
| 42 | 
            +
                  expect(result.id).to eq '1'
         | 
| 43 | 
            +
                  expect(result.name).to eq 'updated name'
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              context 'when provided multiple tuples' do
         | 
| 48 | 
            +
                subject(:command) do
         | 
| 49 | 
            +
                  relation.command(:update, result: :many)
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                before do
         | 
| 53 | 
            +
                  allow(service).to(
         | 
| 54 | 
            +
                    receive(:mutate).and_return(
         | 
| 55 | 
            +
                      value: [
         | 
| 56 | 
            +
                        { name: 'updated name', id: '1', end_date: '01012017' },
         | 
| 57 | 
            +
                        { name: 'updated name 2', id: '2', end_date: '01012018' }
         | 
| 58 | 
            +
                      ]
         | 
| 59 | 
            +
                    )
         | 
| 60 | 
            +
                  )
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                it 'mutates the service with the correct operations' do
         | 
| 64 | 
            +
                  expect(service).to receive(:mutate).with(
         | 
| 65 | 
            +
                    [
         | 
| 66 | 
            +
                      {
         | 
| 67 | 
            +
                        operator: 'SET',
         | 
| 68 | 
            +
                        operand: {
         | 
| 69 | 
            +
                          name: 'updated name',
         | 
| 70 | 
            +
                          id: '1'
         | 
| 71 | 
            +
                        }
         | 
| 72 | 
            +
                      },
         | 
| 73 | 
            +
                      {
         | 
| 74 | 
            +
                        operator: 'SET',
         | 
| 75 | 
            +
                        operand: {
         | 
| 76 | 
            +
                          name: 'updated name 2',
         | 
| 77 | 
            +
                          id: '2'
         | 
| 78 | 
            +
                        }
         | 
| 79 | 
            +
                      }
         | 
| 80 | 
            +
                    ]
         | 
| 81 | 
            +
                  )
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  subject.call([{ name: 'updated name', id: '1' }, { name: 'updated name 2', id: '2' }])
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                it 'returns an array of structs' do
         | 
| 87 | 
            +
                  result = subject.call(
         | 
| 88 | 
            +
                    [{ name: 'updated name', id: '1' }, { name: 'updated name 2', id: '2' }]
         | 
| 89 | 
            +
                  )
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  expect(result.count).to eq 2
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  expect(result).to all be_a(Gladwords::Struct::Campaign)
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Gladwords::Inflector do
         | 
| 4 | 
            +
              it 'correctly pluralizes criterion' do
         | 
| 5 | 
            +
                expect(described_class.pluralize('criterion')).to eq 'criteria'
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              it 'correctly singularizes criteria' do
         | 
| 9 | 
            +
                expect(described_class.singularize('criteria')).to eq 'criterion'
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,91 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Gladwords
         | 
| 4 | 
            +
              RSpec.describe Relation::AssociationMethods do
         | 
| 5 | 
            +
                include_context 'campaigns'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                context 'when the relation is one-to-many' do
         | 
| 8 | 
            +
                  subject { campaigns }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  describe '#join' do
         | 
| 11 | 
            +
                    let(:campaign_id) { 1_338_164_832 }
         | 
| 12 | 
            +
                    let(:filtered_relation) { subject.select(:id).where(id: campaign_id).join(:ad_groups) }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    it 'filters the target relation' do
         | 
| 15 | 
            +
                      returned_ids = filtered_relation.call.flat_map(&:ad_groups).map(&:campaign_id)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      expect(returned_ids).to all eq(campaign_id)
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    it 'returns the filtered combined relationship' do
         | 
| 21 | 
            +
                      ad_groups = filtered_relation.ad_groups.call
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      aggregate_failures do
         | 
| 24 | 
            +
                        expect(ad_groups).to all be_a(Gladwords::Struct::AdGroup)
         | 
| 25 | 
            +
                        expect(ad_groups.pluck(:campaign_id)).to all eq(campaign_id)
         | 
| 26 | 
            +
                      end
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    it 'can query on the combined node' do
         | 
| 30 | 
            +
                      scope = filtered_relation.node(:ad_groups) { |n| n.select(:campaign_id) }
         | 
| 31 | 
            +
                      ad_group = scope.one.ad_groups.first
         | 
| 32 | 
            +
                      expect(ad_group.to_h).to eql(campaign_id: campaign_id)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                context 'when the relation is many-to-one' do
         | 
| 38 | 
            +
                  subject { rom.relations[:ad_groups] }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  describe '#combine' do
         | 
| 41 | 
            +
                    let(:campaign_id) { 1_338_164_832 }
         | 
| 42 | 
            +
                    let(:filtered_relation) { subject.combine(:campaign) }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    it 'filters the target relation' do
         | 
| 45 | 
            +
                      scope = filtered_relation.where(campaign_id: campaign_id)
         | 
| 46 | 
            +
                      ad_group = scope.limit(1).call.one
         | 
| 47 | 
            +
                      expect(ad_group.campaign.id).to eql(campaign_id)
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    it 'can query on the combined node' do
         | 
| 51 | 
            +
                      scope = filtered_relation.where(campaign_id: campaign_id)
         | 
| 52 | 
            +
                      scope = scope.node(:campaign) { |n| n.select(:id) }
         | 
| 53 | 
            +
                      campaign = scope.call.one.campaign
         | 
| 54 | 
            +
                      expect(campaign.to_h).to eql(id: campaign_id)
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                context 'when the relation is has-many through' do
         | 
| 60 | 
            +
                  subject { campaigns }
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  describe '#combine' do
         | 
| 63 | 
            +
                    let(:campaign_id) { 1_338_164_832 }
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    context 'when the combine is nested' do
         | 
| 66 | 
            +
                      let(:filtered_relation) { subject.combine(ad_groups: [:ad_group_ads]) }
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                      it 'includes the nested has_many through' do
         | 
| 69 | 
            +
                        scope = filtered_relation.where(campaign_id: campaign_id).one
         | 
| 70 | 
            +
                        ads = scope.ad_groups.flat_map(&:ad_group_ads)
         | 
| 71 | 
            +
                        expect(ads).not_to be_empty
         | 
| 72 | 
            +
                        expect(ads.map(&:base_campaign_id)).to all eq(campaign_id)
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    context 'when the combine is direct' do
         | 
| 77 | 
            +
                      let(:filtered_relation) { subject.combine(:ad_group_ads) }
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                      it 'includes the nested has_many through' do
         | 
| 80 | 
            +
                        pending 'Combine is not working for direct has many through'
         | 
| 81 | 
            +
                        scope = filtered_relation.where(campaign_id: campaign_id).one
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                        ads = scope.ad_group_ads
         | 
| 84 | 
            +
                        expect(ads).not_to be_empty
         | 
| 85 | 
            +
                        expect(ads.map(&:base_campaign_id)).to all eq(campaign_id)
         | 
| 86 | 
            +
                      end
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
            end
         | 
| @@ -0,0 +1,187 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Gladwords::Relation do
         | 
| 4 | 
            +
              include_context 'campaigns'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              subject { campaigns }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe '#by_pk' do
         | 
| 9 | 
            +
                it 'loads the resource by id and limits the result' do
         | 
| 10 | 
            +
                  new_scope = subject.by_pk('123')
         | 
| 11 | 
            +
                  predicates = new_scope.options.dig(:selector, :predicates)
         | 
| 12 | 
            +
                  paging = new_scope.options.dig(:selector, :paging)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  expect(predicates).to eql([{ field: 'Id', operator: 'IN', values: ['123'] }])
         | 
| 15 | 
            +
                  expect(paging).to eql(number_results: 1)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              describe '#where' do
         | 
| 20 | 
            +
                it 'chains where clauses' do
         | 
| 21 | 
            +
                  old_scope = subject.select(:id, :name).where(id: '123')
         | 
| 22 | 
            +
                  new_scope = old_scope.where(name: 'TestName')
         | 
| 23 | 
            +
                  expected = [{ field: 'Id',   operator: 'IN', values: ['123'] },
         | 
| 24 | 
            +
                              { field: 'Name', operator: 'IN', values: ['TestName'] }]
         | 
| 25 | 
            +
                  predicates = new_scope.options.dig(:selector, :predicates)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  expect(predicates).to match_array(expected)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                it 'de-duplicates where clauses' do
         | 
| 31 | 
            +
                  old_scope = subject.select(:id, :name).where(id: '123')
         | 
| 32 | 
            +
                  new_scope = old_scope.where(id: '123')
         | 
| 33 | 
            +
                  expected = [{ field: 'Id', operator: 'IN', values: ['123'] }]
         | 
| 34 | 
            +
                  predicates = new_scope.options.dig(:selector, :predicates)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  expect(predicates).to match_array(expected)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              describe '#total_count' do
         | 
| 41 | 
            +
                it 'shows the total_count' do
         | 
| 42 | 
            +
                  scope = subject.select(:id, :name).where(name: 'Test Campaign 2')
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  expect(scope.total_count).to eq 1
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              describe '#offset' do
         | 
| 49 | 
            +
                it 'offsets the start_index' do
         | 
| 50 | 
            +
                  scope = subject.offset(10)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  expect(scope.options.dig(:selector, :paging, :start_index)).to eq 10
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              describe '#limit' do
         | 
| 57 | 
            +
                it 'sets the paging[:number_results]' do
         | 
| 58 | 
            +
                  scope = subject.limit(3)
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  expect(scope.options.dig(:selector, :paging, :number_results)).to eq 3
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              describe '#select' do
         | 
| 65 | 
            +
                it 'camelcases the fields' do
         | 
| 66 | 
            +
                  scope = subject.select(:base_campaign_id, :name)
         | 
| 67 | 
            +
                  fields = scope.options.dig(:selector, :fields)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  expect(fields).to contain_exactly 'BaseCampaignId', 'Name'
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                it 'only selects unique fields' do
         | 
| 73 | 
            +
                  scope = subject.select(:name).select(:name)
         | 
| 74 | 
            +
                  fields = scope.options.dig(:selector, :fields)
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  expect(fields).to contain_exactly 'Name'
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                it 'limits requested fields when using #select' do
         | 
| 80 | 
            +
                  repo = Class.new(ROM::Repository[:campaigns]).new(rom)
         | 
| 81 | 
            +
                  result = repo.campaigns.select(:name).limit(1).one
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  expect(result.to_h.keys).to contain_exactly(:name)
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              describe '#pluck' do
         | 
| 88 | 
            +
                it 'returns an array of the ids' do
         | 
| 89 | 
            +
                  ids = subject.pluck(:id).call
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  expect(ids).not_to be_empty
         | 
| 92 | 
            +
                  expect(ids).to all be_a(Integer)
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                it 'only requests the plucked field' do
         | 
| 96 | 
            +
                  expect(campaign_service)
         | 
| 97 | 
            +
                    .to receive(:get)
         | 
| 98 | 
            +
                    .with(a_hash_including(fields: ['Id']))
         | 
| 99 | 
            +
                    .and_return({})
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  scope = subject.pluck(:id)
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  expect(scope.options.dig(:selector, :fields)).to eql(['Id'])
         | 
| 104 | 
            +
                  scope.call
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
              end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              it 'by default selects all selectable fields' do
         | 
| 109 | 
            +
                repo = Class.new(ROM::Repository[:campaigns]).new(rom)
         | 
| 110 | 
            +
                scope = repo.campaigns.where(name: 'Test Campaign 2')
         | 
| 111 | 
            +
                result = scope.call
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                expected = {
         | 
| 114 | 
            +
                  id: 1_338_164_832,
         | 
| 115 | 
            +
                  status: 'ENABLED',
         | 
| 116 | 
            +
                  serving_status: 'SERVING',
         | 
| 117 | 
            +
                  ad_serving_optimization_status: 'OPTIMIZE',
         | 
| 118 | 
            +
                  advertising_channel_type: 'SEARCH',
         | 
| 119 | 
            +
                  advertising_channel_sub_type: nil,
         | 
| 120 | 
            +
                  campaign_trial_type: 'BASE',
         | 
| 121 | 
            +
                  campaign_group_id: nil,
         | 
| 122 | 
            +
                  name: 'Test Campaign 2',
         | 
| 123 | 
            +
                  start_date: '20180329',
         | 
| 124 | 
            +
                  end_date: '20371230',
         | 
| 125 | 
            +
                  universal_app_campaign_info: nil,
         | 
| 126 | 
            +
                  final_url_suffix: nil,
         | 
| 127 | 
            +
                  budget: {
         | 
| 128 | 
            +
                    budget_id: 1_391_080_779,
         | 
| 129 | 
            +
                    name: 'Test Campaign 2',
         | 
| 130 | 
            +
                    amount: a_hash_including(micro_amount: 999_000_000),
         | 
| 131 | 
            +
                    delivery_method: 'STANDARD',
         | 
| 132 | 
            +
                    reference_count: 1,
         | 
| 133 | 
            +
                    is_explicitly_shared: false,
         | 
| 134 | 
            +
                    status: 'ENABLED'
         | 
| 135 | 
            +
                  },
         | 
| 136 | 
            +
                  conversion_optimizer_eligibility: {
         | 
| 137 | 
            +
                    eligible: false,
         | 
| 138 | 
            +
                    rejection_reasons: ['NOT_ENOUGH_CONVERSIONS']
         | 
| 139 | 
            +
                  },
         | 
| 140 | 
            +
                  frequency_cap: nil,
         | 
| 141 | 
            +
                  settings: [{
         | 
| 142 | 
            +
                    setting_type: 'GeoTargetTypeSetting',
         | 
| 143 | 
            +
                    positive_geo_target_type: 'LOCATION_OF_PRESENCE',
         | 
| 144 | 
            +
                    negative_geo_target_type: 'DONT_CARE',
         | 
| 145 | 
            +
                    xsi_type: 'GeoTargetTypeSetting'
         | 
| 146 | 
            +
                  }],
         | 
| 147 | 
            +
                  network_setting: {
         | 
| 148 | 
            +
                    target_google_search: true,
         | 
| 149 | 
            +
                    target_search_network: true,
         | 
| 150 | 
            +
                    target_content_network: false,
         | 
| 151 | 
            +
                    target_partner_search_network: false
         | 
| 152 | 
            +
                  },
         | 
| 153 | 
            +
                  labels: [],
         | 
| 154 | 
            +
                  bidding_strategy_configuration: {
         | 
| 155 | 
            +
                    bidding_strategy_id: nil,
         | 
| 156 | 
            +
                    bidding_strategy_name: nil,
         | 
| 157 | 
            +
                    bidding_strategy_type: 'MANUAL_CPC',
         | 
| 158 | 
            +
                    bidding_strategy_source: nil,
         | 
| 159 | 
            +
                    bidding_scheme: {
         | 
| 160 | 
            +
                      bidding_scheme_type: 'ManualCpcBiddingScheme',
         | 
| 161 | 
            +
                      enhanced_cpc_enabled: false,
         | 
| 162 | 
            +
                      xsi_type: 'ManualCpcBiddingScheme'
         | 
| 163 | 
            +
                    },
         | 
| 164 | 
            +
                    bids: [],
         | 
| 165 | 
            +
                    target_roas_override: nil
         | 
| 166 | 
            +
                  },
         | 
| 167 | 
            +
                  base_campaign_id: 1_338_164_832,
         | 
| 168 | 
            +
                  forward_compatibility_map: [],
         | 
| 169 | 
            +
                  tracking_url_template: nil,
         | 
| 170 | 
            +
                  url_custom_parameters: nil,
         | 
| 171 | 
            +
                  vanity_pharma: nil,
         | 
| 172 | 
            +
                  selective_optimization: nil
         | 
| 173 | 
            +
                }
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                entries = result.map(&:to_h)
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                expect(entries.first).to match(expected)
         | 
| 178 | 
            +
                expect(entries).to contain_exactly(expected)
         | 
| 179 | 
            +
              end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
              describe '#request' do
         | 
| 182 | 
            +
                it 'raises when the method does not exist' do
         | 
| 183 | 
            +
                  expect { subject.request(:bad_method) }
         | 
| 184 | 
            +
                    .to raise_error described_class::InvalidRequestMethodError
         | 
| 185 | 
            +
                end
         | 
| 186 | 
            +
              end
         | 
| 187 | 
            +
            end
         | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'adwords_api/v201809/campaign_service_registry'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Gladwords
         | 
| 6 | 
            +
              RSpec.describe Schema::AttributesInferrer do
         | 
| 7 | 
            +
                let(:schema) { double(name: :campaigns, options: { shitlist: [] }) }
         | 
| 8 | 
            +
                let(:registry) { AdwordsApi::V201809::CampaignService::CampaignServiceRegistry }
         | 
| 9 | 
            +
                let(:gateway) { double(service_registry: registry) }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                describe '#call' do
         | 
| 12 | 
            +
                  declarations = {
         | 
| 13 | 
            +
                    String => %i[
         | 
| 14 | 
            +
                      name
         | 
| 15 | 
            +
                      status
         | 
| 16 | 
            +
                      serving_status
         | 
| 17 | 
            +
                      start_date
         | 
| 18 | 
            +
                      end_date
         | 
| 19 | 
            +
                      ad_serving_optimization_status
         | 
| 20 | 
            +
                      advertising_channel_type
         | 
| 21 | 
            +
                      advertising_channel_sub_type
         | 
| 22 | 
            +
                      campaign_trial_type
         | 
| 23 | 
            +
                      tracking_url_template
         | 
| 24 | 
            +
                      final_url_suffix
         | 
| 25 | 
            +
                    ],
         | 
| 26 | 
            +
                    Integer => %i[id base_campaign_id campaign_group_id]
         | 
| 27 | 
            +
                  }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  declarations.each do |primitive, fields|
         | 
| 30 | 
            +
                    it "maps to #{primitive.name}", skip: fields.delete(skip: true) do
         | 
| 31 | 
            +
                      types, _missing = subject.call(schema, gateway)
         | 
| 32 | 
            +
                      attrs = types.select { |a| a.type.primitive == primitive }
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                      expect(attrs.map(&:name)).to match_array(fields)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  it 'maps nested types to hashes' do
         | 
| 39 | 
            +
                    types, _missing = subject.call(schema, gateway)
         | 
| 40 | 
            +
                    hash_field = types.find { |t| t.name == :budget }
         | 
| 41 | 
            +
                    member_types = hash_field.type.options[:member_types]
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    aggregate_failures do
         | 
| 44 | 
            +
                      expect(member_types[:budget_id].primitive).to eq(Integer)
         | 
| 45 | 
            +
                      expect(member_types[:name].primitive).to eq(String)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  it 'includes read types for known nested hashes' do
         | 
| 50 | 
            +
                    types, _missing = subject.call(schema, gateway)
         | 
| 51 | 
            +
                    hash_field = types.find { |t| t.name == :budget }
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    read_type = hash_field.meta[:read]
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    expect(read_type).to be_a(Dry::Types::Constructor)
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  it 'removes shitlist attributes from schema' do
         | 
| 59 | 
            +
                    allow(schema).to receive(:options).and_return(shitlist: [:end_date])
         | 
| 60 | 
            +
                    types, _missing = subject.call(schema, gateway)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    expect(types.find { |t| t.name == :end_date }).to be_nil
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  it 'created enumerated types' do
         | 
| 66 | 
            +
                    types, _missing = subject.call(schema, gateway)
         | 
| 67 | 
            +
                    status = types.find { |t| t.name == :status }
         | 
| 68 | 
            +
                    type = status.type
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    expect(type).to be_a(Dry::Types::Enum)
         | 
| 71 | 
            +
                    expect(type.options).to eql(values: %w[UNKNOWN ENABLED PAUSED REMOVED])
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  it 'creates enumerated type arrays' do
         | 
| 75 | 
            +
                    types, _missing = subject.call(schema, gateway)
         | 
| 76 | 
            +
                    status = types.find { |t| t.name == :conversion_optimizer_eligibility }
         | 
| 77 | 
            +
                    type = status.type.member_types[:rejection_reasons].type
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    expect(type).to be_a(Dry::Types::Array)
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         |