rack-ketai 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.
Files changed (44) hide show
  1. data/.gitignore +1 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.rdoc +122 -0
  4. data/VERSION +1 -0
  5. data/lib/rack/ketai/carrier/abstract.rb +263 -0
  6. data/lib/rack/ketai/carrier/au.rb +153 -0
  7. data/lib/rack/ketai/carrier/cidrs/au.rb +32 -0
  8. data/lib/rack/ketai/carrier/cidrs/docomo.rb +14 -0
  9. data/lib/rack/ketai/carrier/cidrs/softbank.rb +10 -0
  10. data/lib/rack/ketai/carrier/docomo.rb +157 -0
  11. data/lib/rack/ketai/carrier/emoji/ausjisstrtoemojiid.rb +1391 -0
  12. data/lib/rack/ketai/carrier/emoji/docomosjisstrtoemojiid.rb +759 -0
  13. data/lib/rack/ketai/carrier/emoji/emojidata.rb +836 -0
  14. data/lib/rack/ketai/carrier/emoji/emojiidtotypecast.rb +432 -0
  15. data/lib/rack/ketai/carrier/emoji/softbankutf8strtoemojiid.rb +1119 -0
  16. data/lib/rack/ketai/carrier/emoji/softbankwebcodetoutf8str.rb +499 -0
  17. data/lib/rack/ketai/carrier/general.rb +75 -0
  18. data/lib/rack/ketai/carrier/iphone.rb +16 -0
  19. data/lib/rack/ketai/carrier/softbank.rb +144 -0
  20. data/lib/rack/ketai/carrier/specs/au.rb +1 -0
  21. data/lib/rack/ketai/carrier/specs/docomo.rb +1 -0
  22. data/lib/rack/ketai/carrier/specs/softbank.rb +1 -0
  23. data/lib/rack/ketai/carrier.rb +18 -0
  24. data/lib/rack/ketai/display.rb +16 -0
  25. data/lib/rack/ketai/middleware.rb +36 -0
  26. data/lib/rack/ketai.rb +12 -0
  27. data/spec/spec_helper.rb +11 -0
  28. data/spec/unit/au_filter_spec.rb +96 -0
  29. data/spec/unit/au_spec.rb +240 -0
  30. data/spec/unit/carrier_spec.rb +30 -0
  31. data/spec/unit/display_spec.rb +25 -0
  32. data/spec/unit/docomo_filter_spec.rb +106 -0
  33. data/spec/unit/docomo_spec.rb +344 -0
  34. data/spec/unit/emoticon_filter_spec.rb +91 -0
  35. data/spec/unit/filter_spec.rb +38 -0
  36. data/spec/unit/iphone_spec.rb +16 -0
  37. data/spec/unit/middleware_spec.rb +38 -0
  38. data/spec/unit/softbank_filter_spec.rb +133 -0
  39. data/spec/unit/softbank_spec.rb +200 -0
  40. data/spec/unit/valid_addr_spec.rb +86 -0
  41. data/test/spec_runner.rb +29 -0
  42. data/tools/generate_emoji_dic.rb +434 -0
  43. data/tools/update_speclist.rb +87 -0
  44. metadata +138 -0
