passbook 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "passbook-ios"
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Thomas Lauro", "Lance Gleason"]
12
+ s.date = "2013-01-06"
13
+ s.description = "This gem allows you to create IOS Passbooks. Unlike some, this works with Rails but does not require it."
14
+ s.email = ["thomas@lauro.fr", "lgleason@polyglotprogramminginc.com"]
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".travis.yml",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/passbook.rb",
28
+ "lib/passbook/pkpass.rb",
29
+ "lib/passbook/push_notification.rb",
30
+ "lib/passbook/version.rb",
31
+ "lib/rack/passbook_rack.rb",
32
+ "lib/rails/generators/passbook/config/config_generator.rb",
33
+ "lib/rails/generators/passbook/config/templates/initializer.rb",
34
+ "passbook-ios.gemspec",
35
+ "passbook.gemspec",
36
+ "spec/data/icon.png",
37
+ "spec/data/icon@2x.png",
38
+ "spec/data/logo.png",
39
+ "spec/data/logo@2x.png",
40
+ "spec/lib/passbook/pkpass_spec.rb",
41
+ "spec/lib/passbook/push_notification_spec.rb",
42
+ "spec/lib/rack/passbook_rack_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = "http://github.com/frozon/passbook"
46
+ s.licenses = ["MIT"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = "1.8.24"
49
+ s.summary = "A IOS Passbook generator."
50
+
51
+ if s.respond_to? :specification_version then
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0"])
56
+ s.add_runtime_dependency(%q<grocer>, [">= 0"])
57
+ s.add_development_dependency(%q<rack-test>, [">= 0"])
58
+ s.add_development_dependency(%q<activesupport>, [">= 0"])
59
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
60
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
61
+ s.add_development_dependency(%q<rspec>, [">= 0"])
62
+ s.add_development_dependency(%q<rake>, [">= 0"])
63
+ s.add_development_dependency(%q<yard>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<rubyzip>, [">= 0"])
66
+ s.add_dependency(%q<grocer>, [">= 0"])
67
+ s.add_dependency(%q<rack-test>, [">= 0"])
68
+ s.add_dependency(%q<activesupport>, [">= 0"])
69
+ s.add_dependency(%q<jeweler>, [">= 0"])
70
+ s.add_dependency(%q<simplecov>, [">= 0"])
71
+ s.add_dependency(%q<rspec>, [">= 0"])
72
+ s.add_dependency(%q<rake>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<rubyzip>, [">= 0"])
77
+ s.add_dependency(%q<grocer>, [">= 0"])
78
+ s.add_dependency(%q<rack-test>, [">= 0"])
79
+ s.add_dependency(%q<activesupport>, [">= 0"])
80
+ s.add_dependency(%q<jeweler>, [">= 0"])
81
+ s.add_dependency(%q<simplecov>, [">= 0"])
82
+ s.add_dependency(%q<rspec>, [">= 0"])
83
+ s.add_dependency(%q<rake>, [">= 0"])
84
+ s.add_dependency(%q<yard>, [">= 0"])
85
+ end
86
+ end
87
+
data/passbook.gemspec ADDED
@@ -0,0 +1,87 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "passbook"
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Thomas Lauro", "Lance Gleason"]
12
+ s.date = "2013-02-23"
13
+ s.description = "This gem allows you to create IOS Passbooks. Unlike some, this works with Rails but does not require it."
14
+ s.email = ["thomas@lauro.fr", "lgleason@polyglotprogramminginc.com"]
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".travis.yml",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/passbook.rb",
28
+ "lib/passbook/pkpass.rb",
29
+ "lib/passbook/push_notification.rb",
30
+ "lib/passbook/version.rb",
31
+ "lib/rack/passbook_rack.rb",
32
+ "lib/rails/generators/passbook/config/config_generator.rb",
33
+ "lib/rails/generators/passbook/config/templates/initializer.rb",
34
+ "passbook-ios.gemspec",
35
+ "passbook.gemspec",
36
+ "spec/data/icon.png",
37
+ "spec/data/icon@2x.png",
38
+ "spec/data/logo.png",
39
+ "spec/data/logo@2x.png",
40
+ "spec/lib/passbook/pkpass_spec.rb",
41
+ "spec/lib/passbook/push_notification_spec.rb",
42
+ "spec/lib/rack/passbook_rack_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = "http://github.com/frozon/passbook"
46
+ s.licenses = ["MIT"]
47
+ s.require_paths = ["lib"]
48
+ s.rubygems_version = "1.8.24"
49
+ s.summary = "A IOS Passbook generator."
50
+
51
+ if s.respond_to? :specification_version then
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0"])
56
+ s.add_runtime_dependency(%q<grocer>, [">= 0"])
57
+ s.add_development_dependency(%q<rack-test>, [">= 0"])
58
+ s.add_development_dependency(%q<activesupport>, [">= 0"])
59
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
60
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
61
+ s.add_development_dependency(%q<rspec>, [">= 0"])
62
+ s.add_development_dependency(%q<rake>, [">= 0"])
63
+ s.add_development_dependency(%q<yard>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<rubyzip>, [">= 0"])
66
+ s.add_dependency(%q<grocer>, [">= 0"])
67
+ s.add_dependency(%q<rack-test>, [">= 0"])
68
+ s.add_dependency(%q<activesupport>, [">= 0"])
69
+ s.add_dependency(%q<jeweler>, [">= 0"])
70
+ s.add_dependency(%q<simplecov>, [">= 0"])
71
+ s.add_dependency(%q<rspec>, [">= 0"])
72
+ s.add_dependency(%q<rake>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<rubyzip>, [">= 0"])
77
+ s.add_dependency(%q<grocer>, [">= 0"])
78
+ s.add_dependency(%q<rack-test>, [">= 0"])
79
+ s.add_dependency(%q<activesupport>, [">= 0"])
80
+ s.add_dependency(%q<jeweler>, [">= 0"])
81
+ s.add_dependency(%q<simplecov>, [">= 0"])
82
+ s.add_dependency(%q<rspec>, [">= 0"])
83
+ s.add_dependency(%q<rake>, [">= 0"])
84
+ s.add_dependency(%q<yard>, [">= 0"])
85
+ end
86
+ end
87
+
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Passbook do
4
+
5
+ let (:content) {{
6
+ :formatVersion => 1,
7
+ :passTypeIdentifier => "pass.passbook.test",
8
+ :serialNumber => "001",
9
+ :teamIdentifier => ENV['APPLE_TEAM_ID'],
10
+ :relevantDate => "2012-10-02",
11
+ :locations => [ #TODO
12
+ {
13
+ :longitude => 2.35403,
14
+ :latitude => 48.893855
15
+ }
16
+ ],
17
+ :organizationName => "WorldCo",
18
+ :description => "description",
19
+ :foregroundColor => "rgb(227,210,18)",
20
+ :backgroundColor => "rgb(60, 65, 76)",
21
+ :logoText => "Event",
22
+ :eventTicket => {
23
+ :primaryFields => [
24
+ {
25
+ :key => "date",
26
+ :label => "DATE",
27
+ :value => "date"
28
+ }
29
+ ],
30
+ :backFields => [
31
+ {
32
+ :key => "description",
33
+ :label => "DESCRIPTION",
34
+ :value => "description"
35
+ },
36
+ {
37
+ :key => "aboutUs",
38
+ :label => "MORE",
39
+ :value => "about us"
40
+ }
41
+ ]
42
+ }
43
+ }}
44
+
45
+ let (:pass) {Passbook::PKPass.new content.to_json}
46
+
47
+ context 'signatures' do
48
+ before do
49
+ Passbook.should_receive(:p12_password).and_return 'password'
50
+ end
51
+
52
+ context 'p12_cert_and_key' do
53
+ context 'pem p12 certs' do
54
+ before do
55
+ Passbook.should_receive(:p12_key).twice.and_return 'my_p12_key'
56
+ Passbook.should_receive(:p12_certificate).and_return 'my_p12_certificate'
57
+ File.should_receive(:read).with('my_p12_key').and_return 'my_p12_key_file'
58
+ File.should_receive(:read).with('my_p12_certificate').and_return 'my_p12_certificate_file'
59
+ OpenSSL::PKey::RSA.should_receive(:new).with('my_p12_key_file', 'password').and_return 'my_rsa_key'
60
+ OpenSSL::X509::Certificate.should_receive(:new).with('my_p12_certificate_file').and_return 'my_ssl_p12_cert'
61
+ end
62
+
63
+ subject {pass.get_p12_cert_and_key}
64
+ its([:key]) {should eq 'my_rsa_key'}
65
+ its([:cert]) {should eq 'my_ssl_p12_cert'}
66
+ end
67
+
68
+ context 'p12 files' do
69
+ let (:p12) { double('OpenSSL::PKCS12') }
70
+ let (:final_hash) {{:key => 'my_final_p12_key', :cert => 'my_final_p12_cert'}}
71
+ before do
72
+ p12.should_receive(:key).and_return final_hash[:key]
73
+ p12.should_receive(:certificate).and_return final_hash[:cert]
74
+ Passbook.should_receive(:p12_key).and_return nil
75
+ Passbook.should_receive(:p12_cert).and_return 'my_p12_cert'
76
+ File.should_receive(:read).with('my_p12_cert').and_return 'my_p12_cert_file'
77
+ OpenSSL::PKCS12.should_receive(:new).with('my_p12_cert_file', 'password').and_return p12
78
+ end
79
+
80
+ subject {pass.get_p12_cert_and_key}
81
+ its([:key]) {should eq final_hash[:key]}
82
+ its([:cert]) {should eq final_hash[:cert]}
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'outputs' do
88
+ let (:base_path) {'spec/data'}
89
+ let (:entries) {["pass.json", "manifest.json", "signature", "icon.png", "icon@2x.png", "logo.png", "logo@2x.png"]}
90
+
91
+ before :each do
92
+ pass.addFiles ["#{base_path}/icon.png","#{base_path}/icon@2x.png","#{base_path}/logo.png","#{base_path}/logo@2x.png"]
93
+ pass.should_receive(:createSignature).and_return('Signed by the Honey Badger')
94
+ @file_entries = []
95
+ Zip::ZipInputStream::open(zip_path) {|io|
96
+ while (entry = io.get_next_entry)
97
+ @file_entries << entry.name
98
+ end
99
+ }
100
+ end
101
+
102
+ context 'zip file' do
103
+ let(:zip_path) {pass.file.path}
104
+
105
+ subject {entries}
106
+ it {should eq @file_entries}
107
+ end
108
+
109
+ context 'StringIO' do
110
+ let (:temp_file) {Tempfile.new("pass.pkpass")}
111
+ let (:zip_path) {
112
+ zip_out = pass.stream
113
+ zip_out.class.should eq(Class::StringIO)
114
+ #creating file, re-reading zip to see if correctly formed
115
+ temp_file.write zip_out.string
116
+ temp_file.close
117
+ temp_file.path
118
+ }
119
+
120
+ subject {entries}
121
+ it {should eq @file_entries}
122
+
123
+ after do
124
+ temp_file.delete
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'grocer'
3
+
4
+ describe Passbook::PushNotification do
5
+
6
+ context 'send notification' do
7
+ let(:grocer_pusher) {double 'Grocer'}
8
+ let(:notification) {double 'Grocer::Notification'}
9
+ let(:notification_settings) {{:certificate => './notification_cert.pem', :gateway => 'honeybadger.apple.com'}}
10
+
11
+ before :each do
12
+ Passbook.should_receive(:notification_cert).and_return './notification_cert.pem'
13
+ Grocer::PassbookNotification.should_receive(:new).with(:device_token => 'my token').and_return notification
14
+ grocer_pusher.should_receive(:push).with(notification).and_return 55
15
+ Grocer.should_receive(:pusher).with(notification_settings).and_return grocer_pusher
16
+ Passbook.should_receive(:notification_gateway).and_return 'honeybadger.apple.com'
17
+ end
18
+
19
+ subject {Passbook::PushNotification.send_notification('my token')}
20
+ it {should eq 55}
21
+ end
22
+ end
@@ -0,0 +1,196 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::PassbookRack do
4
+
5
+ let(:register_delete_path) {'/v1/devices/fe772e610be3efafb65ed77772ca311a/registrations/pass.com.polyglotprogramminginc.testpass/27-1'}
6
+ let(:register_delete_params) {{'deviceLibraryIdentifier' => 'fe772e610be3efafb65ed77772ca311a',
7
+ 'passTypeIdentifier' => 'pass.com.polyglotprogramminginc.testpass',
8
+ 'serialNumber' => '27-1'}}
9
+ let(:passes_for_device_path) {'/v1/devices/fe772e610be3efafb65ed77772ca311a/registrations/pass.com.polyglotprogramminginc.testpass'}
10
+ let(:passes_for_device_params) {{'deviceLibraryIdentifier' => 'fe772e610be3efafb65ed77772ca311a',
11
+ 'passTypeIdentifier' => 'pass.com.polyglotprogramminginc.testpass'}}
12
+ let(:latest_pass_path) {'/v1/passes/pass.com.polyglotprogramminginc.testpass/27-1'}
13
+ let(:latest_pass_params) {{'passTypeIdentifier' => 'pass.com.polyglotprogramminginc.testpass',
14
+ 'serialNumber' => '27-1'}}
15
+ let(:log_path) {'/v1/log'}
16
+ let(:push_token) {"8c56f2e787d9c089963960ace834bc2875e3f0cf7745da5b98d58bc6be05b4dc"}
17
+
18
+ context 'find method' do
19
+ let(:passbook_rack) {Rack::PassbookRack.new nil}
20
+
21
+ shared_examples_for 'a method that can handle non passbook urls' do
22
+ context 'incomplete passbook api path' do
23
+ subject {passbook_rack.find_method('/v1/devices/fe772e610be3efafb65ed77772ca311a/registrations')}
24
+ it {should eq nil}
25
+ end
26
+
27
+ context 'no version api path' do
28
+ subject {passbook_rack.find_method('/devices/fe772e610be3efafb65ed77772ca311a/registrations')}
29
+ it {should eq nil}
30
+ end
31
+
32
+ context 'no devices api path' do
33
+ subject {passbook_rack.find_method('/v1/fe772e610be3efafb65ed77772ca311a/registrations')}
34
+ it {should eq nil}
35
+ end
36
+
37
+ context 'no registrations api path' do
38
+ subject {passbook_rack.find_method('/v1/devices/fe772e610be3efafb65ed77772ca311a')}
39
+ it {should eq nil}
40
+ end
41
+ end
42
+
43
+ context 'device register delete' do
44
+ context 'a valid path' do
45
+ subject {passbook_rack.find_method(register_delete_path)}
46
+ its([:method]) {should eq 'device_register_delete'}
47
+ its([:params]) {should eq(register_delete_params) }
48
+ end
49
+
50
+ it_behaves_like 'a method that can handle non passbook urls'
51
+
52
+ end
53
+
54
+ context 'passes for device' do
55
+ subject {passbook_rack.find_method(passes_for_device_path)}
56
+ its([:method]) {should eq 'passes_for_device'}
57
+ its([:params]) {should eq passes_for_device_params }
58
+ end
59
+
60
+ context 'latest pass' do
61
+ subject {passbook_rack.find_method(latest_pass_path)}
62
+ its([:method]) {should eq 'latest_pass'}
63
+ its([:params]) {should eq(latest_pass_params) }
64
+ end
65
+
66
+ context 'latest pass' do
67
+ subject {passbook_rack.find_method(log_path)}
68
+ its([:method]) {should eq 'log'}
69
+ end
70
+ end
71
+
72
+ context 'rack middleware' do
73
+
74
+ context 'register pass' do
75
+ before do
76
+ Passbook::PassbookNotification.should_receive(:register_pass).
77
+ with(register_delete_params.merge!('pushToken' => push_token)).and_return({:status => 201})
78
+ post register_delete_path, {"pushToken" => push_token}.to_json
79
+ end
80
+
81
+ subject {last_response}
82
+ its(:status) {should eq 201}
83
+ end
84
+
85
+ context 'passes for device' do
86
+ context 'with passes' do
87
+ let(:passes_for_device_response) {{'last_updated' => 1, 'serial_numbers' => [343, 234]}}
88
+ before do
89
+ Passbook::PassbookNotification.should_receive(:passes_for_device).
90
+ with(passes_for_device_params).and_return(passes_for_device_response)
91
+ get passes_for_device_path
92
+ end
93
+
94
+ context 'status' do
95
+ subject {last_response.status}
96
+ it {should eq 200}
97
+ end
98
+
99
+ context 'body' do
100
+ subject{JSON.parse(last_response.body)}
101
+ it {should eq passes_for_device_response}
102
+ end
103
+ end
104
+
105
+ context 'without passes' do
106
+ before do
107
+ Passbook::PassbookNotification.should_receive(:passes_for_device).
108
+ with(passes_for_device_params).and_return(nil)
109
+ get passes_for_device_path
110
+ end
111
+
112
+ context 'status' do
113
+ subject {last_response.status}
114
+ it {should eq 204}
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'get latest pass' do
120
+ context 'valid pass' do
121
+ let(:raw_pass) {'some url encoded text'}
122
+
123
+ before do
124
+ Passbook::PassbookNotification.should_receive(:latest_pass).with(latest_pass_params).
125
+ and_return(raw_pass)
126
+ get latest_pass_path
127
+ end
128
+
129
+ subject {last_response}
130
+ its(:status) {should eq 200}
131
+ its(:header) {should eq({'Content-Type' => 'application/vnd.apple.pkpass',
132
+ 'Content-Disposition' => 'attachment', 'filename' => '27-1.pkpass', 'Content-Length' => '21'})}
133
+ its(:body) {should eq raw_pass}
134
+ end
135
+
136
+ context 'no pass' do
137
+ before do
138
+ Passbook::PassbookNotification.should_receive(:latest_pass).with(latest_pass_params).
139
+ and_return(nil)
140
+ get latest_pass_path
141
+ end
142
+
143
+ subject {last_response}
144
+ its(:status) {should eq 204}
145
+ end
146
+ end
147
+
148
+ context 'unregister pass' do
149
+ before do
150
+ Passbook::PassbookNotification.should_receive(:unregister_pass).
151
+ with(register_delete_params).and_return({:status => 200})
152
+ delete register_delete_path
153
+ end
154
+
155
+ subject {last_response}
156
+ its(:status) {should eq 200}
157
+ end
158
+
159
+ context 'log' do
160
+ let(:log_params) {{'logs' => ['some error']}}
161
+ before do
162
+ Passbook::PassbookNotification.should_receive(:log).
163
+ with(log_params)
164
+ post log_path, log_params.to_json
165
+ end
166
+
167
+ subject {last_response}
168
+ its(:status) {should eq 200}
169
+ end
170
+
171
+ context 'non passbook requests' do
172
+ before do
173
+ get '/foo'
174
+ end
175
+
176
+ subject {last_response}
177
+ its(:status) {should eq 200}
178
+ its(:body) {should eq 'test app'}
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ require 'rack/test'
185
+ include Rack::Test::Methods
186
+
187
+ def app
188
+ test_app = lambda do |env|
189
+ [200, {}, 'test app']
190
+ end
191
+
192
+ Rack::PassbookRack.new test_app
193
+ end
194
+
195
+ class Passbook::PassbookNotification
196
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'json'
4
+ require 'simplecov'
5
+ require 'grocer/passbook_notification'
6
+ SimpleCov.start
7
+
8
+ Dir['lib/passbook/**/*.rb'].each {|f| require File.join(File.dirname(__FILE__), '..', f.gsub(/.rb/, ''))}
9
+ Dir['lib/rack/**/*.rb'].each {|f| require File.join(File.dirname(__FILE__), '..', f.gsub(/.rb/, ''))}