psm 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ *~
2
+ *.swp
@@ -0,0 +1,31 @@
1
+ h2. Smail Webservice
2
+
3
+ p. Print Stuff Mail is snail mail webservice, allowing physical letters to be sent from the comfort of your own command line.
4
+
5
+ h2. Installation
6
+
7
+ @gem install psm@
8
+
9
+ That's it.
10
+
11
+ h2. Example
12
+
13
+ <pre>
14
+ <code>
15
+ require 'psm'
16
+
17
+ # PrintStuffMail, aliased as PSM, is our namespace.
18
+ # 1. set your account id.
19
+ PSM.account_id = 'abcdefg'
20
+
21
+ # 2. mail a letter!
22
+ # Since this method will eventually cause a credit card to be charged
23
+ # we've taken extra precautions to label it as dangerous. The exclamation,
24
+ # for one, calls attention to the method, but furthermore a block has to be
25
+ # passed in "confirming" the method.
26
+ PSM.mail!( 'message', 'address', 'return_address') {|c| c.confirm!}
27
+
28
+ # 3. there is no step three.
29
+ </code>
30
+ </pre>
31
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,61 @@
1
+ require 'net/http'
2
+ require 'psm/session'
3
+
4
+ # dependencies
5
+ require 'addressable/uri'
6
+ require 'json'
7
+
8
+ module PrintStuffMail
9
+
10
+ BASE_URL = 's.copypastel.com/psm'
11
+
12
+ class Confirmation
13
+ def initialize; @confirmed = false end
14
+ def confirm!; @confirmed = true end
15
+ def confirmed?; @confirmed end
16
+ end
17
+
18
+ class << self
19
+ attr_accessor :account_id
20
+
21
+ def mail!( message, address, return_address = nil )
22
+ raise(SecurityError, 'no account_id set.') unless account_id
23
+ raise(SecurityError, 'need to confirm!') unless block_given?
24
+ yield c = Confirmation.new
25
+ raise(SecurityError, 'need to confirm!') unless c.confirmed?
26
+
27
+ @session ||= Session.new account_id
28
+ raise unless @session.active? or @session.renew! # needs an error type
29
+ post_letter(message, address, return_address)
30
+ end
31
+
32
+ private
33
+
34
+ def get_session(account_id)
35
+ @session = Session.new(account_id)
36
+ end
37
+
38
+ def post_letter(message, address, return_address = nil)
39
+ uri = Addressable::URI.new :host => PSM::BASE_URL,
40
+ :path => '/letters/print'
41
+ params = { :message => message, :address => address,
42
+ :return_address => return_address,
43
+ :session => @session.id }
44
+ params.delete_if {|k, v| v.nil?}
45
+ uri.query_values = params
46
+ response = Net::HTTP.start(PrintStuffMail::BASE_URL, 80) do |http|
47
+ http.post(uri.path, uri.query)
48
+ end
49
+ JSON.parse response.body
50
+ end
51
+
52
+ end
53
+
54
+ def mail!( message, return_address = nil, &block )
55
+ PSM.mail!( message, self.address, return_address, block)
56
+ end
57
+
58
+ end
59
+
60
+ # Short acronym for quick reference
61
+ PSM = PrintStuffMail
@@ -0,0 +1,57 @@
1
+ require 'net/http'
2
+ require 'date'
3
+
4
+ # dependencies
5
+ require 'json'
6
+
7
+ module PrintStuffMail
8
+
9
+ BASE_URL = 's.copypastel.com/psm'
10
+
11
+ class Session
12
+
13
+ attr_reader :id, :last_response, :expiration
14
+
15
+ def initialize(account_id)
16
+ @account_id = account_id
17
+ raise "(PSM::Session) couldn't get a session token." unless renew! # We don't want to instantiate unless we can get a session
18
+ end
19
+
20
+ def time_left
21
+ @expiration - DateTime.now # returns Rational. Wonder which units...
22
+ end
23
+
24
+ def renew!
25
+ @last_response = get_response
26
+ return false unless @last_response['status'] == 201 # We don't have to error out if we can't renew
27
+ @expiration = DateTime.parse(@last_response['expires'])
28
+ @id = @last_response['id']
29
+ true
30
+ end
31
+
32
+ def expired?
33
+ (@expiration - DateTime.now) < 0
34
+ end
35
+
36
+ def active?
37
+ not expired?
38
+ end
39
+
40
+ alias_method :valid?, :active?
41
+
42
+ private
43
+
44
+ def get_response
45
+ query = "account_id=#{@account_id}"
46
+ begin
47
+ response = Net::HTTP.start(PrintStuffMail::BASE_URL,80) do |http|
48
+ http.post("/sessions", query)
49
+ end
50
+ JSON.parse(response.body)
51
+ rescue
52
+ {}
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,57 @@
1
+ require 'sinatra/base'
2
+ require 'openssl'
3
+ require 'json'
4
+
5
+ class Letter < Struct.new(:message, :address, :return_address); end
6
+
7
+ module Helpers
8
+
9
+ @@encrypter = OpenSSL::PKey::RSA.new
10
+ @@decrypter = OpenSSL::PKey::RSA.new
11
+
12
+ def encrypt(payload)
13
+ CGI.escape @@encrypter.private_encrypt(params.to_json)
14
+ end
15
+
16
+ def decrypt(payload)
17
+ JSON.parse @@encrypter.public_decrypt(CGI.unescape(payload))
18
+ end
19
+
20
+ end
21
+
22
+ class FakePSM < Sinatra::Base
23
+
24
+ # may be extend
25
+ include Helpers
26
+
27
+ post '/psm/sessions' do
28
+ halt 400 unless params[:account_id] == 'john_smith'
29
+ { :status => 201,
30
+ :id => 'abcde',
31
+ :expires => Time.now + 60*30
32
+ }.to_json
33
+ end
34
+
35
+ post '/psm/letters/print' do
36
+ halt 400 unless params[:message] and params[:address] and params[:session] == 'abcde'
37
+ @letter = Letter.new(params[:message], params[:address], params[:return_address])
38
+ { :status => 201,
39
+ :id => 1,
40
+ :message => @letter.message,
41
+ :address => @letter.address,
42
+ :return_address => @letter.return_address,
43
+ :state => :processing }.to_json
44
+ end
45
+
46
+ get '/psm/letters/:id' do
47
+ halt 400 unless params[:token] == 'abcde'
48
+ halt 404 if @letter.nil? or params[:id].to_i != 1
49
+ { :status => 200,
50
+ :id => 1,
51
+ :message => @letter.message,
52
+ :address => @letter.address,
53
+ :return_address => @letter.return_address,
54
+ :state => :processing }.to_json
55
+ end
56
+
57
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path( File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ require 'psm/session'
4
+
5
+ class SessionTest < Test::Unit::TestCase
6
+
7
+ include PrintStuffMail
8
+
9
+ context "A Session" do
10
+
11
+ should "raise an exception if it can't get a token when initializing" do
12
+ assert_raise(RuntimeError) { Session.new 'not_a_valid_account_id' }
13
+ end
14
+
15
+ setup do
16
+ Artifice.activate_with(FakePSM)
17
+ @session = Session.new 'john_smith'
18
+ end
19
+
20
+ context "after being initialized" do
21
+
22
+ should "measure how long it has left before it needs to renew" do
23
+ assert @session.time_left > @session.time_left
24
+ end
25
+
26
+ should "be able to tell if its current token has expired" do
27
+ assert !@session.expired?
28
+ end
29
+
30
+ should "have syntaxtic sugar to check if it's token is still valid" do
31
+ assert @session.valid?
32
+ end
33
+
34
+ should "be able to renew itself" do
35
+ old_expiration = @session.expiration
36
+ sleep 1
37
+ assert @session.renew!
38
+ assert @session.expiration > old_expiration
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,105 @@
1
+ require File.expand_path( File.dirname(__FILE__) + '/test_helper')
2
+
3
+ require 'psm'
4
+
5
+ class PrintStuffMailTest < Test::Unit::TestCase
6
+
7
+ context "PrintStuffMail" do
8
+
9
+ setup do
10
+ Artifice.activate_with(FakePSM)
11
+ @address = "Google\n1600 Amphitheatre Parkway\nMountain View, CA 94043"
12
+ @return_address = "Apple\n1 Infinite Loop\nCupertino, CA 95014"
13
+ @message = "Dear Eric,\nStop stealing my designs!\nLove,\nSteve"
14
+ end
15
+
16
+ should "be referenced by the shorter PSM acronym" do
17
+ assert_equal PrintStuffMail, PSM
18
+ end
19
+
20
+ context "as a singleton" do
21
+
22
+ should "define #mail! (it's a dangerous method)" do
23
+ assert_respond_to PSM, :mail!
24
+ end
25
+
26
+ should "allow to set an account_id" do
27
+ assert_respond_to PSM, :account_id
28
+ assert_respond_to PSM, :account_id=
29
+ end
30
+
31
+ should "raise a SecurityError unless @account_id isn't set" do
32
+ assert_raise(SecurityError) { PSM.mail!(@message, @address) {} }
33
+ end
34
+
35
+ context "with a set account_id" do
36
+
37
+ setup do
38
+ PSM.account_id = 'john_smith'
39
+ end
40
+
41
+ should "raise a SecurityError unless a 'confirm block' is passed (it's a really dangerous method)" do
42
+ assert_raise(SecurityError) { PSM.mail!(@message, @address) {} }
43
+ assert_nothing_raised { PSM.mail!(@message, @address) {|c| c.confirm! } }
44
+ end
45
+
46
+ should "raise an ArgumentError unless @message and @address are specified" do
47
+ assert_raise(ArgumentError) { PSM.mail! }
48
+ assert_raise(ArgumentError) { PSM.mail!(@message) }
49
+ assert_nothing_raised { PSM.mail!(@message, @address, &:confirm!) }
50
+ end
51
+
52
+ should "accept an optional @return_address" do
53
+ assert_nothing_raised do
54
+ PSM.mail!(@message, @address, @return_address, &:confirm!)
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ context "as a module to be #include-d" do
63
+
64
+ setup do
65
+ class Tempfile; include PSM end
66
+ @tempfile = Tempfile.new
67
+ end
68
+
69
+ should "define #mail! in the including class' instances" do
70
+ assert_respond_to @tempfile, :mail!
71
+ end
72
+
73
+ should "raise an ArgumentError unless a @message is passed in" do
74
+ assert_raise(ArgumentError) { @tempfile.mail! }
75
+ assert_raise(NoMethodError) { @tempfile.mail!(@message) }
76
+ end
77
+
78
+ should "raise from within #mail! if #address isn't defined" do
79
+ assert !defined? @tempfile.address
80
+ assert_raise(NoMethodError) { @tempfile.mail!(@message) }
81
+ end
82
+
83
+ end
84
+
85
+ context "when accessing the webservice" do
86
+
87
+ should "post letters" do
88
+ letter = PSM.mail!(@message, @address, @return_address, &:confirm!)
89
+ assert_equal 'processing', letter['state']
90
+ end
91
+
92
+ should_eventually "be able to update a letter's status" do
93
+
94
+ end
95
+
96
+ end
97
+
98
+ teardown do
99
+ Artifice.deactivate
100
+ end
101
+
102
+ end
103
+
104
+
105
+ end
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift File.expand_path( File.dirname(__FILE__) + '/../lib')
2
+
3
+ require File.expand_path( File.dirname(__FILE__) + '/fake_service')
4
+ require 'shoulda'
5
+ require 'artifice'
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: psm
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
+ - ecin
13
+ - daicoden
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-04-26 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: addressable
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 2
30
+ - 1
31
+ - 1
32
+ version: 2.1.1
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 1
44
+ - 2
45
+ - 3
46
+ version: 1.2.3
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: shoulda
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 2
58
+ - 10
59
+ - 3
60
+ version: 2.10.3
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: artifice
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ - 5
73
+ version: "0.5"
74
+ type: :development
75
+ version_requirements: *id004
76
+ description: Print Stuff Mail provides an RESTful API for sending snail mail. This library eases interfacing with it.
77
+ email: "@copypastel.com"
78
+ executables: []
79
+
80
+ extensions: []
81
+
82
+ extra_rdoc_files:
83
+ - README.textile
84
+ files:
85
+ - .gitignore
86
+ - README.textile
87
+ - VERSION
88
+ - lib/psm.rb
89
+ - lib/psm/session.rb
90
+ - test/fake_service.rb
91
+ - test/psm/session_test.rb
92
+ - test/psm_test.rb
93
+ - test/test_helper.rb
94
+ has_rdoc: true
95
+ homepage: http://github.com/copypastel/print_stuff_mail
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options:
100
+ - --charset=UTF-8
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.3.6
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Client library for Print Stuff Mail, the snail mail webservice.
124
+ test_files:
125
+ - test/fake_service.rb
126
+ - test/psm/session_test.rb
127
+ - test/psm_test.rb
128
+ - test/test_helper.rb