Skip to content

Commit

Permalink
Merge pull request #37 from ioki-mobility/times-with-timezone
Browse files Browse the repository at this point in the history
Enforce times with timezone
  • Loading branch information
dal-ioki committed Oct 10, 2023
2 parents 22ba117 + cd6aeb0 commit b628300
Show file tree
Hide file tree
Showing 20 changed files with 383 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 11 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
13 changes: 10 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
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'
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
92 changes: 90 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,38 +1,87 @@
PATH
remote: .
specs:
tickeos_b2b (0.1.0)
tickeos_b2b (0.2.0)
activesupport
faraday
nokogiri
nori

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)
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)
Expand All @@ -47,22 +96,61 @@ 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)
hashdiff (>= 0.4.0, < 2.0.0)
zeitwerk (2.6.11)

PLATFORMS
ruby

DEPENDENCIES
awesome_print
guard-rspec
pry
rake (~> 12.0)
rspec (~> 3.0)
rubocop
rubocop-rspec
simplecov
tickeos_b2b!
webmock

BUNDLED WITH
2.4.10
2.4.19
22 changes: 22 additions & 0 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -18,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) }
Expand Down
7 changes: 7 additions & 0 deletions lib/tickeos_b2b/api/purchase.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'active_support/core_ext/time'

module TickeosB2b
module Api
class Purchase
Expand Down Expand Up @@ -65,6 +67,11 @@ def self.request_body(pre_check:, go:, ticket:, requires_zones:)
def self.validation_date(datetime)
return '' if datetime.blank?

# 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.is_a?(Time) && datetime.utc?

datetime.to_date.to_s
end

Expand Down
2 changes: 1 addition & 1 deletion lib/tickeos_b2b/test_run/product_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion lib/tickeos_b2b/test_run/product_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lib/tickeos_b2b/test_run/purchase.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
35 changes: 34 additions & 1 deletion lib/tickeos_b2b/ticket.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'active_support/core_ext/time'

module TickeosB2b
class Ticket
ATTRIBUTES = [
Expand All @@ -25,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
Expand All @@ -41,6 +46,34 @@ 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)
elsif value.is_a?(Time)
assert_timezone!(value)
else
time = Time.parse(value.to_s)
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')

Expand Down
2 changes: 1 addition & 1 deletion lib/tickeos_b2b/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module TickeosB2b
VERSION = '0.1.0'
VERSION = '0.2.0'
end
Loading

0 comments on commit b628300

Please sign in to comment.