remote-controller 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org/'
2
+
3
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by Evgeny Myasishchev
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,41 @@
1
+ = Remote-controller
2
+ Library to simplify remote controller actions invocation.
3
+
4
+ == Features
5
+ Can invoke actions of remote controllers. Can send any data including files. Does not supports REST like controllers yet.
6
+
7
+ === Supported HTTP methods
8
+ * GET
9
+ * POST
10
+ * Multipart POST
11
+
12
+ == Example
13
+
14
+ #Reusable cookies container to persist cookies between different remote controllers of the same remote application
15
+ cookies_container = RemoteController::CookiesContainer.new
16
+
17
+ #Sessions controller is bound to 'http://my-application.com/sessions' url
18
+ sessions = RemoteController::Base.new('http://my-application.com/sessions')
19
+ sessions.cookies_container = cookies_container
20
+
21
+ # GET: http://my-application.com/sessions/authenticity_token
22
+ # Response is string containing authenticity token. It will also start a new session.
23
+ # CookiesContainer will hold session cookies (and all other cookies) automatically
24
+ authenticity_token = sessions.authenticity_token
25
+
26
+ # POST http://my-application.com/sessions/authorize
27
+ # POST body:
28
+ # => authenticity_token=authenticity+token&login=admin&password=password
29
+ # => in case response is not 200 then exception is thrown
30
+ sessions.authorize(:post, :authenticity_token => authenticity_token, :login => "admin", :password => "password")
31
+
32
+ #Reports controller is bound to 'http://my-application.com/reports'
33
+ reports = RemoteController::Base.new('http://my-application.com/reports')
34
+ sessions.cookies_container = cookies_container #Reusing cookies container to preserve the same session...
35
+
36
+ # POST http://my-application.com/sessions/create
37
+ # POST body is in multipart POST form. It has two parts:
38
+ # => name = New report
39
+ # => attachment = sample1.txt file
40
+ attachment = RemoteController.file_part(File.expand_path("../sample1.txt", __FILE__), "text/plain")
41
+ reports.create(:multipart, :name => "New report", :attachment => attachment)
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the remote_controller plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the remote_controller plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'Remote-controller'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
@@ -0,0 +1 @@
1
+ require 'remote_controller'
@@ -0,0 +1,11 @@
1
+ module RemoteController
2
+ require 'remote_controller/multipart'
3
+ autoload :CookiesContainer, 'remote_controller/cookies_container'
4
+ autoload :CGIHelpers, 'remote_controller/cgi_helpers'
5
+ autoload :Base, 'remote_controller/base'
6
+ autoload :VERSION, 'remote_controller/version'
7
+
8
+ def self.file_part(file_path, content_type)
9
+ RemoteController::Multipart::UploadIO.new(file_path, content_type)
10
+ end
11
+ end
@@ -0,0 +1,91 @@
1
+ require 'net/http'
2
+
3
+ class RemoteController::Base
4
+ include RemoteController::CGIHelpers
5
+
6
+ class RemoteControllerError < StandardError #:nodoc:
7
+ end
8
+
9
+ def initialize(url)
10
+ @url = url
11
+ @error_handlers = []
12
+ end
13
+
14
+ def cookies_container
15
+ @cookies_container = @cookies_container || RemoteController::CookiesContainer.new
16
+ end
17
+
18
+ def cookies_container=(container)
19
+ @cookies_container = container
20
+ end
21
+
22
+ def on_error(&block)
23
+ raise "No block given" unless block_given?
24
+ @error_handlers << block
25
+ end
26
+
27
+ def method_missing(symbol, *args)
28
+
29
+ #Following stuff is required to send requests to remote controllers
30
+ action_name = symbol.to_s
31
+ method = :get
32
+ parameters = {}
33
+
34
+ #Processing arguments
35
+ if args.length > 0
36
+ method = args.shift if([:get, :post, :multipart].include?(args[0]))
37
+ end
38
+ if args.length > 1
39
+ raise RemoteControllerError.new("Invalid arguments.")
40
+ elsif args.length == 1 && (args[0].is_a?(Hash) || args[0].is_a?(String))
41
+ parameters = args.shift
42
+ end
43
+
44
+ #Now we can send the request
45
+ send_request(action_name, method, parameters)
46
+ end
47
+
48
+ private
49
+ def send_request(action_name, method, parameters)
50
+
51
+ uri = URI.parse(@url)
52
+ action_path = "#{uri.path}/#{action_name}"
53
+
54
+ request = nil
55
+
56
+ case method
57
+ when :get
58
+ request = Net::HTTP::Get.new("#{action_path}?#{to_param(parameters)}")
59
+ when :post
60
+ request = Net::HTTP::Post.new(action_path)
61
+ request.body = to_param(parameters)
62
+ when :multipart
63
+ request = Net::HTTP::Post::Multipart.new(action_path, parameters)
64
+ else
65
+ raise RemoteControllerError.new("Unsupported method")
66
+ end
67
+ initialize_request(request)
68
+ response = Net::HTTP.start(uri.host, uri.port) {|http|
69
+ http.request(request)
70
+ }
71
+ process_headers(response)
72
+ begin
73
+ response.value #Will raise error in case response is not 2xx
74
+ rescue
75
+ @error_handlers.each { |e| e.call($!) }
76
+ raise $!
77
+ end
78
+ response.body
79
+ end
80
+
81
+ def initialize_request(request)
82
+ request["cookie"] = cookies_container.to_header unless cookies_container.empty?
83
+ end
84
+
85
+ def process_headers(response)
86
+ cookies = response["set-cookie"]
87
+ if(cookies)
88
+ cookies_container.process(cookies)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,10 @@
1
+ module RemoteController::CGIHelpers
2
+ require 'cgi'
3
+
4
+ def to_param(object)
5
+ return CGI.escape(object.to_s) unless object.is_a? Hash
6
+ object.collect do |key, value|
7
+ "#{CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') }}=#{CGI.escape(value.to_s)}"
8
+ end.sort * '&'
9
+ end
10
+ end
@@ -0,0 +1,31 @@
1
+ require 'webrick/cookie'
2
+
3
+ class RemoteController::CookiesContainer
4
+ def initialize()
5
+ @cookies = {}
6
+ end
7
+
8
+ def [](name)
9
+ @cookies[name]
10
+ end
11
+
12
+ def process(set_cookie_string)
13
+ parsed = WEBrick::Cookie.parse_set_cookies(set_cookie_string)
14
+ parsed.each do |cookie|
15
+ @cookies[cookie.name] = cookie
16
+ end
17
+ end
18
+
19
+ def to_header
20
+ result = ""
21
+ @cookies.each do |name, cookie|
22
+ result << "#{name}=#{cookie.value}; "
23
+ end
24
+ result = result.strip!
25
+ result
26
+ end
27
+
28
+ def empty?
29
+ @cookies.size == 0
30
+ end
31
+ end
@@ -0,0 +1,64 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+
30
+ #Example:
31
+ # url = URI.parse('http://www.example.com/upload')
32
+ # File.open("./image.jpg") do |jpg|
33
+ # req = Net::HTTP::Post::Multipart.new url.path,
34
+ # "file" => UploadIO.new(jpg, "image/jpeg", "image.jpg")
35
+ # res = Net::HTTP.start(url.host, url.port) do |http|
36
+ # http.request(req)
37
+ # end
38
+ # end
39
+
40
+
41
+ require 'net/http'
42
+ require 'stringio'
43
+ require 'cgi'
44
+
45
+ module RemoteController::Multipart end
46
+
47
+ require 'remote_controller/multipart/parts'
48
+ require 'remote_controller/multipart/composite_io'
49
+ require 'remote_controller/multipart/multipartable'
50
+
51
+ module Net #:nodoc:
52
+ class HTTP #:nodoc:
53
+ class Put
54
+ class Multipart < Put
55
+ include RemoteController::Multipart::Multipartable
56
+ end
57
+ end
58
+ class Post #:nodoc:
59
+ class Multipart < Post
60
+ include RemoteController::Multipart::Multipartable
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,111 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ # Concatenate together multiple IO objects into a single, composite IO object
30
+ # for purposes of reading as a single stream.
31
+ #
32
+ # Usage:
33
+ #
34
+ # crio = CompositeReadIO.new(StringIO.new('one'), StringIO.new('two'), StringIO.new('three'))
35
+ # puts crio.read # => "onetwothree"
36
+ #
37
+ class RemoteController::Multipart::CompositeReadIO
38
+ # Create a new composite-read IO from the arguments, all of which should
39
+ # respond to #read in a manner consistent with IO.
40
+ def initialize(*ios)
41
+ @ios = ios.flatten
42
+ end
43
+
44
+ # Read from the IO object, overlapping across underlying streams as necessary.
45
+ def read(amount = nil, buf = nil)
46
+ buffer = buf || ''
47
+ done = if amount; nil; else ''; end
48
+ partial_amount = amount
49
+
50
+ loop do
51
+ result = done
52
+
53
+ while !@ios.empty? && (result = @ios.first.read(partial_amount)) == done
54
+ @ios.shift
55
+ end
56
+
57
+ buffer << result if result
58
+ partial_amount -= result.length if partial_amount && result != done
59
+
60
+ break if partial_amount && partial_amount <= 0
61
+ break if result == done
62
+ end
63
+
64
+ if buffer.length > 0
65
+ buffer
66
+ else
67
+ done
68
+ end
69
+ end
70
+ end
71
+
72
+ # Convenience methods for dealing with files and IO that are to be uploaded.
73
+ module RemoteController::Multipart::UploadIO
74
+ # Create an upload IO suitable for including in the params hash of a
75
+ # Net::HTTP::Post::Multipart.
76
+ #
77
+ # Can take two forms. The first accepts a filename and content type, and
78
+ # opens the file for reading (to be closed by finalizer). The second accepts
79
+ # an already-open IO, but also requires a third argument, the filename from
80
+ # which it was opened.
81
+ #
82
+ # UploadIO.new("file.txt", "text/plain")
83
+ # UploadIO.new(file_io, "text/plain", "file.txt")
84
+ def self.new(filename_or_io, content_type, filename = nil)
85
+ io = filename_or_io
86
+ unless io.respond_to? :read
87
+ io = File.open(filename_or_io)
88
+ filename = filename_or_io
89
+ end
90
+ convert!(io, content_type, File.basename(filename), filename)
91
+ io
92
+ end
93
+
94
+ # Enhance an existing IO for including in the params hash of a
95
+ # Net::HTTP::Post::Multipart by adding #content_type, #original_filename,
96
+ # and #local_path methods to the object's singleton class.
97
+ def self.convert!(io, content_type, original_filename, local_path)
98
+ io.instance_eval(<<-EOS, __FILE__, __LINE__)
99
+ def content_type
100
+ "#{content_type}"
101
+ end
102
+ def original_filename
103
+ "#{original_filename}"
104
+ end
105
+ def local_path
106
+ "#{local_path}"
107
+ end
108
+ EOS
109
+ io
110
+ end
111
+ end
@@ -0,0 +1,40 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ module RemoteController::Multipart::Multipartable
30
+ DEFAULT_BOUNDARY = "-----------1a5ef2de8917334afe03269e42657dea596c90fb"
31
+ def initialize(path, params, headers={}, boundary = DEFAULT_BOUNDARY)
32
+ super(path, headers)
33
+ parts = params.map {|k,v| RemoteController::Multipart::Parts::Part.new(boundary, k, v)}
34
+ parts << RemoteController::Multipart::Parts::EpiloguePart.new(boundary)
35
+ ios = parts.map{|p| p.to_io }
36
+ self.set_content_type("multipart/form-data", { "boundary" => boundary })
37
+ self.content_length = parts.inject(0) {|sum,i| sum + i.length }
38
+ self.body_stream = RemoteController::Multipart::CompositeReadIO.new(*ios)
39
+ end
40
+ end
@@ -0,0 +1,6 @@
1
+ module RemoteController::Multipart::Parts end
2
+
3
+ require 'remote_controller/multipart/parts/part'
4
+ require 'remote_controller/multipart/parts/param_part'
5
+ require 'remote_controller/multipart/parts/file_part'
6
+ require 'remote_controller/multipart/parts/epilogue_part'
@@ -0,0 +1,36 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ # Represents the epilogue or closing boundary.
30
+ class RemoteController::Multipart::Parts::EpiloguePart
31
+ include RemoteController::Multipart::Parts::Part
32
+ def initialize(boundary)
33
+ @part = "--#{boundary}--\r\n"
34
+ @io = StringIO.new(@part)
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ # Represents a part to be filled from file IO.
30
+ class RemoteController::Multipart::Parts::FilePart
31
+ include RemoteController::Multipart::Parts::Part
32
+ attr_reader :length
33
+ def initialize(boundary, name, io)
34
+ file_length = io.respond_to?(:length) ? io.length : File.size(io.path)
35
+ @head = build_head(boundary, name, io.original_filename, io.content_type, file_length)
36
+ @length = @head.length + file_length
37
+ @io = RemoteController::Multipart::CompositeReadIO.new(StringIO.new(@head), io, StringIO.new("\r\n"))
38
+ end
39
+
40
+ def build_head(boundary, name, filename, type, content_len)
41
+ part = ''
42
+ part << "--#{boundary}\r\n"
43
+ part << "Content-Disposition: form-data; name=\"#{name.to_s}\"; filename=\"#{filename}\"\r\n"
44
+ part << "Content-Length: #{content_len}\r\n"
45
+ part << "Content-Type: #{type}\r\n"
46
+ part << "Content-Transfer-Encoding: binary\r\n"
47
+ part << "\r\n"
48
+ end
49
+ end
@@ -0,0 +1,43 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ class RemoteController::Multipart::Parts::ParamPart
30
+ include RemoteController::Multipart::Parts::Part
31
+ def initialize(boundary, name, value)
32
+ @part = build_part(boundary, name, value)
33
+ @io = StringIO.new(@part)
34
+ end
35
+
36
+ def build_part(boundary, name, value)
37
+ part = ''
38
+ part << "--#{boundary}\r\n"
39
+ part << "Content-Disposition: form-data; name=\"#{name.to_s}\"\r\n"
40
+ part << "\r\n"
41
+ part << "#{value}\r\n"
42
+ end
43
+ end
@@ -0,0 +1,45 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ module RemoteController::Multipart::Parts::Part
30
+ def self.new(boundary, name, value)
31
+ if value.respond_to? :content_type
32
+ RemoteController::Multipart::Parts::FilePart.new(boundary, name, value)
33
+ else
34
+ RemoteController::Multipart::Parts::ParamPart.new(boundary, name, value)
35
+ end
36
+ end
37
+
38
+ def length
39
+ @part.length
40
+ end
41
+
42
+ def to_io
43
+ @io
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module RemoteController
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,47 @@
1
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../../../Gemfile', __FILE__)
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'http-testing'
5
+ require 'remote-controller'
6
+
7
+ address = "http://localhost:30013"
8
+ context = HttpTesting::Context.start(30013) do |request, response|
9
+ puts "#{request.request_method} #{address}#{request.path}"
10
+ puts request.body if request.body
11
+ puts ""
12
+
13
+ if request.path == "/sessions/authenticity_token"
14
+ response.body = "authenticity token"
15
+ end
16
+ end
17
+
18
+ #Reusable cookies container to persist cookies between different remote controllers of the same remote application
19
+ cookies_container = RemoteController::CookiesContainer.new
20
+
21
+ #Sessions controller is bound to 'http://localhost:300013/sessions' url
22
+ sessions = RemoteController::Base.new(URI::join(address, "/sessions").to_s)
23
+ sessions.cookies_container = cookies_container
24
+
25
+ # GET: http://localhost:300013/sessions/authenticity_token
26
+ # Response is string containing authenticity token. It will also start a new session.
27
+ # CookiesContainer will hold session cookies (and all other cookies) automatically
28
+ authenticity_token = sessions.authenticity_token
29
+
30
+ # POST http://localhost:300013/sessions/authorize
31
+ # POST body:
32
+ # => authenticity_token=authenticity+token&login=admin&password=password
33
+ # => in case response is not 200 then exception is thrown
34
+ sessions.authorize(:post, :authenticity_token => authenticity_token, :login => "admin", :password => "password")
35
+
36
+ #Reports controller is bound to: http://localhost:300013/reports
37
+ reports = RemoteController::Base.new(URI::join(address, "/reports").to_s)
38
+ sessions.cookies_container = cookies_container #Reusing cookies container to preserve the same session...
39
+
40
+ # POST http://localhost:300013/sessions/create
41
+ # POST body is in multipart post form. It has two parts:
42
+ # => name = New report
43
+ # => attachment = sample1.txt file
44
+ attachment = RemoteController.file_part(File.expand_path("../sample1.txt", __FILE__), "text/plain")
45
+ reports.create(:multipart, :name => "New report", :attachment => attachment)
46
+
47
+ context.wait
@@ -0,0 +1 @@
1
+ Txt file to include as multipart HTTP POST
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/remote_controller/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'remote-controller'
5
+ s.version = RemoteController::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ['Evgeny Myasishchev']
8
+ s.email = ['evgeny.myasishchev@gmail.com']
9
+ s.homepage = 'http://github.com/evgeny-myasishchev/remote-controller'
10
+ s.summary = %q{Helps to invoke actions of remote controllers.}
11
+ s.description = %q{Library to simplify remote controller actions invocation.}
12
+
13
+ s.rubyforge_project = 'remote-controller'
14
+
15
+ s.add_development_dependency 'http-testing', '>= 0.1.3'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
19
+ s.require_paths = ['lib']
20
+ end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RemoteControllerActionArgsTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
7
+
8
+ #Preserving send_request method. We'll need to restore it later
9
+ @origin_method = @controller.method(:send_request)
10
+ end
11
+
12
+ def teardown
13
+ #Restoring original method. Required to make subsequent tests work
14
+ @controller.class.send(:define_method, :send_request, @origin_method)
15
+ end
16
+
17
+ #The test have to be in a separate class because it changes :send_requrest method
18
+ def test_arguments_parsing
19
+ instance = self
20
+
21
+ @controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
22
+ instance.assert_equal "no_args_get", action_name
23
+ instance.assert_equal :get, method
24
+ instance.assert_equal 0, parameters.size
25
+ end
26
+ @controller.no_args_get
27
+
28
+ @controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
29
+ instance.assert_equal "no_args_post", action_name
30
+ instance.assert_equal :post, method
31
+ instance.assert_equal 0, parameters.size
32
+ end
33
+ @controller.no_args_post(:post)
34
+
35
+ @controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
36
+ instance.assert_equal "post_with_args", action_name
37
+ instance.assert_equal :post, method
38
+ instance.assert_equal({:id => 10}, parameters)
39
+ end
40
+ @controller.post_with_args(:post, {:id => 10})
41
+
42
+ @controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
43
+ instance.assert_equal "post_raw_string", action_name
44
+ instance.assert_equal :post, method
45
+ instance.assert_equal("raw_string_data", parameters)
46
+ end
47
+ @controller.post_raw_string(:post, "raw_string_data")
48
+
49
+ @controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
50
+ instance.assert_equal "multipart_with_args", action_name
51
+ instance.assert_equal :multipart, method
52
+ instance.assert_equal({:id => 10}, parameters)
53
+ end
54
+ @controller.multipart_with_args(:multipart, {:id => 10})
55
+
56
+ @controller.class.send(:define_method, :send_request) do |action_name, method, parameters|
57
+ instance.assert_equal "get_with_args", action_name
58
+ instance.assert_equal :get, method
59
+ instance.assert_equal({:id => 10}, parameters)
60
+ end
61
+ #Default method is get
62
+ @controller.get_with_args({:id => 10})
63
+
64
+ assert_raise RemoteController::Base::RemoteControllerError do
65
+ @controller.invalid_args_action("invalid arg", "invalid arg again")
66
+ end
67
+
68
+ assert_raise RemoteController::Base::RemoteControllerError do
69
+ @controller.invalid_args_action(:get, "invalid arg", "invalid arg again")
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,76 @@
1
+ #++
2
+ # (The MIT License)
3
+ #
4
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # 'Software'), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ require File.dirname(__FILE__) + '/test_helper'
26
+ require 'stringio'
27
+
28
+ class RemoteControllerCompositeReadIOTest < Test::Unit::TestCase
29
+
30
+ include RemoteController::Multipart
31
+
32
+ def setup
33
+ @io = CompositeReadIO.new(CompositeReadIO.new(StringIO.new('the '), StringIO.new('quick ')),
34
+ StringIO.new('brown '), StringIO.new('fox'))
35
+ end
36
+
37
+ def test_full_read_from_several_ios
38
+ assert_equal 'the quick brown fox', @io.read
39
+ end
40
+
41
+ def test_partial_read
42
+ assert_equal 'the quick', @io.read(9)
43
+ end
44
+
45
+ def test_partial_read_to_boundary
46
+ assert_equal 'the quick ', @io.read(10)
47
+ end
48
+
49
+ def test_read_with_size_larger_than_available
50
+ assert_equal 'the quick brown fox', @io.read(32)
51
+ end
52
+
53
+ def test_read_into_buffer
54
+ buf = ''
55
+ @io.read(nil, buf)
56
+ assert_equal 'the quick brown fox', buf
57
+ end
58
+
59
+ def test_multiple_reads
60
+ assert_equal 'the ', @io.read(4)
61
+ assert_equal 'quic', @io.read(4)
62
+ assert_equal 'k br', @io.read(4)
63
+ assert_equal 'own ', @io.read(4)
64
+ assert_equal 'fox', @io.read(4)
65
+ end
66
+
67
+ def test_read_after_end
68
+ @io.read
69
+ assert_equal "", @io.read
70
+ end
71
+
72
+ def test_read_after_end_with_amount
73
+ @io.read(32)
74
+ assert_equal nil, @io.read(32)
75
+ end
76
+ end
@@ -0,0 +1,62 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ require File.dirname(__FILE__) + '/test_helper'
30
+
31
+ class RemoteControllerCookiesContainerTest < Test::Unit::TestCase
32
+
33
+ def setup
34
+ @container = RemoteController::CookiesContainer.new()
35
+ end
36
+
37
+ def test_process
38
+ cookies_str = "auth_token=tooken; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT, _session=9999; path=/; HttpOnly"
39
+ @container.process(cookies_str)
40
+ assert_not_nil @container["_session"]
41
+ assert_equal "9999", @container["_session"].value
42
+
43
+ assert_not_nil @container["auth_token"]
44
+ assert_equal "tooken", @container["auth_token"].value
45
+ end
46
+
47
+ def test_to_header
48
+ cookies_str = "auth_token=tooken; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT, _session=9999; path=/; HttpOnly"
49
+ @container.process(cookies_str)
50
+
51
+ assert_equal "_session=9999; auth_token=tooken;", @container.to_header
52
+ end
53
+
54
+ def test_empty?
55
+ assert @container.empty?
56
+
57
+ cookies_str = "_session=9999;"
58
+ @container.process(cookies_str)
59
+
60
+ assert !@container.empty?
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RemoteControllerErrorHandlingTest < Test::Unit::TestCase
4
+ BASE_SERVER_PORT = 5982
5
+
6
+ def setup
7
+ @server_port = BASE_SERVER_PORT + rand(100)
8
+ @context = HttpTesting::Context.new(@server_port)
9
+ @controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
10
+ end
11
+
12
+ def test_exception_raised
13
+ @context.start do |request, response|
14
+ response.status = 500
15
+ end
16
+ assert_raise(Net::HTTPFatalError) { @controller.just_get }
17
+ @context.wait
18
+ end
19
+
20
+ def test_callback_invoked
21
+ first_invoked = false
22
+ second_invoked = false
23
+ @controller.on_error do |error|
24
+ first_invoked = true
25
+ end
26
+ @controller.on_error do |error|
27
+ second_invoked = true
28
+ end
29
+ @context.start do |request, response|
30
+ response.status = 500
31
+ end
32
+ assert_raise(Net::HTTPFatalError) { @controller.just_get }
33
+ @context.wait
34
+
35
+ assert(first_invoked, "First was not invoked")
36
+ assert(second_invoked, "Second was not invoked")
37
+ end
38
+
39
+ end
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RemoteControllerTest < Test::Unit::TestCase
4
+
5
+ BASE_SERVER_PORT = 4982
6
+
7
+ def setup
8
+ @server_port = BASE_SERVER_PORT + rand(100)
9
+ @context = HttpTesting::Context.new(@server_port)
10
+ @controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
11
+ end
12
+
13
+ def test_no_auto_unescape_response
14
+ expected = "String with spaces and some symbols that to be escaped &= <>>><<>><<>"
15
+ @context.start do |request, response|
16
+ response.body = CGI.escape(expected)
17
+ end
18
+ actual = @controller.some_post(:post)
19
+ @context.wait
20
+ assert_equal(CGI.escape(expected), actual)
21
+ end
22
+
23
+ def test_invoke_no_args_get
24
+ @context.start do |request, response|
25
+ assert_equal "GET", request.request_method
26
+ assert_equal "/test_controller/no_args_get", request.path
27
+ end
28
+ @controller.no_args_get
29
+ @context.wait
30
+ end
31
+
32
+ def test_invoke_args_get
33
+ @context.start do |request, response|
34
+ assert_equal "GET", request.request_method
35
+ assert_equal "/test_controller/args_get?arg1=value1&arg2=value2", request.path + "?" + request.query_string
36
+ end
37
+ @controller.args_get(:arg1 => "value1", :arg2 => "value2")
38
+ @context.wait
39
+ end
40
+
41
+ def test_invoke_no_args_post
42
+ @context.start do |request, response|
43
+ assert_equal "POST", request.request_method
44
+ assert_equal "/test_controller/no_args_post", request.path
45
+ end
46
+ @controller.no_args_post(:post)
47
+ @context.wait
48
+ end
49
+
50
+ def test_invoke_args_post
51
+ @context.start do |request, response|
52
+ assert_equal "POST", request.request_method
53
+ assert_equal to_param({:arg1 => "value1", :arg2 => "value2"}), request.body
54
+ end
55
+ @controller.args_post(:post, {:arg1 => "value1", :arg2 => "value2"})
56
+ @context.wait
57
+ end
58
+
59
+ def test_invoke_string_post
60
+ @context.start do |request, response|
61
+ assert_equal "POST", request.request_method
62
+ assert_equal "string_post", request.body
63
+ end
64
+ @controller.args_post(:post, "string_post")
65
+ @context.wait
66
+ end
67
+
68
+ def test_invoke_args_multipart
69
+ @context.start do |request, response|
70
+ assert_equal "POST", request.request_method
71
+ assert_equal ["multipart/form-data; boundary=#{Net::HTTP::Post::Multipart::DEFAULT_BOUNDARY}"], request.header["content-type"]
72
+ end
73
+ @controller.args_post(:multipart, {:arg1 => "value1", :arg2 => "value2"})
74
+ @context.wait
75
+ end
76
+
77
+ def test_cookies_container
78
+ @context.start do |request, response|
79
+ response["set-cookie"] = "_session=9999; path=/; HttpOnly"
80
+ end
81
+ @controller.action
82
+ @context.wait
83
+
84
+ assert_equal "9999", @controller.cookies_container["_session"].value
85
+
86
+ container = @controller.cookies_container
87
+
88
+ @controller = RemoteController::Base.new("http://localhost:#{@server_port}/test_controller")
89
+ @controller.cookies_container = container
90
+ @context.start do |request, response|
91
+ assert_equal 1, request.cookies.length
92
+ assert_equal "_session=9999;", request.cookies[0].to_s
93
+ end
94
+ @controller.action
95
+ @context.wait
96
+ end
97
+ end
@@ -0,0 +1,91 @@
1
+ #--
2
+ # (c) Copyright 2007-2008 Nick Sieger.
3
+ # See the file README.txt included with the distribution for
4
+ # software license details.
5
+ #++
6
+ # (The MIT License)
7
+ #
8
+ # Copyright (c) 2007-2009 Nick Sieger <nick@nicksieger.com>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # 'Software'), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+
29
+ require File.dirname(__FILE__) + '/test_helper'
30
+
31
+ class RemoteControllerMultiPartTest < Test::Unit::TestCase
32
+
33
+ include RemoteController::Multipart
34
+
35
+ TEMP_FILE = "temp.txt"
36
+
37
+ HTTPPost = Struct.new("HTTPPost", :content_length, :body_stream, :content_type)
38
+ HTTPPost.module_eval do
39
+ def set_content_type(type, params = {})
40
+ self.content_type = type + params.map{|k,v|"; #{k}=#{v}"}.join('')
41
+ end
42
+ end
43
+
44
+ def setup
45
+ @another_temp_file = "#{File.dirname(__FILE__)}/../tmp/temp_file.txt"
46
+ end
47
+
48
+ def teardown
49
+ File.delete(TEMP_FILE) rescue nil
50
+ File.delete(@another_temp_file) rescue nil
51
+ end
52
+
53
+ def test_form_multipart_body
54
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
55
+ @io = File.open(TEMP_FILE)
56
+ UploadIO.convert! @io, "text/plain", TEMP_FILE, TEMP_FILE
57
+ assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
58
+ end
59
+ def test_form_multipart_body_no_text_plain
60
+ File.open(@another_temp_file, "w") {|f| f << "1234567890"}
61
+ @io = File.open(@another_temp_file)
62
+ UploadIO.convert! @io, "application/octet-stream", File.basename(@another_temp_file), File.basename(@another_temp_file)
63
+ file_part = RemoteController::Multipart::Parts::FilePart.new("--bound", "name", @io)
64
+ end
65
+ def test_form_multipart_body_put
66
+ File.open(TEMP_FILE, "w") {|f| f << "1234567890"}
67
+ @io = File.open(TEMP_FILE)
68
+ UploadIO.convert! @io, "text/plain", TEMP_FILE, TEMP_FILE
69
+ assert_results Net::HTTP::Put::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
70
+ end
71
+
72
+ def test_form_multipart_body_with_stringio
73
+ @io = StringIO.new("1234567890")
74
+ UploadIO.convert! @io, "text/plain", TEMP_FILE, TEMP_FILE
75
+ assert_results Net::HTTP::Post::Multipart.new("/foo/bar", :foo => 'bar', :file => @io)
76
+ end
77
+
78
+ def assert_results(post)
79
+ assert post.content_length && post.content_length > 0
80
+ assert post.body_stream
81
+ assert_equal "multipart/form-data; boundary=#{Multipartable::DEFAULT_BOUNDARY}", post['content-type']
82
+ body = post.body_stream.read
83
+ boundary_regex = Regexp.quote Multipartable::DEFAULT_BOUNDARY
84
+ assert body =~ /1234567890/
85
+ # ensure there is at least one boundary
86
+ assert body =~ /^--#{boundary_regex}\r\n/
87
+ # ensure there is an epilogue
88
+ assert body =~ /^--#{boundary_regex}--\r\n/
89
+ assert body =~ /text\/plain/
90
+ end
91
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+
3
+ ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
4
+ require 'bundler/setup'
5
+
6
+ require 'test/unit'
7
+ require 'cgi'
8
+ require 'http-testing'
9
+ require 'remote_controller'
10
+
11
+ class Test::Unit::TestCase
12
+ include RemoteController::CGIHelpers
13
+ end
data/tmp/.gitkeep ADDED
File without changes
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remote-controller
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Evgeny Myasishchev
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-09-12 00:00:00 +03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: http-testing
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 1
30
+ - 3
31
+ version: 0.1.3
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: Library to simplify remote controller actions invocation.
35
+ email:
36
+ - evgeny.myasishchev@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - MIT-LICENSE
47
+ - README.rdoc
48
+ - Rakefile
49
+ - lib/remote-controller.rb
50
+ - lib/remote_controller.rb
51
+ - lib/remote_controller/base.rb
52
+ - lib/remote_controller/cgi_helpers.rb
53
+ - lib/remote_controller/cookies_container.rb
54
+ - lib/remote_controller/multipart.rb
55
+ - lib/remote_controller/multipart/composite_io.rb
56
+ - lib/remote_controller/multipart/multipartable.rb
57
+ - lib/remote_controller/multipart/parts.rb
58
+ - lib/remote_controller/multipart/parts/epilogue_part.rb
59
+ - lib/remote_controller/multipart/parts/file_part.rb
60
+ - lib/remote_controller/multipart/parts/param_part.rb
61
+ - lib/remote_controller/multipart/parts/part.rb
62
+ - lib/remote_controller/version.rb
63
+ - lib/samples/sample1.rb
64
+ - lib/samples/sample1.txt
65
+ - remote-controller.gemspec
66
+ - test/remote_controller_action_args_test.rb
67
+ - test/remote_controller_composite_io_test.rb
68
+ - test/remote_controller_cookies_container_test.rb
69
+ - test/remote_controller_error_handling_test.rb
70
+ - test/remote_controller_test.rb
71
+ - test/remote_controller_test_multipart_test.rb
72
+ - test/test_helper.rb
73
+ - tmp/.gitkeep
74
+ has_rdoc: true
75
+ homepage: http://github.com/evgeny-myasishchev/remote-controller
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project: remote-controller
100
+ rubygems_version: 1.3.6
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Helps to invoke actions of remote controllers.
104
+ test_files: []
105
+