Skip to content

Instantly share code, notes, and snippets.

@foton
Created May 2, 2018 08:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save foton/9a37127f6bda643866960e89678da460 to your computer and use it in GitHub Desktop.
Save foton/9a37127f6bda643866960e89678da460 to your computer and use it in GitHub Desktop.
Base Service class `.call` with any type of params, returning instace with `#success?`,`#failure?`, `#errors`, `#result` methods.
module Service
class Base
attr_reader :result
def self.call(*args, **keyword_args)
new(*args, **keyword_args).tap do |service|
service.instance_variable_set(
"@result",
service.call
)
end
end
def call
raise NotImplementedError
end
def success?
!failure?
end
def failure?
errors.any?
end
def errors
@errors ||= Errors.new
end
end
end
module Service
class Errors < Hash
def add(key, value, _opts = {})
self[key] ||= []
self[key] << value
self[key].uniq!
end
def add_multiple_errors(errors_hash)
errors_hash.each do |key, values|
values.each { |value| add key, value }
end
end
def each
each_key do |field|
self[field].each { |message| yield field, message }
end
end
end
end
# frozen_string_literal: true
require 'uri'
require 'net/sftp'
module Service
module Sftp
class Uploader < Service::Base
STATUS_START = :start
STATUS_UPLOAD_RUNNING = :running
STATUS_UPLOADED = :uploaded
STATUS_UPLOAD_FAILED = :upload_failed
def initialize(file_path:, config:)
@from = file_path
@config = config
end
def call
@status = STATUS_START
if validate_config && validate_file
do_sftp_upload
else
@status = STATUS_UPLOAD_FAILED
end
@result = info
end
private
def validate_config
if @config['uri'].blank?
errors.add(:config, "SFTP config have empty 'uri' key")
@sftp_uri = OpenStruct.new(host: '', port: 0, path: '')
else
@sftp_uri = URI(@config['uri'])
# these can have empty values, but keys must be present
%w[username password].each do |key|
errors.add(:config, "SFTP config do not have #{key} key") unless @config.key?(key)
end
end
errors[:config].nil?
end
def validate_file
return true if File.file?(@from)
errors.add(:file, "File '#{@from}' was not found")
end
def do_sftp_upload
@status = STATUS_UPLOAD_RUNNING
upload!
@status = STATUS_UPLOADED
rescue Net::SFTP::StatusException => e
@status = STATUS_UPLOAD_FAILED
errors.add(:network, e.message)
end
def info
{ from: @from.to_s,
to: "sftp://#{@sftp_uri.host}:#{@sftp_uri.port || 115}#{to_path}",
status: @status }
end
def upload!
within_connection do |conn|
conn.upload!(@from.to_s, to_path)
end
end
def within_connection
Net::SFTP.start(sftp_uri.host,
config['username'],
port: (sftp_uri.port || 115), password: config['password']) do |conn|
yield conn
end
end
def filename
File.basename(@from.to_s)
end
def to_path
(@sftp_uri.path + '/' + filename).gsub('//', '/')
end
end
end
end
# frozen_string_literal: true
require 'rails_helper.rb'
RSpec.describe Service::Sftp::Uploader do
let(:service_class) { Service::Sftp::Uploader }
before(:context) do
@sftp_config = { 'uri' => Figaro.env.NIKE_SFTP_URI,
'username' => Figaro.env.NIKE_SFTP_USERNAME,
'password' => Figaro.env.NIKE_SFTP_PASSWORD }
@valid_path = __FILE__ # this file always exists, right?
@filename = File.basename(@valid_path)
end
it 'fails if config is not present' do
expect_any_instance_of(service_class).not_to receive(:upload!)
uploader = service_class.call(file_path: @valid_path, config: {})
expect(uploader.failure?).to be(true)
expect(uploader.result).to eq(from: @valid_path,
to: 'sftp://:0/' + @filename,
status: :upload_failed)
expect(uploader.errors[:config]).to eq(["SFTP config have empty 'uri' key"])
end
it 'uploads the file from path' do
expect_any_instance_of(service_class).to receive(:upload!).and_return(true)
uploader = service_class.call(file_path: @valid_path, config: @sftp_config)
expect(uploader.success?).to be(true)
expect(uploader.result).to eq(from: @valid_path,
to: @sftp_config['uri'].gsub(%r{\/$}, '') + '/' + @filename,
status: :uploaded)
end
it 'raise error if file do not exists' do
filename = 'my_file.xml'
not_valid_path = '/deep/into/dark/' + filename
expect_any_instance_of(service_class).not_to receive(:upload!)
uploader = service_class.call(file_path: not_valid_path, config: @sftp_config)
expect(uploader.success?).to be(false)
expect(uploader.failure?).to be(true)
expect(uploader.result).to eq(from: not_valid_path,
to: @sftp_config['uri'].gsub(%r{\/$}, '') + '/' + filename,
status: :upload_failed)
expect(uploader.errors[:file]).to eq(["File '#{not_valid_path}' was not found"])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment