From 2eae0e8e6b024637fc1d59a131ff0cbff68ed2bd Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 26 Sep 2023 14:14:07 +0200 Subject: [PATCH 1/9] Add "activesupport" to gemspec --- Gemfile.lock | 16 +++++++++++++++- tickeos_b2b.gemspec | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9d91ed7..a1862c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,6 +2,7 @@ PATH remote: . specs: tickeos_b2b (0.1.0) + activesupport faraday nokogiri nori @@ -9,10 +10,17 @@ PATH GEM remote: https://rubygems.org/ specs: + activesupport (6.1.7.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) awesome_print (1.9.2) coderay (1.1.3) + concurrent-ruby (1.2.2) crack (0.4.5) rexml diff-lcs (1.5.0) @@ -21,8 +29,11 @@ GEM ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) hashdiff (1.0.1) + i18n (1.14.1) + concurrent-ruby (~> 1.0) method_source (1.0.0) mini_portile2 (2.8.1) + minitest (5.20.0) nokogiri (1.14.3) mini_portile2 (~> 2.8.0) racc (~> 1.4) @@ -48,10 +59,13 @@ GEM rspec-support (~> 3.12.0) rspec-support (3.12.0) ruby2_keywords (0.0.5) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) + zeitwerk (2.6.11) PLATFORMS ruby @@ -65,4 +79,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.4.10 + 2.4.19 diff --git a/tickeos_b2b.gemspec b/tickeos_b2b.gemspec index 177c106..990618b 100644 --- a/tickeos_b2b.gemspec +++ b/tickeos_b2b.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'faraday' gem.add_dependency 'nokogiri' gem.add_dependency 'nori' + gem.add_dependency 'activesupport' gem.metadata = { 'homepage_uri' => 'https://github.com/dbdrive/tickeos_b2b', From 6132bcff50dce39d5a185f84216699188d76d27e Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 26 Sep 2023 14:59:25 +0200 Subject: [PATCH 2/9] Enable activesupport time-extension and fix purchase-api call --- lib/tickeos_b2b/api/purchase.rb | 4 ++ spec/spec_helper.rb | 1 + spec/tickeos_b2b/api/purchase_spec.rb | 54 ++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/tickeos_b2b/api/purchase.rb b/lib/tickeos_b2b/api/purchase.rb index 484ec9f..288c52e 100644 --- a/lib/tickeos_b2b/api/purchase.rb +++ b/lib/tickeos_b2b/api/purchase.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'active_support/core_ext/time' + module TickeosB2b module Api class Purchase @@ -65,6 +67,8 @@ def self.request_body(pre_check:, go:, ticket:, requires_zones:) def self.validation_date(datetime) return '' if datetime.blank? + raise ArgumentError, 'no proper Time with timezone given' if !datetime.kind_of?(Time) || datetime.utc? + datetime.to_date.to_s end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b3bae9f..408d263 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,7 @@ require 'bundler/setup' require 'tickeos_b2b' require 'webmock/rspec' +require 'active_support/core_ext/time' require 'awesome_print' require 'pry' diff --git a/spec/tickeos_b2b/api/purchase_spec.rb b/spec/tickeos_b2b/api/purchase_spec.rb index 3bb663d..63fa851 100644 --- a/spec/tickeos_b2b/api/purchase_spec.rb +++ b/spec/tickeos_b2b/api/purchase_spec.rb @@ -67,7 +67,7 @@ serial_ordering: 'ord_123', transaction_id: '123ABC', sub_ref_id: 'kein Zuschlag', - validation_date: Date.new(2020, 9, 2), + validation_date:, first_name: 'Json', last_name: 'Statham', location_id: 'Master:8123456', @@ -75,10 +75,62 @@ ) end + let(:validation_date) { ActiveSupport::TimeZone['Europe/Berlin'].local(2020, 9, 2, 12, 0, 0) } + describe '#request_body' do it 'returns the correct xml request body' do expect(operation).to eq(request_body) end + + describe '#validation_date' do + context 'when ticket.validation_date is nil' do + let(:validation_date) { nil } + + it 'does not raise an error' do + expect { operation }.not_to raise_error + end + end + + context 'when ticket.validation_date is a Date' do + let(:validation_date) { Date.new(2020, 9, 2) } + + it 'raises an error' do + expect { operation }.to raise_error ArgumentError, 'no proper Time with timezone given' + end + end + + context 'when ticket.validation_date is a DateTime without timezone info' do + let(:validation_date) { DateTime.new(2020, 9, 2, 23) } + + it 'raises an error' do + expect { operation }.to raise_error ArgumentError, 'no proper Time with timezone given' + end + end + + context 'when ticket.validation_date is a DateTime with timezone info' do + let(:validation_date) { ActiveSupport::TimeZone['Europe/Berlin'].local(2020, 9, 2, 23, 0, 0) } + + it 'does not raise an error' do + expect { operation }.not_to raise_error + end + + context 'when local time is at very start of day' do + let(:validation_date) { ActiveSupport::TimeZone['Europe/Berlin'].local(2020, 9, 1, 0, 0, 0) } + + it 'considers the correct date of the given timezone' do + expect(operation).to include '2020-09-01' + end + end + + context 'when local time is at very end of day' do + let(:validation_date) { ActiveSupport::TimeZone['Europe/Berlin'].local(2020, 9, 1, 23, 59, 59) } + + it 'considers the correct date of the given timezone' do + expect(operation).to include '2020-09-01' + end + end + end + end end describe '#request_method' do From f3eba5c81593573d1754a834ab8d0b5f324985a6 Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 08:50:27 +0200 Subject: [PATCH 3/9] Enable rubocop and guard --- .gitignore | 3 ++ .rubocop.yml | 11 +++++++ Gemfile | 7 +++++ Gemfile.lock | 74 +++++++++++++++++++++++++++++++++++++++++++++ Guardfile | 22 ++++++++++++++ spec/spec_helper.rb | 14 +++++++++ 6 files changed, 131 insertions(+) create mode 100644 .rubocop.yml create mode 100644 Guardfile diff --git a/.gitignore b/.gitignore index bf9a19d..70a5e35 100644 --- a/.gitignore +++ b/.gitignore @@ -74,3 +74,6 @@ yarn-debug.log* # Ignore mac OS specific files .DS_Store + +# Cached common rubocop config +.rubocop-https---dbdrive-github-io-triebwerk-rubocop-common-yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..a4e6981 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,11 @@ +AllCops: + TargetRubyVersion: 2.7 + +inherit_from: + - https://dbdrive.github.io/triebwerk/rubocop_common.yml + +Style/OpenStructUse: + Enabled: no + +RSpec/NestedGroups: + Max: 4 diff --git a/Gemfile b/Gemfile index 133fd56..e76276c 100644 --- a/Gemfile +++ b/Gemfile @@ -10,4 +10,11 @@ group :development, :test do gem 'awesome_print' gem 'pry' gem 'webmock' + gem 'rubocop', require: false + gem 'rubocop-rspec', require: false +end + +group :test do + gem 'guard-rspec' + gem 'simplecov', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index a1862c1..3e9924f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,32 +18,70 @@ GEM zeitwerk (~> 2.3) addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) + ast (2.4.2) awesome_print (1.9.2) + base64 (0.1.1) coderay (1.1.3) concurrent-ruby (1.2.2) crack (0.4.5) rexml diff-lcs (1.5.0) + docile (1.4.0) faraday (2.7.4) faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) faraday-net_http (3.0.2) + ffi (1.15.5) + formatador (0.3.0) + guard (2.18.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) i18n (1.14.1) concurrent-ruby (~> 1.0) + json (2.6.3) + language_server-protocol (3.17.0.3) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + lumberjack (1.2.8) method_source (1.0.0) mini_portile2 (2.8.1) minitest (5.20.0) + nenv (0.3.0) nokogiri (1.14.3) mini_portile2 (~> 2.8.0) racc (~> 1.4) nori (2.6.0) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + parallel (1.23.0) + parser (3.2.2.3) + ast (~> 2.4.1) + racc pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) public_suffix (5.0.1) racc (1.6.2) + rainbow (3.1.1) rake (12.3.3) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + regexp_parser (2.8.1) rexml (3.2.5) rspec (3.12.0) rspec-core (~> 3.12.0) @@ -58,9 +96,41 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-support (3.12.0) + rubocop (1.56.0) + base64 (~> 0.1.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.2.2.3) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.18.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.23.1) + rubocop (~> 1.33) + rubocop-rspec (2.23.2) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + shellany (0.0.1) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) + thor (1.2.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (2.4.2) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -72,9 +142,13 @@ PLATFORMS DEPENDENCIES awesome_print + guard-rspec pry rake (~> 12.0) rspec (~> 3.0) + rubocop + rubocop-rspec + simplecov tickeos_b2b! webmock diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..a978b8d --- /dev/null +++ b/Guardfile @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :rspec, cmd: "bundle exec #{'bin/spring' if ENV.fetch('USE_SPRING', 'no') == 'yes'} " \ + 'rspec --format=documentation --tag focus' do + # watch(%r{.*}) { |m| puts "Change detected on #{m.inspect}"; nil } + + watch(%r{^spec/.+_spec\.rb$}) + + watch(%r{^lib/(.+)\.rb$}) do |m| + "spec/#{m[1]}_spec.rb" + end + + watch('spec/spec_helper.rb') do + 'spec' + end + + # Factories + watch(%r{^spec/factories/(.+)\.rb}) { 'spec/models/factories_spec.rb' } +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 408d263..3b8cb08 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,4 +19,18 @@ config.expect_with :rspec do |c| c.syntax = :expect end + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Guard tries to run only tests with the focus tag, if this would filter out + # all specs, run all tests. + config.run_all_when_everything_filtered = true end From 38b31ca42898675c8b381cb11a2ac1f83311708c Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 09:19:54 +0200 Subject: [PATCH 4/9] Enable activesupport and set breaking timezone as spec-default to detect time params without any additional tz data --- bin/console | 1 + lib/tickeos_b2b/ticket.rb | 2 ++ spec/spec_helper.rb | 2 ++ 3 files changed, 5 insertions(+) diff --git a/bin/console b/bin/console index 0df5d4d..177dcc4 100755 --- a/bin/console +++ b/bin/console @@ -3,6 +3,7 @@ require 'bundler/setup' require 'tickeos_b2b' require 'awesome_print' +require 'active_support/core_ext/time' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/lib/tickeos_b2b/ticket.rb b/lib/tickeos_b2b/ticket.rb index 400a752..b9412a6 100644 --- a/lib/tickeos_b2b/ticket.rb +++ b/lib/tickeos_b2b/ticket.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'active_support/core_ext/time' + module TickeosB2b class Ticket ATTRIBUTES = [ diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3b8cb08..0ec92b9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,8 @@ # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = '.rspec_status' + Time.zone = 'Monrovia' # Monrovia has the same offset as UTC, but is not considered to be an UTC time by ActiveSupport + # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! From eeaada7f74e5b672d1aee8335305a4be0d13e848 Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 09:20:59 +0200 Subject: [PATCH 5/9] Only accept Ticket#validation_date times with CET/CEST timezone/offset --- README.md | 4 +- lib/tickeos_b2b/api/purchase.rb | 5 +- lib/tickeos_b2b/ticket.rb | 27 ++++++- spec/tickeos_b2b/api/purchase_spec.rb | 8 +-- spec/tickeos_b2b/product_spec.rb | 2 +- spec/tickeos_b2b/ticket_spec.rb | 100 +++++++++++++++++++++++++- 6 files changed, 136 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7ce458a..c03e54f 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ options = { :ref_id, # Ticket reference id :quantity, # Number of tickets :serial_product, # Ticket id - :date_to_validate, # Validation date + :date_to_validate, # Validation date (time can also be passed, but it must have a CET/CEST timezone attached to it) :location_id, # Location id (for communication with HAFAS) :sub_ref_id, # Subproduct reference id :transaction_id, # Payment transaction id @@ -113,7 +113,7 @@ options = { last_name: 'Statham', serial_ordering: 'unique_id_123', serial_product: '123', - date_to_validate: Time.now, + date_to_validate: ActiveSupport::TimeZone['Berlin'].now, sub_ref_id: 'AB', transaction_id: '2L1JHBFT49TC1', quantity: '1', diff --git a/lib/tickeos_b2b/api/purchase.rb b/lib/tickeos_b2b/api/purchase.rb index 288c52e..6801e5d 100644 --- a/lib/tickeos_b2b/api/purchase.rb +++ b/lib/tickeos_b2b/api/purchase.rb @@ -67,7 +67,10 @@ def self.request_body(pre_check:, go:, ticket:, requires_zones:) def self.validation_date(datetime) return '' if datetime.blank? - raise ArgumentError, 'no proper Time with timezone given' if !datetime.kind_of?(Time) || datetime.utc? + # As Ticket#validation_date= already performs validation-checks, this is just a redundant check if + # validation_date will be passed by some other unexpected way. After a couple of weeks we can remove this check + # again, if no errors occur. + raise ArgumentError, 'no proper Time with timezone given' if datetime.kind_of?(Time) && datetime.utc? datetime.to_date.to_s end diff --git a/lib/tickeos_b2b/ticket.rb b/lib/tickeos_b2b/ticket.rb index b9412a6..7a2e756 100644 --- a/lib/tickeos_b2b/ticket.rb +++ b/lib/tickeos_b2b/ticket.rb @@ -27,11 +27,14 @@ class Ticket :price_vat_rate ].freeze + DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\Z/ + VALID_TIME_ZONES = %w[CET CEST].freeze + attr_accessor(*ATTRIBUTES) def initialize(**kwargs) ATTRIBUTES.each do |attr| - instance_variable_set("@#{attr}", kwargs[attr]) + public_send("#{attr}=", kwargs[attr]) end self.state = :new @@ -43,6 +46,28 @@ def attributes end.to_h end + # a validation date will always be transformed to a valid Date or Time object whenever possible. + # Time object must also have a timezone included to prevent misinterpretations for the according validation_date. + # E.g.: During german summertime someone buys a ticket at/for 1:00am. In UTC resspecting the timezone offset the date + # of the previous day would be applied, causing for the ticket not to be offered. + def validation_date=(value) + value = if value.blank? + nil + elsif DATE_REGEX.match(value.to_s) + Date.parse(value.to_s) + else + time = Time.parse(value.to_s) + unless VALID_TIME_ZONES.include?(time.zone) + raise ArgumentError, + 'Time without CET/CEST timezone is not supported' + end + + time + end + + @validation_date = value + end + def self.load_ticket_data(ticket:, response:) response = response.dig('TICKeosProxy', 'txPurchaseResponse') || response.dig('TICKeosProxy', 'txResponse') diff --git a/spec/tickeos_b2b/api/purchase_spec.rb b/spec/tickeos_b2b/api/purchase_spec.rb index 63fa851..6c78aa4 100644 --- a/spec/tickeos_b2b/api/purchase_spec.rb +++ b/spec/tickeos_b2b/api/purchase_spec.rb @@ -94,16 +94,16 @@ context 'when ticket.validation_date is a Date' do let(:validation_date) { Date.new(2020, 9, 2) } - it 'raises an error' do - expect { operation }.to raise_error ArgumentError, 'no proper Time with timezone given' + it 'does not raise an error' do + expect { operation }.not_to raise_error end end context 'when ticket.validation_date is a DateTime without timezone info' do - let(:validation_date) { DateTime.new(2020, 9, 2, 23) } + let(:validation_date) { DateTime.new(2020, 9, 2, 23).utc } it 'raises an error' do - expect { operation }.to raise_error ArgumentError, 'no proper Time with timezone given' + expect { operation }.to raise_error ArgumentError, 'Time without CET/CEST timezone is not supported' end end diff --git a/spec/tickeos_b2b/product_spec.rb b/spec/tickeos_b2b/product_spec.rb index 539bf5e..a41da33 100644 --- a/spec/tickeos_b2b/product_spec.rb +++ b/spec/tickeos_b2b/product_spec.rb @@ -110,7 +110,7 @@ { first_name: 'Json', last_name: 'Statham', - validation_date: Time.now, + validation_date: ActiveSupport::TimeZone['Europe/Berlin'].local(2020, 9, 2, 23, 0, 0), sub_ref_id: 'kein Zuschlag' } end diff --git a/spec/tickeos_b2b/ticket_spec.rb b/spec/tickeos_b2b/ticket_spec.rb index 8fd04b5..c9b68de 100644 --- a/spec/tickeos_b2b/ticket_spec.rb +++ b/spec/tickeos_b2b/ticket_spec.rb @@ -53,7 +53,7 @@ { first_name: 'Json', last_name: 'Statham', - validation_date: Time.now, + validation_date: ActiveSupport::TimeZone['Berlin'].now, sub_ref_id: 'kein Zuschlag', location_id: '123456789', product_type_role: 'abo_zones' @@ -64,6 +64,12 @@ it 'creates a new Ticket object' do expect(described_class.new).to be_an_instance_of(TickeosB2b::Ticket) end + + it 'calls custom-setter for :validation_date' do + validation_date = ActiveSupport::TimeZone['Berlin'].now + expect_any_instance_of(described_class).to receive(:validation_date=).with(validation_date).and_call_original + described_class.new(validation_date:) + end end describe '.attributes' do @@ -97,6 +103,98 @@ end end + describe '#validation_date=' do + let(:value) { nil } + let(:instance) { described_class.new } + let(:operation) { instance.validation_date = value } + + context 'when nil is given' do + let(:value) { nil } + + it 'changes the validation_date to given value' do + instance.instance_variable_set(:@validation_date, :any_date) + expect { operation }.to change(instance, :validation_date).to(nil) + end + end + + context 'when empty string is given' do + let(:value) { '' } + + it 'changes the validation_date to nil' do + instance.instance_variable_set(:@validation_date, :any_date) + expect { operation }.to change(instance, :validation_date).to(nil) + end + end + + context 'when Date is given' do + let(:value) { Date.current } + + it 'changes the validation_date to given value' do + expect { operation }.to change(instance, :validation_date).to(value) + end + end + + context 'when date-string is given' do + let(:value) { Date.current.to_s } + + it 'changes the validation_date to a Date object' do + expect { operation }.to change(instance, :validation_date).to(kind_of(Date)) + end + end + + context 'when Time is given' do + context 'when Time has CET timezone defined' do + let(:value) { ActiveSupport::TimeZone['Berlin'].local(2020, 11, 1, 23, 59, 59) } + + it 'changes the validation_date to given value' do + expect { operation }.to change(instance, :validation_date).to(value) + end + end + + context 'when Time has CEST timezone defined' do + let(:value) { ActiveSupport::TimeZone['Berlin'].local(2020, 8, 1, 23, 59, 59) } + + it 'changes the validation_date to given value' do + expect { operation }.to change(instance, :validation_date).to(value) + end + end + + context 'when Time has no timezone / is UTC' do + let(:value) { ActiveSupport::TimeZone['UTC'].local(2020, 9, 1, 23, 59, 59) } + + it 'raises an ArgumentError' do + expect { operation }.to raise_error(ArgumentError).with_message('Time without CET/CEST timezone is not supported') + end + end + + context 'when Time has any other timezone defined' do + let(:value) { ActiveSupport::TimeZone['Hawaii'].local(2020, 9, 1, 23, 59, 59) } + + it 'raises an ArgumentError' do + expect { operation }.to raise_error(ArgumentError).with_message('Time without CET/CEST timezone is not supported') + end + end + end + + context 'when time-string is given' do + context 'when time-string has a timezone defined' do + let(:value) { ActiveSupport::TimeZone['Berlin'].local(2020, 9, 1, 23, 59, 59).to_s } + + it 'changes the validation_date to a Date object' do + expect { operation }.to change(instance, :validation_date).to(kind_of(Time)) + end + end + + context 'when time-string has timezone / is UTC' do + let(:value) { Time.now.utc.to_s } + + it 'raises an ArgumentError' do + expect { operation }.to raise_error(ArgumentError).with_message('Time without CET/CEST timezone is not supported') + end + end + end + end + describe '.load_ticket_data' do let(:purchased_ticket) do described_class.load_ticket_data( From bde2b71239d13e5b19e5b25107cb06bdafd940f1 Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 09:21:54 +0200 Subject: [PATCH 6/9] Apply Berlin timezone dates for tickeos version-dates --- lib/tickeos_b2b/test_run/product_data.rb | 2 +- lib/tickeos_b2b/test_run/product_list.rb | 2 +- lib/tickeos_b2b/test_run/purchase.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tickeos_b2b/test_run/product_data.rb b/lib/tickeos_b2b/test_run/product_data.rb index 66fb30f..c911419 100644 --- a/lib/tickeos_b2b/test_run/product_data.rb +++ b/lib/tickeos_b2b/test_run/product_data.rb @@ -10,7 +10,7 @@ def self.load!(options, reference_id) Nori.new.parse( Nokogiri::XML::Builder.new do |xml| - xml.TICKeosProxy(apiVersion: '1.0', version: Time.now.strftime('%Y-%m-%d'), instanceName: 'test') do + xml.TICKeosProxy(apiVersion: '1.0', version: ActiveSupport::TimeZone['Berlin'].now.strftime('%Y-%m-%d'), instanceName: 'test') do xml.txProductDataResponse(productReferenceId: '', timeIntervalOptionType: 'type') do xml.Product( id: options[:id], diff --git a/lib/tickeos_b2b/test_run/product_list.rb b/lib/tickeos_b2b/test_run/product_list.rb index cc884db..f8c8e72 100644 --- a/lib/tickeos_b2b/test_run/product_list.rb +++ b/lib/tickeos_b2b/test_run/product_list.rb @@ -8,7 +8,7 @@ def self.product_list(options = {}) Nori.new.parse( Nokogiri::XML::Builder.new do |xml| - xml.TICKeosProxy(apiVersion: '1.0', version: Time.now.strftime('%Y-%m-%d'), instanceName: 'test') do + xml.TICKeosProxy(apiVersion: '1.0', version: ActiveSupport::TimeZone['Berlin'].now.strftime('%Y-%m-%d'), instanceName: 'test') do xml.txProductResponse(method: 'product_list') do options.each do |opt| opt = opt.transform_keys(&:to_sym) diff --git a/lib/tickeos_b2b/test_run/purchase.rb b/lib/tickeos_b2b/test_run/purchase.rb index 1f211d8..43d90d7 100644 --- a/lib/tickeos_b2b/test_run/purchase.rb +++ b/lib/tickeos_b2b/test_run/purchase.rb @@ -8,7 +8,7 @@ def self.purchase(options, reference_id) Nori.new.parse( Nokogiri::XML::Builder.new do |xml| - xml.TICKeosProxy(apiVersion: '1.0', version: Time.now.strftime('%Y-%m-%d'), instanceName: 'test') do + xml.TICKeosProxy(apiVersion: '1.0', version: ActiveSupport::TimeZone['Berlin'].now.strftime('%Y-%m-%d'), instanceName: 'test') do xml.txPurchaseResponse do xml.ordering(server_ordering_serial: options[:server_ordering_serial]) xml.productData( From 27018392a27e7a085edf604416654e39aa7d4eae Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 15:20:39 +0200 Subject: [PATCH 7/9] Check for same offset when validation_date is given as string --- lib/tickeos_b2b/ticket.rb | 18 ++++++++++++------ spec/tickeos_b2b/ticket_spec.rb | 10 +++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/tickeos_b2b/ticket.rb b/lib/tickeos_b2b/ticket.rb index 7a2e756..3be3931 100644 --- a/lib/tickeos_b2b/ticket.rb +++ b/lib/tickeos_b2b/ticket.rb @@ -55,19 +55,25 @@ def validation_date=(value) nil elsif DATE_REGEX.match(value.to_s) Date.parse(value.to_s) + elsif value.is_a?(Time) + assert_timezone!(value) else time = Time.parse(value.to_s) - unless VALID_TIME_ZONES.include?(time.zone) - raise ArgumentError, - 'Time without CET/CEST timezone is not supported' - end - - time + assert_timezone!(time) end @validation_date = value end + def assert_timezone!(time) + valid_timezone = VALID_TIME_ZONES.include?(time.zone) || + time.in_time_zone(ActiveSupport::TimeZone['Berlin']).utc_offset == time.utc_offset + + raise ArgumentError, 'Time without CET/CEST timezone is not supported' unless valid_timezone + + time + end + def self.load_ticket_data(ticket:, response:) response = response.dig('TICKeosProxy', 'txPurchaseResponse') || response.dig('TICKeosProxy', 'txResponse') diff --git a/spec/tickeos_b2b/ticket_spec.rb b/spec/tickeos_b2b/ticket_spec.rb index c9b68de..5e8f849 100644 --- a/spec/tickeos_b2b/ticket_spec.rb +++ b/spec/tickeos_b2b/ticket_spec.rb @@ -185,13 +185,21 @@ end end - context 'when time-string has timezone / is UTC' do + context 'when time-string is in UTC' do let(:value) { Time.now.utc.to_s } it 'raises an ArgumentError' do expect { operation }.to raise_error(ArgumentError).with_message('Time without CET/CEST timezone is not supported') end end + + context 'when time-string has some other offset than the local time' do + let(:value) { ActiveSupport::TimeZone['Hawaii'].local(2020, 9, 1, 23, 59, 59).to_s } + + it 'raises an ArgumentError' do + expect { operation }.to raise_error(ArgumentError).with_message('Time without CET/CEST timezone is not supported') + end + end end end From 04f9000226a2b671b6f44ef6a68362f44b4edd55 Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 09:22:47 +0200 Subject: [PATCH 8/9] Ebertize --- Gemfile | 6 +++--- bin/console | 2 +- lib/tickeos_b2b/api/purchase.rb | 2 +- spec/tickeos_b2b/product_spec.rb | 2 +- spec/tickeos_b2b_spec.rb | 2 +- tickeos_b2b.gemspec | 11 ++++++----- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index e76276c..a80a243 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,10 @@ -source "https://rubygems.org" +source 'https://rubygems.org' # Specify your gem's dependencies in tickeos_b2b.gemspec gemspec -gem "rake", "~> 12.0" -gem "rspec", "~> 3.0" +gem 'rake', '~> 12.0' +gem 'rspec', '~> 3.0' group :development, :test do gem 'awesome_print' diff --git a/bin/console b/bin/console index 177dcc4..e2df794 100755 --- a/bin/console +++ b/bin/console @@ -19,7 +19,7 @@ def reload!(print = true) # Main project directory. root_dir = File.expand_path('..', __dir__) # Directories within the project that should be reloaded. - reload_dirs = %w{lib} + reload_dirs = %w[lib] # Loop through and reload every file in all relevant project directories. reload_dirs.each do |dir| Dir.glob("#{root_dir}/#{dir}/**/*.rb").each { |f| load(f) } diff --git a/lib/tickeos_b2b/api/purchase.rb b/lib/tickeos_b2b/api/purchase.rb index 6801e5d..e2e6d3d 100644 --- a/lib/tickeos_b2b/api/purchase.rb +++ b/lib/tickeos_b2b/api/purchase.rb @@ -70,7 +70,7 @@ def self.validation_date(datetime) # As Ticket#validation_date= already performs validation-checks, this is just a redundant check if # validation_date will be passed by some other unexpected way. After a couple of weeks we can remove this check # again, if no errors occur. - raise ArgumentError, 'no proper Time with timezone given' if datetime.kind_of?(Time) && datetime.utc? + raise ArgumentError, 'no proper Time with timezone given' if datetime.is_a?(Time) && datetime.utc? datetime.to_date.to_s end diff --git a/spec/tickeos_b2b/product_spec.rb b/spec/tickeos_b2b/product_spec.rb index a41da33..2c52c93 100644 --- a/spec/tickeos_b2b/product_spec.rb +++ b/spec/tickeos_b2b/product_spec.rb @@ -33,7 +33,7 @@ For any rides from valididy date and time on all public transports. 24-h name Ticket {desc en} - + diff --git a/spec/tickeos_b2b_spec.rb b/spec/tickeos_b2b_spec.rb index b732e09..3949216 100644 --- a/spec/tickeos_b2b_spec.rb +++ b/spec/tickeos_b2b_spec.rb @@ -6,7 +6,7 @@ let(:version) { '0.1.0' } it 'has the correct version number' do - expect(TickeosB2b::VERSION).not_to be nil + expect(TickeosB2b::VERSION).not_to be_nil expect(TickeosB2b::VERSION).to eq(version) end end diff --git a/tickeos_b2b.gemspec b/tickeos_b2b.gemspec index 990618b..0e475a7 100644 --- a/tickeos_b2b.gemspec +++ b/tickeos_b2b.gemspec @@ -16,15 +16,16 @@ Gem::Specification.new do |gem| gem.require_paths = %w[lib] gem.extra_rdoc_files = %w[CHANGELOG.md LICENSE.md README.md Rakefile lib spec] + gem.add_dependency 'activesupport' gem.add_dependency 'faraday' gem.add_dependency 'nokogiri' gem.add_dependency 'nori' - gem.add_dependency 'activesupport' gem.metadata = { - 'homepage_uri' => 'https://github.com/dbdrive/tickeos_b2b', - 'changelog_uri' => "https://github.com/dbdrive/tickeos_b2b/releases/tag/v#{gem.version}", - 'source_code_uri' => 'https://github.com/dbdrive/tickeos_b2b', - 'bug_tracker_uri' => 'https://github.com/dbdrive/tickeos_b2b/issues' + 'homepage_uri' => 'https://github.com/dbdrive/tickeos_b2b', + 'changelog_uri' => "https://github.com/dbdrive/tickeos_b2b/releases/tag/v#{gem.version}", + 'source_code_uri' => 'https://github.com/dbdrive/tickeos_b2b', + 'bug_tracker_uri' => 'https://github.com/dbdrive/tickeos_b2b/issues', + 'rubygems_mfa_required' => 'true' } end From cd6aeb0016a183f409c456a740a48a3da9956844 Mon Sep 17 00:00:00 2001 From: Daniel Loy Date: Tue, 10 Oct 2023 09:22:58 +0200 Subject: [PATCH 9/9] Bump version to 0.2.0 --- CHANGELOG.md | 11 +++++++++++ Gemfile.lock | 2 +- lib/tickeos_b2b/version.rb | 2 +- spec/tickeos_b2b_spec.rb | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fabf68f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +## [Unreleased] + +## [0.2.0] - 2023-10-10 + +- *breaking:* Ticket#validation_date only accepts times with CET/CEST offset +- Added CHANGELOG +- Added rubocop support + +## [0.1.0] - 2022-09-07 + +- Initial release diff --git a/Gemfile.lock b/Gemfile.lock index 3e9924f..85cee27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - tickeos_b2b (0.1.0) + tickeos_b2b (0.2.0) activesupport faraday nokogiri diff --git a/lib/tickeos_b2b/version.rb b/lib/tickeos_b2b/version.rb index 559bf8c..85faf9b 100644 --- a/lib/tickeos_b2b/version.rb +++ b/lib/tickeos_b2b/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module TickeosB2b - VERSION = '0.1.0' + VERSION = '0.2.0' end diff --git a/spec/tickeos_b2b_spec.rb b/spec/tickeos_b2b_spec.rb index 3949216..dc145ea 100644 --- a/spec/tickeos_b2b_spec.rb +++ b/spec/tickeos_b2b_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe TickeosB2b do - let(:version) { '0.1.0' } + let(:version) { '0.2.0' } it 'has the correct version number' do expect(TickeosB2b::VERSION).not_to be_nil