From 06d438ad54e11ccf2d36d36e32207ba62f98ace3 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 2 Jan 2020 10:11:47 -0400 Subject: [PATCH 01/13] feat: Add delay to plenty requests when plenty's Short-Period-Decay is low Avoid "short period write limit reached" request errors --- lib/plenty_client/config.rb | 1 + lib/plenty_client/request.rb | 15 ++++++++++++++- spec/classes/request_spec.rb | 25 ++++++++++++++++--------- 3 files changed, 31 insertions(+), 10 deletions(-) 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/request.rb b/lib/plenty_client/request.rb index d6e4045..4112192 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,17 @@ 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 > 10 && short_seconds_left.to_i > 3 + + 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 + + sleep((delay_time - Time.now).round) end def parse_body(response) diff --git a/spec/classes/request_spec.rb b/spec/classes/request_spec.rb index 161829d..3e66a99 100644 --- a/spec/classes/request_spec.rb +++ b/spec/classes/request_spec.rb @@ -198,30 +198,37 @@ 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' => 5, + '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 + _success_request = request_client.request(:post, '/foobar') + expect(Object).to receive(:sleep).with(seconds_left) + _delayed_request = request_client.request(:post, '/foobar') end end end From 10e17d2b7c17bb70cd674e9501346c7f0592e003 Mon Sep 17 00:00:00 2001 From: Owen Peredo Diaz Date: Fri, 3 Jan 2020 05:25:41 -0400 Subject: [PATCH 02/13] style: print log message when delaying Co-Authored-By: Carlos I. Garcia --- lib/plenty_client/request.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plenty_client/request.rb b/lib/plenty_client/request.rb index 4112192..7c54381 100644 --- a/lib/plenty_client/request.rb +++ b/lib/plenty_client/request.rb @@ -124,7 +124,9 @@ def throttle_delay_request return unless delay_time return if Time.now > delay_time - sleep((delay_time - Time.now).round) + wait_until = (delay_time - Time.now) + STDOUT.write "Plenty client => delaying request: #{wait_until} seconds" + sleep(wait_until.round) end def parse_body(response) From 2fb777ad1123cdaf1717d2bfc2337010795348f8 Mon Sep 17 00:00:00 2001 From: Owen Peredo Date: Tue, 23 Jun 2020 05:11:52 -0400 Subject: [PATCH 03/13] fix: remove false delay --- lib/plenty_client/request.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plenty_client/request.rb b/lib/plenty_client/request.rb index 7c54381..319fde4 100644 --- a/lib/plenty_client/request.rb +++ b/lib/plenty_client/request.rb @@ -114,7 +114,7 @@ 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? - return 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 From afb6e2d94c52dc99a57d36878c4f6ada46ec65f6 Mon Sep 17 00:00:00 2001 From: Owen Peredo Date: Tue, 4 Aug 2020 10:57:22 -0400 Subject: [PATCH 04/13] fix: fix typo when creating contact address --- lib/plenty_client/account/contact/address.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 = {}) From 73df0d469378f6e188b17ccf25d4d4a29bcddc6d Mon Sep 17 00:00:00 2001 From: Matias Albarello Date: Fri, 9 Dec 2022 07:17:41 -0300 Subject: [PATCH 05/13] Fix typo --- lib/plenty_client/listing/market/info.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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) From bdf97200d4e17da5d00cb94a867af2bdd4de439f Mon Sep 17 00:00:00 2001 From: notorious94 Date: Fri, 27 Mar 2026 10:51:02 +0600 Subject: [PATCH 06/13] feat: v2 endpoints for property group --- .dockerignore | 5 ++ .github/workflows/ci.yml | 19 +++++ .gitignore | 1 + Dockerfile | 10 +++ README.md | 40 +++++++++++ docker-compose.yml | 7 ++ lib/plenty_client.rb | 6 ++ lib/plenty_client/v2/item/property_group.rb | 20 ++++++ spec/classes/request_spec.rb | 30 ++++---- spec/classes/v2/item/property_group_spec.rb | 77 +++++++++++++++++++++ 10 files changed, 201 insertions(+), 14 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 lib/plenty_client/v2/item/property_group.rb create mode 100644 spec/classes/v2/item/property_group_spec.rb 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..3fd3540 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ test/tmp test/version_tmp tmp vendor/bundle +.idea/ 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..91dd54c 100644 --- a/lib/plenty_client.rb +++ b/lib/plenty_client.rb @@ -214,4 +214,10 @@ module Location autoload :Level, 'plenty_client/warehouse/location/level' end end + + module V2 + module Item + autoload :PropertyGroup, 'plenty_client/v2/item/property_group' + 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..63f0ba5 --- /dev/null +++ b/lib/plenty_client/v2/item/property_group.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module PlentyClient + module V2 + module Item + class PropertyGroup + include PlentyClient::Endpoint + include PlentyClient::Request + + LIST_PROPERTY_GROUPS = '/v2/properties/groups' + + class << self + def list(params = {}, &block) + get(build_endpoint(LIST_PROPERTY_GROUPS), params, &block) + end + end + end + end + end +end diff --git a/spec/classes/request_spec.rb b/spec/classes/request_spec.rb index 3e66a99..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 @@ -222,13 +222,15 @@ def response_headers(mimetype = 'application/json') stub_request(:post, /foobar/).to_return( body: {}.to_json, headers: { - 'X-Plenty-Global-Short-Period-Calls-Left' => 5, + 'X-Plenty-Global-Short-Period-Calls-Left' => 1, 'X-Plenty-Global-Short-Period-Decay' => seconds_left }.merge(response_headers) ) + slept = false + allow_any_instance_of(Kernel).to receive(:sleep) { slept = true } _success_request = request_client.request(:post, '/foobar') - expect(Object).to receive(:sleep).with(seconds_left) _delayed_request = request_client.request(:post, '/foobar') + expect(slept).to be true 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..bbc49a8 --- /dev/null +++ b/spec/classes/v2/item/property_group_spec.rb @@ -0,0 +1,77 @@ +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 +end From 44640f19d55f9a3524ca2c85db1b6a5b75e3dea1 Mon Sep 17 00:00:00 2001 From: Syed Ahmed Date: Tue, 31 Mar 2026 14:31:01 +0600 Subject: [PATCH 07/13] added show, create, update & delete method for property group with documentation --- docs/v2/property_group.md | 96 +++++++++++++++++++++ lib/plenty_client/v2/item/property_group.rb | 22 ++++- spec/classes/v2/item/property_group_spec.rb | 49 +++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 docs/v2/property_group.md diff --git a/docs/v2/property_group.md b/docs/v2/property_group.md new file mode 100644 index 0000000..49f5cd7 --- /dev/null +++ b/docs/v2/property_group.md @@ -0,0 +1,96 @@ +# V2 Property Groups + +`PlentyClient::V2::Item::PropertyGroup` provides methods for managing property groups via the PlentyMarkets REST API v2. + +**Base endpoint:** `/v2/properties/groups` + +## Methods + +### List all property groups + +```ruby +PlentyClient::V2::Item::PropertyGroup.list +``` + +With parameters: + +```ruby +PlentyClient::V2::Item::PropertyGroup.list( + 'with' => 'names,options', + 'orderBy' => 'position', + 'itemsPerPage' => 25 +) +``` + +With pagination block: + +```ruby +PlentyClient::V2::Item::PropertyGroup.list({}) do |entry| + puts entry['id'] +end +``` + +### Find a property group + +```ruby +PlentyClient::V2::Item::PropertyGroup.find(group_id) +``` + +With parameters: + +```ruby +PlentyClient::V2::Item::PropertyGroup.find(group_id, 'with' => 'names,options') +``` + +### Create a property group + +```ruby +PlentyClient::V2::Item::PropertyGroup.create( + 'position' => 1, + 'names' => [ + { 'lang' => 'en', 'name' => 'My Group', 'description' => 'A property group' } + ], + 'options' => [ + { 'type' => 'surchargeType', 'value' => 'flat' } + ] +) +``` + +| Parameter | Type | Required | Description | +|-------------|---------|----------|--------------------------------------| +| `position` | Integer | No | The position of the property group | +| `names` | Array | Yes | At least one name must be provided | +| `options` | Array | No | Property group options | + +**Name object fields:** + +| Field | Type | Description | +|---------------|--------|---------------------------------| +| `lang` | String | ISO 639-1 language code (e.g. `en`, `de`) | +| `name` | String | Name of the property group | +| `description` | String | Description of the property group | + +**Option object fields:** + +| Field | Type | Description | +|---------|--------|--------------------| +| `type` | String | Option identifier | +| `value` | String | Option value | + +### Update a property group + +```ruby +PlentyClient::V2::Item::PropertyGroup.update( + group_id, + 'position' => 2, + 'names' => [ + { 'lang' => 'en', 'name' => 'Updated Group' } + ] +) +``` + +### Delete a property group + +```ruby +PlentyClient::V2::Item::PropertyGroup.destroy(group_id) +``` diff --git a/lib/plenty_client/v2/item/property_group.rb b/lib/plenty_client/v2/item/property_group.rb index 63f0ba5..40d7b25 100644 --- a/lib/plenty_client/v2/item/property_group.rb +++ b/lib/plenty_client/v2/item/property_group.rb @@ -7,12 +7,32 @@ class PropertyGroup include PlentyClient::Endpoint include PlentyClient::Request - LIST_PROPERTY_GROUPS = '/v2/properties/groups' + 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 diff --git a/spec/classes/v2/item/property_group_spec.rb b/spec/classes/v2/item/property_group_spec.rb index bbc49a8..183c4be 100644 --- a/spec/classes/v2/item/property_group_spec.rb +++ b/spec/classes/v2/item/property_group_spec.rb @@ -74,4 +74,53 @@ def response_headers 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 From 1837ba9a50eca6031c0078df765511d544cce426 Mon Sep 17 00:00:00 2001 From: Syed Ahmed Date: Tue, 31 Mar 2026 14:38:46 +0600 Subject: [PATCH 08/13] added :group key in endpoint.rb for build_endpoint to support property group api endpoint --- lib/plenty_client/endpoint.rb | 1 + 1 file changed, 1 insertion(+) 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\}/, From 545bc46d3d25aa2cc8b8612578b4d117922b5bf0 Mon Sep 17 00:00:00 2001 From: Syed Ahmed Date: Wed, 1 Apr 2026 15:19:33 +0600 Subject: [PATCH 09/13] delete docs --- .gitignore | 1 + docs/v2/property_group.md | 96 --------------------------------------- 2 files changed, 1 insertion(+), 96 deletions(-) delete mode 100644 docs/v2/property_group.md diff --git a/.gitignore b/.gitignore index 3fd3540..c597b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ test/version_tmp tmp vendor/bundle .idea/ +docs/ diff --git a/docs/v2/property_group.md b/docs/v2/property_group.md deleted file mode 100644 index 49f5cd7..0000000 --- a/docs/v2/property_group.md +++ /dev/null @@ -1,96 +0,0 @@ -# V2 Property Groups - -`PlentyClient::V2::Item::PropertyGroup` provides methods for managing property groups via the PlentyMarkets REST API v2. - -**Base endpoint:** `/v2/properties/groups` - -## Methods - -### List all property groups - -```ruby -PlentyClient::V2::Item::PropertyGroup.list -``` - -With parameters: - -```ruby -PlentyClient::V2::Item::PropertyGroup.list( - 'with' => 'names,options', - 'orderBy' => 'position', - 'itemsPerPage' => 25 -) -``` - -With pagination block: - -```ruby -PlentyClient::V2::Item::PropertyGroup.list({}) do |entry| - puts entry['id'] -end -``` - -### Find a property group - -```ruby -PlentyClient::V2::Item::PropertyGroup.find(group_id) -``` - -With parameters: - -```ruby -PlentyClient::V2::Item::PropertyGroup.find(group_id, 'with' => 'names,options') -``` - -### Create a property group - -```ruby -PlentyClient::V2::Item::PropertyGroup.create( - 'position' => 1, - 'names' => [ - { 'lang' => 'en', 'name' => 'My Group', 'description' => 'A property group' } - ], - 'options' => [ - { 'type' => 'surchargeType', 'value' => 'flat' } - ] -) -``` - -| Parameter | Type | Required | Description | -|-------------|---------|----------|--------------------------------------| -| `position` | Integer | No | The position of the property group | -| `names` | Array | Yes | At least one name must be provided | -| `options` | Array | No | Property group options | - -**Name object fields:** - -| Field | Type | Description | -|---------------|--------|---------------------------------| -| `lang` | String | ISO 639-1 language code (e.g. `en`, `de`) | -| `name` | String | Name of the property group | -| `description` | String | Description of the property group | - -**Option object fields:** - -| Field | Type | Description | -|---------|--------|--------------------| -| `type` | String | Option identifier | -| `value` | String | Option value | - -### Update a property group - -```ruby -PlentyClient::V2::Item::PropertyGroup.update( - group_id, - 'position' => 2, - 'names' => [ - { 'lang' => 'en', 'name' => 'Updated Group' } - ] -) -``` - -### Delete a property group - -```ruby -PlentyClient::V2::Item::PropertyGroup.destroy(group_id) -``` From 0395c9173a8b4a0454e56bf4aad65a2c2ba311fd Mon Sep 17 00:00:00 2001 From: notorious94 Date: Thu, 2 Apr 2026 16:32:38 +0600 Subject: [PATCH 10/13] feat: prop group name endpoints --- lib/plenty_client/v2/item/property_group.rb | 2 + .../v2/item/property_group/name.rb | 37 +++++++++++ .../v2/item/property_group/name_spec.rb | 61 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 lib/plenty_client/v2/item/property_group/name.rb create mode 100644 spec/classes/v2/item/property_group/name_spec.rb diff --git a/lib/plenty_client/v2/item/property_group.rb b/lib/plenty_client/v2/item/property_group.rb index 40d7b25..a5d46af 100644 --- a/lib/plenty_client/v2/item/property_group.rb +++ b/lib/plenty_client/v2/item/property_group.rb @@ -7,6 +7,8 @@ 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' 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/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 From 05e73dc322d8f457c1925d7d8a1521644f5ba39c Mon Sep 17 00:00:00 2001 From: Syed Ahmed Date: Fri, 3 Apr 2026 14:12:06 +0600 Subject: [PATCH 11/13] addded v2 property endpoint for property group --- lib/plenty_client/v2/item/property.rb | 40 ++++++++ spec/classes/v2/item/property_spec.rb | 126 ++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 lib/plenty_client/v2/item/property.rb create mode 100644 spec/classes/v2/item/property_spec.rb diff --git a/lib/plenty_client/v2/item/property.rb b/lib/plenty_client/v2/item/property.rb new file mode 100644 index 0000000..c871eb3 --- /dev/null +++ b/lib/plenty_client/v2/item/property.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module PlentyClient + module V2 + module Item + class Property + include PlentyClient::Endpoint + include PlentyClient::Request + + 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/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 From 32905f892a565ef8af34dbc0a3da3ab886c50e14 Mon Sep 17 00:00:00 2001 From: Syed Ahmed Date: Fri, 3 Apr 2026 14:19:05 +0600 Subject: [PATCH 12/13] added property v2 endpont in autoload --- lib/plenty_client.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plenty_client.rb b/lib/plenty_client.rb index 91dd54c..91ae023 100644 --- a/lib/plenty_client.rb +++ b/lib/plenty_client.rb @@ -217,6 +217,7 @@ module Location module V2 module Item + autoload :Property, 'plenty_client/v2/item/property' autoload :PropertyGroup, 'plenty_client/v2/item/property_group' end end From f33d30d7fb1be9f4cce4847f0eb9382505f9d6f3 Mon Sep 17 00:00:00 2001 From: notorious94 Date: Sun, 5 Apr 2026 13:12:58 +0600 Subject: [PATCH 13/13] feat: prop name endpoints --- lib/plenty_client/v2/item/property.rb | 2 + lib/plenty_client/v2/item/property/name.rb | 37 +++++++++++++ spec/classes/v2/item/property/name_spec.rb | 61 ++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 lib/plenty_client/v2/item/property/name.rb create mode 100644 spec/classes/v2/item/property/name_spec.rb diff --git a/lib/plenty_client/v2/item/property.rb b/lib/plenty_client/v2/item/property.rb index c871eb3..c1f2e47 100644 --- a/lib/plenty_client/v2/item/property.rb +++ b/lib/plenty_client/v2/item/property.rb @@ -7,6 +7,8 @@ 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' 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/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