diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f67ae7d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +Gemfile.lock +.git +.idea +tmp +pkg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3914b7b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build + run: docker compose build + + - name: Run specs + run: docker compose run console rspec diff --git a/.gitignore b/.gitignore index f5c81f6..c597b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ test/tmp test/version_tmp tmp vendor/bundle +.idea/ +docs/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c349808 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM ruby:3.1 + +WORKDIR /gem + +RUN gem install 'faraday:1.10.3' 'typhoeus:1.4.0' \ + && gem install json rspec webmock byebug + +COPY . . + +CMD ["irb", "-I", "lib", "-r", "plenty_client"] diff --git a/README.md b/README.md index 89da51f..f32fd20 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,46 @@ PlentyClient::Item::Variation.routes - update all modules to classes and make it inherit from a base class to remove `extend PlentyClient::Request` etc - create a configure block to set authentication parameters +## Testing with Docker + +You can test the gem endpoints in an interactive Ruby console without installing it into a project. + +### Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) + +### Usage + +Build and start the console: + +```bash +docker compose run console +``` + +This drops you into an IRB session with `plenty_client` pre-loaded. Configure your credentials and start testing: + +```ruby +PlentyClient::Config.site_url = 'https://your-instance.plentymarkets.com' +PlentyClient::Config.api_user = 'your_user' +PlentyClient::Config.api_password = 'your_password' + +# v1 example +PlentyClient::Item.list + +# v2 example +PlentyClient::V2::Item::PropertyGroup.list( + 'with' => 'names,options', + 'orderBy' => 'position', + 'itemsPerPage' => 25 +) +``` + +To rebuild after making changes to the Dockerfile or gemspec: + +```bash +docker compose build --no-cache +``` + ## Contributing Bug reports and pull requests are welcome. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a7b95ec --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + console: + build: . + stdin_open: true + tty: true + volumes: + - .:/gem diff --git a/lib/plenty_client.rb b/lib/plenty_client.rb index 788cec8..91ae023 100644 --- a/lib/plenty_client.rb +++ b/lib/plenty_client.rb @@ -214,4 +214,11 @@ module Location autoload :Level, 'plenty_client/warehouse/location/level' end end + + module V2 + module Item + autoload :Property, 'plenty_client/v2/item/property' + autoload :PropertyGroup, 'plenty_client/v2/item/property_group' + end + end end diff --git a/lib/plenty_client/account/contact/address.rb b/lib/plenty_client/account/contact/address.rb index 2369e59..5c03cee 100644 --- a/lib/plenty_client/account/contact/address.rb +++ b/lib/plenty_client/account/contact/address.rb @@ -23,8 +23,9 @@ def list(contact_id, address_type = '', headers = {}, &block) headers, &block) end - def create(body = {}) - post("#{CONTACT_ADDRESS_BASE_PATH}#{CREATE_A_CONTACT_ADDRESS}", body) + def create(contact_id, body = {}) + post(build_endpoint("#{CONTACT_ADDRESS_BASE_PATH}#{CREATE_A_CONTACT_ADDRESS}", + contact: contact_id), body) end def update(contact_id, address_id, body = {}) diff --git a/lib/plenty_client/config.rb b/lib/plenty_client/config.rb index 230aca4..cb37cf0 100644 --- a/lib/plenty_client/config.rb +++ b/lib/plenty_client/config.rb @@ -11,6 +11,7 @@ class InvalidCredentials < StandardError; end class << self attr_accessor :site_url, :api_user, :api_password, :access_token, :refresh_token, :log, :expiry_date, :plenty_id + attr_accessor :request_wait_until attr_writer :attempt_count def validate_credentials diff --git a/lib/plenty_client/endpoint.rb b/lib/plenty_client/endpoint.rb index 39ffb39..3718bcc 100644 --- a/lib/plenty_client/endpoint.rb +++ b/lib/plenty_client/endpoint.rb @@ -32,6 +32,7 @@ def build_endpoint(api_endpoint_template, arguments = {}) directory: /\{directoryId\}/, document: /\{documentId\}/, fitment: /\{fitmentId\}/, + group: /\{groupId\}/, image: /\{imageId\}/, item: /\{itemId\}/, item_set: /\{itemSetId\}/, diff --git a/lib/plenty_client/listing/market/info.rb b/lib/plenty_client/listing/market/info.rb index 5833947..c879495 100644 --- a/lib/plenty_client/listing/market/info.rb +++ b/lib/plenty_client/listing/market/info.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# PlentyClient::Listing::Market::Info.list module PlentyClient module Listing module Market @@ -7,7 +8,8 @@ class Info include PlentyClient::Endpoint include PlentyClient::Request - LIST_LISTINGS_MARKET_INFO = '/listings/markets/info' + # https://developers.plentymarkets.com/en-gb/plentymarkets-rest-api/index.html#/Listing/get_rest_listings_markets_infos + LIST_LISTINGS_MARKET_INFO = '/listings/markets/infos' class << self def list(headers = {}, &block) diff --git a/lib/plenty_client/request.rb b/lib/plenty_client/request.rb index d6e4045..319fde4 100644 --- a/lib/plenty_client/request.rb +++ b/lib/plenty_client/request.rb @@ -14,6 +14,8 @@ def request(http_method, path, params = {}) params = stringify_symbol_keys(params) if params.is_a?(Hash) + throttle_delay_request + perform(http_method, path, params) end @@ -84,6 +86,7 @@ def perform(http_method, path, params = {}) verb = http_method.to_s.downcase params = params.to_json unless %w[get delete].include?(verb) response = conn.send(verb, base_url(path), params) + throttle_check_short_period(response) assert_success_status_code(response) parse_body(response) end @@ -111,7 +114,19 @@ def throttle_check_short_period(response_header) short_calls_left = response_header['X-Plenty-Global-Short-Period-Calls-Left'] short_seconds_left = response_header['X-Plenty-Global-Short-Period-Decay'] return if short_calls_left&.empty? || short_seconds_left&.empty? - sleep(short_seconds_left.to_i + 1) if short_calls_left.to_i <= 10 && short_seconds_left.to_i < 3 + return if short_calls_left.to_i > 1 + + PlentyClient::Config.request_wait_until = Time.now + short_seconds_left.to_i + end + + def throttle_delay_request + delay_time = PlentyClient::Config.request_wait_until + return unless delay_time + return if Time.now > delay_time + + wait_until = (delay_time - Time.now) + STDOUT.write "Plenty client => delaying request: #{wait_until} seconds" + sleep(wait_until.round) end def parse_body(response) diff --git a/lib/plenty_client/v2/item/property.rb b/lib/plenty_client/v2/item/property.rb new file mode 100644 index 0000000..c1f2e47 --- /dev/null +++ b/lib/plenty_client/v2/item/property.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module PlentyClient + module V2 + module Item + class Property + include PlentyClient::Endpoint + include PlentyClient::Request + + autoload :Name, 'plenty_client/v2/item/property/name' + + LIST_PROPERTIES = '/v2/properties' + GET_PROPERTY = '/v2/properties/{propertyId}' + CREATE_PROPERTY = '/v2/properties' + UPDATE_PROPERTY = '/v2/properties/{propertyId}' + DELETE_PROPERTY = '/v2/properties/{propertyId}' + + class << self + def list(params = {}, &block) + get(build_endpoint(LIST_PROPERTIES), params, &block) + end + + def find(property_id, params = {}, &block) + get(build_endpoint(GET_PROPERTY, property: property_id), params, &block) + end + + def create(body = {}) + post(build_endpoint(CREATE_PROPERTY), body) + end + + def update(property_id, body = {}) + put(build_endpoint(UPDATE_PROPERTY, property: property_id), body) + end + + def destroy(property_id) + delete(build_endpoint(DELETE_PROPERTY, property: property_id)) + end + end + end + end + end +end diff --git a/lib/plenty_client/v2/item/property/name.rb b/lib/plenty_client/v2/item/property/name.rb new file mode 100644 index 0000000..fafaf3c --- /dev/null +++ b/lib/plenty_client/v2/item/property/name.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module PlentyClient + module V2 + module Item + class Property + class Name + include PlentyClient::Endpoint + include PlentyClient::Request + + CREATE_PROPERTY_NAME = '/v2/properties/names' + GET_PROPERTY_NAME = '/v2/properties/names/{nameId}' + UPDATE_PROPERTY_NAME = '/v2/properties/names/{nameId}' + DELETE_PROPERTY_NAME = '/v2/properties/names/{nameId}' + + class << self + def find(name_id, params = {}, &block) + get(build_endpoint(GET_PROPERTY_NAME, name: name_id), params, &block) + end + + def create(body = {}) + post(build_endpoint(CREATE_PROPERTY_NAME), body) + end + + def update(name_id, body = {}) + put(build_endpoint(UPDATE_PROPERTY_NAME, name: name_id), body) + end + + def destroy(name_id) + delete(build_endpoint(DELETE_PROPERTY_NAME, name: name_id)) + end + end + end + end + end + end +end diff --git a/lib/plenty_client/v2/item/property_group.rb b/lib/plenty_client/v2/item/property_group.rb new file mode 100644 index 0000000..a5d46af --- /dev/null +++ b/lib/plenty_client/v2/item/property_group.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module PlentyClient + module V2 + module Item + class PropertyGroup + include PlentyClient::Endpoint + include PlentyClient::Request + + autoload :Name, 'plenty_client/v2/item/property_group/name' + + LIST_PROPERTY_GROUPS = '/v2/properties/groups' + GET_PROPERTY_GROUP = '/v2/properties/groups/{groupId}' + CREATE_PROPERTY_GROUP = '/v2/properties/groups' + UPDATE_PROPERTY_GROUP = '/v2/properties/groups/{groupId}' + DELETE_PROPERTY_GROUP = '/v2/properties/groups/{groupId}' + + class << self + def list(params = {}, &block) + get(build_endpoint(LIST_PROPERTY_GROUPS), params, &block) + end + + def find(group_id, params = {}, &block) + get(build_endpoint(GET_PROPERTY_GROUP, group: group_id), params, &block) + end + + def create(body = {}) + post(build_endpoint(CREATE_PROPERTY_GROUP), body) + end + + def update(group_id, body = {}) + put(build_endpoint(UPDATE_PROPERTY_GROUP, group: group_id), body) + end + + def destroy(group_id) + delete(build_endpoint(DELETE_PROPERTY_GROUP, group: group_id)) + end + end + end + end + end +end diff --git a/lib/plenty_client/v2/item/property_group/name.rb b/lib/plenty_client/v2/item/property_group/name.rb new file mode 100644 index 0000000..50ac0af --- /dev/null +++ b/lib/plenty_client/v2/item/property_group/name.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module PlentyClient + module V2 + module Item + class PropertyGroup + class Name + include PlentyClient::Endpoint + include PlentyClient::Request + + CREATE_PROPERTY_GROUP_NAME = '/v2/properties/groups/names' + GET_PROPERTY_GROUP_NAME = '/v2/properties/groups/names/{nameId}' + UPDATE_PROPERTY_GROUP_NAME = '/v2/properties/groups/names/{nameId}' + DELETE_PROPERTY_GROUP_NAME = '/v2/properties/groups/names/{nameId}' + + class << self + def find(name_id, params = {}, &block) + get(build_endpoint(GET_PROPERTY_GROUP_NAME, name: name_id), params, &block) + end + + def create(body = {}) + post(build_endpoint(CREATE_PROPERTY_GROUP_NAME), body) + end + + def update(name_id, body = {}) + put(build_endpoint(UPDATE_PROPERTY_GROUP_NAME, name: name_id), body) + end + + def destroy(name_id) + delete(build_endpoint(DELETE_PROPERTY_GROUP_NAME, name: name_id)) + end + end + end + end + end + end +end diff --git a/spec/classes/request_spec.rb b/spec/classes/request_spec.rb index 161829d..4ee844d 100644 --- a/spec/classes/request_spec.rb +++ b/spec/classes/request_spec.rb @@ -92,29 +92,29 @@ def response_headers(mimetype = 'application/json') describe 'wrappers for #request' do describe '#post' do it 'calls #request with :post and rest of params' do - expect(request_client).to receive(:request).with(:post, '/index.php', 'param1' => 'value1') - request_client.post('/index.php', 'param1' => 'value1') + expect(request_client).to receive(:request).with(:post, '/index.php', { 'param1' => 'value1' }) + request_client.post('/index.php', { 'param1' => 'value1' }) end end describe '#put' do it 'calls #request with :put and rest of params' do - expect(request_client).to receive(:request).with(:put, '/index.php', 'param1' => 'value1') - request_client.put('/index.php', 'param1' => 'value1') + expect(request_client).to receive(:request).with(:put, '/index.php', { 'param1' => 'value1' }) + request_client.put('/index.php', { 'param1' => 'value1' }) end end describe '#patch' do it 'calls #request with :patch and rest of params' do - expect(request_client).to receive(:request).with(:patch, '/index.php', 'param1' => 'value1') - request_client.patch('/index.php', 'param1' => 'value1') + expect(request_client).to receive(:request).with(:patch, '/index.php', { 'param1' => 'value1' }) + request_client.patch('/index.php', { 'param1' => 'value1' }) end end describe '#delete' do it 'calls #request with :delete and rest of params' do - expect(request_client).to receive(:request).with(:delete, '/index.php', 'param1' => 'value1') - request_client.delete('/index.php', 'param1' => 'value1') + expect(request_client).to receive(:request).with(:delete, '/index.php', { 'param1' => 'value1' }) + request_client.delete('/index.php', { 'param1' => 'value1' }) end end @@ -122,8 +122,8 @@ def response_headers(mimetype = 'application/json') context 'when called without a block' do context 'when called without page param' do it 'calls #request with :get and rest of params, merged with page: 1' do - expect(request_client).to receive(:request).with(:get, '/index.php', 'p1' => 'v1', 'page' => 1) - request_client.get('/index.php', 'p1' => 'v1') + expect(request_client).to receive(:request).with(:get, '/index.php', { 'p1' => 'v1', 'page' => 1 }) + request_client.get('/index.php', { 'p1' => 'v1' }) end end @@ -131,8 +131,8 @@ def response_headers(mimetype = 'application/json') it 'calls #request with :get and unchanged params' do expect(request_client) .to receive(:request) - .with(:get, '/index.php', hash_including('p1' => 'v1', 'page' => 100)) - request_client.get('/index.php', 'p1' => 'v1', 'page' => 100) + .with(:get, '/index.php', hash_including({ 'p1' => 'v1', 'page' => 100 })) + request_client.get('/index.php', { 'p1' => 'v1', 'page' => 100 }) end end end @@ -198,30 +198,39 @@ def response_headers(mimetype = 'application/json') end end - xdescribe 'throttle check' do + describe 'throttle check' do context 'short period' do before do stub_api_tokens end it 'enough calls left' do - valid_request = stub_request(:post, /foobar/).to_return( + stub_request(:post, /foobar/).to_return( body: {}.to_json, - headers: { 'X-Plenty-Global-Short-Period-Calls-Left' => 50, 'X-Plenty-Global-Short-Period-Decay' => 5 } + headers: { + 'X-Plenty-Global-Short-Period-Calls-Left' => 50, + 'X-Plenty-Global-Short-Period-Decay' => 5 + }.merge(response_headers) ) + _success_request = request_client.request(:post, '/foobar') expect(Object).not_to receive(:sleep) request_client.request(:post, '/foobar') - expect(valid_request).to have_been_made.once end it 'limit reached' do - limited_request = stub_request(:post, /foobar/).to_return( + seconds_left = 2 + stub_request(:post, /foobar/).to_return( body: {}.to_json, - headers: { 'X-Plenty-Global-Short-Period-Calls-Left' => 5, 'X-Plenty-Global-Short-Period-Decay' => 2 } + headers: { + 'X-Plenty-Global-Short-Period-Calls-Left' => 1, + 'X-Plenty-Global-Short-Period-Decay' => seconds_left + }.merge(response_headers) ) - expect(Object).to receive(:sleep).with(3) - request_client.request(:post, '/foobar') - expect(limited_request).to have_been_made.once + slept = false + allow_any_instance_of(Kernel).to receive(:sleep) { slept = true } + _success_request = request_client.request(:post, '/foobar') + _delayed_request = request_client.request(:post, '/foobar') + expect(slept).to be true end end end diff --git a/spec/classes/v2/item/property/name_spec.rb b/spec/classes/v2/item/property/name_spec.rb new file mode 100644 index 0000000..c686b91 --- /dev/null +++ b/spec/classes/v2/item/property/name_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +RSpec.describe PlentyClient::V2::Item::Property::Name do + before(:each) do + PlentyClient::Config.site_url = 'https://www.example.com' + PlentyClient::Config.api_user = 'example' + PlentyClient::Config.api_password = 'secret' + PlentyClient::Config.access_token = 'ACCESS_TOKEN' + PlentyClient::Config.refresh_token = 'REFRESH_TOKEN' + PlentyClient::Config.expiry_date = Time.now + 86400 + end + + describe '.find' do + it 'calls get with the correct endpoint and params' do + expect(described_class).to receive(:get) + .with('/v2/properties/names/7', { 'with' => 'property' }) + described_class.find(7, 'with' => 'property') + end + + it 'calls get with default empty params' do + expect(described_class).to receive(:get).with('/v2/properties/names/7', {}) + described_class.find(7) + end + end + + describe '.create' do + it 'calls post with the correct endpoint and body' do + body = { 'propertyId' => 1, 'lang' => 'en', 'name' => 'Color', 'description' => 'The color' } + expect(described_class).to receive(:post) + .with('/v2/properties/names', body) + described_class.create(body) + end + + it 'calls post with default empty body' do + expect(described_class).to receive(:post).with('/v2/properties/names', {}) + described_class.create + end + end + + describe '.update' do + it 'calls put with the correct endpoint and body' do + body = { 'name' => 'Updated Name', 'description' => 'Updated description' } + expect(described_class).to receive(:put) + .with('/v2/properties/names/7', body) + described_class.update(7, body) + end + + it 'calls put with default empty body' do + expect(described_class).to receive(:put).with('/v2/properties/names/7', {}) + described_class.update(7) + end + end + + describe '.destroy' do + it 'calls delete with the correct endpoint' do + expect(described_class).to receive(:delete) + .with('/v2/properties/names/7') + described_class.destroy(7) + end + end +end diff --git a/spec/classes/v2/item/property_group/name_spec.rb b/spec/classes/v2/item/property_group/name_spec.rb new file mode 100644 index 0000000..3d25e0b --- /dev/null +++ b/spec/classes/v2/item/property_group/name_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +RSpec.describe PlentyClient::V2::Item::PropertyGroup::Name do + before(:each) do + PlentyClient::Config.site_url = 'https://www.example.com' + PlentyClient::Config.api_user = 'example' + PlentyClient::Config.api_password = 'secret' + PlentyClient::Config.access_token = 'ACCESS_TOKEN' + PlentyClient::Config.refresh_token = 'REFRESH_TOKEN' + PlentyClient::Config.expiry_date = Time.now + 86400 + end + + describe '.find' do + it 'calls get with the correct endpoint and params' do + expect(described_class).to receive(:get) + .with('/v2/properties/groups/names/7', { 'with' => 'group' }) + described_class.find(7, 'with' => 'group') + end + + it 'calls get with default empty params' do + expect(described_class).to receive(:get).with('/v2/properties/groups/names/7', {}) + described_class.find(7) + end + end + + describe '.create' do + it 'calls post with the correct endpoint and body' do + body = { 'groupId' => 1, 'lang' => 'en', 'name' => 'Test Name' } + expect(described_class).to receive(:post) + .with('/v2/properties/groups/names', body) + described_class.create(body) + end + + it 'calls post with default empty body' do + expect(described_class).to receive(:post).with('/v2/properties/groups/names', {}) + described_class.create + end + end + + describe '.update' do + it 'calls put with the correct endpoint and body' do + body = { 'name' => 'Updated Name' } + expect(described_class).to receive(:put) + .with('/v2/properties/groups/names/7', body) + described_class.update(7, body) + end + + it 'calls put with default empty body' do + expect(described_class).to receive(:put).with('/v2/properties/groups/names/7', {}) + described_class.update(7) + end + end + + describe '.destroy' do + it 'calls delete with the correct endpoint' do + expect(described_class).to receive(:delete) + .with('/v2/properties/groups/names/7') + described_class.destroy(7) + end + end +end diff --git a/spec/classes/v2/item/property_group_spec.rb b/spec/classes/v2/item/property_group_spec.rb new file mode 100644 index 0000000..183c4be --- /dev/null +++ b/spec/classes/v2/item/property_group_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +RSpec.describe PlentyClient::V2::Item::PropertyGroup do + before(:each) do + PlentyClient::Config.site_url = 'https://www.example.com' + PlentyClient::Config.api_user = 'example' + PlentyClient::Config.api_password = 'secret' + PlentyClient::Config.access_token = 'ACCESS_TOKEN' + PlentyClient::Config.refresh_token = 'REFRESH_TOKEN' + PlentyClient::Config.expiry_date = Time.now + 86400 + end + + def response_headers + { 'Content-Type' => 'application/json; charset=UTF-8' } + end + + describe '.list' do + context 'without a block' do + it 'calls get with the correct endpoint and params' do + expect(described_class).to receive(:get) + .with('/v2/properties/groups', { 'with' => 'names,options', 'itemsPerPage' => 25 }) + described_class.list('with' => 'names,options', 'itemsPerPage' => 25) + end + + it 'calls get with default empty params' do + expect(described_class).to receive(:get).with('/v2/properties/groups', {}) + described_class.list + end + end + + context 'with a block' do + before do + stub_request(:get, /example\.com\/rest\/v2\/properties\/groups/) + .to_return do |r| + query = CGI.parse(r.uri.query) + page = query['page'][0].to_i + { + body: { + page: page, + totalsCount: 2, + isLastPage: (page == 2), + entries: [ + { 'id' => page * 10 + 1, 'groupType' => 'select' }, + { 'id' => page * 10 + 2, 'groupType' => 'multi' } + ] + }.to_json, + headers: response_headers + } + end + end + + it 'paginates through all pages' do + described_class.list({}) { |_entry| } + expect(WebMock).to have_requested(:get, /v2\/properties\/groups/).times(2) + end + + it 'yields entries from each page' do + expect { |b| described_class.list({}, &b) }.to yield_control.exactly(2).times + end + end + + context 'with query parameters' do + it 'forwards params to the API' do + stub_request(:get, /example\.com\/rest\/v2\/properties\/groups/) + .to_return( + body: { page: 1, isLastPage: true, entries: [] }.to_json, + headers: response_headers + ) + + described_class.list('with' => 'names,options', 'orderBy' => 'position', 'itemsPerPage' => 25) + + expect(WebMock).to have_requested(:get, /v2\/properties\/groups/) + .with(query: hash_including('with' => 'names,options', 'orderBy' => 'position', 'itemsPerPage' => '25')) + end + end + end + + describe '.find' do + it 'calls get with the correct endpoint and params' do + expect(described_class).to receive(:get) + .with('/v2/properties/groups/42', { 'with' => 'names' }) + described_class.find(42, 'with' => 'names') + end + + it 'calls get with default empty params' do + expect(described_class).to receive(:get).with('/v2/properties/groups/42', {}) + described_class.find(42) + end + end + + describe '.create' do + it 'calls post with the correct endpoint and body' do + body = { 'position' => 1, 'names' => [{ 'lang' => 'en', 'name' => 'Test Group' }] } + expect(described_class).to receive(:post) + .with('/v2/properties/groups', body) + described_class.create(body) + end + + it 'calls post with default empty body' do + expect(described_class).to receive(:post).with('/v2/properties/groups', {}) + described_class.create + end + end + + describe '.update' do + it 'calls put with the correct endpoint and body' do + body = { 'position' => 2, 'names' => [{ 'lang' => 'en', 'name' => 'Updated Group' }] } + expect(described_class).to receive(:put) + .with('/v2/properties/groups/42', body) + described_class.update(42, body) + end + + it 'calls put with default empty body' do + expect(described_class).to receive(:put).with('/v2/properties/groups/42', {}) + described_class.update(42) + end + end + + describe '.destroy' do + it 'calls delete with the correct endpoint' do + expect(described_class).to receive(:delete) + .with('/v2/properties/groups/42') + described_class.destroy(42) + end + end +end diff --git a/spec/classes/v2/item/property_spec.rb b/spec/classes/v2/item/property_spec.rb new file mode 100644 index 0000000..403b744 --- /dev/null +++ b/spec/classes/v2/item/property_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +RSpec.describe PlentyClient::V2::Item::Property do + before(:each) do + PlentyClient::Config.site_url = 'https://www.example.com' + PlentyClient::Config.api_user = 'example' + PlentyClient::Config.api_password = 'secret' + PlentyClient::Config.access_token = 'ACCESS_TOKEN' + PlentyClient::Config.refresh_token = 'REFRESH_TOKEN' + PlentyClient::Config.expiry_date = Time.now + 86400 + end + + def response_headers + { 'Content-Type' => 'application/json; charset=UTF-8' } + end + + describe '.list' do + context 'without a block' do + it 'calls get with the correct endpoint and params' do + expect(described_class).to receive(:get) + .with('/v2/properties', { 'with' => 'names', 'itemsPerPage' => 25 }) + described_class.list('with' => 'names', 'itemsPerPage' => 25) + end + + it 'calls get with default empty params' do + expect(described_class).to receive(:get).with('/v2/properties', {}) + described_class.list + end + end + + context 'with a block' do + before do + stub_request(:get, /example\.com\/rest\/v2\/properties/) + .to_return do |r| + query = CGI.parse(r.uri.query) + page = query['page'][0].to_i + { + body: { + page: page, + totalsCount: 2, + isLastPage: (page == 2), + entries: [ + { 'id' => page * 10 + 1, 'cast' => 'string' }, + { 'id' => page * 10 + 2, 'cast' => 'int' } + ] + }.to_json, + headers: response_headers + } + end + end + + it 'paginates through all pages' do + described_class.list({}) { |_entry| } + expect(WebMock).to have_requested(:get, /v2\/properties/).times(2) + end + + it 'yields entries from each page' do + expect { |b| described_class.list({}, &b) }.to yield_control.exactly(2).times + end + end + + context 'with query parameters' do + it 'forwards params to the API' do + stub_request(:get, /example\.com\/rest\/v2\/properties/) + .to_return( + body: { page: 1, isLastPage: true, entries: [] }.to_json, + headers: response_headers + ) + + described_class.list('with' => 'names,options', 'orderBy' => 'position', 'itemsPerPage' => 25) + + expect(WebMock).to have_requested(:get, /v2\/properties/) + .with(query: hash_including('with' => 'names,options', 'orderBy' => 'position', 'itemsPerPage' => '25')) + end + end + end + + describe '.find' do + it 'calls get with the correct endpoint and params' do + expect(described_class).to receive(:get) + .with('/v2/properties/42', { 'with' => 'names' }) + described_class.find(42, 'with' => 'names') + end + + it 'calls get with default empty params' do + expect(described_class).to receive(:get).with('/v2/properties/42', {}) + described_class.find(42) + end + end + + describe '.create' do + it 'calls post with the correct endpoint and body' do + body = { 'cast' => 'string', 'type' => 'item', 'names' => [{ 'lang' => 'en', 'name' => 'Color' }] } + expect(described_class).to receive(:post) + .with('/v2/properties', body) + described_class.create(body) + end + + it 'calls post with default empty body' do + expect(described_class).to receive(:post).with('/v2/properties', {}) + described_class.create + end + end + + describe '.update' do + it 'calls put with the correct endpoint and body' do + body = { 'cast' => 'int', 'names' => [{ 'lang' => 'en', 'name' => 'Weight' }] } + expect(described_class).to receive(:put) + .with('/v2/properties/42', body) + described_class.update(42, body) + end + + it 'calls put with default empty body' do + expect(described_class).to receive(:put).with('/v2/properties/42', {}) + described_class.update(42) + end + end + + describe '.destroy' do + it 'calls delete with the correct endpoint' do + expect(described_class).to receive(:delete) + .with('/v2/properties/42') + described_class.destroy(42) + end + end +end