From d647a9d202d8525bc3ed0460979a6d651f99acc6 Mon Sep 17 00:00:00 2001 From: alex707 Date: Mon, 29 Sep 2025 18:23:03 +0300 Subject: [PATCH] available_routes endpoint --- .gitignore | 1 + .ruby-gemset | 1 + .ruby-version | 2 +- Gemfile | 4 + Gemfile.lock | 8 + .../api/v1/available_routes_controller.rb | 44 ++ app/controllers/api/v1/base_controller.rb | 6 + app/interactors/api/v1/available_routes.rb | 94 +++ app/interactors/base_interaction.rb | 3 + config/initializers/constants.rb | 3 + config/routes.rb | 6 + .../v1/available_routes_controller_spec.rb | 30 + spec/factories/permitted_route.rb | 11 + spec/factories/segments.rb | 12 + .../api/v1/available_routes_spec.rb | 649 ++++++++++++++++++ spec/rails_helper.rb | 2 +- spec/support/factory_bot.rb | 3 + 17 files changed, 877 insertions(+), 2 deletions(-) create mode 100644 .ruby-gemset create mode 100644 app/controllers/api/v1/available_routes_controller.rb create mode 100644 app/controllers/api/v1/base_controller.rb create mode 100644 app/interactors/api/v1/available_routes.rb create mode 100644 app/interactors/base_interaction.rb create mode 100644 config/initializers/constants.rb create mode 100644 spec/controllers/api/v1/available_routes_controller_spec.rb create mode 100644 spec/factories/permitted_route.rb create mode 100644 spec/factories/segments.rb create mode 100644 spec/interactions/api/v1/available_routes_spec.rb create mode 100644 spec/support/factory_bot.rb diff --git a/.gitignore b/.gitignore index 216acfa..b54449a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ /.idea /.claude CLAUDE.md +.byebug_history diff --git a/.ruby-gemset b/.ruby-gemset new file mode 100644 index 0000000..4bfdb15 --- /dev/null +++ b/.ruby-gemset @@ -0,0 +1 @@ +CL_test_assignment diff --git a/.ruby-version b/.ruby-version index 54a6fdc..1cf8253 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-3.4 \ No newline at end of file +3.4.6 diff --git a/Gemfile b/Gemfile index c2667cf..0d036e7 100644 --- a/Gemfile +++ b/Gemfile @@ -38,12 +38,16 @@ gem "thruster", require: false # CSV processing for seed data gem "csv" +gem 'active_interaction' +gem 'memery' + group :development, :test do gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" gem "brakeman", require: false gem "rubocop-rails-omakase", require: false gem 'factory_bot_rails' + gem 'byebug' gem 'rspec-rails' end diff --git a/Gemfile.lock b/Gemfile.lock index fb82a8a..2fbee1d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,6 +44,9 @@ GEM erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) + active_interaction (5.5.0) + activemodel (>= 5.2, < 9) + activesupport (>= 5.2, < 9) activejob (8.0.2.1) activesupport (= 8.0.2.1) globalid (>= 0.3.6) @@ -82,6 +85,7 @@ GEM brakeman (7.1.0) racc builder (3.3.0) + byebug (12.0.0) concurrent-ruby (1.3.5) connection_pool (2.5.3) crass (1.0.6) @@ -139,6 +143,7 @@ GEM net-pop net-smtp marcel (1.0.4) + memery (1.8.0) mini_mime (1.1.5) minitest (5.25.5) msgpack (1.8.0) @@ -340,12 +345,15 @@ PLATFORMS x86_64-linux-musl DEPENDENCIES + active_interaction bootsnap brakeman + byebug csv debug factory_bot_rails kamal + memery pg (>= 1.1) puma (>= 5.0) rails (~> 8.0.2, >= 8.0.2.1) diff --git a/app/controllers/api/v1/available_routes_controller.rb b/app/controllers/api/v1/available_routes_controller.rb new file mode 100644 index 0000000..c84e344 --- /dev/null +++ b/app/controllers/api/v1/available_routes_controller.rb @@ -0,0 +1,44 @@ +module Api + module V1 + class AvailableRoutesController < BaseController + def index + result = [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T05:45:00.000Z", + "arrival_time" => "2024-01-02T18:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6224", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-01T05:45:00.000Z", + "sta" => "2024-01-01T07:40:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-01T20:25:00.000Z", + "sta" => "2024-01-02T02:30:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2514", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-02T13:40:00.000Z", + "sta" => "2024-01-02T18:05:00.000Z" + } + ] + } + ] + + render json: result, status: :ok + end + end + end +end diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb new file mode 100644 index 0000000..bbd8a65 --- /dev/null +++ b/app/controllers/api/v1/base_controller.rb @@ -0,0 +1,6 @@ +module Api + module V1 + class BaseController < ActionController::API + end + end +end diff --git a/app/interactors/api/v1/available_routes.rb b/app/interactors/api/v1/available_routes.rb new file mode 100644 index 0000000..71b3afd --- /dev/null +++ b/app/interactors/api/v1/available_routes.rb @@ -0,0 +1,94 @@ +module Api::V1 + class AvailableRoutes < BaseInteraction + string :carrier + string :origin_iata + string :destination_iata + string :departure_from + string :departure_to + + def execute + response + end + + private + + def transfer_iata + PermittedRoute + .find_by(carrier:, origin_iata:, destination_iata:) + .transfer_iata_codes + .flat_map { _1.length == 6 ? [_1[0,3], _1[3,3]] : _1[0,3] }.uniq + end + + def origin_segments + Segment.where(airline: carrier, origin_iata:, destination_iata: transfer_iata).order(:sta) + end + memoize :origin_segments + + def connection_segments + Segment.where(airline: carrier, origin_iata: transfer_iata, destination_iata: transfer_iata).order(:sta) + end + memoize :connection_segments + + def destination_segments + Segment.where(airline: carrier, origin_iata: transfer_iata, destination_iata:).order(:sta) + end + memoize :destination_segments + + def segment_hash(segment) + { + 'carrier' => segment.airline, + 'segment_number' => segment.segment_number, + 'origin_iata' => segment.origin_iata, + 'destination_iata' => segment.destination_iata, + 'std' => segment.std.iso8601(3), + 'sta' => segment.sta.iso8601(3) + } + end + + def total_segment(origin_segment, destination_segment, segments) + { + "origin_iata" => origin_segment.origin_iata, + "destination_iata" => destination_segment.destination_iata, + "departure_time" => origin_segment.std.iso8601(3), + "arrival_time" => destination_segment.sta.iso8601(3), + "segments" => segments + } + end + + def response + total_segments = [] + origin_segments.each do |origin_segment| + destination_segments.each do |destination_segment| + transfers = connection_segments.select { origin_segment.destination_iata == _1.origin_iata || _1.destination_iata == destination_segment.origin_iata } + + if transfers.any? && transfers.any? { origin_segment.destination_iata == _1.origin_iata && _1.destination_iata == destination_segment.origin_iata } + transfers.each do |transfer| + next if origin_segment.std > transfer.sta + next if transfer.std > destination_segment.sta + next if (transfer.sta - transfer.std) / 1.minute > MAX_CONNECTION_TIME + next if (transfer.sta - transfer.std) / 1.minute <= MIN_CONNECTION_TIME + + segments = [] + segments << segment_hash(origin_segment) + segments << segment_hash(transfer) + segments << segment_hash(destination_segment) + + total_segments << total_segment(origin_segment, destination_segment, segments) + end + else + next if origin_segment.std > destination_segment.sta + next if origin_segment.destination_iata != destination_segment.origin_iata + + segments = [] + segments << segment_hash(origin_segment) + segments << segment_hash(destination_segment) + + total_segments << total_segment(origin_segment, destination_segment, segments) + end + end + end + + total_segments + end + end +end diff --git a/app/interactors/base_interaction.rb b/app/interactors/base_interaction.rb new file mode 100644 index 0000000..b96debd --- /dev/null +++ b/app/interactors/base_interaction.rb @@ -0,0 +1,3 @@ +class BaseInteraction < ActiveInteraction::Base + include Memery +end diff --git a/config/initializers/constants.rb b/config/initializers/constants.rb new file mode 100644 index 0000000..b455f3d --- /dev/null +++ b/config/initializers/constants.rb @@ -0,0 +1,3 @@ +# Константы времени пересадок +MIN_CONNECTION_TIME = ENV.fetch('MIN_CONNECTION_TIME', 480).to_i # минимальное время для пересадки, мин (8 часов) +MAX_CONNECTION_TIME = ENV.fetch('MAX_CONNECTION_TIME', 2880).to_i # максимальное время ожидания, мин (48 часов) diff --git a/config/routes.rb b/config/routes.rb index a125ef0..d3307e1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,12 @@ # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check + namespace :api do + namespace :v1 do + resources :available_routes, only: %i[index] + end + end + # Defines the root path route ("/") # root "posts#index" end diff --git a/spec/controllers/api/v1/available_routes_controller_spec.rb b/spec/controllers/api/v1/available_routes_controller_spec.rb new file mode 100644 index 0000000..b6cfaa8 --- /dev/null +++ b/spec/controllers/api/v1/available_routes_controller_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe Api::V1::AvailableRoutesController, type: :controller do + describe '#index' do + + context 'simple segment' do + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[OVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB') } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME') } + + subject(:send_request) do + get :index, params: { + carrier: 'S7', + origin_iata: 'UUS', + destination_iata: 'DME', + departure_from: '2024-01-01', + departure_to: '2024-01-07', + } + end + + it 'return status 200' do + expect(subject).to have_http_status :ok + end + + it 'return not empty body' do + expect(subject.body).to_not be_empty + end + end + end +end diff --git a/spec/factories/permitted_route.rb b/spec/factories/permitted_route.rb new file mode 100644 index 0000000..685329e --- /dev/null +++ b/spec/factories/permitted_route.rb @@ -0,0 +1,11 @@ +FactoryBot.define do + factory :permitted_route do + sequence(:id) + + carrier { 'S7' } + origin_iata { 'UUS' } + destination_iata { 'DME' } + direct { true } + transfer_iata_codes { %w[OVB KHV IKT VVOOVB] } + end +end diff --git a/spec/factories/segments.rb b/spec/factories/segments.rb new file mode 100644 index 0000000..d1eff94 --- /dev/null +++ b/spec/factories/segments.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + factory :segment do + sequence(:id) + + airline { 'S7' } + segment_number { '0321' } + origin_iata { 'UUS' } + destination_iata { 'DME' } + std { Time.new(2024, 1, 1, 5, 45, 0) } + sta { Time.new(2024, 1, 2, 18, 5, 0) } + end +end diff --git a/spec/interactions/api/v1/available_routes_spec.rb b/spec/interactions/api/v1/available_routes_spec.rb new file mode 100644 index 0000000..f4aaefc --- /dev/null +++ b/spec/interactions/api/v1/available_routes_spec.rb @@ -0,0 +1,649 @@ +require 'rails_helper' + +RSpec.describe Api::V1::AvailableRoutes do + describe '#call' do + describe 'for one route' do + describe 'simple segment' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[OVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB', std: Time.parse('2024-01-01 22:45:00 UTC'), sta: Time.parse('2024-01-02 05:00:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2508', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-02 05:35:00 UTC'), sta: Time.parse('2024-01-02 10:05:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T22:45:00.000Z", + "arrival_time" => "2024-01-02T10:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-01T22:45:00.000Z", + "sta" => "2024-01-02T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2508", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-02T05:35:00.000Z", + "sta" => "2024-01-02T10:05:00.000Z" + } + ] + } + ] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + + describe 'connection segment' do + context 'segment with correct min connection time' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[VVOOVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '6204', origin_iata: 'UUS', destination_iata: 'VVO', std: Time.parse('2024-01-04 05:20:00 UTC'), sta: Time.parse('2024-01-04 07:15:00 UTC')) } + let!(:segment12) { create(:segment, airline: 'S7', segment_number: '5202', origin_iata: 'VVO', destination_iata: 'OVB', std: Time.parse('2024-01-04 20:10:00 UTC'), sta: Time.parse('2024-01-06 02:15:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2508', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-06 05:35:00 UTC'), sta: Time.parse('2024-01-06 10:05:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-04T05:20:00.000Z", + "arrival_time" => "2024-01-06T10:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6204", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-04T05:20:00.000Z", + "sta" => "2024-01-04T07:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-04T20:10:00.000Z", + "sta" => "2024-01-06T02:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2508", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-06T05:35:00.000Z", + "sta" => "2024-01-06T10:05:00.000Z" + } + ] + } + ] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + + context 'segment with less than 8 hour min_connection_time' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[VVOOVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '6202', origin_iata: 'UUS', destination_iata: 'VVO', std: Time.parse('2024-01-05 11:05:00 UTC'), sta: Time.parse('2024-01-05 12:55:00')) } + let!(:segment12) { create(:segment, airline: 'S7', segment_number: '5204', origin_iata: 'VVO', destination_iata: 'OVB', std: Time.parse('2024-01-05 20:55:00 UTC'), sta: Time.parse('2024-01-06 02:15:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-06 23:20:00 UTC'), sta: Time.parse('2024-01-07 03:55:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + + context 'segment with more than 48 hours max_connection_time' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[VVOOVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '6204', origin_iata: 'UUS', destination_iata: 'VVO', std: Time.parse('2024-01-01 07:30:00 UTC'), sta: Time.parse('2024-01-01 09:25:00')) } + let!(:segment12) { create(:segment, airline: 'S7', segment_number: '5202', origin_iata: 'VVO', destination_iata: 'OVB', std: Time.parse('2024-01-01 20:05:00 UTC'), sta: Time.parse('2024-01-07 02:15:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-06 23:20:00 UTC'), sta: Time.parse('2024-01-07 03:55:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + end + + describe 'two simple segments' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[OVB]) } + let!(:segment4) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-03 23:20:00 UTC'), sta: Time.parse('2024-01-04 03:55:00 UTC')) } + let!(:segment3) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB', std: Time.parse('2024-01-02 22:45:00 UTC'), sta: Time.parse('2024-01-03 05:00:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2508', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-02 05:35:00 UTC'), sta: Time.parse('2024-01-02 10:05:00 UTC')) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB', std: Time.parse('2024-01-01 22:45:00 UTC'), sta: Time.parse('2024-01-02 05:00:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T22:45:00.000Z", + "arrival_time" => "2024-01-02T10:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-01T22:45:00.000Z", + "sta" => "2024-01-02T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2508", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-02T05:35:00.000Z", + "sta" => "2024-01-02T10:05:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T22:45:00.000Z", + "arrival_time" => "2024-01-04T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-01T22:45:00.000Z", + "sta" => "2024-01-02T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-03T23:20:00.000Z", + "sta" => "2024-01-04T03:55:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-02T22:45:00.000Z", + "arrival_time" => "2024-01-04T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-02T22:45:00.000Z", + "sta" => "2024-01-03T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-03T23:20:00.000Z", + "sta" => "2024-01-04T03:55:00.000Z" + } + ] + } + ] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + + describe 'two connection segments' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[VVOOVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '6204', origin_iata: 'UUS', destination_iata: 'VVO', std: Time.parse('2024-01-01 05:20:00 UTC'), sta: Time.parse('2024-01-01 07:15:00 UTC')) } + let!(:segment12) { create(:segment, airline: 'S7', segment_number: '5202', origin_iata: 'VVO', destination_iata: 'OVB', std: Time.parse('2024-01-01 20:10:00 UTC'), sta: Time.parse('2024-01-03 02:15:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2508', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-03 05:35:00 UTC'), sta: Time.parse('2024-01-03 10:05:00 UTC')) } + let!(:segment3) { create(:segment, airline: 'S7', segment_number: '6202', origin_iata: 'UUS', destination_iata: 'VVO', std: Time.parse('2024-01-03 11:05:00 UTC'), sta: Time.parse('2024-01-03 12:55:00 UTC')) } + let!(:segment34) { create(:segment, airline: 'S7', segment_number: '5202', origin_iata: 'VVO', destination_iata: 'OVB', std: Time.parse('2024-01-03 20:05:00 UTC'), sta: Time.parse('2024-01-05 02:15:00 UTC')) } + let!(:segment4) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-05 23:20:00 UTC'), sta: Time.parse('2024-01-06 03:55:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T05:20:00.000Z", + "arrival_time" => "2024-01-03T10:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6204", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-01T05:20:00.000Z", + "sta" => "2024-01-01T07:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-01T20:10:00.000Z", + "sta" => "2024-01-03T02:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2508", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-03T05:35:00.000Z", + "sta" => "2024-01-03T10:05:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T05:20:00.000Z", + "arrival_time" => "2024-01-06T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6204", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-01T05:20:00.000Z", + "sta" => "2024-01-01T07:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-01T20:10:00.000Z", + "sta" => "2024-01-03T02:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-05T23:20:00.000Z", + "sta" => "2024-01-06T03:55:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T05:20:00.000Z", + "arrival_time" => "2024-01-06T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6204", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-01T05:20:00.000Z", + "sta" => "2024-01-01T07:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-03T20:05:00.000Z", + "sta" => "2024-01-05T02:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-05T23:20:00.000Z", + "sta" => "2024-01-06T03:55:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-03T11:05:00.000Z", + "arrival_time" => "2024-01-06T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6202", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-03T11:05:00.000Z", + "sta" => "2024-01-03T12:55:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-03T20:05:00.000Z", + "sta" => "2024-01-05T02:15:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-05T23:20:00.000Z", + "sta" => "2024-01-06T03:55:00.000Z" + } + ] + } + ] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + end + + describe 'for several routes' do + describe 'simple segments' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[OVB IKT]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB', std: Time.parse('2024-01-01 22:45:00 UTC'), sta: Time.parse('2024-01-02 05:00:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2508', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-02 05:35:00 UTC'), sta: Time.parse('2024-01-02 10:05:00 UTC')) } + let!(:segment3) { create(:segment, airline: 'S7', segment_number: '6384', origin_iata: 'UUS', destination_iata: 'IKT', std: Time.parse('2024-01-04 22:35:00 UTC'), sta: Time.parse('2024-01-05 02:55:00 UTC')) } + let!(:segment4) { create(:segment, airline: 'S7', segment_number: '3020', origin_iata: 'IKT', destination_iata: 'DME', std: Time.parse('2024-01-05 04:05:00 UTC'), sta: Time.parse('2024-01-05 10:35:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T22:45:00.000Z", + "arrival_time" => "2024-01-02T10:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-01T22:45:00.000Z", + "sta" => "2024-01-02T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2508", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-02T05:35:00.000Z", + "sta" => "2024-01-02T10:05:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-04T22:35:00.000Z", + "arrival_time" => "2024-01-05T10:35:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6384", + "origin_iata" => "UUS", + "destination_iata" => "IKT", + "std" => "2024-01-04T22:35:00.000Z", + "sta" => "2024-01-05T02:55:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "3020", + "origin_iata" => "IKT", + "destination_iata" => "DME", + "std" => "2024-01-05T04:05:00.000Z", + "sta" => "2024-01-05T10:35:00.000Z" + } + ] + } + ] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + + describe 'simple and connection segments' do + subject { described_class.run!(**parameters) } + + let!(:permitted_route) { create(:permitted_route, carrier: 'S7', origin_iata: 'UUS', destination_iata: 'DME', direct: true, transfer_iata_codes: %w[OVB IKT VVOOVB]) } + let!(:segment1) { create(:segment, airline: 'S7', segment_number: '5258', origin_iata: 'UUS', destination_iata: 'OVB', std: Time.parse('2024-01-01 22:45:00 UTC'), sta: Time.parse('2024-01-02 05:00:00 UTC')) } + let!(:segment2) { create(:segment, airline: 'S7', segment_number: '2508', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-02 05:35:00 UTC'), sta: Time.parse('2024-01-02 10:05:00 UTC')) } + let!(:segment3) { create(:segment, airline: 'S7', segment_number: '6384', origin_iata: 'UUS', destination_iata: 'IKT', std: Time.parse('2024-01-04 22:35:00 UTC'), sta: Time.parse('2024-01-05 02:55:00 UTC')) } + let!(:segment4) { create(:segment, airline: 'S7', segment_number: '3020', origin_iata: 'IKT', destination_iata: 'DME', std: Time.parse('2024-01-05 04:05:00 UTC'), sta: Time.parse('2024-01-05 10:35:00 UTC')) } + let!(:segment5) { create(:segment, airline: 'S7', segment_number: '6202', origin_iata: 'UUS', destination_iata: 'VVO', std: Time.parse('2024-01-06 11:05:00 UTC'), sta: Time.parse('2024-01-06 12:55:00 UTC')) } + let!(:segment56) { create(:segment, airline: 'S7', segment_number: '5202', origin_iata: 'VVO', destination_iata: 'OVB', std: Time.parse('2024-01-06 20:05:00 UTC'), sta: Time.parse('2024-01-07 06:05:00 UTC')) } + let!(:segment6) { create(:segment, airline: 'S7', segment_number: '2502', origin_iata: 'OVB', destination_iata: 'DME', std: Time.parse('2024-01-06 23:20:00 UTC'), sta: Time.parse('2024-01-07 03:55:00 UTC')) } + + let(:parameters) { + { + "carrier" => "S7", + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_from" => "2024-01-01", + "departure_to" => "2024-01-07" + } + } + + let(:response) { + [ + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T22:45:00.000Z", + "arrival_time" => "2024-01-02T10:05:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-01T22:45:00.000Z", + "sta" => "2024-01-02T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2508", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-02T05:35:00.000Z", + "sta" => "2024-01-02T10:05:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-01T22:45:00.000Z", + "arrival_time" => "2024-01-07T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "5258", + "origin_iata" => "UUS", + "destination_iata" => "OVB", + "std" => "2024-01-01T22:45:00.000Z", + "sta" => "2024-01-02T05:00:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-06T23:20:00.000Z", + "sta" => "2024-01-07T03:55:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-04T22:35:00.000Z", + "arrival_time" => "2024-01-05T10:35:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6384", + "origin_iata" => "UUS", + "destination_iata" => "IKT", + "std" => "2024-01-04T22:35:00.000Z", + "sta" => "2024-01-05T02:55:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "3020", + "origin_iata" => "IKT", + "destination_iata" => "DME", + "std" => "2024-01-05T04:05:00.000Z", + "sta" => "2024-01-05T10:35:00.000Z" + } + ] + }, + { + "origin_iata" => "UUS", + "destination_iata" => "DME", + "departure_time" => "2024-01-06T11:05:00.000Z", + "arrival_time" => "2024-01-07T03:55:00.000Z", + "segments" => [ + { + "carrier" => "S7", + "segment_number" => "6202", + "origin_iata" => "UUS", + "destination_iata" => "VVO", + "std" => "2024-01-06T11:05:00.000Z", + "sta" => "2024-01-06T12:55:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "5202", + "origin_iata" => "VVO", + "destination_iata" => "OVB", + "std" => "2024-01-06T20:05:00.000Z", + "sta" => "2024-01-07T06:05:00.000Z" + }, + { + "carrier" => "S7", + "segment_number" => "2502", + "origin_iata" => "OVB", + "destination_iata" => "DME", + "std" => "2024-01-06T23:20:00.000Z", + "sta" => "2024-01-07T03:55:00.000Z" + } + ] + } + ] + } + + it 'return available routes' do + expect(subject).to eq response + end + end + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 79811b8..8434957 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -23,7 +23,7 @@ # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. # -# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } +Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } # Ensures that the test database schema matches the current schema file. # If there are pending migrations it will invoke `db:test:prepare` to diff --git a/spec/support/factory_bot.rb b/spec/support/factory_bot.rb new file mode 100644 index 0000000..c7890e4 --- /dev/null +++ b/spec/support/factory_bot.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods +end