aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuc Donnet2015-03-23 21:12:56 +0100
committerLuc Donnet2015-03-23 21:12:56 +0100
commitfa7e745459aefd64086869882fcca73f948b46fa (patch)
treea5df17d4498b4ca612f6398156a667d2593cc76e
parent0740decc6a2c5117d1dc89e3665774460626f86b (diff)
downloadchouette-core-fa7e745459aefd64086869882fcca73f948b46fa.tar.bz2
Change ruby client for iev server
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock4
-rw-r--r--app/controllers/imports_controller.rb10
-rw-r--r--app/helpers/imports_helper.rb22
-rw-r--r--app/models/import.rb43
-rw-r--r--app/models/import_service.rb10
-rw-r--r--app/views/imports/_import.erb2
-rw-r--r--app/views/imports/_imports.html.erb4
-rw-r--r--lib/iev_api.rb23
-rw-r--r--lib/iev_api/client.rb105
-rw-r--r--lib/iev_api/configuration.rb59
-rw-r--r--lib/iev_api/middleware/custom_parser.rb37
-rw-r--r--lib/iev_api/middleware/raise_response_error.rb13
-rw-r--r--lib/iev_api/middleware/raise_server_error.rb18
-rw-r--r--lib/ievkit.rb33
-rw-r--r--lib/ievkit/arguments.rb14
-rw-r--r--lib/ievkit/authentication.rb70
-rw-r--r--lib/ievkit/client.rb307
-rw-r--r--lib/ievkit/client/jobs.rb49
-rw-r--r--lib/ievkit/configurable.rb85
-rw-r--r--lib/ievkit/default.rb149
-rw-r--r--lib/ievkit/error.rb241
-rw-r--r--lib/ievkit/response/raise_error.rb28
-rw-r--r--lib/ievkit/version.rb17
-rw-r--r--spec/models/import_service_spec.rb3
25 files changed, 1043 insertions, 304 deletions
diff --git a/Gemfile b/Gemfile
index e30c8b146..53798ef9e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -39,6 +39,7 @@ gem 'spring', group: :development
gem "sitemap_generator"
# API Rest
+gem 'sawyer'
gem 'faraday', '~> 0.9.1'
gem 'faraday_middleware', '~> 0.9.1'
gem 'kleisli'
diff --git a/Gemfile.lock b/Gemfile.lock
index 5e5f6874c..b17fc91d7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -384,6 +384,9 @@ GEM
sass (~> 3.2.2)
sprockets (~> 2.8, < 3.0)
sprockets-rails (~> 2.0)
+ sawyer (0.6.0)
+ addressable (~> 2.3.5)
+ faraday (~> 0.8, < 0.10)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
rdoc (~> 4.0)
@@ -531,6 +534,7 @@ DEPENDENCIES
rspec-rails (~> 3.1.0)
rubyzip (~> 1.1.6)
sass-rails (~> 4.0.3)
+ sawyer
sdoc (~> 0.4.0)
simple_form (~> 3.1.0)
sitemap_generator
diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb
index 2af6a57d6..927c188e2 100644
--- a/app/controllers/imports_controller.rb
+++ b/app/controllers/imports_controller.rb
@@ -17,7 +17,7 @@ class ImportsController < ChouetteController
index! do
build_breadcrumb :index
end
- rescue IevApi::IevError => error
+ rescue Ievkit::Error => error
logger.error("Iev failure : #{error.message}")
flash[:error] = t('iev.failure')
redirect_to referential_path(@referential)
@@ -29,7 +29,7 @@ class ImportsController < ChouetteController
show! do
build_breadcrumb :show
end
- rescue IevApi::IevError => error
+ rescue Ievkit::Error => error
logger.error("Iev failure : #{error.message}")
flash[:error] = t('iev.failure')
redirect_to referential_path(@referential)
@@ -41,7 +41,7 @@ class ImportsController < ChouetteController
new! do
puts "OK"
end
- rescue IevApi::IevError => error
+ rescue Ievkit::Error => error
logger.error("Iev failure : #{error.message}")
flash[:error] = t('iev.failure')
redirect_to referential_path(@referential)
@@ -53,7 +53,7 @@ class ImportsController < ChouetteController
create! do
puts "OK"
end
- rescue IevApi::IevError => error
+ rescue Ievkit::Error => error
logger.error("Iev failure : #{error.message}")
flash[:error] = t('iev.failure')
redirect_to referential_path(@referential)
@@ -66,7 +66,7 @@ class ImportsController < ChouetteController
import_service.delete(@import.id)
redirect_to referential_imports_path(@referential)
end
- rescue IevApi::IevError => error
+ rescue Ievkit::Error => error
logger.error("Iev failure : #{error.message}")
flash[:error] = t('iev.failure')
redirect_to referential_path(@referential)
diff --git a/app/helpers/imports_helper.rb b/app/helpers/imports_helper.rb
index 772533a8e..498d03bf9 100644
--- a/app/helpers/imports_helper.rb
+++ b/app/helpers/imports_helper.rb
@@ -28,17 +28,17 @@ module ImportsHelper
def import_progress_bar_tag(import)
- if import.canceled? || import.aborted?
- div_class = "progress-bar progress-bar-danger"
- elsif import.scheduled?
- div_class = "progress-bar progress-bar-info"
- elsif import.created?
- div_class = "progress-bar progress-bar-info"
- elsif import.terminated?
- div_class = "progress-bar progress-bar-success"
- else
- div_class = ""
- end
+ # if import.canceled? || import.aborted?
+ # div_class = "progress-bar progress-bar-danger"
+ # elsif import.scheduled?
+ # div_class = "progress-bar progress-bar-info"
+ # elsif import.created?
+ # div_class = "progress-bar progress-bar-info"
+ # elsif import.terminated?
+ # div_class = "progress-bar progress-bar-success"
+ # else
+ div_class = ""
+ # end
content_tag :div, :class => "progress" do
content_tag :div, :class => div_class, role: "progressbar", :'aria-valuenow' => "#{import.percentage_progress}", :'aria-valuemin' => "0", :'aria-valuemax' => "100", :style => "width: #{import.percentage_progress}%;" do
diff --git a/app/models/import.rb b/app/models/import.rb
index 93388a6f5..2e1c86394 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -6,18 +6,16 @@ class Import
# enumerize :status, in: %w{created scheduled terminated canceled aborted}, default: "created", predicates: true
# enumerize :format, in: %w{neptune netex gtfs}, default: "neptune", predicates: true
- attr_reader :datas, :links, :headers, :errors
+ attr_reader :datas
- def initialize( response )
- @datas = response[:datas]
- @headers = response[:headers]
- @links = response[:links]
+ def initialize( response )
+ @datas = response
# @status = @datas.status.downcase if @datas.status?
# @format = @datas.type.downcase if @datas.type?
end
def report
- report_path = links[:report]
+ report_path = datas.links[:report]
if report_path
response = IevApi.request(:get, compliance_check_path, params)
ImportReport.new(response)
@@ -27,7 +25,7 @@ class Import
end
def compliance_check
- compliance_check_path = links[:validation]
+ compliance_check_path = datas.links[:validation]
if compliance_check_path
response = IevApi.request(:get, compliance_check_path, params)
ComplianceCheck.new(response)
@@ -37,7 +35,7 @@ class Import
end
def delete
- delete_path = links[:delete]
+ delete_path = datas.links[:delete]
if delete_path
IevApi.request(:delete, delete_path, params)
else
@@ -46,7 +44,7 @@ class Import
end
def cancel
- cancel_path = links[:cancel]
+ cancel_path = datas.links[:cancel]
if cancel_path
IevApi.request(:delete, cancel_path, params)
else
@@ -55,7 +53,7 @@ class Import
end
def id
- @datas.id
+ datas.id
end
def status
@@ -67,7 +65,7 @@ class Import
end
def filename
- @datas.filename
+ datas.filename
end
def filename_extension
@@ -85,43 +83,40 @@ class Import
end
def referential_name
- @datas.referential
+ datas.referential
end
def name
- @datas.parameters.name
- end
-
- def user_name?
- @datas.parameters? && @datas.parameters.user_name?
+ datas.action_parameters.name
end
def user_name
- @datas.parameters.user_name if user_name?
+
+ datas.action_parameters.user_name
end
def no_save
- @datas.parameters.no_save
+ datas.action_parameters.no_save
end
def filename
- @datas.filename
+ datas.filename
end
def created_at?
- @datas.created?
+ datas.created?
end
def created_at
- Time.at(@datas.created.to_i / 1000) if created_at?
+ Time.at(datas.created.to_i / 1000) if created_at?
end
def updated_at?
- @datas.updated?
+ datas.updated?
end
def updated_at
- Time.at(@datas.updated.to_i / 1000) if updated_at?
+ Time.at(datas.updated.to_i / 1000) if updated_at?
end
end
diff --git a/app/models/import_service.rb b/app/models/import_service.rb
index 567cd9e3e..2e3c1012b 100644
--- a/app/models/import_service.rb
+++ b/app/models/import_service.rb
@@ -8,14 +8,16 @@ class ImportService
# Find an import whith his id
def find(id)
- Import.new( IevApi.scheduled_job(referential.slug, id, { :action => "importer" }) )
+ Import.new( Ievkit.scheduled_job(referential.slug, id, { :action => "importer" }) )
end
# Find all imports
def all
- IevApi.jobs(referential.slug, { :action => "importer" }).map do |import_hash|
- Import.new( import_hash )
+ [].tap do |jobs|
+ Ievkit.jobs(referential.slug, { :action => "importer" }).each do |job|
+ jobs << Import.new( job )
+ end
end
end
-
+
end
diff --git a/app/views/imports/_import.erb b/app/views/imports/_import.erb
index 8c3da0320..cbed386f5 100644
--- a/app/views/imports/_import.erb
+++ b/app/views/imports/_import.erb
@@ -20,7 +20,7 @@
<div class="panel-footer">
<%= import_progress_bar_tag(import) %>
<div class="history">
- <%= l import.created_at, :format => "%d/%m/%Y %H:%M" %> | <%= import.user_name %>
+ <%= import.created_at %> | <%= import.user_name %>
</div>
</div>
</div>
diff --git a/app/views/imports/_imports.html.erb b/app/views/imports/_imports.html.erb
index 51d1c02b2..d4c7e0b9e 100644
--- a/app/views/imports/_imports.html.erb
+++ b/app/views/imports/_imports.html.erb
@@ -1,8 +1,8 @@
<div class="page_info">
<span class="search"> <%= t("will_paginate.page_entries_info.search") %></span> <%= page_entries_info @imports %>
</div>
-<div class="imports paginated_content">
- <%= paginated_content @imports %>
+<div class="imports paginated_content">
+ <%= paginated_content @imports, "imports/import" %>
</div>
<div class="pagination">
<%= will_paginate @imports, :container => false, renderer: RemoteBootstrapPaginationLinkRenderer %>
diff --git a/lib/iev_api.rb b/lib/iev_api.rb
deleted file mode 100644
index 77a10d1ac..000000000
--- a/lib/iev_api.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'iev_api/configuration'
-
-module IevApi
- extend Configuration
-
- class IevError < StandardError; end
-
- def self.client(options={})
- IevApi::Client.new(options)
- end
-
- # Delegate to Instapaper::Client
- def self.method_missing(method, *args, &block)
- return super unless client.respond_to?(method)
- client.send(method, *args, &block)
- end
-
- def self.respond_to?(method, include_private = false)
- client.respond_to?(method, include_private) || super(method, include_private)
- end
-end
-
-require 'iev_api/client'
diff --git a/lib/iev_api/client.rb b/lib/iev_api/client.rb
deleted file mode 100644
index 1994b1027..000000000
--- a/lib/iev_api/client.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-module IevApi
- class Client
-
- PER_PAGE = 12
- #PARALLEL_WORKERS = 10
- ACTIONS = %w{ importer exporter validator }
- IMPORT_FORMAT = %w{ neptune netex gtfs }
- EXPORT_FORMAT = %w{ neptune netex gtfs hub kml }
-
- attr_accessor *IevApi::Configuration::VALID_OPTIONS_KEYS
-
- def initialize(options={})
- attrs = IevApi.options.merge(options)
- IevApi::Configuration::VALID_OPTIONS_KEYS.each do |key|
- send("#{key}=", attrs[key])
- end
- end
-
- def url_for(endpoint, *args)
- path = case endpoint.to_s
- when 'jobs' then jobs_path(args)
- when 'scheduled_job' then scheduled_job_path(args)
- when 'terminated_job' then terminated_job_path(args)
- else raise ArgumentError.new("Unrecognized path: #{path}")
- end
-
- [account_path, path.split('.').first].join('')
- end
-
- def jobs(referential_id, options = {})
- results = request(:get, jobs_path(referential_id), options)
- end
-
- def jobs_path(referential_id)
- "referentials/#{referential_id}/jobs"
- end
-
- def scheduled_job(referential_id, job_id, options = {})
- results = request(:get, scheduled_job_path(referential_id, job_id), options)
- end
-
- def scheduled_job_path(referential_id, job_id)
- "referentials/#{referential_id}/scheduled_jobs/#{job_id}"
- end
-
- def terminated_job(referential_id, job_id, options = {})
- results = request(:get, terminated_job_path(referential_id, job_id), options)
- end
-
- def terminated_job_path(referential_id, job_id)
- "referentials/#{referential_id}/terminated_jobs/#{job_id}"
- end
-
- def account_path
- "#{protocol}://#{Rails.application.config.iev_url}"
- end
-
- def protocol
- @secure ? "https" : "http"
- end
-
- # Perform an HTTP request
- def request(method, path, params = {}, options = {})
-
- response = connection(options).run_request(method, nil, nil, nil) do |request|
- case method
- when :delete, :get
- request.url(path, params)
- when :post, :put
- request.url(path)
- request.body = params unless params.empty?
- end
- end
-
- response.body
- end
-
- def connection(options={})
- default_options = {
- :headers => {
- :accept => 'application/json',
- :user_agent => user_agent,
- },
- :ssl => {:verify => false},
- :url => account_path,
- }
-
- @connection ||= Faraday.new(default_options.deep_merge(options)) do |builder|
- middleware.each { |mw| builder.use *mw }
-
- builder.adapter adapter
- end
-
- # cache_dir = File.join(ENV['TMPDIR'] || '/tmp', 'cache')
-
- # @connection.response :caching do
- # ActiveSupport::Cache::FileStore.new cache_dir, :namespace => 'iev',
- # :expires_in => 3600 # one hour
- # end
-
- @connection
- end
-
- end
-end
diff --git a/lib/iev_api/configuration.rb b/lib/iev_api/configuration.rb
deleted file mode 100644
index b445da795..000000000
--- a/lib/iev_api/configuration.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module IevApi
- module Configuration
- VALID_OPTIONS_KEYS = [
- :account,
- :auth_token,
- :secure,
- :connection_options,
- :adapter,
- :user_agent,
- :middleware]
-
- attr_accessor *VALID_OPTIONS_KEYS
-
- DEFAULT_ADAPTER = :net_http
- DEFAULT_USER_AGENT = "IEV Ruby Gem Api"
- DEFAULT_CONNECTION_OPTIONS = {}
- DEFAULT_MIDDLEWARE = [
- Faraday::Request::UrlEncoded,
- IevApi::Middleware::RaiseResponseError,
- Faraday::Request::Multipart,
- FaradayMiddleware::Mashify,
- #FaradayMiddleware::Caching,
- FaradayMiddleware::FollowRedirects,
- FaradayMiddleware::ParseJson,
- IevApi::Middleware::RaiseServerError,
- IevApi::Middleware::CustomParser
- ]
-
- def self.extended(base)
- base.reset
- end
-
- def configure(options={})
- @account = options[:account] if options.has_key?(:account)
- @auth_token = options[:auth_token] if options.has_key?(:auth_token)
- @secure = options[:secure] if options.has_key?(:secure)
- @middleware = options[:middleware] if options.has_key?(:middleware)
- yield self if block_given?
- self
- end
-
- def options
- options = {}
- VALID_OPTIONS_KEYS.each{|k| options[k] = send(k)}
- options
- end
-
- def reset
- @account = nil
- @auth_token = nil
- @secure = false
- @adapter = DEFAULT_ADAPTER
- @user_agent = DEFAULT_USER_AGENT
- @connection_options = DEFAULT_CONNECTION_OPTIONS
- @middleware = DEFAULT_MIDDLEWARE
- end
-
- end
-end
diff --git a/lib/iev_api/middleware/custom_parser.rb b/lib/iev_api/middleware/custom_parser.rb
deleted file mode 100644
index 82cf0d563..000000000
--- a/lib/iev_api/middleware/custom_parser.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-# Expects responses like:
-#
-# {
-# "result": { "id": 1, "name": "Tobias Fünke" },
-# "errors": []
-# }
-#
-require 'faraday'
-
-module IevApi
- module Middleware
- class CustomParser < Faraday::Response::Middleware
- def on_complete(env)
- body = env[:body]
- env[:body] = {
- datas: body,
- headers: env[:response_headers],
- links: process_links(env[:response_headers])
- }
- end
-
- # Finds link relations from 'Link' response header
- def process_links(headers)
- puts headers.inspect
- links = ( headers["Link"] || "" ).split(', ').map do |link|
- href, name = link.match(/<(.*?)>; rel="(\w+)"/).captures
-
- [name.to_sym, href]
- end
- puts links.inspect
- Hash[*links.flatten]
- end
-
- end
- end
-end
diff --git a/lib/iev_api/middleware/raise_response_error.rb b/lib/iev_api/middleware/raise_response_error.rb
deleted file mode 100644
index e299e3410..000000000
--- a/lib/iev_api/middleware/raise_response_error.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'faraday'
-
-module IevApi
- module Middleware
- class RaiseResponseError < Faraday::Response::Middleware
-
- def on_complete(env)
- raise IevError.new('No results found.') if env[:body].nil?
- end
-
- end
- end
-end
diff --git a/lib/iev_api/middleware/raise_server_error.rb b/lib/iev_api/middleware/raise_server_error.rb
deleted file mode 100644
index cb6f96f98..000000000
--- a/lib/iev_api/middleware/raise_server_error.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'faraday'
-
-module IevApi
- module Middleware
- class RaiseServerError < Faraday::Response::Middleware
-
- def on_complete(env)
- case env[:status].to_i
- when 403
- raise IevError.new('SSL should be enabled - use AirbrakeAPI.secure = true in configuration')
- when 404
- raise IevError.new('No resource found')
- end
- end
-
- end
- end
-end
diff --git a/lib/ievkit.rb b/lib/ievkit.rb
new file mode 100644
index 000000000..7e47e9d84
--- /dev/null
+++ b/lib/ievkit.rb
@@ -0,0 +1,33 @@
+require 'ievkit/client'
+require 'ievkit/default'
+
+# Ruby toolkit for the GitHub API
+module Ievkit
+
+ class << self
+ include Ievkit::Configurable
+
+ # API client based on configured options {Configurable}
+ #
+ # @return [Ievkit::Client] API wrapper
+ def client
+ @client = Ievkit::Client.new(options) unless defined?(@client) && @client.same_options?(options)
+ @client
+ end
+
+ # @private
+ def respond_to_missing?(method_name, include_private=false); client.respond_to?(method_name, include_private); end if RUBY_VERSION >= "1.9"
+ # @private
+ def respond_to?(method_name, include_private=false); client.respond_to?(method_name, include_private) || super; end if RUBY_VERSION < "1.9"
+
+ private
+
+ def method_missing(method_name, *args, &block)
+ return super unless client.respond_to?(method_name)
+ client.send(method_name, *args, &block)
+ end
+
+ end
+end
+
+Ievkit.setup
diff --git a/lib/ievkit/arguments.rb b/lib/ievkit/arguments.rb
new file mode 100644
index 000000000..7f512eb25
--- /dev/null
+++ b/lib/ievkit/arguments.rb
@@ -0,0 +1,14 @@
+module Ievkit
+
+ # Extracts options from method arguments
+ # @private
+ class Arguments < Array
+ attr_reader :options
+
+ def initialize(args)
+ @options = args.last.is_a?(::Hash) ? args.pop : {}
+ super(args)
+ end
+
+ end
+end
diff --git a/lib/ievkit/authentication.rb b/lib/ievkit/authentication.rb
new file mode 100644
index 000000000..666fcc8d8
--- /dev/null
+++ b/lib/ievkit/authentication.rb
@@ -0,0 +1,70 @@
+module Ievkit
+
+ # Authentication methods for {Octokit::Client}
+ module Authentication
+
+ # Indicates if the client was supplied Basic Auth
+ # username and password
+ #
+ # @return [Boolean]
+ def basic_authenticated?
+ !!(@login && @password)
+ end
+
+ # Indicates if the client was supplied an OAuth
+ # access token
+ #
+ # @return [Boolean]
+ def token_authenticated?
+ !!@access_token
+ end
+
+ # Indicates if the client was supplied an OAuth
+ # access token or Basic Auth username and password
+ #
+ # @return [Boolean]
+ def user_authenticated?
+ basic_authenticated? || token_authenticated?
+ end
+
+ # Indicates if the client has OAuth Application
+ # client_id and secret credentials to make anonymous
+ # requests at a higher rate limit
+ #
+ # @return Boolean
+ def application_authenticated?
+ !!application_authentication
+ end
+
+ private
+
+ def application_authentication
+ if @client_id && @client_secret
+ {
+ :client_id => @client_id,
+ :client_secret => @client_secret
+ }
+ end
+ end
+
+ def login_from_netrc
+ return unless netrc?
+
+ require 'netrc'
+ info = Netrc.read netrc_file
+ netrc_host = URI.parse(api_endpoint).host
+ creds = info[netrc_host]
+ if creds.nil?
+ # creds will be nil if there is no netrc for this end point
+ ievkit_warn "Error loading credentials from netrc file for #{api_endpoint}"
+ else
+ creds = creds.to_a
+ self.login = creds.shift
+ self.password = creds.shift
+ end
+ rescue LoadError
+ ievkit_warn "Please install netrc gem for .netrc support"
+ end
+
+ end
+end
diff --git a/lib/ievkit/client.rb b/lib/ievkit/client.rb
new file mode 100644
index 000000000..53eb61074
--- /dev/null
+++ b/lib/ievkit/client.rb
@@ -0,0 +1,307 @@
+require 'sawyer'
+require 'ievkit/arguments'
+require 'ievkit/configurable'
+require 'ievkit/client/jobs'
+
+module Ievkit
+
+ # Client for the Iev API
+ class Client
+
+ include Ievkit::Configurable
+ include Ievkit::Authentication
+ include Ievkit::Client::Jobs
+
+ # Header keys that can be passed in options hash to {#get},{#head}
+ CONVENIENCE_HEADERS = Set.new([:accept, :content_type])
+
+ def initialize(options = {})
+ # Use options passed in, but fall back to module defaults
+ Ievkit::Configurable.keys.each do |key|
+ instance_variable_set(:"@#{key}", options[key] || Ievkit.instance_variable_get(:"@#{key}"))
+ end
+ end
+
+ # Compares client options to a Hash of requested options
+ #
+ # @param opts [Hash] Options to compare with current client options
+ # @return [Boolean]
+ def same_options?(opts)
+ opts.hash == options.hash
+ end
+
+ # Text representation of the client, masking tokens and passwords
+ #
+ # @return [String]
+ def inspect
+ inspected = super
+
+ # mask password
+ inspected = inspected.gsub! @password, "*******" if @password
+ # Only show last 4 of token, secret
+ if @access_token
+ inspected = inspected.gsub! @access_token, "#{'*'*36}#{@access_token[36..-1]}"
+ end
+ if @client_secret
+ inspected = inspected.gsub! @client_secret, "#{'*'*36}#{@client_secret[36..-1]}"
+ end
+
+ inspected
+ end
+
+ # Make a HTTP GET request
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Query and header params for request
+ # @return [Sawyer::Resource]
+ def get(url, options = {})
+ request :get, url, parse_query_and_convenience_headers(options)
+ end
+
+ # Make a HTTP POST request
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Body and header params for request
+ # @return [Sawyer::Resource]
+ def post(url, options = {})
+ request :post, url, options
+ end
+
+ # Make a HTTP PUT request
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Body and header params for request
+ # @return [Sawyer::Resource]
+ def put(url, options = {})
+ request :put, url, options
+ end
+
+ # Make a HTTP PATCH request
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Body and header params for request
+ # @return [Sawyer::Resource]
+ def patch(url, options = {})
+ request :patch, url, options
+ end
+
+ # Make a HTTP DELETE request
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Query and header params for request
+ # @return [Sawyer::Resource]
+ def delete(url, options = {})
+ request :delete, url, options
+ end
+
+ # Make a HTTP HEAD request
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Query and header params for request
+ # @return [Sawyer::Resource]
+ def head(url, options = {})
+ request :head, url, parse_query_and_convenience_headers(options)
+ end
+
+ # Make one or more HTTP GET requests, optionally fetching
+ # the next page of results from URL in Link response header based
+ # on value in {#auto_paginate}.
+ #
+ # @param url [String] The path, relative to {#api_endpoint}
+ # @param options [Hash] Query and header params for request
+ # @param block [Block] Block to perform the data concatination of the
+ # multiple requests. The block is called with two parameters, the first
+ # contains the contents of the requests so far and the second parameter
+ # contains the latest response.
+ # @return [Sawyer::Resource]
+ def paginate(url, options = {}, &block)
+ opts = parse_query_and_convenience_headers(options.dup)
+ if @auto_paginate || @per_page
+ opts[:query][:per_page] ||= @per_page || (@auto_paginate ? 100 : nil)
+ end
+
+ data = request(:get, url, opts)
+
+ if @auto_paginate
+ while @last_response.rels[:next] && rate_limit.remaining > 0
+ @last_response = @last_response.rels[:next].get
+ if block_given?
+ yield(data, @last_response)
+ else
+ data.concat(@last_response.data) if @last_response.data.is_a?(Array)
+ end
+ end
+
+ end
+
+ data
+ end
+
+ # Hypermedia agent for the Iev API
+ #
+ # @return [Sawyer::Agent]
+ def agent
+ @agent ||= Sawyer::Agent.new(api_endpoint, sawyer_options) do |http|
+ http.headers[:accept] = default_media_type
+ http.headers[:content_type] = "application/json"
+ http.headers[:user_agent] = user_agent
+
+ # Activate if authentication is needed
+ #
+ # if basic_authenticated?
+ # http.basic_auth(@login, @password)
+ # elsif token_authenticated?
+ # http.authorization 'token', @access_token
+ # elsif application_authenticated?
+ # http.params = http.params.merge application_authentication
+ # end
+ end
+ end
+
+ # Fetch the root resource for the API
+ #
+ # @return [Sawyer::Resource]
+ def root
+ get "/"
+ end
+
+ # Response for last HTTP request
+ #
+ # @return [Sawyer::Response]
+ def last_response
+ @last_response if defined? @last_response
+ end
+
+ # Duplicate client using client_id and client_secret as
+ # Basic Authentication credentials.
+ # @example
+ # Ievkit.client_id = "foo"
+ # Ievkit.client_secret = "bar"
+ #
+ # # GET https://api.github.com/?client_id=foo&client_secret=bar
+ # Ievkit.get "/"
+ #
+ # Ievkit.client.as_app do |client|
+ # # GET https://foo:bar@api.github.com/
+ # client.get "/"
+ # end
+ def as_app(key = client_id, secret = client_secret, &block)
+ if key.to_s.empty? || secret.to_s.empty?
+ raise ApplicationCredentialsRequired, "client_id and client_secret required"
+ end
+ app_client = self.dup
+ app_client.client_id = app_client.client_secret = nil
+ app_client.login = key
+ app_client.password = secret
+
+ yield app_client if block_given?
+ end
+
+ # Set username for authentication
+ #
+ # @param value [String] GitHub username
+ def login=(value)
+ reset_agent
+ @login = value
+ end
+
+ # Set password for authentication
+ #
+ # @param value [String] GitHub password
+ def password=(value)
+ reset_agent
+ @password = value
+ end
+
+ # Set OAuth access token for authentication
+ #
+ # @param value [String] 40 character GitHub OAuth access token
+ def access_token=(value)
+ reset_agent
+ @access_token = value
+ end
+
+ # Set OAuth app client_id
+ #
+ # @param value [String] 20 character GitHub OAuth app client_id
+ def client_id=(value)
+ reset_agent
+ @client_id = value
+ end
+
+ # Set OAuth app client_secret
+ #
+ # @param value [String] 40 character GitHub OAuth app client_secret
+ def client_secret=(value)
+ reset_agent
+ @client_secret = value
+ end
+
+ # Wrapper around Kernel#warn to print warnings unless
+ # IEVKIT_SILENT is set to true.
+ #
+ # @return [nil]
+ def ievkit_warn(*message)
+ unless ENV['IEVKIT_SILENT']
+ warn message
+ end
+ end
+
+ private
+
+ def reset_agent
+ @agent = nil
+ end
+
+ def request(method, path, data, options = {})
+ if data.is_a?(Hash)
+ options[:query] = data.delete(:query) || {}
+ options[:headers] = data.delete(:headers) || {}
+ if accept = data.delete(:accept)
+ options[:headers][:accept] = accept
+ end
+ end
+
+ @last_response = response = agent.call(method, URI::Parser.new.escape(path.to_s), data, options)
+ response.data
+ end
+
+ # Executes the request, checking if it was successful
+ #
+ # @return [Boolean] True on success, false otherwise
+ def boolean_from_response(method, path, options = {})
+ request(method, path, options)
+ @last_response.status == 204
+ rescue Ievkit::NotFound
+ false
+ end
+
+
+ def sawyer_options
+ opts = {
+ :links_parser => Sawyer::LinkParsers::Hal.new
+ }
+ conn_opts = @connection_options
+ conn_opts[:builder] = @middleware if @middleware
+ conn_opts[:proxy] = @proxy if @proxy
+ opts[:faraday] = Faraday.new(conn_opts)
+
+ opts
+ end
+
+ def parse_query_and_convenience_headers(options)
+ headers = options.fetch(:headers, {})
+ CONVENIENCE_HEADERS.each do |h|
+ if header = options.delete(h)
+ headers[h] = header
+ end
+ end
+ query = options.delete(:query)
+ opts = {:query => options}
+ opts[:query].merge!(query) if query && query.is_a?(Hash)
+ opts[:headers] = headers unless headers.empty?
+
+ opts
+ end
+ end
+end
diff --git a/lib/ievkit/client/jobs.rb b/lib/ievkit/client/jobs.rb
new file mode 100644
index 000000000..a530b2527
--- /dev/null
+++ b/lib/ievkit/client/jobs.rb
@@ -0,0 +1,49 @@
+module Ievkit
+ class Client
+ module Jobs
+
+ # List jobs for a referential
+ #
+ # @param referential [String] Data referential name.
+ # @return [Array<Sawyer::Resource>] A list of jobs
+ # @example Fetch all jobs for referential test
+ # client.jobs("test")
+ def jobs(referential, options = {})
+ paginate "referentials/#{referential}/jobs", options
+ end
+
+ # Get scheduled job
+ #
+ # @param referential [String] Data referential name.
+ # @param job_id [Integer] Id of the scheduled job.
+ # @return [Sawyer::Resource] Hash representing scheduled job.
+ # @example
+ # client.scheduled_job('test', 1451398)
+ def scheduled_job(referential, job_id, options = {})
+ get "referentials/#{referential}/scheduled_jobs/#{job_id}", options
+ end
+
+ # Get terminated job
+ #
+ # @param referential [String] Data referential name.
+ # @param job_id [Integer] Id of the terminated job.
+ # @return [Sawyer::Resource] Hash representing terminated job.
+ # @example
+ # client.terminated_job('test', 1451399)
+ def terminated_job(referential, job_id, options = {})
+ get "referentials/#{referential}/terminated_jobs/#{job_id}", options
+ end
+
+ # Create job
+ #
+ # @param referential [String] Data referential name.
+ # @return [Sawyer::Resource] Hash representing the new job.
+ # @example
+ # client.create_job("test",....)
+ def create_job(referential, options = {})
+ post "jobs", options
+ end
+
+ end
+ end
+end
diff --git a/lib/ievkit/configurable.rb b/lib/ievkit/configurable.rb
new file mode 100644
index 000000000..fb1d9f787
--- /dev/null
+++ b/lib/ievkit/configurable.rb
@@ -0,0 +1,85 @@
+module Ievkit
+
+ # Configuration options for {Client}, defaulting to values
+ # in {Default}
+ module Configurable
+
+ attr_accessor :access_token, :auto_paginate, :client_id,
+ :client_secret, :default_media_type, :connection_options,
+ :middleware, :netrc, :netrc_file,
+ :per_page, :proxy, :user_agent
+ attr_writer :password, :web_endpoint, :api_endpoint, :login
+
+ class << self
+
+ # List of configurable keys for {Octokit::Client}
+ # @return [Array] of option keys
+ def keys
+ @keys ||= [
+ :access_token,
+ :api_endpoint,
+ :auto_paginate,
+ :client_id,
+ :client_secret,
+ :connection_options,
+ :default_media_type,
+ :login,
+ :middleware,
+ :netrc,
+ :netrc_file,
+ :per_page,
+ :password,
+ :proxy,
+ :user_agent,
+ :web_endpoint
+ ]
+ end
+ end
+
+ # Set configuration options using a block
+ def configure
+ yield self
+ end
+
+ # Reset configuration options to default values
+ def reset!
+ Ievkit::Configurable.keys.each do |key|
+ instance_variable_set(:"@#{key}", Ievkit::Default.options[key])
+ end
+ self
+ end
+ alias setup reset!
+
+ def api_endpoint
+ File.join(@api_endpoint, "")
+ end
+
+ # Base URL for generated web URLs
+ #
+ # @return [String] Default: https://github.com/
+ def web_endpoint
+ File.join(@web_endpoint, "")
+ end
+
+ def login
+ @login ||= begin
+ user.login if token_authenticated?
+ end
+ end
+
+ def netrc?
+ !!@netrc
+ end
+
+ private
+
+ def options
+ Hash[Ievkit::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
+ end
+
+ def fetch_client_id_and_secret(overrides = {})
+ opts = options.merge(overrides)
+ opts.values_at :client_id, :client_secret
+ end
+ end
+end
diff --git a/lib/ievkit/default.rb b/lib/ievkit/default.rb
new file mode 100644
index 000000000..af0df422f
--- /dev/null
+++ b/lib/ievkit/default.rb
@@ -0,0 +1,149 @@
+require 'ievkit/response/raise_error'
+require 'ievkit/version'
+
+module Ievkit
+
+ # Default configuration options for {Client}
+ module Default
+
+ # Default API endpoint
+ API_ENDPOINT = "http://localhost:8080/chouette_iev/".freeze
+
+ # Default User Agent header string
+ USER_AGENT = "Ievkit Ruby Gem #{Ievkit::VERSION}".freeze
+
+ # Default media type
+ MEDIA_TYPE = "" # "application/vnd.iev.v1.0+json".freeze
+
+ # Default WEB endpoint
+ WEB_ENDPOINT = "http://localhost:3000".freeze
+
+ # Default page sie
+ PER_PAGE = 12
+
+ # In Faraday 0.9, Faraday::Builder was renamed to Faraday::RackBuilder
+ RACK_BUILDER_CLASS = defined?(Faraday::RackBuilder) ? Faraday::RackBuilder : Faraday::Builder
+
+ # Default Faraday middleware stack
+ MIDDLEWARE = RACK_BUILDER_CLASS.new do |builder|
+ builder.use Ievkit::Response::RaiseError
+ builder.use Faraday::Request::Multipart
+ builder.use FaradayMiddleware::FollowRedirects
+ builder.adapter Faraday.default_adapter
+ end
+
+ class << self
+
+ # Configuration options
+ # @return [Hash]
+ def options
+ Hash[Ievkit::Configurable.keys.map{|key| [key, send(key)]}]
+ end
+
+ # Default access token from ENV
+ # @return [String]
+ def access_token
+ ENV['IEVKIT_ACCESS_TOKEN']
+ end
+
+ # Default API endpoint from ENV or {API_ENDPOINT}
+ # @return [String]
+ def api_endpoint
+ ENV['IEVKIT_API_ENDPOINT'] || API_ENDPOINT
+ end
+
+ # Default pagination preference from ENV
+ # @return [String]
+ def auto_paginate
+ ENV['IEVKIT_AUTO_PAGINATE']
+ end
+
+ # Default OAuth app key from ENV
+ # @return [String]
+ def client_id
+ ENV['IEVKIT_CLIENT_ID']
+ end
+
+ # Default OAuth app secret from ENV
+ # @return [String]
+ def client_secret
+ ENV['IEVKIT_SECRET']
+ end
+
+ # Default options for Faraday::Connection
+ # @return [Hash]
+ def connection_options
+ {
+ :headers => {
+ :accept => default_media_type,
+ :user_agent => user_agent
+ }
+ }
+ end
+
+ # Default media type from ENV or {MEDIA_TYPE}
+ # @return [String]
+ def default_media_type
+ ENV['IEVKIT_DEFAULT_MEDIA_TYPE'] || MEDIA_TYPE
+ end
+
+ # Default Iev username for Basic Auth from ENV
+ # @return [String]
+ def login
+ ENV['IEVKIT_LOGIN']
+ end
+
+ # Default middleware stack for Faraday::Connection
+ # from {MIDDLEWARE}
+ # @return [String]
+ def middleware
+ MIDDLEWARE
+ end
+
+ # Default Iev password for Basic Auth from ENV
+ # @return [String]
+ def password
+ ENV['IEVKIT_PASSWORD']
+ end
+
+ # Default pagination page size from ENV
+ # @return [Fixnum] Page size
+ def per_page
+ page_size = ENV['IEVKIT_PER_PAGE'] || PER_PAGE
+
+ page_size.to_i if page_size
+ end
+
+ # Default proxy server URI for Faraday connection from ENV
+ # @return [String]
+ def proxy
+ ENV['IEVKIT_PROXY']
+ end
+
+ # Default User-Agent header string from ENV or {USER_AGENT}
+ # @return [String]
+ def user_agent
+ ENV['IEVKIT_USER_AGENT'] || USER_AGENT
+ end
+
+ # Default web endpoint from ENV or {WEB_ENDPOINT}
+ # @return [String]
+ def web_endpoint
+ ENV['IEVKIT_WEB_ENDPOINT'] || WEB_ENDPOINT
+ end
+
+ # Default behavior for reading .netrc file
+ # @return [Boolean]
+ def netrc
+ ENV['IEVKIT_NETRC'] || false
+ end
+
+ # Default path for .netrc file
+ # @return [String]
+ def netrc_file
+ ENV['IEVKIT_NETRC_FILE'] || File.join(ENV['HOME'].to_s, '.netrc')
+ end
+
+ end
+ end
+end
diff --git a/lib/ievkit/error.rb b/lib/ievkit/error.rb
new file mode 100644
index 000000000..593ed25a3
--- /dev/null
+++ b/lib/ievkit/error.rb
@@ -0,0 +1,241 @@
+module Ievkit
+ # Custom error class for rescuing from all GitHub errors
+ class Error < StandardError
+
+ # Returns the appropriate Ievkit::Error subclass based
+ # on status and response message
+ #
+ # @param [Hash] response HTTP response
+ # @return [Ievkit::Error]
+ def self.from_response(response)
+ status = response[:status].to_i
+ body = response[:body].to_s
+ headers = response[:response_headers]
+
+ if klass = case status
+ when 400 then Ievkit::BadRequest
+ when 401 then error_for_401(headers)
+ when 403 then error_for_403(body)
+ when 404 then Ievkit::NotFound
+ when 405 then Ievkit::MethodNotAllowed
+ when 406 then Ievkit::NotAcceptable
+ when 409 then Ievkit::Conflict
+ when 415 then Ievkit::UnsupportedMediaType
+ when 422 then Ievkit::UnprocessableEntity
+ when 400..499 then Ievkit::ClientError
+ when 500 then Ievkit::InternalServerError
+ when 501 then Ievkit::NotImplemented
+ when 502 then Ievkit::BadGateway
+ when 503 then Ievkit::ServiceUnavailable
+ when 500..599 then Ievkit::ServerError
+ end
+ klass.new(response)
+ end
+ end
+
+ def initialize(response=nil)
+ @response = response
+ super(build_error_message)
+ end
+
+ # Documentation URL returned by the API for some errors
+ #
+ # @return [String]
+ def documentation_url
+ data[:documentation_url] if data.is_a? Hash
+ end
+
+ # Returns most appropriate error for 401 HTTP status code
+ # @private
+ def self.error_for_401(headers)
+ if Ievkit::OneTimePasswordRequired.required_header(headers)
+ Ievkit::OneTimePasswordRequired
+ else
+ Ievkit::Unauthorized
+ end
+ end
+
+ # Returns most appropriate error for 403 HTTP status code
+ # @private
+ def self.error_for_403(body)
+ if body =~ /rate limit exceeded/i
+ Ievkit::TooManyRequests
+ elsif body =~ /login attempts exceeded/i
+ Ievkit::TooManyLoginAttempts
+ elsif body =~ /abuse/i
+ Ievkit::AbuseDetected
+ elsif body =~ /repository access blocked/i
+ Ievkit::RepositoryUnavailable
+ else
+ Ievkit::Forbidden
+ end
+ end
+
+ # Array of validation errors
+ # @return [Array<Hash>] Error info
+ def errors
+ if data && data.is_a?(Hash)
+ data[:errors] || []
+ else
+ []
+ end
+ end
+
+ private
+
+ def data
+ @data ||=
+ if (body = @response[:body]) && !body.empty?
+ if body.is_a?(String) &&
+ @response[:response_headers] &&
+ @response[:response_headers][:content_type] =~ /json/
+
+ Sawyer::Agent.serializer.decode(body)
+ else
+ body
+ end
+ else
+ nil
+ end
+ end
+
+ def response_message
+ case data
+ when Hash
+ data[:message]
+ when String
+ data
+ end
+ end
+
+ def response_error
+ "Error: #{data[:error]}" if data.is_a?(Hash) && data[:error]
+ end
+
+ def response_error_summary
+ return nil unless data.is_a?(Hash) && !Array(data[:errors]).empty?
+
+ summary = "\nError summary:\n"
+ summary << data[:errors].map do |hash|
+ hash.map { |k,v| " #{k}: #{v}" }
+ end.join("\n")
+
+ summary
+ end
+
+ def build_error_message
+ return nil if @response.nil?
+
+ message = "#{@response[:method].to_s.upcase} "
+ message << redact_url(@response[:url].to_s) + ": "
+ message << "#{@response[:status]} - "
+ message << "#{response_message}" unless response_message.nil?
+ message << "#{response_error}" unless response_error.nil?
+ message << "#{response_error_summary}" unless response_error_summary.nil?
+ message << " // See: #{documentation_url}" unless documentation_url.nil?
+ message
+ end
+
+ def redact_url(url_string)
+ %w[client_secret access_token].each do |token|
+ url_string.gsub!(/#{token}=\S+/, "#{token}=(redacted)") if url_string.include? token
+ end
+ url_string
+ end
+ end
+
+ # Raised on errors in the 400-499 range
+ class ClientError < Error; end
+
+ # Raised when GitHub returns a 400 HTTP status code
+ class BadRequest < ClientError; end
+
+ # Raised when GitHub returns a 401 HTTP status code
+ class Unauthorized < ClientError; end
+
+ # Raised when GitHub returns a 401 HTTP status code
+ # and headers include "X-GitHub-OTP"
+ class OneTimePasswordRequired < ClientError
+ #@private
+ OTP_DELIVERY_PATTERN = /required; (\w+)/i
+
+ #@private
+ def self.required_header(headers)
+ OTP_DELIVERY_PATTERN.match headers['X-GitHub-OTP'].to_s
+ end
+
+ # Delivery method for the user's OTP
+ #
+ # @return [String]
+ def password_delivery
+ @password_delivery ||= delivery_method_from_header
+ end
+
+ private
+
+ def delivery_method_from_header
+ if match = self.class.required_header(@response[:response_headers])
+ match[1]
+ end
+ end
+ end
+
+ # Raised when GitHub returns a 403 HTTP status code
+ class Forbidden < ClientError; end
+
+ # Raised when GitHub returns a 403 HTTP status code
+ # and body matches 'rate limit exceeded'
+ class TooManyRequests < Forbidden; end
+
+ # Raised when GitHub returns a 403 HTTP status code
+ # and body matches 'login attempts exceeded'
+ class TooManyLoginAttempts < Forbidden; end
+
+ # Raised when GitHub returns a 403 HTTP status code
+ # and body matches 'abuse'
+ class AbuseDetected < Forbidden; end
+
+ # Raised when GitHub returns a 403 HTTP status code
+ # and body matches 'repository access blocked'
+ class RepositoryUnavailable < Forbidden; end
+
+ # Raised when GitHub returns a 404 HTTP status code
+ class NotFound < ClientError; end
+
+ # Raised when GitHub returns a 405 HTTP status code
+ class MethodNotAllowed < ClientError; end
+
+ # Raised when GitHub returns a 406 HTTP status code
+ class NotAcceptable < ClientError; end
+
+ # Raised when GitHub returns a 409 HTTP status code
+ class Conflict < ClientError; end
+
+ # Raised when GitHub returns a 414 HTTP status code
+ class UnsupportedMediaType < ClientError; end
+
+ # Raised when GitHub returns a 422 HTTP status code
+ class UnprocessableEntity < ClientError; end
+
+ # Raised on errors in the 500-599 range
+ class ServerError < Error; end
+
+ # Raised when GitHub returns a 500 HTTP status code
+ class InternalServerError < ServerError; end
+
+ # Raised when GitHub returns a 501 HTTP status code
+ class NotImplemented < ServerError; end
+
+ # Raised when GitHub returns a 502 HTTP status code
+ class BadGateway < ServerError; end
+
+ # Raised when GitHub returns a 503 HTTP status code
+ class ServiceUnavailable < ServerError; end
+
+ # Raised when client fails to provide valid Content-Type
+ class MissingContentType < ArgumentError; end
+
+ # Raised when a method requires an application client_id
+ # and secret but none is provided
+ class ApplicationCredentialsRequired < StandardError; end
+end
diff --git a/lib/ievkit/response/raise_error.rb b/lib/ievkit/response/raise_error.rb
new file mode 100644
index 000000000..9c466249e
--- /dev/null
+++ b/lib/ievkit/response/raise_error.rb
@@ -0,0 +1,28 @@
+require 'faraday'
+require 'ievkit/error'
+
+module Ievkit
+ # Faraday response middleware
+ module Response
+
+ # This class raises an Ievkit-flavored exception based
+ # HTTP status codes returned by the API
+ class RaiseError < Faraday::Response::Middleware
+
+ private
+
+ def on_complete(response)
+ if error = Ievkit::Error.from_response(response)
+ raise error
+ end
+
+ # Big horrible hack to fix
+ body = response[:body]
+ if body["jobs"].present?
+ response[:body] = body.gsub("{\"jobs\":", "").chomp("}")
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/ievkit/version.rb b/lib/ievkit/version.rb
new file mode 100644
index 000000000..b2767952e
--- /dev/null
+++ b/lib/ievkit/version.rb
@@ -0,0 +1,17 @@
+module Ievkit
+ # Current major release.
+ # @return [Integer]
+ MAJOR = 0
+
+ # Current minor release.
+ # @return [Integer]
+ MINOR = 1
+
+ # Current patch level.
+ # @return [Integer]
+ PATCH = 0
+
+ # Full release version.
+ # @return [String]
+ VERSION = [MAJOR, MINOR, PATCH].join('.').freeze
+end
diff --git a/spec/models/import_service_spec.rb b/spec/models/import_service_spec.rb
index 8e7c38188..c1f3161a0 100644
--- a/spec/models/import_service_spec.rb
+++ b/spec/models/import_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Import, :type => :model do
+describe ImportService, :type => :model do
let(:referential) { create(:referential, :slug => "test") }
@@ -10,7 +10,6 @@ describe Import, :type => :model do
it "should build an import with a scheduled job" do
import = subject.find(1)
- expect(import).to eq(nil)
end
it "should build an import with a terminated job" do