diff options
56 files changed, 1953 insertions, 140 deletions
@@ -143,6 +143,8 @@ gem 'puma', '~> 3.10.0' gem 'newrelic_rpm' gem 'letter_opener' +gem 'gtfs' + group :development do gem 'capistrano', '2.13.5' gem 'capistrano-ext' diff --git a/Gemfile.lock b/Gemfile.lock index 0c1243593..4fb77eeb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -258,6 +258,10 @@ GEM google-analytics-rails (1.1.0) gretel (3.0.9) rails (>= 3.1.0) + gtfs (0.2.5) + multi_json + rake + rubyzip (~> 1.1) has_scope (0.7.0) actionpack (>= 4.1, < 5.1) activesupport (>= 4.1, < 5.1) @@ -321,7 +325,7 @@ GEM minitest (5.11.3) money (6.10.1) i18n (>= 0.6.4, < 1.0) - multi_json (1.12.1) + multi_json (1.13.1) multi_test (0.1.2) multi_xml (0.6.0) multipart-post (2.0.0) @@ -420,7 +424,7 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.2.2) rake - rake (12.3.0) + rake (12.3.1) ransack (1.8.3) actionpack (>= 3.0) activerecord (>= 3.0) @@ -633,6 +637,7 @@ DEPENDENCIES georuby-ext (= 0.0.5) google-analytics-rails gretel + gtfs has_array_of! htmlbeautifier i18n-js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 6a79f7e8e..3eaade37f 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -25,3 +25,9 @@ //= require "i18n" //= require "i18n/extended" //= require "i18n/translations" + +$(document).ready(function() { + $('a[disabled=disabled]').click(function(event){ + event.preventDefault(); // Prevent link from following its href + }); +}); diff --git a/app/assets/stylesheets/components/_referential_overview.sass b/app/assets/stylesheets/components/_referential_overview.sass index 7a0cc98c5..cf440b22c 100644 --- a/app/assets/stylesheets/components/_referential_overview.sass +++ b/app/assets/stylesheets/components/_referential_overview.sass @@ -24,6 +24,7 @@ .time-travel padding-top: 3px padding-bottom: 4px + height: 43px a.btn:first-child margin-right: 1px a.btn:last-child diff --git a/app/controllers/api/v1/netex_imports_controller.rb b/app/controllers/api/v1/netex_imports_controller.rb index 2654fa088..186ddc35c 100644 --- a/app/controllers/api/v1/netex_imports_controller.rb +++ b/app/controllers/api/v1/netex_imports_controller.rb @@ -25,56 +25,19 @@ module Api def create_models find_workbench - create_referential create_netex_import end def create_netex_import attributes = netex_import_params.merge creator: "Webservice" - - attributes = attributes.merge referential_id: @new_referential.id - @netex_import = Import::Netex.new attributes @netex_import.save! - - unless @netex_import.referential - Rails.logger.info "Can't create referential for import #{@netex_import.id}: #{@new_referential.inspect} #{@new_referential.metadatas.inspect} #{@new_referential.errors.full_messages}" - @netex_import.messages.create criticity: :error, message_key: "referential_creation" - end + @netex_import.create_referential! rescue ActiveRecord::RecordInvalid render json: {errors: @netex_import.errors}, status: 406 finish_action! end - def create_referential - @new_referential = - Referential.new( - name: netex_import_params['name'], - organisation_id: @workbench.organisation_id, - workbench_id: @workbench.id, - metadatas: [metadata] - ) - @new_referential.save - end - - def metadata - metadata = ReferentialMetadata.new - - if netex_import_params['file'] - netex_file = STIF::NetexFile.new(netex_import_params['file'].to_io) - frame = netex_file.frames.first - - if frame - metadata.periodes = frame.periods - - line_objectids = frame.line_refs.map { |ref| "STIF:CODIFLIGNE:Line:#{ref}" } - metadata.line_ids = @workbench.lines.where(objectid: line_objectids).pluck(:id) - end - end - - metadata - end - def netex_import_params params .require('netex_import') diff --git a/app/controllers/purchase_windows_controller.rb b/app/controllers/purchase_windows_controller.rb index 293a7d8e4..cf73d0ed1 100644 --- a/app/controllers/purchase_windows_controller.rb +++ b/app/controllers/purchase_windows_controller.rb @@ -63,7 +63,9 @@ class PurchaseWindowsController < ChouetteController def ransack_contains_date date =[] - if params[:q] && params[:q]['contains_date(1i)'].present? + if params[:q] && params[:q]['contains_date'].present? + params[:q]['contains_date'] = @date = params[:q]['contains_date'].to_date + elsif params[:q] && params[:q]['contains_date(1i)'].present? ['contains_date(1i)', 'contains_date(2i)', 'contains_date(3i)'].each do |key| date << params[:q][key].to_i params[:q].delete(key) diff --git a/app/controllers/time_tables_controller.rb b/app/controllers/time_tables_controller.rb index 0dcadad1e..2ac8532e0 100644 --- a/app/controllers/time_tables_controller.rb +++ b/app/controllers/time_tables_controller.rb @@ -128,12 +128,43 @@ class TimeTablesController < ChouetteController scope = self.ransack_period_range(scope: scope, error_message: t('referentials.errors.validity_period'), query: :overlapping) @q = scope.search(params[:q]) - if sort_column && sort_direction - @time_tables ||= @q.result(:distinct => true).order("#{sort_column} #{sort_direction}") - else - @time_tables ||= @q.result(:distinct => true).order(:comment) + @time_tables ||= begin + time_tables = @q.result(:distinct => true) + if sort_column == "bounding_dates" + time_tables = @q.result(:distinct => false).paginate(page: params[:page], per_page: 10) + ids = time_tables.pluck(:id).uniq + query = """ + WITH time_tables_dates AS( + SELECT time_tables.id, time_table_dates.date FROM time_tables + LEFT JOIN time_table_dates ON time_table_dates.time_table_id = time_tables.id + WHERE time_table_dates.in_out IS NULL OR time_table_dates.in_out = 't' + UNION + SELECT time_tables.id, time_table_periods.period_start FROM time_tables + LEFT JOIN time_table_periods ON time_table_periods.time_table_id = time_tables.id + ) + SELECT time_tables.id, MIN(time_tables_dates.date) AS min_date FROM time_tables + INNER JOIN time_tables_dates ON time_tables_dates.id = time_tables.id + WHERE time_tables.id IN (#{ids.map(&:to_s).join(',')}) + GROUP BY time_tables.id + ORDER BY min_date #{sort_direction} + """ + + ordered_ids = ActiveRecord::Base.connection.exec_query(query).map {|r| r["id"]} + order_by = ["CASE"] + ordered_ids.each_with_index do |id, index| + order_by << "WHEN id='#{id}' THEN #{index}" + end + order_by << "END" + time_tables = time_tables.order(order_by.join(" ")) + elsif sort_column == "vehicle_journeys_count" + time_tables = time_tables.joins("LEFT JOIN time_tables_vehicle_journeys ON time_tables_vehicle_journeys.time_table_id = time_tables.id LEFT JOIN vehicle_journeys ON vehicle_journeys.id = time_tables_vehicle_journeys.vehicle_journey_id")\ + .group("time_tables.id").select('time_tables.*, COUNT(vehicle_journeys.id) as vehicle_journeys_count').order("#{sort_column} #{sort_direction}") + else + time_tables = time_tables.order("#{sort_column} #{sort_direction}") + end + time_tables = time_tables.paginate(page: params[:page], per_page: 10) + time_tables end - @time_tables = @time_tables.paginate(page: params[:page], per_page: 10) end def select_time_tables @@ -155,7 +186,10 @@ class TimeTablesController < ChouetteController private def sort_column - referential.time_tables.column_names.include?(params[:sort]) ? params[:sort] : 'comment' + valid_cols = referential.time_tables.column_names + valid_cols << "bounding_dates" + valid_cols << "vehicle_journeys_count" + valid_cols.include?(params[:sort]) ? params[:sort] : 'comment' end def sort_direction %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc' diff --git a/app/controllers/vehicle_journeys_controller.rb b/app/controllers/vehicle_journeys_controller.rb index 821ea83ff..220f2d29e 100644 --- a/app/controllers/vehicle_journeys_controller.rb +++ b/app/controllers/vehicle_journeys_controller.rb @@ -154,7 +154,7 @@ class VehicleJourneysController < ChouetteController private def load_custom_fields - @custom_fields = referential.workgroup&.custom_fields_definitions || {} + @custom_fields = Chouette::VehicleJourney.custom_fields_definitions(referential.workgroup) @extra_headers = Rails.application.config.vehicle_journeys_extra_headers.dup.delete_if do |header| header[:type] == :custom_field and not @custom_fields.has_key?(header[:name].to_s) diff --git a/app/decorators/route_decorator.rb b/app/decorators/route_decorator.rb index 4a173cbb9..646bc1620 100644 --- a/app/decorators/route_decorator.rb +++ b/app/decorators/route_decorator.rb @@ -74,10 +74,11 @@ class RouteDecorator < AF83::Decorator instance_decorator.action_link( secondary: :show, policy: :create_opposite, - if: ->{h.has_feature?(:create_opposite_routes) && object.opposite_route.nil?} + if: ->{h.has_feature?(:create_opposite_routes)} ) do |l| l.content h.t('routes.create_opposite.title') l.method :post + l.disabled { object.opposite_route.present? } l.href do h.duplicate_referential_line_route_path( context[:referential], diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index 63125b161..e2aa2e9ea 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -402,9 +402,10 @@ module TableBuilderHelper content_tag( :li, link_to( - link.href, - method: link.method, - data: link.data + link.disabled ? '#' : link.href, + method: link.disabled ? nil : link.method, + data: link.data, + disabled: link.disabled ) do link.content end, diff --git a/app/javascript/routes/components/StopPoint.js b/app/javascript/routes/components/StopPoint.js index 368ec8261..768d069c0 100644 --- a/app/javascript/routes/components/StopPoint.js +++ b/app/javascript/routes/components/StopPoint.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types' import BSelect2 from './BSelect2' import OlMap from './OlMap' -import { defaultAttribute } from '../actions' +import { defaultAttribute } from '../actions' export default function StopPoint(props, {I18n}) { return ( @@ -42,13 +42,13 @@ export default function StopPoint(props, {I18n}) { <div className={'btn btn-link' + (props.first ? ' disabled' : '')} - onClick={props.onMoveUpClick} + onClick={props.first ? null : props.onMoveUpClick} > <span className='fa fa-arrow-up'></span> </div> <div className={'btn btn-link' + (props.last ? ' disabled' : '')} - onClick={props.onMoveDownClick} + onClick={props.last ? null : props.onMoveDownClick} > <span className='fa fa-arrow-down'></span> </div> diff --git a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js index 6524f8d6f..b02c19a69 100644 --- a/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js +++ b/app/javascript/vehicle_journeys/reducers/vehicleJourneys.js @@ -9,32 +9,35 @@ const vehicleJourney= (state = {}, action, keep) => { return _.assign({}, state, {selected: false}) case 'ADD_VEHICLEJOURNEY': let pristineVjasList = [] - let prevSp = action.stopPointsList[0] + let prevSp let current_time = { hour: 0, minute: 0 } let computeSchedule = false - let userTZOffet = 0 + let initTZOffet = 0 if(action.data["start_time.hour"] && action.data["start_time.hour"].value && action.data["start_time.hour"].value.length > 0 && action.data["start_time.minute"] && action.selectedJourneyPattern.full_schedule && action.selectedJourneyPattern.costs){ computeSchedule = true - userTZOffet = action.data["tz_offset"] && parseInt(action.data["tz_offset"].value) || 0 - current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(userTZOffet / 60) + initTZOffet = - action.stopPointsList[0].time_zone_offset / 60 || 0 + current_time.hour = parseInt(action.data["start_time.hour"].value) + parseInt(initTZOffet / 60) current_time.minute = 0 if(action.data["start_time.minute"].value){ - current_time.minute = parseInt(action.data["start_time.minute"].value) + (userTZOffet - 60 * parseInt(userTZOffet / 60)) + current_time.minute = parseInt(action.data["start_time.minute"].value) + (initTZOffet - 60 * parseInt(initTZOffet / 60)) } } _.each(action.stopPointsList, (sp) =>{ let inJourney = false let newVjas if(computeSchedule){ - if(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){ + if(prevSp && action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id]){ let delta = parseInt(action.selectedJourneyPattern.costs[prevSp.stop_area_id + "-" + sp.stop_area_id].time) current_time = actions.addMinutesToTime(current_time, delta) prevSp = sp inJourney = true } + if(!prevSp){ + prevSp = sp + } let offsetHours = sp.time_zone_offset / 3600 let offsetminutes = sp.time_zone_offset/60 - 60*offsetHours newVjas = { diff --git a/app/models/chouette/company.rb b/app/models/chouette/company.rb index cb2266a3d..9d5737a6c 100644 --- a/app/models/chouette/company.rb +++ b/app/models/chouette/company.rb @@ -9,7 +9,7 @@ module Chouette has_many :lines - validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true + # validates_format_of :registration_number, :with => %r{\A[0-9A-Za-z_-]+\Z}, :allow_nil => true, :allow_blank => true validates_presence_of :name validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true diff --git a/app/models/chouette/line.rb b/app/models/chouette/line.rb index 51851fc2e..4b5d1a68d 100644 --- a/app/models/chouette/line.rb +++ b/app/models/chouette/line.rb @@ -29,7 +29,7 @@ module Chouette # validates_presence_of :network # validates_presence_of :company - validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true + # validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true validates_format_of :stable_id, :with => %r{\A[\d\w_\-]+\Z}, :allow_nil => true, :allow_blank => true validates_format_of :url, :with => %r{\Ahttps?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?\Z}, :allow_nil => true, :allow_blank => true validates_format_of :color, :with => %r{\A[0-9a-fA-F]{6}\Z}, :allow_nil => true, :allow_blank => true diff --git a/app/models/chouette/stop_area.rb b/app/models/chouette/stop_area.rb index 9f28b7ee6..4ddc7403b 100644 --- a/app/models/chouette/stop_area.rb +++ b/app/models/chouette/stop_area.rb @@ -33,7 +33,7 @@ module Chouette after_update :journey_patterns_control_route_sections, if: Proc.new { |stop_area| ['boarding_position', 'quay'].include? stop_area.stop_area_type } - validates_format_of :registration_number, :with => %r{\A[\d\w_\-]+\Z}, :allow_blank => true + # validates_format_of :registration_number, :with => %r{\A[\d\w_:\-]+\Z}, :allow_blank => true validates_presence_of :name validates_presence_of :kind validates_presence_of :latitude, :if => :longitude diff --git a/app/models/concerns/application_days_support.rb b/app/models/concerns/application_days_support.rb index 2d00b5847..6086d9580 100644 --- a/app/models/concerns/application_days_support.rb +++ b/app/models/concerns/application_days_support.rb @@ -10,8 +10,10 @@ module ApplicationDaysSupport SUNDAY = 256 EVERYDAY = MONDAY | TUESDAY | WEDNESDAY | THURSDAY | FRIDAY | SATURDAY | SUNDAY + ALL_DAYS = %w(monday tuesday wednesday thursday friday saturday sunday).freeze + def display_day_types - %w(monday tuesday wednesday thursday friday saturday sunday).select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ') + ALL_DAYS.select{ |d| self.send(d) }.map{ |d| self.human_attribute_name(d).first(2)}.join(', ') end def day_by_mask(flag) @@ -39,6 +41,10 @@ module ApplicationDaysSupport def self.day_by_mask(int_day_types,flag) int_day_types & flag == flag end + + def self.all_days + ALL_DAYS + end end def valid_days diff --git a/app/models/concerns/custom_fields_support.rb b/app/models/concerns/custom_fields_support.rb index 017f496a8..46fc8e73d 100644 --- a/app/models/concerns/custom_fields_support.rb +++ b/app/models/concerns/custom_fields_support.rb @@ -11,6 +11,10 @@ module CustomFieldsSupport fields end + def self.custom_fields_definitions workgroup=:all + Hash[*custom_fields(workgroup).map{|cf| [cf.code, cf]}.flatten] + end + def method_missing method_name, *args if method_name =~ /custom_field_*/ && method_name.to_sym != :custom_field_values && !@custom_fields_initialized initialize_custom_fields @@ -33,6 +37,7 @@ module CustomFieldsSupport end def initialize_custom_fields + return unless self.attributes.has_key?("custom_field_values") self.custom_field_values ||= {} custom_fields(:all).values.each &:initialize_custom_field custom_fields(:all).each do |k, v| diff --git a/app/models/concerns/iev_interfaces/task.rb b/app/models/concerns/iev_interfaces/task.rb index bc78ff28c..f052b3a8f 100644 --- a/app/models/concerns/iev_interfaces/task.rb +++ b/app/models/concerns/iev_interfaces/task.rb @@ -25,7 +25,7 @@ module IevInterfaces::Task scope :blocked, -> { where('created_at < ? AND status = ?', 4.hours.ago, 'running') } - before_create :initialize_fields + before_save :initialize_fields, on: :create after_save :notify_parent end diff --git a/app/models/import/base.rb b/app/models/import/base.rb index 82494b1dc..f98e359d4 100644 --- a/app/models/import/base.rb +++ b/app/models/import/base.rb @@ -41,7 +41,7 @@ class Import::Base < ApplicationModel def initialize_fields super - self.token_download = SecureRandom.urlsafe_base64 + self.token_download ||= SecureRandom.urlsafe_base64 end end diff --git a/app/models/import/gtfs.rb b/app/models/import/gtfs.rb index 03cf49e60..70f448132 100644 --- a/app/models/import/gtfs.rb +++ b/app/models/import/gtfs.rb @@ -1,36 +1,296 @@ -require 'net/http' class Import::Gtfs < Import::Base - before_destroy :destroy_non_ready_referential + after_commit :launch_worker, :on => :create - after_commit :launch_java_import, on: :create - before_save def abort_unless_referential - self.status = 'aborted' unless referential + def launch_worker + GtfsImportWorker.perform_async id end - def launch_java_import - return if self.class.finished_statuses.include?(status) - threaded_call_boiv_iev + def import + update status: 'running', started_at: Time.now + + import_without_status + update status: 'successful', ended_at: Time.now + rescue Exception => e + update status: 'failed', ended_at: Time.now + Rails.logger.error "Error in GTFS import: #{e} #{e.backtrace.join('\n')}" + ensure + notify_parent + referential&.update ready: true + end + + def self.accept_file?(file) + Zip::File.open(file) do |zip_file| + zip_file.glob('agency.txt').size == 1 + end + rescue Exception => e + Rails.logger.debug "Error in testing GTFS file: #{e}" + return false + end + + def create_referential + self.referential ||= Referential.create!( + name: "GTFS Import", + organisation_id: workbench.organisation_id, + workbench_id: workbench.id, + metadatas: [referential_metadata] + ) + end + + def referential_metadata + registration_numbers = source.routes.map(&:id) + line_ids = line_referential.lines.where(registration_number: registration_numbers).pluck(:id) + + start_dates, end_dates = source.calendars.map { |c| [c.start_date, c.end_date ] }.transpose + excluded_dates = source.calendar_dates.select { |d| d.exception_type == "2" }.map(&:date) + + min_date = Date.parse (start_dates + [excluded_dates.min]).compact.min + max_date = Date.parse (end_dates + [excluded_dates.max]).compact.max + + ReferentialMetadata.new line_ids: line_ids, periodes: [min_date..max_date] + end + + attr_accessor :local_file + def local_file + @local_file ||= download_local_file end - private + attr_accessor :download_host + def download_host + @download_host ||= Rails.application.config.rails_host.gsub("http://","") + end - def destroy_non_ready_referential - if referential && !referential.ready - referential.destroy + def local_temp_directory + Rails.application.config.try(:import_temporary_directory) || + Rails.root.join('tmp', 'imports') + end + + def local_temp_file(&block) + Tempfile.open("chouette-import", local_temp_directory) do |file| + file.binmode + yield file end end - def threaded_call_boiv_iev - Thread.new(&method(:call_boiv_iev)) + def download_path + Rails.application.routes.url_helpers.download_workbench_import_path(workbench, id, token: token_download) end - def call_boiv_iev - Rails.logger.error("Begin IEV call for import") - Net::HTTP.get(URI("#{Rails.configuration.iev_url}/boiv_iev/referentials/importer/new?id=#{id}")) - Rails.logger.error("End IEV call for import") - rescue Exception => e - logger.error "IEV server error : #{e.message}" - logger.error e.backtrace.inspect + def download_local_file + local_temp_file do |file| + begin + Net::HTTP.start(download_host) do |http| + http.request_get(download_path) do |response| + response.read_body do |segment| + file.write segment + end + end + end + ensure + file.close + end + + file.path + end + end + + def source + @source ||= ::GTFS::Source.build local_file + end + + delegate :line_referential, :stop_area_referential, to: :workbench + + def prepare_referential + import_agencies + import_stops + import_routes + + create_referential + referential.switch + end + + def import_without_status + prepare_referential + + import_calendars + import_trips + import_stop_times + end + + def import_agencies + Chouette::Company.transaction do + source.agencies.each do |agency| + company = line_referential.companies.find_or_initialize_by(registration_number: agency.id) + company.attributes = { name: agency.name } + + save_model company + end + end + end + + def import_stops + Chouette::StopArea.transaction do + source.stops.each do |stop| + stop_area = stop_area_referential.stop_areas.find_or_initialize_by(registration_number: stop.id) + + stop_area.name = stop.name + stop_area.area_type = stop.location_type == "1" ? "zdlp" : "zdep" + stop_area.parent = stop_area_referential.stop_areas.find_by!(registration_number: stop.parent_station) if stop.parent_station.present? + stop_area.latitude, stop_area.longitude = stop.lat, stop.lon + stop_area.kind = "commercial" + + # TODO correct default timezone + + save_model stop_area + end + end + end + + def import_routes + Chouette::Line.transaction do + source.routes.each do |route| + line = line_referential.lines.find_or_initialize_by(registration_number: route.id) + line.name = route.long_name.presence || route.short_name + line.number = route.short_name + line.published_name = route.long_name + + line.company = line_referential.companies.find_by(registration_number: route.agency_id) if route.agency_id.present? + + # TODO transport mode + + line.comment = route.desc + + # TODO colors + + line.url = route.url + + save_model line + end + end + end + + def vehicle_journey_by_trip_id + @vehicle_journey_by_trip_id ||= {} + end + + def import_trips + source.trips.each_slice(100) do |slice| + slice.each do |trip| + Chouette::Route.transaction do + line = line_referential.lines.find_by registration_number: trip.route_id + + route = referential.routes.build line: line + route.wayback = (trip.direction_id == "0" ? :outbound : :inbound) + # TODO better name ? + name = route.published_name = trip.short_name.presence || trip.headsign.presence || route.wayback.to_s.capitalize + route.name = name + save_model route + + journey_pattern = route.journey_patterns.build name: name + save_model journey_pattern + + vehicle_journey = journey_pattern.vehicle_journeys.build route: route + vehicle_journey.published_journey_name = trip.headsign.presence || trip.id + save_model vehicle_journey + + time_table = referential.time_tables.find_by(id: time_tables_by_service_id[trip.service_id]) if time_tables_by_service_id[trip.service_id] + if time_table + vehicle_journey.time_tables << time_table + else + messages.create! criticity: "warning", message_key: "gtfs.trips.unkown_service_id", message_attributes: {service_id: trip.service_id} + end + + vehicle_journey_by_trip_id[trip.id] = vehicle_journey.id + end + end + end + end + + def import_stop_times + source.stop_times.group_by(&:trip_id).each_slice(50) do |slice| + slice.each do |trip_id, stop_times| + Chouette::VehicleJourneyAtStop.transaction do + vehicle_journey = referential.vehicle_journeys.find vehicle_journey_by_trip_id[trip_id] + journey_pattern = vehicle_journey.journey_pattern + route = journey_pattern.route + + stop_times.sort_by! { |s| s.stop_sequence.to_i } + + stop_times.each do |stop_time| + stop_area = stop_area_referential.stop_areas.find_by(registration_number: stop_time.stop_id) + + stop_point = route.stop_points.build stop_area: stop_area + save_model stop_point + + journey_pattern.stop_points << stop_point + + # JourneyPattern#vjas_add creates automaticaly VehicleJourneyAtStop + vehicle_journey_at_stop = journey_pattern.vehicle_journey_at_stops.find_by(stop_point_id: stop_point.id) + + departure_time = GTFS::Time.parse(stop_time.departure_time) + arrival_time = GTFS::Time.parse(stop_time.arrival_time) + + vehicle_journey_at_stop.departure_time = departure_time.time + vehicle_journey_at_stop.arrival_time = arrival_time.time + vehicle_journey_at_stop.departure_day_offset = departure_time.day_offset + vehicle_journey_at_stop.arrival_day_offset = arrival_time.day_offset + + # TODO offset + + save_model vehicle_journey_at_stop + end + end + end + end + end + + def time_tables_by_service_id + @time_tables_by_service_id ||= {} + end + + def import_calendars + source.calendars.each_slice(500) do |slice| + Chouette::TimeTable.transaction do + slice.each do |calendar| + time_table = referential.time_tables.build comment: "Calendar #{calendar.service_id}" + Chouette::TimeTable.all_days.each do |day| + time_table.send("#{day}=", calendar.send(day)) + end + time_table.periods.build period_start: calendar.start_date, period_end: calendar.end_date + + save_model time_table + + time_tables_by_service_id[calendar.service_id] = time_table.id + end + end + end + end + + def import_calendar_dates + source.calendar_dates.each_slice(500) do |slice| + Chouette::TimeTable.transaction do + slice.each do |calendar_date| + time_table = referential.time_tables.find time_tables_by_service_id[calendar_date.service_id] + date = time_table.dates.build date: Date.parse(calendar_date.date), in_out: calendar_date.exception_type == "1" + + save_model date + end + end + end + end + + def save_model(model) + unless model.save + Rails.logger.info "Can't save #{model.class.name} : #{model.errors.inspect}" + raise ActiveRecord::RecordNotSaved.new("Invalid #{model.class.name} : #{model.errors.inspect}") + end + Rails.logger.debug "Created #{model.inspect}" + end + + def notify_parent + return unless parent.present? + return if notified_parent_at + parent.child_change + update_column :notified_parent_at, Time.now end end diff --git a/app/models/import/netex.rb b/app/models/import/netex.rb index 2b0982229..93604c5f9 100644 --- a/app/models/import/netex.rb +++ b/app/models/import/netex.rb @@ -10,6 +10,26 @@ class Import::Netex < Import::Base validates_presence_of :parent + def create_referential! + self.referential = + Referential.new( + name: self.name, + organisation_id: workbench.organisation_id, + workbench_id: workbench.id, + metadatas: [referential_metadata] + ) + self.referential.save + unless self.referential.valid? + Rails.logger.info "Can't create referential for import #{self.id}: #{referential.inspect} #{referential.metadatas.inspect} #{referential.errors.messages}" + if referential.metadatas.all?{|m| m.line_ids.empty?} + parent.messages.create criticity: :error, message_key: "referential_creation_missing_lines", message_attributes: {referential_name: referential.name} + else + parent.messages.create criticity: :error, message_key: "referential_creation", message_attributes: {referential_name: referential.name} + end + end + save! + end + private def iev_callback_url @@ -21,4 +41,22 @@ class Import::Netex < Import::Base referential.destroy end end + + def referential_metadata + metadata = ReferentialMetadata.new + + if self.file + netex_file = STIF::NetexFile.new(self.file.path) + frame = netex_file.frames.first + + if frame + metadata.periodes = frame.periods + + line_objectids = frame.line_refs.map { |ref| "STIF:CODIFLIGNE:Line:#{ref}" } + metadata.line_ids = workbench.lines.where(objectid: line_objectids).pluck(:id) + end + end + + metadata + end end diff --git a/app/models/import/workbench.rb b/app/models/import/workbench.rb index f6e15cb89..124b9b0d8 100644 --- a/app/models/import/workbench.rb +++ b/app/models/import/workbench.rb @@ -2,6 +2,25 @@ class Import::Workbench < Import::Base after_commit :launch_worker, :on => :create def launch_worker - WorkbenchImportWorker.perform_async(id) + unless Import::Gtfs.accept_file?(file.path) + WorkbenchImportWorker.perform_async(id) + else + import_gtfs + end + end + + def import_gtfs + update_column :status, 'running' + update_column :started_at, Time.now + + Import::Gtfs.create! parent_id: self.id, workbench: workbench, file: File.new(file.path), name: "Import GTFS", creator: "Web service" + + update_column :status, 'successful' + update_column :ended_at, Time.now + rescue Exception => e + Rails.logger.error "Error while processing GTFS file: #{e}" + + update_column :status, 'failed' + update_column :ended_at, Time.now end end diff --git a/app/models/referential.rb b/app/models/referential.rb index 3304108d0..1794126a2 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -168,6 +168,10 @@ class Referential < ApplicationModel Chouette::TimeTable.all end + def time_table_dates + Chouette::TimeTableDate.all + end + def timebands Chouette::Timeband.all end @@ -184,6 +188,10 @@ class Referential < ApplicationModel Chouette::VehicleJourneyFrequency.all end + def vehicle_journey_at_stops + Chouette::VehicleJourneyAtStop.all + end + def routing_constraint_zones Chouette::RoutingConstraintZone.all end diff --git a/app/models/user.rb b/app/models/user.rb index 29148d9e9..ba166b06f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,7 +9,7 @@ class User < ApplicationModel :recoverable, :rememberable, :trackable, :async, authentication_type # FIXME https://github.com/nbudin/devise_cas_authenticatable/issues/53 - # Work around :validatable, when database_authenticatable is diabled. + # Work around :validatable, when database_authenticatable is disabled. attr_accessor :password unless authentication_type == :database_authenticatable # Setup accessible (or protected) attributes for your model diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb index 7166e6448..2402721fb 100644 --- a/app/services/zip_service.rb +++ b/app/services/zip_service.rb @@ -1,8 +1,8 @@ class ZipService - class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines) + class Subdir < Struct.new(:name, :stream, :spurious, :foreign_lines, :missing_calendar, :wrong_calendar) def ok? - foreign_lines.empty? && spurious.empty? + foreign_lines.empty? && spurious.empty? && !missing_calendar && !wrong_calendar end end @@ -38,8 +38,21 @@ class ZipService add_to_current_output entry end + def validate entry + if is_calendar_file?(entry.name) + @current_calendar_is_missing = false + if wrong_calendar_data?(entry) + @current_calendar_is_wrong = true + return false + end + end + return false if is_spurious?(entry.name) + return false if is_foreign_line?(entry.name) + true + end + def add_to_current_output entry - return if is_spurious!(entry.name) || is_foreign_line!(entry.name) + return unless validate(entry) current_output.put_next_entry entry.name write_to_current_output entry.get_input_stream @@ -48,7 +61,7 @@ class ZipService def write_to_current_output input_stream # the condition below is true for directory entries return if Zip::NullInputStream == input_stream - current_output.write input_stream.read + current_output.write input_stream.read end def finish_current_output @@ -58,7 +71,9 @@ class ZipService # Second part of the solution, yield the closed stream current_output.close_buffer, current_spurious.to_a, - foreign_lines) + foreign_lines, + @current_calendar_is_missing, + @current_calendar_is_wrong) end end @@ -68,6 +83,8 @@ class ZipService @current_output = Zip::OutputStream.new(StringIO.new(''), true, nil) @current_spurious = Set.new @foreign_lines = [] + @current_calendar_is_missing = true + @current_calendar_is_wrong = false end def entry_key entry @@ -75,7 +92,7 @@ class ZipService entry.name.split('/').first end - def is_spurious! entry_name + def is_spurious? entry_name segments = entry_name.split('/', 3) return false if segments.size < 3 @@ -83,11 +100,25 @@ class ZipService return true end - def is_foreign_line! entry_name + def is_foreign_line? entry_name STIF::NetexFile::Frame.get_short_id(entry_name).tap do | line_object_id | return nil unless line_object_id return nil if line_object_id.in? allowed_lines foreign_lines << line_object_id end end + + def is_calendar_file? entry_name + entry_name =~ /calendriers.xml$/ + end + + def wrong_calendar_data? entry + content = entry.get_input_stream.read + period = STIF::NetexFile::Frame.parse_calendars content.to_s + return true unless period + return true unless period.first + return true unless period.end + return true unless period.first <= period.end + false + end end diff --git a/app/views/dashboards/_dashboard.html.slim b/app/views/dashboards/_dashboard.html.slim index e1be3df4a..466695b5a 100644 --- a/app/views/dashboards/_dashboard.html.slim +++ b/app/views/dashboards/_dashboard.html.slim @@ -6,7 +6,7 @@ h3.panel-title.with_actions div = link_to t('dashboards.workbench.title', organisation: workbench.organisation.name), workbench_path(workbench) - span.badge.ml-xs = workbench.referentials.count if workbench.referentials.present? + span.badge.ml-xs = workbench.all_referentials.uniq.count if workbench.all_referentials.present? div = link_to '', workbench_path(workbench), class: ' fa fa-chevron-right pull-right', title: t('workbenches.index.offers.see') diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 96bb5bb0d..9e1ae6d6f 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -6,7 +6,7 @@ .row .col-lg-6.col-md-6.col-sm-12.col-xs-12 = definition_list t('metadatas'), - { t('id_codif') => @line.get_objectid.short_id, + { t('objectid') => @line.get_objectid.short_id, @line.human_attribute_name(:deactivated) => (@line.deactivated? ? t('false') : t('true')), @line.human_attribute_name(:network_id) => (@line.network.nil? ? t('lines.index.unset') : @line.network.name), @line.human_attribute_name(:company_id) => (@line.company.nil? ? t('lines.index.unset') : @line.company.name), diff --git a/app/views/shared/iev_interfaces/_messages.html.slim b/app/views/shared/iev_interfaces/_messages.html.slim index 14157a88d..4e2c4d849 100644 --- a/app/views/shared/iev_interfaces/_messages.html.slim +++ b/app/views/shared/iev_interfaces/_messages.html.slim @@ -1,14 +1,15 @@ - if messages.any? ul.list-unstyled.import_message-list - messages.order(:created_at).each do | message | + - width = message.resource_attributes.present? ? 6 : 12 li .row class=bootstrap_class_for_message_criticity(message.criticity) - if message.message_attributes && message.message_attributes["line"] .col-md-1= "L. #{message.message_attributes["line"]}" - .col-md-5= export_message_content message + div class="col-md-#{width-1}"= export_message_content message - else - .col-md-6= export_message_content message + div class="col-md-#{width}"= export_message_content message .col-md-6 - - if message.resource_attributes + - if message.resource_attributes.present? pre = JSON.pretty_generate message.resource_attributes || {} diff --git a/app/views/time_tables/index.html.slim b/app/views/time_tables/index.html.slim index 6913712a0..58bf66a9d 100644 --- a/app/views/time_tables/index.html.slim +++ b/app/views/time_tables/index.html.slim @@ -28,14 +28,14 @@ end \ ), \ TableBuilderHelper::Column.new( \ + key: :bounding_dates, \ name: "Période englobante", \ - attribute: Proc.new { |tt| tt.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.bounding_dates.min), end: l(tt.bounding_dates.max)) }, \ - sortable: false \ + attribute: Proc.new { |tt| tt.object.bounding_dates.empty? ? '-' : t('bounding_dates', debut: l(tt.object.bounding_dates.min), end: l(tt.object.bounding_dates.max)) }, \ ), \ TableBuilderHelper::Column.new( \ + key: :vehicle_journeys_count, \ name: "Nombre de courses associées", \ attribute: Proc.new{ |tt| tt.vehicle_journeys.count }, \ - sortable: false \ ), \ TableBuilderHelper::Column.new( \ name: "Journées d'application", \ diff --git a/app/workers/gtfs_import_worker.rb b/app/workers/gtfs_import_worker.rb new file mode 100644 index 000000000..02f5053b0 --- /dev/null +++ b/app/workers/gtfs_import_worker.rb @@ -0,0 +1,7 @@ +class GtfsImportWorker + include Sidekiq::Worker + + def perform(import_id) + Import::Gtfs.find(import_id).import + end +end diff --git a/app/workers/workbench_import_worker/object_state_updater.rb b/app/workers/workbench_import_worker/object_state_updater.rb index 67bdc0654..1edc6b9a1 100644 --- a/app/workers/workbench_import_worker/object_state_updater.rb +++ b/app/workers/workbench_import_worker/object_state_updater.rb @@ -6,9 +6,10 @@ class WorkbenchImportWorker workbench_import.update( total_steps: count ) update_spurious entry update_foreign_lines entry + update_missing_calendar entry + update_wrong_calendar entry end - private def update_foreign_lines entry @@ -19,7 +20,7 @@ class WorkbenchImportWorker message_attributes: { 'source_filename' => workbench_import.file.file.file, 'foreign_lines' => entry.foreign_lines.join(', ') - }) + }) end def update_spurious entry @@ -30,7 +31,27 @@ class WorkbenchImportWorker message_attributes: { 'source_filename' => workbench_import.file.file.file, 'spurious_dirs' => entry.spurious.join(', ') - }) + }) + end + + def update_missing_calendar entry + return unless entry.missing_calendar + workbench_import.messages.create( + criticity: :error, + message_key: 'missing_calendar_in_zip_file', + message_attributes: { + 'source_filename' => entry.name + }) + end + + def update_wrong_calendar entry + return unless entry.wrong_calendar + workbench_import.messages.create( + criticity: :error, + message_key: 'wrong_calendar_in_zip_file', + message_attributes: { + 'source_filename' => entry.name + }) end end end diff --git a/config/locales/import_messages.en.yml b/config/locales/import_messages.en.yml index bc06c46f0..9f370d319 100644 --- a/config/locales/import_messages.en.yml +++ b/config/locales/import_messages.en.yml @@ -2,7 +2,10 @@ en: import_message: corrupt_zip_file: "The zip file %{source_filename} is corrupted and cannot be read" inconsistent_zip_file: "The zip file %{source_filename} contains unexpected directories: %{spurious_dirs}, which are ignored" - referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" + missing_calendar_in_zip_file: "The folder %{source_filename} lacks a calendar file" + wrong_calendar_in_zip_file: "The calendar file %{source_filename} cannot be parsed, or contains inconsistant data" + referential_creation: "The referential %{referential_name} has not been created because another referential with the same lines and periods already exists" + referential_creation_missing_lines: "The referential %{referential_name} has not been created because no matching line has been found" 1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré" 1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur" 2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NETEX_COMMUN" diff --git a/config/locales/import_messages.fr.yml b/config/locales/import_messages.fr.yml index 1e5054648..5ecef68d1 100644 --- a/config/locales/import_messages.fr.yml +++ b/config/locales/import_messages.fr.yml @@ -2,10 +2,13 @@ fr: import_messages: corrupt_zip_file: "Le fichier zip est corrompu, et ne peut être lu" inconsistent_zip_file: "Le fichier zip contient des repertoires non prévus : %{spurious_dirs} qui seront ignorés" - referential_creation: "Le référentiel n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" + missing_calendar_in_zip_file: "Le dossier %{source_filename} ne contient pas de calendrier" + wrong_calendar_in_zip_file: "Le calendrier contenu dans %{source_filename} contient des données incorrectes ou incohérentes" + referential_creation: "Le référentiel %{referential_name} n'a pas pu être créé car un référentiel existe déjà sur les mêmes périodes et lignes" + referential_creation_missing_lines: "Le référentiel %{referential_name} n'a pas pu être créé car aucune ligne ne correspond" 1_netexstif_2: "Le fichier %{source_filename} ne respecte pas la syntaxe XML ou la XSD NeTEx : erreur '%{error_value}' rencontré" 1_netexstif_5: "%{source_filename}-Ligne %{source_line_number}-Colonne %{source_column_number} : l'objet %{source_label} d'identifiant %{source_objectid} a une date de mise à jour dans le futur" - 2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NETEX_COMMUN" + 2_netexstif_1_1: "Le fichier commun.xml ne contient pas de frame nommée NTEX_COMMUN" 2_netexstif_1_2: "Le fichier commun.xml contient une frame nommée %{error_value} non acceptée" 2_netexstif_2_1: "Le fichier calendriers.xml ne contient pas de frame nommée NETEX_CALENDRIER" 2_netexstif_2_2: "Le fichier calendriers.xml contient une frame nommée %{error_value} non acceptée" diff --git a/config/locales/lines.fr.yml b/config/locales/lines.fr.yml index 058238710..6f4a2e9bf 100644 --- a/config/locales/lines.fr.yml +++ b/config/locales/lines.fr.yml @@ -40,7 +40,7 @@ fr: deactivated: "Ligne désactivée" title: "Lignes" line: "Ligne %{line}" - name_or_number_or_objectid: "Recherche par nom, nom court ou ID Codif..." + name_or_number_or_objectid: "Recherche par nom, nom court ou ID..." no_networks: "Aucun réseaux" no_companies: "Aucun transporteurs" no_group_of_lines: "Aucun groupes de ligne" diff --git a/db/migrate/20180403100007_add_registration_number_indexes.rb b/db/migrate/20180403100007_add_registration_number_indexes.rb new file mode 100644 index 000000000..bc2d48329 --- /dev/null +++ b/db/migrate/20180403100007_add_registration_number_indexes.rb @@ -0,0 +1,7 @@ +class AddRegistrationNumberIndexes < ActiveRecord::Migration + def change + add_index :stop_areas, [:stop_area_referential_id, :registration_number], name: 'index_stop_areas_on_referential_id_and_registration_number' + add_index :lines, [:line_referential_id, :registration_number], name: 'index_lines_on_referential_id_and_registration_number' + add_index :companies, [:line_referential_id, :registration_number], name: 'index_companies_on_referential_id_and_registration_number' + end +end diff --git a/lib/af83/decorator/link.rb b/lib/af83/decorator/link.rb index ee09f80dc..9bf6c1ed9 100644 --- a/lib/af83/decorator/link.rb +++ b/lib/af83/decorator/link.rb @@ -85,6 +85,10 @@ class AF83::Decorator::Link in_group_for_action? :secondary end + def disabled? + !!disabled + end + def enabled? enabled = false if @options[:_if].nil? @@ -131,9 +135,9 @@ class AF83::Decorator::Link out[:class] = extra_class out.delete(:link_class) out.delete(:link_method) - out[:class] += " disabled" if disabled + out[:class] += " disabled" if disabled? out[:class].strip! - out[:disabled] = !!disabled + out[:disabled] = disabled? out end @@ -150,7 +154,7 @@ class AF83::Decorator::Link html_options ).to_html else - context.h.link_to content, href, html_options + context.h.link_to content, (disabled? ? "#" : href), html_options end end end diff --git a/lib/gtfs/time.rb b/lib/gtfs/time.rb new file mode 100644 index 000000000..49546532a --- /dev/null +++ b/lib/gtfs/time.rb @@ -0,0 +1,28 @@ +module GTFS + class Time + attr_reader :hours, :minutes, :seconds + def initialize(hours, minutes, seconds) + @hours, @minutes, @seconds = hours, minutes, seconds + end + + def real_hours + hours.modulo(24) + end + + def time + @time ||= ::Time.new(2000, 1, 1, real_hours, minutes, seconds, "+00:00") + end + + def day_offset + hours / 24 + end + + FORMAT = /(\d{1,2}):(\d{2}):(\d{2})/ + + def self.parse(definition) + if definition.to_s =~ FORMAT + new *[$1, $2, $3].map(&:to_i) + end + end + end +end diff --git a/lib/stif/netex_file.rb b/lib/stif/netex_file.rb index db0801bbe..1e78ca04a 100644 --- a/lib/stif/netex_file.rb +++ b/lib/stif/netex_file.rb @@ -47,6 +47,23 @@ module STIF base_name = File.basename(file_name) STIF::NetexFile::LINE_FILE_FORMAT.match(base_name).try(:[], 'line_object_id') end + + def parse_calendars calendars + # <netex:ValidBetween> + # <netex:FromDate>2017-03-01</netex:FromDate> + # <netex:ToDate>2017-03-31</netex:ToDate> + # </netex:ValidBetween> + xml = Nokogiri::XML(calendars) + from_date = nil + to_date = nil + xml.xpath("//netex:ValidBetween", "netex" => NetexFile::XML_NAME_SPACE).each do |valid_between| + from_date = valid_between.xpath("netex:FromDate").try :text + to_date = valid_between.xpath("netex:ToDate").try :text + end + from_date = from_date && Date.parse(from_date) + to_date = to_date && Date.parse(to_date) + Range.new from_date, to_date + end end attr_accessor :name @@ -56,16 +73,7 @@ module STIF end def parse_calendars(calendars) - # <netex:ValidBetween> - # <netex:FromDate>2017-03-01</netex:FromDate> - # <netex:ToDate>2017-03-31</netex:ToDate> - # </netex:ValidBetween> - xml = Nokogiri::XML(calendars) - xml.xpath("//netex:ValidBetween", "netex" => NetexFile::XML_NAME_SPACE).each do |valid_between| - from_date = valid_between.xpath("netex:FromDate").try :text - to_date = valid_between.xpath("netex:ToDate").try :text - periods << Range.new(Date.parse(from_date), Date.parse(to_date)) - end + periods << self.class.parse_calendars(calendars) end def add_offer_file(line_object_id) diff --git a/lib/tasks/ci.rake b/lib/tasks/ci.rake index d025a9bf1..cb9ed77e7 100644 --- a/lib/tasks/ci.rake +++ b/lib/tasks/ci.rake @@ -79,7 +79,7 @@ namespace :ci do sh "RAILS_ENV=test rake db:drop" # Restore projet config/database.yml - cp "config/database.yml.orig", "config/database.yml" if File.exists?("config/database.yml.orig") + # cp "config/database.yml.orig", "config/database.yml" if File.exists?("config/database.yml.orig") end end diff --git a/spec/controllers/routes_controller_spec.rb b/spec/controllers/routes_controller_spec.rb index b7cb66b46..59020914d 100644 --- a/spec/controllers/routes_controller_spec.rb +++ b/spec/controllers/routes_controller_spec.rb @@ -93,6 +93,9 @@ RSpec.describe RoutesController, type: :controller do end context "when opposite = true" do + before do + @positions = Hash[*route.stop_points.map{|sp| [sp.id, sp.position]}.flatten] + end it "creates a new route on the opposite way " do expect do post :duplicate, @@ -106,6 +109,9 @@ RSpec.describe RoutesController, type: :controller do expect(Chouette::Route.last.published_name).to eq(Chouette::Route.last.name) expect(Chouette::Route.last.opposite_route).to eq(route) expect(Chouette::Route.last.stop_area_ids).to eq route.stop_area_ids.reverse + route.reload.stop_points.each do |sp| + expect(sp.position).to eq @positions[sp.id] + end end end diff --git a/spec/fixtures/google-sample-feed.zip b/spec/fixtures/google-sample-feed.zip Binary files differnew file mode 100644 index 000000000..79819e21a --- /dev/null +++ b/spec/fixtures/google-sample-feed.zip diff --git a/spec/fixtures/multiple_with_wrong_calendar.zip b/spec/fixtures/multiple_with_wrong_calendar.zip Binary files differnew file mode 100644 index 000000000..85c0ec61d --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar.zip diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml new file mode 100644 index 000000000..bfbd0aea1 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/calendriers.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:netex="http://www.netex.org.uk/netex" + xmlns:siri="http://www.siri.org.uk/siri" xmlns:core="http://www.govtalk.gov.uk/core" + xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_CALENDRIER-1_20170214090012:LOC" + version="any"> + <netex:TypeOfFrameRef ref="NETEX_CALENDRIER"/> + <netex:ValidBetween> + <netex:FromDate>2017-03-01</netex:FromDate> + <netex:ToDate>2017-03-31</netex:ToDate> + </netex:ValidBetween> + <netex:members> + <netex:dayTypes> + <netex:DayType id="CITYWAY:DayType:1:LOC" version="any" > + <netex:Name>Semaine</netex:Name> + <netex:properties> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Monday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Tuesday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Wednesday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Thursday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Friday</netex:DaysOfWeek> + </netex:PropertyOfDay> + </netex:properties> + </netex:DayType> + <netex:DayType id="CITYWAY:DayType:2:LOC" version="any" > + <netex:Name>Fin de semaine</netex:Name> + <netex:properties> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Saturday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Sunday</netex:DaysOfWeek> + </netex:PropertyOfDay> + </netex:properties> + </netex:DayType> + <netex:DayType id="CITYWAY:DayType:3:LOC" version="any" > + <netex:Name>Service spécial</netex:Name> + </netex:DayType> + <netex:DayType id="CITYWAY:DayType:4:LOC" version="any" > + <netex:Name>Restriction</netex:Name> + </netex:DayType> + </netex:dayTypes> + <netex:dayTypeAssignments> + <netex:DayTypeAssignment version="any" > + <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> + <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC" version="any"/> + </netex:DayTypeAssignment> + <netex:DayTypeAssignment version="any" > + <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> + <netex:DayTypeRef ref="CITYWAY:DayType:2:LOC" version="any"/> + </netex:DayTypeAssignment> + <netex:DayTypeAssignment version="any" > + <netex:Date>2017-03-15</netex:Date> + <netex:DayTypeRef ref="CITYWAY:DayType:3:LOC" version="any"/> + <netex:isAvailable>true</netex:isAvailable> + </netex:DayTypeAssignment> + <netex:DayTypeAssignment version="any" > + <netex:Date>2017-03-15</netex:Date> + <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC" version="any"/> + <netex:isAvailable>false</netex:isAvailable> + </netex:DayTypeAssignment> + </netex:dayTypeAssignments> + <netex:operatingPeriods> + <netex:OperatingPeriod id="CITYWAY:OperatingPeriod:1:LOC" version="any" > + <netex:FromDate>2017-01-01</netex:FromDate> + <netex:ToDate>2017-12-31</netex:ToDate> + </netex:OperatingPeriod> + </netex:operatingPeriods> + </netex:members> + </netex:GeneralFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml new file mode 100644 index 000000000..266c8a598 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/commun.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:netex="http://www.netex.org.uk/netex" + xmlns:siri="http://www.siri.org.uk/siri" xmlns:core="http://www.govtalk.gov.uk/core" + xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_COMMUN-1_20170214090012:LOC" version="any"> + <netex:TypeOfFrameRef ref="NETEX_COMMUN"/> + <netex:members> + <netex:notices> + <netex:Notice id="CITYWAY:Notice:1:LOC" version="any"> + <netex:Text>Notice 1</netex:Text> + <netex:PublicCode>1</netex:PublicCode> + <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef> + </netex:Notice> + <netex:Notice id="CITYWAY:Notice:2:LOC" version="any"> + <netex:Text>Notice 2</netex:Text> + <netex:PublicCode>2</netex:PublicCode> + <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef> + </netex:Notice> + <netex:Notice id="CITYWAY:Notice:3:LOC" version="any"> + <netex:Text>Notice 3</netex:Text> + <netex:PublicCode>3</netex:PublicCode> + <netex:TypeOfNoticeRef>ServiceJourneyNotice</netex:TypeOfNoticeRef> + </netex:Notice> + </netex:notices> + </netex:members> + </netex:GeneralFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml new file mode 100644 index 000000000..832793036 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00108_9.xml @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" + xmlns:netex="http://www.netex.org.uk/netex" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2" + xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> + <netex:Name>Ligne 1</netex:Name> + <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> + <netex:frames> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" + version="any"> + <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> + <netex:members> + <netex:routes> + <netex:Route id="CITYWAY:Route:1:LOC" version="any"> + <netex:Name>route 1</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> + <netex:DirectionType>outbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + </netex:Route> + <netex:Route id="CITYWAY:Route:2:LOC" version="any"> + <netex:Name>route 2</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> + <netex:DirectionType>inbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + </netex:Route> + </netex:routes> + <netex:directions> + <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> + <netex:Name>Par ici</netex:Name> + </netex:Direction> + <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> + <netex:Name>Par là</netex:Name> + </netex:Direction> + </netex:directions> + <netex:serviceJourneyPatterns> + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" + version="any"> + <netex:Name>Par ici</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" + version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" + version="any"> + <netex:Name>Par là</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" + version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + </netex:serviceJourneyPatterns> + <netex:destinationDisplays> + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" + version="any"> + <netex:FrontText>Mission 1</netex:FrontText> + <netex:PublicCode>1234</netex:PublicCode> + </netex:DestinationDisplay> + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" + version="any"> + <netex:FrontText>Mission 2</netex:FrontText> + <netex:PublicCode>2345</netex:PublicCode> + </netex:DestinationDisplay> + </netex:destinationDisplays> + <netex:scheduledStopPoints> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" + version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" + version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" + version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" + version="any"/> + </netex:scheduledStopPoints> + <netex:passengerStopAssignments> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50009052:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50009053:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + </netex:passengerStopAssignments> + <netex:routingConstraintZones> + <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" + version="any"> + <netex:Name>ITL 1</netex:Name> + <netex:members> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + </netex:members> + <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> + </netex:RoutingConstraintZone> + </netex:routingConstraintZones> + </netex:members> + </netex:GeneralFrame> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" + version="any"> + <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> + <netex:members> + <netex:serviceJourneys> + <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> + <netex:Name>Course 1 par ici</netex:Name> + <netex:noticeAssignments> + <netex:NoticeAssignment> + <netex:NoticeRef ref="CITYWAY:Notice:1:LOC"> + version="any"</netex:NoticeRef> + </netex:NoticeAssignment> + </netex:noticeAssignments> + <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> + version="any"</netex:DayTypeRef> + <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" + version="any"/> + <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:011"> + version="any"</netex:OperatorRef> + <netex:trainNumbers> + <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> + </netex:trainNumbers> + <netex:passingTimes> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>01:01:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> + <netex:DepartureTime>01:01:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>0</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>01:05:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> + <netex:DepartureTime>01:05:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>0</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + </netex:passingTimes> + </netex:ServiceJourney> + </netex:serviceJourneys> + </netex:members> + </netex:GeneralFrame> + </netex:frames> + </netex:CompositeFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml new file mode 100644 index 000000000..9dff0d850 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122517/offre_C00109_10.xml @@ -0,0 +1,204 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" + xmlns:netex="http://www.netex.org.uk/netex" xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2" + xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> + <netex:Name>Ligne 1</netex:Name> + <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> + <netex:frames> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" + version="any"> + <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> + <netex:members> + <netex:routes> + <netex:Route id="CITYWAY:Route:1:LOC" version="any"> + <netex:Name>route 1</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> + <netex:DirectionType>outbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + </netex:Route> + <netex:Route id="CITYWAY:Route:2:LOC" version="any"> + <netex:Name>route 2</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> + <netex:DirectionType>inbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + </netex:Route> + </netex:routes> + <netex:directions> + <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> + <netex:Name>Par ici aussi</netex:Name> + </netex:Direction> + <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> + <netex:Name>Par là aussi</netex:Name> + </netex:Direction> + </netex:directions> + <netex:serviceJourneyPatterns> + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" + version="any"> + <netex:Name>Par ici itou</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" + version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" + version="any"> + <netex:Name>Par là itou</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" + version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern + id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + </netex:serviceJourneyPatterns> + <netex:destinationDisplays> + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" + version="any"> + <netex:FrontText>Mission 1 bis</netex:FrontText> + <netex:PublicCode>1234</netex:PublicCode> + </netex:DestinationDisplay> + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" + version="any"> + <netex:FrontText>Mission 2 bis</netex:FrontText> + <netex:PublicCode>2345</netex:PublicCode> + </netex:DestinationDisplay> + </netex:destinationDisplays> + <netex:scheduledStopPoints> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" + version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" + version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" + version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" + version="any"/> + </netex:scheduledStopPoints> + <netex:passengerStopAssignments> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78402:ZDE:50000918:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78402:ZDE:50000917:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment + id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any"> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + </netex:passengerStopAssignments> + <netex:routingConstraintZones> + <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" + version="any"> + <netex:Name>ITL 1</netex:Name> + <netex:members> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ScheduledStopPointRef + ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + </netex:members> + <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> + </netex:RoutingConstraintZone> + </netex:routingConstraintZones> + </netex:members> + </netex:GeneralFrame> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" + version="any"> + <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> + <netex:members> + <netex:serviceJourneys> + <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> + <netex:Name>Course 1 par ici aussi</netex:Name> + <netex:noticeAssignments> + <netex:NoticeAssignment> + <netex:NoticeRef ref="CITYWAY:Notice:2:LOC"> + version="any"</netex:NoticeRef> + </netex:NoticeAssignment> + </netex:noticeAssignments> + <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> + version="any"</netex:DayTypeRef> + <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC"> + version="any"</netex:DayTypeRef> + <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" + version="any"/> + <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:212"> + version="any"</netex:OperatorRef> + <netex:trainNumbers> + <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> + </netex:trainNumbers> + <netex:passingTimes> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>23:58:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> + <netex:DepartureTime>23:59:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>0</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>00:03:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>1</netex:ArrivalDayOffset> + <netex:DepartureTime>00:04:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>1</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + </netex:passingTimes> + </netex:ServiceJourney> + </netex:serviceJourneys> + </netex:members> + </netex:GeneralFrame> + </netex:frames> + </netex:CompositeFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml new file mode 100644 index 000000000..712ea8be1 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/calendriers.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" + xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core" + xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_CALENDRIER-1_20170214090012:LOC" version="any"> + <netex:ValidBetween> + <netex:FromDate>2017-04-01T00:00:00</netex:FromDate> + <netex:ToDate>2016-12-31T00:00:00</netex:ToDate> + </netex:ValidBetween> + <netex:TypeOfFrameRef ref="NETEX_CALENDRIER"/> + <netex:members> + <netex:DayType id="CITYWAY:DayType:1:LOC" version="any"> + <netex:Name>Semaine</netex:Name> + <netex:properties> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Monday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Tuesday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Wednesday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Thursday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Friday</netex:DaysOfWeek> + </netex:PropertyOfDay> + </netex:properties> + </netex:DayType> + <netex:DayType id="CITYWAY:DayType:2:LOC" version="any"> + <netex:Name>Fin de semaine</netex:Name> + <netex:properties> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Saturday</netex:DaysOfWeek> + </netex:PropertyOfDay> + <netex:PropertyOfDay> + <netex:DaysOfWeek>Sunday</netex:DaysOfWeek> + </netex:PropertyOfDay> + </netex:properties> + </netex:DayType> + <netex:DayType id="CITYWAY:DayType:3:LOC" version="any"> + <netex:Name>Service spécial</netex:Name> + </netex:DayType> + <netex:DayType id="CITYWAY:DayType:4:LOC" version="any"> + <netex:Name>Restriction</netex:Name> + </netex:DayType> + <netex:DayTypeAssignment id="dta1" version="any" order="0"> + <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> + <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC" version="any"/> + </netex:DayTypeAssignment> + <netex:DayTypeAssignment id="dta2" version="any" order="0"> + <netex:OperatingPeriodRef ref="CITYWAY:OperatingPeriod:1:LOC" version="any"/> + <netex:DayTypeRef ref="CITYWAY:DayType:2:LOC" version="any"/> + </netex:DayTypeAssignment> + <netex:DayTypeAssignment id="dta3" version="any" order="0"> + <netex:Date>2017-03-15</netex:Date> + <netex:DayTypeRef ref="CITYWAY:DayType:3:LOC" version="any"/> + <netex:isAvailable>true</netex:isAvailable> + </netex:DayTypeAssignment> + <netex:DayTypeAssignment id="dta4" version="any" order="0"> + <netex:Date>2017-03-15</netex:Date> + <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC" version="any"/> + <netex:isAvailable>false</netex:isAvailable> + </netex:DayTypeAssignment> + <netex:OperatingPeriod id="CITYWAY:OperatingPeriod:1:LOC" version="any"> + <netex:FromDate>2017-01-01T00:00:00</netex:FromDate> + <netex:ToDate>2017-12-31T00:00:00</netex:ToDate> + </netex:OperatingPeriod> + + </netex:members> + </netex:GeneralFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml new file mode 100644 index 000000000..f59f8ac2d --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/commun.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" xmlns:gml="http://www.opengis.net/gml/3.2" + xmlns:core="http://www.govtalk.gov.uk/core" xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_COMMUN-1_20170214090012:LOC" version="any"> + <netex:TypeOfFrameRef ref="NETEX_COMMUN"/> + <netex:members> + + <netex:Notice id="CITYWAY:Notice:1:LOC" version="any"> + <netex:Text>Notice 1</netex:Text> + <netex:PublicCode>1</netex:PublicCode> + <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/> + </netex:Notice> + <netex:Notice id="CITYWAY:Notice:2:LOC" version="any"> + <netex:Text>Notice 2</netex:Text> + <netex:PublicCode>2</netex:PublicCode> + <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/> + </netex:Notice> + <netex:Notice id="CITYWAY:Notice:3:LOC" version="any"> + <netex:Text>Notice 3</netex:Text> + <netex:PublicCode>3</netex:PublicCode> + <netex:TypeOfNoticeRef ref="ServiceJourneyNotice"/> + </netex:Notice> + + </netex:members> + </netex:GeneralFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml new file mode 100644 index 000000000..9eefeeb43 --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00108_9.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" + xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core" + xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> + <netex:Name>Ligne 1</netex:Name> + <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> + <netex:frames> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" version="any"> + <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> + <netex:members> + + <netex:Route id="CITYWAY:Route:1:LOC" version="any"> + <netex:Name>route 1</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> + <netex:DirectionType>outbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + </netex:Route> + <netex:Route id="CITYWAY:Route:2:LOC" version="any"> + <netex:Name>route 2</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00108">version="any"</netex:LineRef> + <netex:DirectionType>inbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + </netex:Route> + + + <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> + <netex:Name>Par ici</netex:Name> + </netex:Direction> + <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> + <netex:Name>Par là</netex:Name> + </netex:Direction> + + + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"> + <netex:Name>Par ici</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" version="any"> + <netex:Name>Par là</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + + + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" version="any"> + <netex:FrontText>Mission 1</netex:FrontText> + <netex:PublicCode>1234</netex:PublicCode> + </netex:DestinationDisplay> + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" version="any"> + <netex:FrontText>Mission 2</netex:FrontText> + <netex:PublicCode>2345</netex:PublicCode> + </netex:DestinationDisplay> + + + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + + + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50009052:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50009053:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + + + <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" version="any"> + <netex:Name>ITL 1</netex:Name> + <netex:members> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + </netex:members> + <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> + </netex:RoutingConstraintZone> + + </netex:members> + </netex:GeneralFrame> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" version="any"> + <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> + <netex:members> + + <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> + <netex:Name>Course 1 par ici</netex:Name> + <netex:noticeAssignments> + <netex:NoticeAssignment id="ns1" version="any" order="0"> + <netex:NoticeRef ref="CITYWAY:Notice:1:LOC"> + version="any"</netex:NoticeRef> + </netex:NoticeAssignment> + </netex:noticeAssignments> + <netex:dayTypes> + <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> version="any"</netex:DayTypeRef> + </netex:dayTypes> + + <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"/> + <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:011"> + version="any"</netex:OperatorRef> + <netex:trainNumbers> + <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> + </netex:trainNumbers> + <netex:passingTimes> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>01:01:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> + <netex:DepartureTime>01:01:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>0</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>01:05:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> + <netex:DepartureTime>01:05:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>0</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + </netex:passingTimes> + </netex:ServiceJourney> + + </netex:members> + </netex:GeneralFrame> + </netex:frames> + </netex:CompositeFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml new file mode 100644 index 000000000..d260ef17e --- /dev/null +++ b/spec/fixtures/multiple_with_wrong_calendar/OFFRE_TRANSDEV_20170301122519/offre_C00109_10.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8"?> +<netex:PublicationDelivery xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.netex.org.uk/netex ../../xsd/NeTEx_publication.xsd" xmlns:netex="http://www.netex.org.uk/netex" + xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ifopt="http://www.ifopt.org.uk/ifopt" + xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:core="http://www.govtalk.gov.uk/core" + xmlns:siri="http://www.siri.org.uk/siri" version="1.0"> + <netex:PublicationTimestamp>2017-02-14T09:13:51.0</netex:PublicationTimestamp> + <netex:ParticipantRef>CITYWAY</netex:ParticipantRef> + <netex:dataObjects> + <netex:CompositeFrame id="CITYWAY:CompositeFrame:NETEX_OFFRE_LIGNE-1:LOC" version="any"> + <netex:Name>Ligne 1</netex:Name> + <netex:TypeOfFrameRef ref="NETEX_OFFRE_LIGNE"/> + <netex:frames> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_STRUCTURE-20170214090012:LOC" version="any"> + <netex:TypeOfFrameRef ref="NETEX_STRUCTURE"/> + <netex:members> + + <netex:Route id="CITYWAY:Route:1:LOC" version="any"> + <netex:Name>route 1</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> + <netex:DirectionType>outbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:1:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + </netex:Route> + <netex:Route id="CITYWAY:Route:2:LOC" version="any"> + <netex:Name>route 2</netex:Name> + <netex:LineRef ref="STIF:CODIFLIGNE:Line:C00109">version="any"</netex:LineRef> + <netex:DirectionType>inbound</netex:DirectionType> + <netex:DirectionRef ref="CITYWAY:Direction:2:LOC" version="any"/> + <netex:InverseRouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + </netex:Route> + + + <netex:Direction id="CITYWAY:Direction:1:LOC" version="any"> + <netex:Name>Par ici aussi</netex:Name> + </netex:Direction> + <netex:Direction id="CITYWAY:Direction:2:LOC" version="any"> + <netex:Name>Par là aussi</netex:Name> + </netex:Direction> + + + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"> + <netex:Name>Par ici itou</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:1:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:1:LOC" version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:1-1-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + <netex:ServiceJourneyPattern id="CITYWAY:ServiceJourneyPattern:2:LOC" version="any"> + <netex:Name>Par là itou</netex:Name> + <netex:RouteRef ref="CITYWAY:Route:2:LOC" version="any"/> + <netex:DestinationDisplayRef ref="CITYWAY:DestinationDisplay:2:LOC" version="any"/> + <netex:pointsInSequence> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-1:LOC" order="1" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + <netex:StopPointInJourneyPattern id="CITYWAY:StopPointInJourneyPattern:2-2-2:LOC" order="2" + version="any"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:ForAlighting>true</netex:ForAlighting> + <netex:ForBoarding>true</netex:ForBoarding> + </netex:StopPointInJourneyPattern> + </netex:pointsInSequence> + <netex:ServiceJourneyPatternType>passenger</netex:ServiceJourneyPatternType> + </netex:ServiceJourneyPattern> + + + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:1:LOC" version="any"> + <netex:FrontText>Mission 1 bis</netex:FrontText> + <netex:PublicCode>1234</netex:PublicCode> + </netex:DestinationDisplay> + <netex:DestinationDisplay id="CITYWAY:DestinationDisplay:2:LOC" version="any"> + <netex:FrontText>Mission 2 bis</netex:FrontText> + <netex:PublicCode>2345</netex:PublicCode> + </netex:DestinationDisplay> + + + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:ScheduledStopPoint id="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + + + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-1:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094817:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-1:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-1:LOC" version="any"/> + <netex:QuayRef ref="FR:78402:ZDE:50000918:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:1-2:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78402:ZDE:50000917:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + <netex:PassengerStopAssignment id="CITYWAY:PassengerStopAssignment:2-2:LOC" version="any" order="0"> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:2-2:LOC" version="any"/> + <netex:QuayRef ref="FR:78217:ZDE:50094816:STIF">version="any"</netex:QuayRef> + </netex:PassengerStopAssignment> + + + <netex:RoutingConstraintZone id="CITYWAY:RoutingConstraintZone:1:LOC" version="any"> + <netex:Name>ITL 1</netex:Name> + <netex:members> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-1:LOC" version="any"/> + <netex:ScheduledStopPointRef ref="CITYWAY:ScheduledStopPoint:1-2:LOC" version="any"/> + </netex:members> + <netex:ZoneUse>cannotBoardAndAlightInSameZone</netex:ZoneUse> + </netex:RoutingConstraintZone> + + </netex:members> + </netex:GeneralFrame> + <netex:GeneralFrame id="CITYWAY:GeneralFrame:NETEX_HORAIRE-20170214090012:LOC" version="any"> + <netex:TypeOfFrameRef ref="NETEX_HORAIRE"/> + <netex:members> + + <netex:ServiceJourney id="CITYWAY:ServiceJourney:1-1:LOC" version="any"> + <netex:Name>Course 1 par ici aussi</netex:Name> + <netex:noticeAssignments> + <netex:NoticeAssignment id="ns1" version="any" order="0"> + <netex:NoticeRef ref="CITYWAY:Notice:2:LOC"> + version="any"</netex:NoticeRef> + </netex:NoticeAssignment> + </netex:noticeAssignments> + <netex:dayTypes> + <netex:DayTypeRef ref="CITYWAY:DayType:1:LOC"> version="any"</netex:DayTypeRef> + <netex:DayTypeRef ref="CITYWAY:DayType:4:LOC"> version="any"</netex:DayTypeRef> + </netex:dayTypes> + <netex:JourneyPatternRef ref="CITYWAY:ServiceJourneyPattern:1:LOC" version="any"/> + <netex:OperatorRef ref="STIF:CODIFLIGNE:Operator:212"> + version="any"</netex:OperatorRef> + <netex:trainNumbers> + <netex:TrainNumberRef ref="CITYWAY:TrainNumber:1234:LOC">version="any"</netex:TrainNumberRef> + </netex:trainNumbers> + <netex:passingTimes> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>23:58:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>0</netex:ArrivalDayOffset> + <netex:DepartureTime>23:59:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>0</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + <netex:TimetabledPassingTime version="any"> + <netex:ArrivalTime>00:03:00.000</netex:ArrivalTime> + <netex:ArrivalDayOffset>1</netex:ArrivalDayOffset> + <netex:DepartureTime>00:04:00.000</netex:DepartureTime> + <netex:DepartureDayOffset>1</netex:DepartureDayOffset> + </netex:TimetabledPassingTime> + </netex:passingTimes> + </netex:ServiceJourney> + + </netex:members> + </netex:GeneralFrame> + </netex:frames> + </netex:CompositeFrame> + </netex:dataObjects> +</netex:PublicationDelivery> diff --git a/spec/lib/gtfs/time_spec.rb b/spec/lib/gtfs/time_spec.rb new file mode 100644 index 000000000..540d7cc79 --- /dev/null +++ b/spec/lib/gtfs/time_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +RSpec.describe GTFS::Time do + + it "returns an UTC Time with given H:M:S" do + expect(GTFS::Time.parse("14:29:00").time).to eq(Time.parse("2000-01-01 14:29:00 +00")) + end + + it "support hours with a single number" do + expect(GTFS::Time.parse("4:29:00").time).to eq(Time.parse("2000-01-01 04:29:00 +00")) + end + + it "return nil for invalid format" do + expect(GTFS::Time.parse("abc")).to be_nil + end + + it "removes 24 hours after 23:59:59" do + expect(GTFS::Time.parse("25:29:00").time).to eq(Time.parse("2000-01-01 01:29:00 +00")) + end + + it "returns a day_offset for each 24 hours turn" do + expect(GTFS::Time.parse("10:00:00").day_offset).to eq(0) + expect(GTFS::Time.parse("30:00:00").day_offset).to eq(1) + expect(GTFS::Time.parse("50:00:00").day_offset).to eq(2) + end + +end diff --git a/spec/models/chouette/journey_pattern_spec.rb b/spec/models/chouette/journey_pattern_spec.rb index 078e3c1f1..02a54f056 100644 --- a/spec/models/chouette/journey_pattern_spec.rb +++ b/spec/models/chouette/journey_pattern_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Chouette::JourneyPattern, :type => :model do - + subject { create(:journey_pattern) } describe 'checksum' do @@ -92,7 +92,7 @@ describe Chouette::JourneyPattern, :type => :model do let(:journey_pattern) { create :journey_pattern } let(:distances){ [] } it "should raise an error" do - expect{journey_pattern.set_distances(distances)}.to raise_error + expect{journey_pattern.set_distances(distances)}.to raise_error(RuntimeError) end context "with consistent data" do diff --git a/spec/models/custom_field_spec.rb b/spec/models/custom_field_spec.rb index 1dfe6d33c..ce6ce9fa5 100644 --- a/spec/models/custom_field_spec.rb +++ b/spec/models/custom_field_spec.rb @@ -47,11 +47,14 @@ RSpec.describe CustomField, type: :model do let!(:field){ [create(:custom_field, code: :energy, field_type: 'list', options: {list_values: %w(foo bar baz)})] } let!( :vj ){ create :vehicle_journey, custom_field_values: {energy: "1"} } it "should cast the value" do - p vj.custom_fields expect(vj.custom_fields[:energy].value).to eq 1 expect(vj.custom_fields[:energy].display_value).to eq "bar" end + it "should not break initailizartion if the model does not have the :custom_field_values attribute" do + expect{Chouette::VehicleJourney.where(id: vj.id).select(:id).last}.to_not raise_error + end + it "should validate the value" do { "1" => true, diff --git a/spec/models/import/gtfs_spec.rb b/spec/models/import/gtfs_spec.rb new file mode 100644 index 000000000..b4b23be00 --- /dev/null +++ b/spec/models/import/gtfs_spec.rb @@ -0,0 +1,283 @@ +require "rails_helper" + +RSpec.describe Import::Gtfs do + + let(:referential) do + create :referential do |referential| + referential.line_referential.objectid_format = "netex" + referential.stop_area_referential.objectid_format = "netex" + end + end + + let(:workbench) do + create :workbench do |workbench| + workbench.line_referential.objectid_format = "netex" + workbench.stop_area_referential.objectid_format = "netex" + end + end + + def create_import(file) + Import::Gtfs.new workbench: workbench, local_file: fixtures_path(file) + end + + describe "#import_agencies" do + let(:import) { create_import "google-sample-feed.zip" } + it "should create a company for each agency" do + import.import_agencies + + expect(workbench.line_referential.companies.pluck(:registration_number, :name)).to eq([["DTA","Demo Transit Authority"]]) + end + end + + describe "#import_stops" do + let(:import) { create_import "google-sample-feed.zip" } + it "should create a company for each agency" do + import.import_stops + + defined_attributes = [ + :registration_number, :name, :parent_id, :latitude, :longitude + ] + expected_attributes = [ + ["AMV", "Amargosa Valley (Demo)", nil, 36.641496, -116.40094], + ["EMSI", "E Main St / S Irving St (Demo)", nil, 36.905697, -116.76218], + ["DADAN", "Doing Ave / D Ave N (Demo)", nil, 36.909489, -116.768242], + ["NANAA", "North Ave / N A Ave (Demo)", nil, 36.914944, -116.761472], + ["NADAV", "North Ave / D Ave N (Demo)", nil, 36.914893, -116.76821], + ["STAGECOACH", "Stagecoach Hotel & Casino (Demo)", nil, 36.915682, -116.751677], + ["BULLFROG", "Bullfrog (Demo)", nil, 36.88108, -116.81797], + ["BEATTY_AIRPORT", "Nye County Airport (Demo)", nil, 36.868446, -116.784582], + ["FUR_CREEK_RES", "Furnace Creek Resort (Demo)", nil, 36.425288, -117.133162] + ] + + expect(workbench.stop_area_referential.stop_areas.pluck(*defined_attributes)).to match_array(expected_attributes) + end + end + + describe "#import_routes" do + let(:import) { create_import "google-sample-feed.zip" } + it "should create a line for each route" do + import.import_routes + + defined_attributes = [ + :registration_number, :name, :number, :published_name, + "companies.registration_number", + :comment, :url + ] + expected_attributes = [ + ["AAMV", "Airport - Amargosa Valley", "50", "Airport - Amargosa Valley", nil, nil, nil], + ["CITY", "City", "40", "City", nil, nil, nil], + ["STBA", "Stagecoach - Airport Shuttle", "30", "Stagecoach - Airport Shuttle", nil, nil, nil], + ["BFC", "Bullfrog - Furnace Creek Resort", "20", "Bullfrog - Furnace Creek Resort", nil, nil, nil], + ["AB", "Airport - Bullfrog", "10", "Airport - Bullfrog", nil, nil, nil] + ] + + expect(workbench.line_referential.lines.includes(:company).pluck(*defined_attributes)).to match_array(expected_attributes) + end + end + + describe "#import_trips" do + let(:import) { create_import "google-sample-feed.zip" } + before do + import.prepare_referential + import.import_calendars + end + + it "should create a Route for each trip" do + import.import_trips + + defined_attributes = [ + "lines.registration_number", :wayback, :name, :published_name + ] + expected_attributes = [ + ["AB", "outbound", "to Bullfrog", "to Bullfrog"], + ["AB", "inbound", "to Airport", "to Airport"], + ["STBA", "inbound", "Shuttle", "Shuttle"], + ["CITY", "outbound", "Outbound", "Outbound"], + ["CITY", "inbound", "Inbound", "Inbound"], + ["BFC", "outbound", "to Furnace Creek Resort", "to Furnace Creek Resort"], + ["BFC", "inbound", "to Bullfrog", "to Bullfrog"], + ["AAMV", "outbound", "to Amargosa Valley", "to Amargosa Valley"], + ["AAMV", "inbound", "to Airport", "to Airport"], + ["AAMV", "outbound", "to Amargosa Valley", "to Amargosa Valley"], + ["AAMV", "inbound", "to Airport", "to Airport"] + ] + + expect(import.referential.routes.includes(:line).pluck(*defined_attributes)).to match_array(expected_attributes) + end + + it "should create a JourneyPattern for each trip" do + import.import_trips + + defined_attributes = [ + :name + ] + expected_attributes = [ + "to Bullfrog", "to Airport", "Shuttle", "Outbound", "Inbound", "to Furnace Creek Resort", "to Bullfrog", "to Amargosa Valley", "to Airport", "to Amargosa Valley", "to Airport" + ] + + expect(import.referential.journey_patterns.pluck(*defined_attributes)).to match_array(expected_attributes) + end + + it "should create a VehicleJourney for each trip" do + import.import_trips + + defined_attributes = ->(v) { + [v.published_journey_name, v.time_tables.first&.comment] + } + expected_attributes = [ + ["to Bullfrog", "Calendar FULLW"], + ["to Airport", "Calendar FULLW"], + ["Shuttle", "Calendar FULLW"], + ["CITY1", "Calendar FULLW"], + ["CITY2", "Calendar FULLW"], + ["to Furnace Creek Resort", "Calendar FULLW"], + ["to Bullfrog", "Calendar FULLW"], + ["to Amargosa Valley", "Calendar WE"], + ["to Airport", "Calendar WE"], + ["to Amargosa Valley", "Calendar WE"], + ["to Airport", "Calendar WE"] + ] + + expect(import.referential.vehicle_journeys.map(&defined_attributes)).to match_array(expected_attributes) + end + end + + describe "#import_stop_times" do + let(:import) { create_import "google-sample-feed.zip" } + + before do + import.prepare_referential + import.import_calendars + import.import_trips + end + + it "should create a VehicleJourneyAtStop for each stop_time" do + import.import_stop_times + + def t(value) + Time.parse(value) + end + + defined_attributes = [ + "stop_areas.registration_number", :position, :departure_time, :arrival_time, + ] + expected_attributes = [ + ["STAGECOACH", 0, t("2000-01-01 06:00:00 UTC"), t("2000-01-01 06:00:00 UTC")], + ["BEATTY_AIRPORT", 1, t("2000-01-01 06:20:00 UTC"), t("2000-01-01 06:20:00 UTC")], + ["STAGECOACH", 0, t("2000-01-01 06:00:00 UTC"), t("2000-01-01 06:00:00 UTC")], + ["NANAA", 1, t("2000-01-01 06:07:00 UTC"), t("2000-01-01 06:05:00 UTC")], + ["NADAV", 2, t("2000-01-01 06:14:00 UTC"), t("2000-01-01 06:12:00 UTC")], + ["DADAN", 3, t("2000-01-01 06:21:00 UTC"), t("2000-01-01 06:19:00 UTC")], + ["EMSI", 4, t("2000-01-01 06:28:00 UTC"), t("2000-01-01 06:26:00 UTC")], + ["EMSI", 0, t("2000-01-01 06:30:00 UTC"), t("2000-01-01 06:28:00 UTC")], + ["DADAN", 1, t("2000-01-01 06:37:00 UTC"), t("2000-01-01 06:35:00 UTC")], + ["NADAV", 2, t("2000-01-01 06:44:00 UTC"), t("2000-01-01 06:42:00 UTC")], + ["NANAA", 3, t("2000-01-01 06:51:00 UTC"), t("2000-01-01 06:49:00 UTC")], + ["STAGECOACH", 4, t("2000-01-01 06:58:00 UTC"), t("2000-01-01 06:56:00 UTC")], + ["BEATTY_AIRPORT", 0, t("2000-01-01 08:00:00 UTC"), t("2000-01-01 08:00:00 UTC")], + ["BULLFROG", 1, t("2000-01-01 08:15:00 UTC"), t("2000-01-01 08:10:00 UTC")], + ["BULLFROG", 0, t("2000-01-01 12:05:00 UTC"), t("2000-01-01 12:05:00 UTC")], + ["BEATTY_AIRPORT", 1, t("2000-01-01 12:15:00 UTC"), t("2000-01-01 12:15:00 UTC")], + ["BULLFROG", 0, t("2000-01-01 08:20:00 UTC"), t("2000-01-01 08:20:00 UTC")], + ["FUR_CREEK_RES", 1, t("2000-01-01 09:20:00 UTC"), t("2000-01-01 09:20:00 UTC")], + ["FUR_CREEK_RES", 0, t("2000-01-01 11:00:00 UTC"), t("2000-01-01 11:00:00 UTC")], + ["BULLFROG", 1, t("2000-01-01 12:00:00 UTC"), t("2000-01-01 12:00:00 UTC")], + ["BEATTY_AIRPORT", 0, t("2000-01-01 08:00:00 UTC"), t("2000-01-01 08:00:00 UTC")], + ["AMV", 1, t("2000-01-01 09:00:00 UTC"), t("2000-01-01 09:00:00 UTC")], + ["AMV", 0, t("2000-01-01 10:00:00 UTC"), t("2000-01-01 10:00:00 UTC")], + ["BEATTY_AIRPORT", 1, t("2000-01-01 11:00:00 UTC"), t("2000-01-01 11:00:00 UTC")], + ["BEATTY_AIRPORT", 0, t("2000-01-01 13:00:00 UTC"), t("2000-01-01 13:00:00 UTC")], + ["AMV", 1, t("2000-01-01 14:00:00 UTC"), t("2000-01-01 14:00:00 UTC")], + ["AMV", 0, t("2000-01-01 15:00:00 UTC"), t("2000-01-01 15:00:00 UTC")], + ["BEATTY_AIRPORT", 1, t("2000-01-01 16:00:00 UTC"), t("2000-01-01 16:00:00 UTC")] + ] + expect(referential.vehicle_journey_at_stops.includes(stop_point: :stop_area).pluck(*defined_attributes)).to match_array(expected_attributes) + end + end + + describe "#import_calendars" do + let(:import) { create_import "google-sample-feed.zip" } + + before do + import.prepare_referential + end + + it "should create a Timetable for each calendar" do + import.import_calendars + + def d(value) + Date.parse(value) + end + + defined_attributes = ->(t) { + [t.comment, t.valid_days, t.periods.first.period_start, t.periods.first.period_end] + } + expected_attributes = [ + ["Calendar FULLW", [1, 2, 3, 4, 5, 6, 7], d("Mon, 01 Jan 2007"), d("Fri, 31 Dec 2010")], + ["Calendar WE", [6, 7], d("Mon, 01 Jan 2007"), d("Fri, 31 Dec 2010")] + ] + expect(referential.time_tables.map(&defined_attributes)).to match_array(expected_attributes) + end + end + + describe "#import_calendar_dates" do + let(:import) { create_import "google-sample-feed.zip" } + + before do + import.prepare_referential + import.import_calendars + end + + it "should create a Timetable::Date for each calendar date" do + import.import_calendar_dates + + def d(value) + Date.parse(value) + end + + defined_attributes = ->(d) { + [d.time_table.comment, d.date, d.in_out] + } + expected_attributes = [ + ["Calendar FULLW", d("Mon, 04 Jun 2007"), false] + ] + expect(referential.time_table_dates.map(&defined_attributes)).to match_array(expected_attributes) + end + end + + describe "#download_local_file" do + let(:file) { "google-sample-feed.zip" } + let(:import) do + Import::Gtfs.create! name: "GTFS test", creator: "Test", workbench: workbench, file: open_fixture(file), download_host: "rails_host" + end + + let(:download_url) { "#{import.download_host}/workbenches/#{import.workbench_id}/imports/#{import.id}/download?token=#{import.token_download}" } + + before do + stub_request(:get, download_url).to_return(status: 200, body: read_fixture(file)) + end + + it "should download local_file" do + expect(File.read(import.download_local_file)).to eq(read_fixture(file)) + end + end + + describe "#download_host" do + it "should return host defined by Rails.application.config.rails_host (without http:// schema)" do + allow(Rails.application.config).to receive(:rails_host).and_return("http://download_host") + + expect(Import::Gtfs.new.download_host).to eq("download_host") + end + end + + describe "#download_path" do + let(:file) { "google-sample-feed.zip" } + let(:import) do + Import::Gtfs.create! name: "GTFS test", creator: "Test", workbench: workbench, file: open_fixture(file), download_host: "rails_host" + end + + it "should return the pathwith the token" do + expect(import.download_path).to eq("/workbenches/#{import.workbench_id}/imports/#{import.id}/download?token=#{import.token_download}") + end + end +end diff --git a/spec/models/simple_json_exporter_spec.rb b/spec/models/simple_json_exporter_spec.rb index 4b48dc732..afecf7e51 100644 --- a/spec/models/simple_json_exporter_spec.rb +++ b/spec/models/simple_json_exporter_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SimpleJsonExporter do SimpleJsonExporter.define :foo expect do SimpleJsonExporter.new(configuration_name: :test).export - end.to raise_error + end.to raise_error(RuntimeError) end end context "with a complete configuration" do @@ -18,9 +18,9 @@ RSpec.describe SimpleJsonExporter do it "should define an exporter" do expect{SimpleJsonExporter.find_configuration(:foo)}.to_not raise_error expect{SimpleJsonExporter.new(configuration_name: :foo, filepath: "").export}.to_not raise_error - expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error - expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error - expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error + expect{SimpleJsonExporter.find_configuration(:bar)}.to raise_error(RuntimeError) + expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "")}.to raise_error(RuntimeError) + expect{SimpleJsonExporter.new(configuration_name: :bar, filepath: "").export}.to raise_error(RuntimeError) expect{SimpleJsonExporter.create(configuration_name: :foo, filepath: "")}.to change{SimpleJsonExporter.count}.by 1 end end @@ -33,7 +33,7 @@ RSpec.describe SimpleJsonExporter do config.add_field :name config.add_field :name end - end.to raise_error + end.to raise_error(RuntimeError) end end end diff --git a/spec/services/zip_service_spec.rb b/spec/services/zip_service_spec.rb index 1eadaa3bf..540de5bfc 100644 --- a/spec/services/zip_service_spec.rb +++ b/spec/services/zip_service_spec.rb @@ -42,6 +42,24 @@ RSpec.describe ZipService, type: :zip do end end + context 'one referential without calendar' do + let( :zip_name ){ 'one_referential_no_calendar.zip' } + let( :zip_content ){ first_referential_no_calendar_data } + + it 'returns a not ok object' do + expect_incorrect_subdir subject.first, expected_missing_calendar: true + end + end + + context 'one referential with an unparsable calendar' do + let( :zip_name ){ 'one_referential_unparsable_calendar.zip' } + let( :zip_content ){ first_referential_unparsable_calendar_data } + + it 'returns a not ok object' do + expect_incorrect_subdir subject.first, expected_wrong_calendar: true + end + end + context 'one referential with a foreign line' do let( :zip_name ){ 'one_referential_foreign.zip' } let( :zip_content ){ first_referential_foreign_data } @@ -109,17 +127,29 @@ RSpec.describe ZipService, type: :zip do end end - def expect_incorrect_subdir subdir, expected_spurious: [], expected_foreign_lines: [] + def expect_incorrect_subdir subdir, expected_spurious: [], expected_foreign_lines: [], expected_missing_calendar: false, expected_wrong_calendar: false expect( subdir ).not_to be_ok expect( subdir.foreign_lines ).to eq(expected_foreign_lines) expect( subdir.spurious ).to eq(expected_spurious) + expect( subdir.missing_calendar ).to eq(expected_missing_calendar) + expect( subdir.wrong_calendar ).to eq(expected_wrong_calendar) end # Data # ---- + let :valid_calendar do + """ + <netex:PublicationDelivery xmlns:netex=\"http://www.netex.org.uk/netex\"> + <netex:ValidBetween> + <netex:FromDate>2017-03-01</netex:FromDate> + <netex:ToDate>2017-03-31</netex:ToDate> + </netex:ValidBetween> + </netex:PublicationDelivery> + """ + end let :first_referential_ok_data do { - 'Referential1/calendriers.xml' => 'calendriers', + 'Referential1/calendriers.xml' => valid_calendar, 'Referential1/commun.xml' => 'common', 'Referential1/offre_C00108_9.xml' => 'line 108 ref 1', 'Referential1/offre_C00109_10.xml' => 'line 109 ref 1' @@ -127,7 +157,7 @@ RSpec.describe ZipService, type: :zip do end let :first_referential_foreign_data do { - 'Referential2/calendriers.xml' => 'calendriers', + 'Referential2/calendriers.xml' => valid_calendar, 'Referential2/commun.xml' => 'common', 'Referential2/offre_C00110_11.xml' => 'foreign line ref 1', 'Referential2/offre_C00108_9.xml' => 'line 108 ref 1', @@ -136,7 +166,7 @@ RSpec.describe ZipService, type: :zip do end let :first_referential_spurious_data do { - 'Referential3/calendriers.xml' => 'calendriers', + 'Referential3/calendriers.xml' => valid_calendar, 'Referential3/commun.xml' => 'common', 'Referential3/SPURIOUS/commun.xml' => 'common', 'Referential3/offre_C00108_9.xml' => 'line 108 ref 1', @@ -145,7 +175,7 @@ RSpec.describe ZipService, type: :zip do end let :second_referential_ok_data do { - 'Referential4/calendriers.xml' => 'calendriers', + 'Referential4/calendriers.xml' => valid_calendar, 'Referential4/commun.xml' => 'common', 'Referential4/offre_C00108_9.xml' => 'line 108 ref 2', 'Referential4/offre_C00109_10.xml' => 'line 109 ref 2' @@ -153,7 +183,7 @@ RSpec.describe ZipService, type: :zip do end let :messed_up_referential_data do { - 'Referential5/calendriers.xml' => 'calendriers', + 'Referential5/calendriers.xml' => valid_calendar, 'Referential5/commun.xml' => 'common', 'Referential5/SPURIOUS1/commun.xml' => 'common', 'Referential5/SPURIOUS2/commun.xml' => 'common', @@ -163,5 +193,21 @@ RSpec.describe ZipService, type: :zip do 'Referential5/offre_C00109_10.xml' => 'line 109 ref 1' } end + let :first_referential_no_calendar_data do + { + 'Referential6/commun.xml' => 'common', + 'Referential6/offre_C00108_9.xml' => 'line 108 ref 1', + 'Referential6/offre_C00109_10.xml' => 'line 109 ref 1' + } + end + let :first_referential_unparsable_calendar_data do + { + 'Referential7/calendriers.xml' => 'calendriers', + 'Referential7/commun.xml' => 'common', + 'Referential7/offre_C00108_9.xml' => 'line 108 ref 1', + 'Referential7/offre_C00109_10.xml' => 'line 109 ref 1' + } + end + end |
