psm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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