@@ -0,0 +1,240 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rack/ketai/carrier/au'
3
+ describe "Rack::Ketai::Carrier::Au" do
4
+
5
+ before(:each) do
6
+
7
+ end
8
+
9
+ describe "WAP2.0ブラウザ搭載端末で" do
10
+
11
+ # http://ke-tai.org/blog/2008/09/08/phoneid/
12
+ # http://www.au.kddi.com/ezfactory/tec/spec/4_4.html
13
+
14
+ describe "EZ番号(サブスクライバID)を取得できたとき" do
15
+
16
+ before(:each) do
17
+ @env = Rack::MockRequest.env_for('http://hoge.com/dummy',
18
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0',
19
+ 'HTTP_X_UP_SUBNO' => '01234567890123_xx.ezweb.ne.jp')
20
+ @mobile = Rack::Ketai::Carrier::Au.new(@env)
21
+ end
22
+
23
+ it "#subscriberid でEZ番号を取得できること" do
24
+ @mobile.subscriberid.should == '01234567890123_xx.ezweb.ne.jp'
25
+ end
26
+
27
+ it "#deviceid は nil なこと" do
28
+ @mobile.deviceid.should be_nil
29
+ end
30
+
31
+ it "#ident でEZ番号を取得できること" do
32
+ @mobile.ident.should == @mobile.subscriberid
33
+ @mobile.ident.should == '01234567890123_xx.ezweb.ne.jp'
34
+ end
35
+
36
+ it "#name で機種名を取得できること" do
37
+ @mobile.name.should == 'SA31'
38
+ end
39
+
40
+ end
41
+
42
+ describe "#cache_size でキャッシュ容量を取得するとき" do
43
+
44
+ it "環境変数を使用すること" do
45
+ env = Rack::MockRequest.env_for('http://hoge.com/dummy',
46
+ 'HTTP_USER_AGENT' => 'KDDI-HI3B UP.Browser/6.2.0.13.2 (GUI) MMP/2.0',
47
+ 'HTTP_X_UP_DEVCAP_MAX_PDU' => '131072')
48
+ mobile = Rack::Ketai::Carrier::Au.new(env)
49
+ mobile.cache_size.should == 131072
50
+ end
51
+
52
+ #
53
+ it "環境変数で取得できない古い機種のときは8220Byteにしとく(適当)" do
54
+ env = Rack::MockRequest.env_for('http://hoge.com/dummy',
55
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-SYT4 UP.Link/3.4.5.6')
56
+ mobile = Rack::Ketai::Carrier::Au.new(env)
57
+ mobile.cache_size.should == 8220
58
+ end
59
+
60
+ end
61
+
62
+ describe "ディスプレイ情報を取得するとき" do
63
+
64
+ describe "既知の端末のとき" do
65
+
66
+ it "環境変数を優先すること" do
67
+ @env = Rack::MockRequest.env_for('http://hoge.com/dummy',
68
+ 'HTTP_USER_AGENT' => 'KDDI-TS31 UP.Browser/6.2.0.8 (GUI) MMP/2.0',
69
+ 'HTTP_X_UP_DEVCAP_SCREENPIXELS' => '1024,768',
70
+ 'HTTP_X_UP_DEVCAP_SCREENDEPTH' => '8')
71
+ @mobile = Rack::Ketai::Carrier::Au.new(@env)
72
+ display = @mobile.display
73
+ display.should_not be_nil
74
+ display.colors.should == 256
75
+ display.width.should == 1024
76
+ display.height.should == 768
77
+
78
+ @env = Rack::MockRequest.env_for('http://hoge.com/dummy',
79
+ 'HTTP_USER_AGENT' => 'KDDI-TS31 UP.Browser/6.2.0.8 (GUI) MMP/2.0')
80
+ @mobile = Rack::Ketai::Carrier::Au.new(@env)
81
+ display = @mobile.display
82
+ display.should_not be_nil
83
+ display.colors.should == 65536
84
+ display.width.should == 229
85
+ display.height.should == 270
86
+ end
87
+
88
+ end
89
+
90
+ describe "未知の端末のとき" do
91
+
92
+ it "環境変数から設定すること" do
93
+ @env = Rack::MockRequest.env_for('http://hoge.com/dummy',
94
+ 'HTTP_USER_AGENT' => 'KDDI-XX01 UP.Browser/6.2.0.8 (GUI) MMP/2.0',
95
+ 'HTTP_X_UP_DEVCAP_SCREENPIXELS' => '1024,768',
96
+ 'HTTP_X_UP_DEVCAP_SCREENDEPTH' => '8')
97
+ @mobile = Rack::Ketai::Carrier::Au.new(@env)
98
+ display = @mobile.display
99
+ display.should_not be_nil
100
+ display.colors.should == 256
101
+ display.width.should == 1024
102
+ display.height.should == 768
103
+ end
104
+
105
+ it "環境変数が無かったら慌てず騒がず nil を返す" do
106
+ @env = Rack::MockRequest.env_for('http://hoge.com/dummy',
107
+ 'HTTP_USER_AGENT' => 'KDDI-XX01 UP.Browser/6.2.0.8 (GUI) MMP/2.0')
108
+ @mobile = Rack::Ketai::Carrier::Au.new(@env)
109
+ display = @mobile.display
110
+ display.should_not be_nil
111
+ display.colors.should be_nil
112
+ display.width.should be_nil
113
+ display.height.should be_nil
114
+ end
115
+
116
+ end
117
+
118
+ end
119
+
120
+ describe "EZ番号が取得できないとき" do
121
+
122
+ before(:each) do
123
+ @env = Rack::MockRequest.env_for('http://hoge.com/dummy',
124
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0')
125
+ @mobile = Rack::Ketai::Carrier::Au.new(@env)
126
+ end
127
+
128
+ it "#subscriberid は nil を返すこと" do
129
+ @mobile.subscriberid.should be_nil
130
+ end
131
+
132
+ it "#deviceid は nil を返すこと" do
133
+ @mobile.deviceid.should be_nil
134
+ end
135
+
136
+ it "#ident は nil を返すこと" do
137
+ @mobile.ident.should be_nil
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ describe "#supports_cookie? を使うとき" do
144
+
145
+ # Au のCookie対応状況
146
+ # 全機種対応(GW側で保持)
147
+ # SSL接続時はWAP2.0ブラウザ搭載端末でのみ端末に保持したCookieを送出
148
+ # http://www.au.kddi.com/ezfactory/tec/spec/cookie.html
149
+
150
+ it "WAP1.0端末(HTTP)のとき true を返すこと" do
151
+ {
152
+ 'http://hoge.com/dummy' => {
153
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-KCTE UP.Link/3.4.5.9'
154
+ },
155
+ 'http://hoge.com/dummy' => {
156
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-KCTE UP.Link/3.4.5.9',
157
+ 'HTTPS' => 'OFF'
158
+ },
159
+ 'http://hoge.com/dummy' => {
160
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-KCTE UP.Link/3.4.5.9',
161
+ 'X_FORWARDED_PROTO' => 'http'
162
+ }
163
+ }.each do |url, opt|
164
+ env = Rack::MockRequest.env_for(url, opt)
165
+ mobile = Rack::Ketai::Carrier::Au.new(env)
166
+ mobile.should be_respond_to(:supports_cookie?)
167
+ mobile.should be_supports_cookie
168
+ end
169
+
170
+ end
171
+
172
+ it "WAP1.0端末(HTTPS)のとき false を返すこと" do
173
+ {
174
+ 'https://hoge.com/dummy' => {
175
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-KCTE UP.Link/3.4.5.9',
176
+ 'HTTPS' => 'on'
177
+ },
178
+ 'https://hoge.com/dummy' => {
179
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-KCTE UP.Link/3.4.5.9',
180
+ 'HTTPS' => 'ON'
181
+ },
182
+ 'http://hoge.com/dummy' => {
183
+ 'HTTP_USER_AGENT' => 'UP.Browser/3.04-KCTE UP.Link/3.4.5.9',
184
+ 'X_FORWARDED_PROTO' => 'https' # RAILS的 リバースプロキシのバックエンドでHTTPSを判断する方法
185
+ }
186
+ }.each do |url, opt|
187
+ env = Rack::MockRequest.env_for(url, opt)
188
+ mobile = Rack::Ketai::Carrier::Au.new(env)
189
+ mobile.should be_respond_to(:supports_cookie?)
190
+ mobile.should_not be_supports_cookie
191
+ end
192
+ end
193
+
194
+ it "WAP2.0端末(HTTP)のとき true を返すこと" do
195
+ {
196
+ 'http://hoge.com/dummy' => {
197
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0'
198
+ },
199
+ 'http://hoge.com/dummy' => {
200
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0',
201
+ 'HTTPS' => 'OFF'
202
+ },
203
+ 'http://hoge.com/dummy' => {
204
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0',
205
+ 'X_FORWARDED_PROTO' => 'http'
206
+ }
207
+ }.each do |url, opt|
208
+ env = Rack::MockRequest.env_for(url, opt)
209
+ mobile = Rack::Ketai::Carrier::Au.new(env)
210
+ mobile.should be_respond_to(:supports_cookie?)
211
+ mobile.should be_supports_cookie
212
+ end
213
+
214
+ end
215
+
216
+ it "WAP2.0端末(HTTPS)のとき true を返すこと" do
217
+ {
218
+ 'https://hoge.com/dummy' => {
219
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0',
220
+ 'HTTPS' => 'on'
221
+ },
222
+ 'https://hoge.com/dummy' => {
223
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0',
224
+ 'HTTPS' => 'ON'
225
+ },
226
+ 'http://hoge.com/dummy' => {
227
+ 'HTTP_USER_AGENT' => 'KDDI-SA31 UP.Browser/6.2.0.7.3.129 (GUI) MMP/2.0',
228
+ 'X_FORWARDED_PROTO' => 'https' # RAILS的 リバースプロキシのバックエンドでHTTPSを判断する方法
229
+ }
230
+ }.each do |url, opt|
231
+ env = Rack::MockRequest.env_for(url, opt)
232
+ mobile = Rack::Ketai::Carrier::Au.new(env)
233
+ mobile.should be_respond_to(:supports_cookie?)
234
+ mobile.should be_supports_cookie
235
+ end
236
+ end
237
+
238
+ end
239
+
240
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ describe Rack::Ketai::Carrier, "#load を実行するとき" do
4
+
5
+ it "適切なキャリアを設定できること" do
6
+ {
7
+ 'DoCoMo/1.0/N505i' => Rack::Ketai::Carrier::Docomo, # Mova
8
+ 'DoCoMo/2.0 P903i' => Rack::Ketai::Carrier::Docomo, # FOMA
9
+ 'KDDI-CA39 UP.Browser/6.2.0.13.1.5 (GUI) MMP/2.0' => Rack::Ketai::Carrier::Au, # WAP2.0 MMP2.0
10
+ 'KDDI-TS21 UP.Browser/6.0.2.273 (GUI) MMP/1.1' => Rack::Ketai::Carrier::Au, # WAP2.0 MMP1.1
11
+ 'SoftBank/1.0/930SH/SHJ001[/Serial] Browser/NetFront/3.4 Profile/MIDP-2.0 Configuration/CLDC-1.1' => Rack::Ketai::Carrier::Softbank, # SoftBank 3GC
12
+ 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_0_1 like Mac OS X; ja-jp) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5B108 Safari/525.20' => Rack::Ketai::Carrier::IPhone,
13
+ 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; Q312461; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR 2.0.50727)' => nil, # IE8
14
+ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1' => nil,
15
+ 'Opera/9.21 (Windows NT 5.1; U; ja)' => nil,
16
+ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP) AppleWebKit/525.19 (KHTML, like Gecko) Version/3.1.2 Safari/525.21' => nil,
17
+ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.29 Safari/525.13' => nil,
18
+ }.each do |ua, carrier|
19
+ env = Rack::MockRequest.env_for('http://hoge.com/dummy','HTTP_USER_AGENT' => ua)
20
+ obj = Rack::Ketai::Carrier.load(env)
21
+ if carrier
22
+ obj.should be_is_a(carrier)
23
+ obj.should be_mobile
24
+ else
25
+ obj.should_not be_mobile
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rack/ketai/display'
3
+ describe "Rack::Ketai::Display" do
4
+
5
+ before(:each) do
6
+ @display1 = Rack::Ketai::Display.new
7
+ @display2 = Rack::Ketai::Display.new(:colors => 256, :width => 240, :height => 360)
8
+ end
9
+
10
+ it "#colors で色数を取得可能なこと、未設定ならnil" do
11
+ @display1.colors.should be_nil
12
+ @display2.colors.should == 256
13
+ end
14
+
15
+ it "#width でブラウザ横幅を取得可能なこと、未設定ならnil" do
16
+ @display1.width.should be_nil
17
+ @display2.width.should == 240
18
+ end
19
+
20
+ it "#height でブラウザ縦幅を取得可能なこと、未設定ならnil" do
21
+ @display1.height.should be_nil
22
+ @display2.height.should == 360
23
+ end
24
+
25
+ end
@@ -0,0 +1,106 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'kconv'
3
+ require 'rack/ketai/carrier/docomo'
4
+ describe Rack::Ketai::Carrier::Docomo::Filter, "内部エンコーディングに変換する時" do
5
+
6
+ before(:each) do
7
+ @filter = Rack::Ketai::Carrier::Docomo::Filter.new
8
+ end
9
+
10
+ it "POSTデータ中のSJISバイナリの絵文字を絵文字IDに変換すること" do
11
+ Rack::Ketai::Carrier::Docomo::Filter::EMOJI_TO_EMOJIID.should_not be_empty
12
+ Rack::Ketai::Carrier::Docomo::Filter::EMOJI_TO_EMOJIID.each do |emoji, emojiid|
13
+ postdata = CGI.escape("message=今日はいい".tosjis + emoji + "ですね。".tosjis)
14
+ postdata.force_encoding('Shift_JIS') if postdata.respond_to?(:force_encoding)
15
+
16
+ env = Rack::MockRequest.env_for('http://hoge.com/dummy',
17
+ 'HTTP_USER_AGENT' => 'DoCoMo/2.0 P903i',
18
+ :method => 'POST', # rack 1.1.0 以降ではこれがないとパーサが動かない
19
+ :input => postdata)
20
+ env = @filter.inbound(env)
21
+ request = Rack::Request.new(env)
22
+ request.params['message'].should == '今日はいい[e:'+format("%03X", emojiid)+']ですね。'
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ describe Rack::Ketai::Carrier::Docomo::Filter, "外部エンコーディングに変換する時" do
29
+
30
+ before(:each) do
31
+ @filter = Rack::Ketai::Carrier::Docomo::Filter.new
32
+ end
33
+
34
+ it "データ中の絵文字IDをSJISの絵文字コードに変換すること" do
35
+ Rack::Ketai::Carrier::Docomo::Filter::EMOJI_TO_EMOJIID.should_not be_empty
36
+ Rack::Ketai::Carrier::Docomo::Filter::EMOJI_TO_EMOJIID.each do |emoji, emojiid|
37
+ resdata = "今日はいい".tosjis + emoji + "ですね。".tosjis
38
+
39
+ status, headers, body = @filter.outbound(200, { "Content-Type" => "text/html"}, ['今日はいい[e:'+format("%03X", emojiid)+']ですね。'])
40
+
41
+ body[0].should == resdata
42
+ end
43
+
44
+ # 複数の絵文字IDに割り当てられている絵文字
45
+ hotel = [0xF8CA].pack('n*')
46
+ harts = [0xF994].pack('n*')
47
+ [hotel, harts].each{ |e| e.force_encoding('Shift_JIS') if e.respond_to?(:force_encoding) }
48
+ resdata = "ラブホテル".tosjis + hotel + harts
49
+ status, headers, body = @filter.outbound(200, { "Content-Type" => "text/html"}, ['ラブホテル[e:4B8]'])
50
+
51
+ body[0].should == resdata
52
+
53
+ end
54
+
55
+ it "Content-typeが指定なし,text/html, application/xhtml+xml 以外の時はフィルタを適用しないこと" do
56
+ Rack::Ketai::Carrier::Docomo::Filter::EMOJI_TO_EMOJIID.should_not be_empty
57
+ Rack::Ketai::Carrier::Docomo::Filter::EMOJI_TO_EMOJIID.each do |emoji, emojiid|
58
+ internaldata = '今日はいい[e:'+format("%03X", emojiid)+']ですね。'
59
+ %w(text/plain text/xml text/json application/json text/javascript application/rss+xml image/jpeg).each do |contenttype|
60
+ status, headers, body = @filter.outbound(200, { "Content-Type" => contenttype }, [internaldata])
61
+ body[0].should == internaldata
62
+ end
63
+ end
64
+ end
65
+
66
+ it "データ中に絵文字ID=絵文字IDだが絵文字!=絵文字IDのIDが含まれているとき、正しく逆変換できること" do
67
+ emoji = [0xF995].pack('n')
68
+ emoji.force_encoding('Shift_JIS') if emoji.respond_to?(:force_encoding)
69
+ resdata = "たとえば".tosjis+emoji+"「e-330 HAPPY FACE WITH OPEN MOUTH」とか。".tosjis
70
+
71
+ status, headers, body = @filter.outbound(200, { "Content-Type" => "text/html"}, ["たとえば[e:330]「e-330 HAPPY FACE WITH OPEN MOUTH」とか。"])
72
+
73
+ body[0].should == resdata
74
+ end
75
+
76
+ it "データ中にドコモにはない絵文字IDが存在するとき、代替文字を表示すること" do
77
+ resdata = "黒い矢印[#{[0x2190].pack('U')}]です".tosjis # 左黒矢印
78
+
79
+ status, headers, body = @filter.outbound(200, { "Content-Type" => "text/html"}, ['黒い矢印[e:AFB]です'])
80
+
81
+ body[0].should == resdata
82
+ end
83
+
84
+ it "Content-typeを適切に書き換えられること" do
85
+ [
86
+ [nil, nil],
87
+ ['text/html', 'application/xhtml+xml; charset=shift_jis'],
88
+ ['text/html; charset=utf-8', 'application/xhtml+xml; charset=shift_jis'],
89
+ ['text/html;charset=utf-8', 'application/xhtml+xml;charset=shift_jis'],
90
+ ['application/xhtml+xml', 'application/xhtml+xml; charset=shift_jis'],
91
+ ['application/xhtml+xml; charset=utf-8', 'application/xhtml+xml; charset=shift_jis'],
92
+ ['application/xhtml+xml;charset=utf-8', 'application/xhtml+xml;charset=shift_jis'],
93
+ ['text/javascript', 'text/javascript'],
94
+ ['text/json', 'text/json'],
95
+ ['application/json', 'application/json'],
96
+ ['text/javascript+json', 'text/javascript+json'],
97
+ ['image/jpeg', 'image/jpeg'],
98
+ ['application/octet-stream', 'application/octet-stream'],
99
+ ].each do |content_type, valid_content_type|
100
+ status, headers, body = @filter.outbound(200, { "Content-Type" => content_type}, ['適当な本文'])
101
+ headers['Content-Type'].should == valid_content_type
102
+ end
103
+ end
104
+
105
+ end
106
+