david 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +2 -2
- data/Gemfile +8 -2
- data/Gemfile.lock +84 -44
- data/README.md +87 -5
- data/benchmarks/Gemfile +1 -0
- data/benchmarks/Gemfile.lock +3 -1
- data/benchmarks/coapbench.sh +20 -0
- data/benchmarks/quick.sh +2 -0
- data/benchmarks/rackup/Gemfile +10 -0
- data/benchmarks/rackup/Gemfile.lock +159 -0
- data/benchmarks/rackup/grape.ru +18 -0
- data/benchmarks/rackup/rack.ru +7 -0
- data/benchmarks/rackup/rails.ru +14 -0
- data/benchmarks/rps.rb +14 -2
- data/benchmarks/stress.sh +17 -0
- data/david.gemspec +0 -3
- data/experiments/Gemfile +6 -0
- data/experiments/concurrency/Gemfile +3 -0
- data/experiments/concurrency/stub.rb +88 -0
- data/experiments/hash_key.rb +64 -0
- data/experiments/string_concat.rb +21 -0
- data/experiments/symbol_to_proc.rb +15 -0
- data/experiments/thread_safe.rb +40 -0
- data/lib/david.rb +8 -4
- data/lib/david/actor.rb +1 -11
- data/lib/david/app_config.rb +112 -0
- data/lib/david/exchange.rb +124 -0
- data/lib/david/fake_logger.rb +11 -0
- data/lib/david/garbage_collector.rb +9 -17
- data/lib/david/guerilla/rack/utils.rb +18 -0
- data/lib/david/interop.rb +4 -0
- data/lib/david/interop/mandatory_etsi.rb +4 -0
- data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
- data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
- data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
- data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
- data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
- data/lib/david/observe.rb +24 -29
- data/lib/david/rails/action_controller/base.rb +9 -7
- data/lib/david/railties/config.rb +4 -4
- data/lib/david/registry.rb +22 -0
- data/lib/david/server.rb +56 -56
- data/lib/david/server/constants.rb +1 -0
- data/lib/david/server/mapping.rb +43 -11
- data/lib/david/server/mid_cache.rb +29 -0
- data/lib/david/server/multicast.rb +7 -5
- data/lib/david/server/respond.rb +53 -43
- data/lib/david/server/utility.rb +2 -1
- data/lib/david/show_exceptions.rb +12 -8
- data/lib/david/transmitter.rb +44 -0
- data/lib/david/trap.rb +10 -0
- data/lib/david/version.rb +1 -1
- data/lib/rack/handler/david.rb +6 -10
- data/lib/rack/hello_world.rb +25 -0
- data/spec/app_config_spec.rb +56 -0
- data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
- data/spec/dummy/app/controllers/tests_controller.rb +9 -0
- data/spec/dummy/config/application.rb +4 -6
- data/spec/dummy/config/environments/development.rb +2 -2
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/config/routes.rb +13 -53
- data/spec/interop/mandatory_spec.rb +100 -0
- data/spec/observe_spec.rb +8 -7
- data/spec/resource_discovery_spec.rb +3 -3
- data/spec/server_spec.rb +60 -15
- data/spec/spec_helper.rb +21 -2
- metadata +40 -33
- data/lib/david/request.rb +0 -80
- data/lib/david/server/deduplication.rb +0 -21
- data/lib/david/server/options.rb +0 -79
data/lib/david/server/utility.rb
CHANGED
@@ -6,7 +6,8 @@ module David
|
|
6
6
|
# This can only use each on body and currently does not support streaming.
|
7
7
|
def body_to_string(body)
|
8
8
|
s = ''
|
9
|
-
body.each { |line| s << line
|
9
|
+
body.each { |line| s << line << "\r\n" }
|
10
|
+
body.close if body.respond_to?(:close)
|
10
11
|
s.chomp
|
11
12
|
end
|
12
13
|
end
|
@@ -22,18 +22,23 @@ module David
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def render_exception(
|
25
|
+
def render_exception(e)
|
26
26
|
body = {
|
27
|
-
error:
|
28
|
-
message:
|
27
|
+
error: e.class.to_s,
|
28
|
+
message: e.message
|
29
29
|
}
|
30
30
|
|
31
|
-
log(
|
31
|
+
log.error([body[:error], body[:message]].join(': '))
|
32
32
|
|
33
33
|
body = body.to_json
|
34
34
|
|
35
|
-
code =
|
36
|
-
|
35
|
+
code = if defined?(ActiveRecord) && e.is_a?(ActiveRecord::RecordNotFound)
|
36
|
+
404
|
37
|
+
elsif e.is_a?(ActionController::RoutingError)
|
38
|
+
404
|
39
|
+
else
|
40
|
+
500
|
41
|
+
end
|
37
42
|
|
38
43
|
[code,
|
39
44
|
{
|
@@ -44,9 +49,8 @@ module David
|
|
44
49
|
]
|
45
50
|
end
|
46
51
|
|
47
|
-
def log
|
52
|
+
def log
|
48
53
|
@logger ||= @env['rack.logger']
|
49
|
-
@logger.send(level, message) if @logger
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module David
|
2
|
+
class Transmitter
|
3
|
+
include Registry
|
4
|
+
|
5
|
+
AF_INET6 = 'AF_INET6'.freeze
|
6
|
+
|
7
|
+
def initialize(socket)
|
8
|
+
@log = Celluloid.logger
|
9
|
+
@socket = socket || server.socket
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO Retransmissions
|
13
|
+
def send(exchange)
|
14
|
+
host = normalize_host(exchange.host)
|
15
|
+
|
16
|
+
@socket.send(exchange.message.to_wire, 0, host, exchange.port)
|
17
|
+
|
18
|
+
@log.info('-> ' + exchange.to_s)
|
19
|
+
@log.debug(exchange.message.inspect)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def ipv6?
|
25
|
+
@socket.addr[0] == AF_INET6
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize_host(host)
|
29
|
+
ip = IPAddr.new(host)
|
30
|
+
|
31
|
+
if ipv6? && ip.ipv4?
|
32
|
+
ip = ip.ipv4_mapped
|
33
|
+
end
|
34
|
+
rescue ArgumentError
|
35
|
+
begin
|
36
|
+
host = Resolv.getaddress(host)
|
37
|
+
retry
|
38
|
+
rescue Resolv::ResolvError
|
39
|
+
end
|
40
|
+
else
|
41
|
+
ip.to_s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/david/trap.rb
ADDED
data/lib/david/version.rb
CHANGED
data/lib/rack/handler/david.rb
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
module Rack
|
2
2
|
module Handler
|
3
3
|
class David
|
4
|
-
DEFAULT_OPTIONS = {
|
5
|
-
:Host => ENV['RACK_ENV'] == 'development' ? '::1' : '::',
|
6
|
-
:Port => ::CoAP::PORT
|
7
|
-
}
|
8
|
-
|
9
4
|
def self.run(app, options={})
|
10
|
-
options = DEFAULT_OPTIONS.merge(options)
|
11
|
-
|
12
5
|
g = Celluloid::SupervisionGroup.run!
|
13
6
|
|
14
7
|
g.supervise_as(:server, ::David::Server, app, options)
|
15
|
-
g.supervise_as(:observe, ::David::Observe) if options[:Observe] != false
|
16
8
|
g.supervise_as(:gc, ::David::GarbageCollector)
|
17
9
|
|
10
|
+
unless options[:Observe] == 'false'
|
11
|
+
g.supervise_as(:observe, ::David::Observe)
|
12
|
+
end
|
13
|
+
|
18
14
|
begin
|
19
|
-
|
15
|
+
Celluloid::Actor[:server].run
|
20
16
|
rescue Interrupt
|
21
17
|
Celluloid.logger.info 'Terminated'
|
22
18
|
Celluloid.logger = nil
|
@@ -25,7 +21,7 @@ module Rack
|
|
25
21
|
end
|
26
22
|
|
27
23
|
def self.valid_options
|
28
|
-
host, port = DEFAULT_OPTIONS.values_at(:Host, :Port)
|
24
|
+
host, port = AppConfig::DEFAULT_OPTIONS.values_at(:Host, :Port)
|
29
25
|
|
30
26
|
{
|
31
27
|
'Block=BOOLEAN' => 'Support for blockwise transfer (default: true)',
|
data/lib/rack/hello_world.rb
CHANGED
@@ -60,6 +60,31 @@ module Rack
|
|
60
60
|
{'Content-Type' => 'text/plain'},
|
61
61
|
[Time.now.to_s]
|
62
62
|
]
|
63
|
+
when '/cbor'
|
64
|
+
require 'json'
|
65
|
+
|
66
|
+
body = JSON.parse(env['rack.input'].read).to_s +
|
67
|
+
env['coap.cbor'].to_s
|
68
|
+
|
69
|
+
[200,
|
70
|
+
{
|
71
|
+
'Content-Type' => 'text/plain',
|
72
|
+
'Content-Length' => body.bytesize.to_s
|
73
|
+
},
|
74
|
+
[body]
|
75
|
+
]
|
76
|
+
when '/json'
|
77
|
+
require 'json'
|
78
|
+
|
79
|
+
body = {'Hello' => 'World!'}.to_json
|
80
|
+
|
81
|
+
[200,
|
82
|
+
{
|
83
|
+
'Content-Type' => 'application/json',
|
84
|
+
'Content-Length' => body.bytesize.to_s
|
85
|
+
},
|
86
|
+
[body]
|
87
|
+
]
|
63
88
|
else
|
64
89
|
[404, {}, ['']]
|
65
90
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AppConfig do
|
4
|
+
let(:port) { random_port }
|
5
|
+
|
6
|
+
let!(:server) { supervised_server(:Port => port, :CBOR => true) }
|
7
|
+
|
8
|
+
it { expect(subject).to be_a(Hash) }
|
9
|
+
|
10
|
+
describe '#choose_host' do
|
11
|
+
let(:method) do
|
12
|
+
->(*args) { subject.send(:choose_host, *args) }
|
13
|
+
end
|
14
|
+
|
15
|
+
it { expect(method.call(nil)).to eq(nil) }
|
16
|
+
it { expect(method.call('::')).to eq('::') }
|
17
|
+
it { expect(method.call('::1')).to eq('::1') }
|
18
|
+
it { expect(method.call('localhost')).to eq('::1') }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#choose_port' do
|
22
|
+
let(:method) do
|
23
|
+
->(*args) { subject.send(:choose_port, *args) }
|
24
|
+
end
|
25
|
+
|
26
|
+
it { expect(method.call(nil)).to eq(nil) }
|
27
|
+
it { expect(method.call('1')).to be_a(Fixnum) }
|
28
|
+
it { expect(method.call('1')).to eq(1) }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#default_to_true' do
|
32
|
+
let(:method) do
|
33
|
+
->(*args) { subject.send(:default_to_true, *args) }
|
34
|
+
end
|
35
|
+
|
36
|
+
it { expect(method.call(:block, nil)).to eq(true) }
|
37
|
+
it { expect(method.call(:block, true)).to eq(true) }
|
38
|
+
it { expect(method.call(:block, 'true')).to eq(true) }
|
39
|
+
|
40
|
+
it { expect(method.call(:block, false)).to eq(false) }
|
41
|
+
it { expect(method.call(:block, 'false')).to eq(false) }
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#default_to_false' do
|
45
|
+
let(:method) do
|
46
|
+
->(*args) { subject.send(:default_to_false, *args) }
|
47
|
+
end
|
48
|
+
|
49
|
+
it { expect(method.call(:cbor, nil)).to eq(false) }
|
50
|
+
it { expect(method.call(:cbor, false)).to eq(false) }
|
51
|
+
it { expect(method.call(:cbor, 'false')).to eq(false) }
|
52
|
+
|
53
|
+
it { expect(method.call(:cbor, true)).to eq(true) }
|
54
|
+
it { expect(method.call(:cbor, 'true')).to eq(true) }
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class EtsisController < ActionController::Base
|
2
|
+
def show
|
3
|
+
headers['Content-Type'] = 'text/plain'
|
4
|
+
head 2.05
|
5
|
+
end
|
6
|
+
|
7
|
+
def update
|
8
|
+
head 2.01
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
head 2.04
|
13
|
+
end
|
14
|
+
|
15
|
+
def destroy
|
16
|
+
head 2.02
|
17
|
+
end
|
18
|
+
|
19
|
+
def seg
|
20
|
+
show
|
21
|
+
end
|
22
|
+
|
23
|
+
def query
|
24
|
+
show
|
25
|
+
end
|
26
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
require File.expand_path('../boot', __FILE__)
|
2
2
|
|
3
3
|
# Pick the frameworks you want:
|
4
|
-
require "active_record/railtie"
|
4
|
+
# require "active_record/railtie"
|
5
5
|
require "action_controller/railtie"
|
6
|
-
require "action_mailer/railtie"
|
7
|
-
require "action_view/railtie"
|
8
|
-
require "sprockets/railtie"
|
6
|
+
# require "action_mailer/railtie"
|
7
|
+
# require "action_view/railtie"
|
8
|
+
# require "sprockets/railtie"
|
9
9
|
|
10
10
|
Bundler.require(*Rails.groups)
|
11
11
|
|
@@ -22,8 +22,6 @@ module Dummy
|
|
22
22
|
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
23
23
|
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
24
24
|
# config.i18n.default_locale = :de
|
25
|
-
|
26
|
-
config.coap.cbor = false
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -14,13 +14,13 @@ Rails.application.configure do
|
|
14
14
|
config.action_controller.perform_caching = false
|
15
15
|
|
16
16
|
# Don't care if the mailer can't send.
|
17
|
-
config.action_mailer.raise_delivery_errors = false
|
17
|
+
# config.action_mailer.raise_delivery_errors = false
|
18
18
|
|
19
19
|
# Print deprecation notices to the Rails logger.
|
20
20
|
config.active_support.deprecation = :log
|
21
21
|
|
22
22
|
# Raise an error on page load if there are pending migrations.
|
23
|
-
config.active_record.migration_error = :page_load
|
23
|
+
# config.active_record.migration_error = :page_load
|
24
24
|
|
25
25
|
# Debug mode disables concatenation and preprocessing of assets.
|
26
26
|
# This option may cause significant delays in view rendering with a large
|
@@ -21,7 +21,7 @@ Rails.application.configure do
|
|
21
21
|
config.action_controller.perform_caching = false
|
22
22
|
|
23
23
|
# Raise exceptions instead of rendering exception templates.
|
24
|
-
config.action_dispatch.show_exceptions =
|
24
|
+
config.action_dispatch.show_exceptions = true
|
25
25
|
|
26
26
|
# Disable request forgery protection in test environment.
|
27
27
|
config.action_controller.allow_forgery_protection = false
|
@@ -29,7 +29,7 @@ Rails.application.configure do
|
|
29
29
|
# Tell Action Mailer not to deliver emails to the real world.
|
30
30
|
# The :test delivery method accumulates sent emails in the
|
31
31
|
# ActionMailer::Base.deliveries array.
|
32
|
-
config.action_mailer.delivery_method = :test
|
32
|
+
# config.action_mailer.delivery_method = :test
|
33
33
|
|
34
34
|
# Print deprecation notices to the stderr.
|
35
35
|
config.active_support.deprecation = :stderr
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -1,58 +1,18 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
# You can have the root of your site routed with "root"
|
6
|
-
# root 'welcome#index'
|
7
|
-
|
8
|
-
# Example of regular route:
|
9
|
-
# get 'products/:id' => 'catalog#view'
|
10
|
-
|
11
|
-
# Example of named route that can be invoked with purchase_url(id: product.id)
|
12
|
-
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
|
13
|
-
|
14
|
-
# Example resource route (maps HTTP verbs to controller actions automatically):
|
15
|
-
# resources :products
|
16
|
-
|
17
|
-
# Example resource route with options:
|
18
|
-
# resources :products do
|
19
|
-
# member do
|
20
|
-
# get 'short'
|
21
|
-
# post 'toggle'
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# collection do
|
25
|
-
# get 'sold'
|
26
|
-
# end
|
27
|
-
# end
|
28
|
-
|
29
|
-
# Example resource route with sub-resources:
|
30
|
-
# resources :products do
|
31
|
-
# resources :comments, :sales
|
32
|
-
# resource :seller
|
33
|
-
# end
|
34
|
-
|
35
|
-
# Example resource route with more complex sub-resources:
|
36
|
-
# resources :products do
|
37
|
-
# resources :comments
|
38
|
-
# resources :sales do
|
39
|
-
# get 'recent', on: :collection
|
40
|
-
# end
|
41
|
-
# end
|
2
|
+
# For testing resource discovery middleware
|
3
|
+
resources :things
|
42
4
|
|
43
|
-
#
|
44
|
-
|
45
|
-
# post 'toggle'
|
46
|
-
# end
|
47
|
-
# resources :posts, concerns: :toggleable
|
48
|
-
# resources :photos, concerns: :toggleable
|
5
|
+
# Requests per second benchmark
|
6
|
+
get 'hello' => 'tests#benchmark'
|
49
7
|
|
50
|
-
#
|
51
|
-
|
52
|
-
# # Directs /admin/products/* to Admin::ProductsController
|
53
|
-
# # (app/controllers/admin/products_controller.rb)
|
54
|
-
# resources :products
|
55
|
-
# end
|
8
|
+
# CBOR transcoding tests
|
9
|
+
get 'cbor' => 'tests#cbor'
|
56
10
|
|
57
|
-
|
11
|
+
# ETSI Plugtests
|
12
|
+
get 'test' => 'etsis#show'
|
13
|
+
post 'test' => 'etsis#update'
|
14
|
+
put 'test' => 'etsis#create'
|
15
|
+
delete 'test' => 'etsis#destroy'
|
16
|
+
get 'seg1/seg2/seg3' => 'etsis#seg'
|
17
|
+
get 'query' => 'etsis#query'
|
58
18
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
[
|
4
|
+
Interop::MandatoryETSI::Grape,
|
5
|
+
Interop::MandatoryETSI::Hobbit,
|
6
|
+
Interop::MandatoryETSI::NYNY,
|
7
|
+
Interop::MandatoryETSI::Rack,
|
8
|
+
Interop::MandatoryETSI::Sinatra,
|
9
|
+
Rails.application
|
10
|
+
].each do |app|
|
11
|
+
describe "ETSI Plugstests, Mandatory, #{app.to_s.split('::').last}" do
|
12
|
+
let!(:server) { supervised_server(:MinimalMapping => true, app: app) }
|
13
|
+
|
14
|
+
[:con, :non].each do |tt|
|
15
|
+
context tt do
|
16
|
+
it 'TD_COAP_CORE_0{1,5}' do
|
17
|
+
mid, response = req(:get, '/test', tt: tt)
|
18
|
+
|
19
|
+
expect(response).to be_a(CoAP::Message)
|
20
|
+
expect(response.mcode).to eq([2, 5])
|
21
|
+
expect(response.mid).to eq(mid)
|
22
|
+
expect(response.options[:content_format]).to eq(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'TD_COAP_CORE_0{2,6}' do
|
26
|
+
mid, response = req(:post, '/test', tt: tt, payload: 'foo',
|
27
|
+
content_format: 0)
|
28
|
+
|
29
|
+
expect(response).to be_a(CoAP::Message)
|
30
|
+
expect(response.mcode).to eq([2, 1])
|
31
|
+
expect(response.mid).to eq(mid)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'TD_COAP_CORE_0{3,7}' do
|
35
|
+
mid, response = req(:put, '/test', tt: tt, payload: 'foo',
|
36
|
+
content_format: 0)
|
37
|
+
|
38
|
+
expect(response).to be_a(CoAP::Message)
|
39
|
+
expect(response.mcode).to eq([2, 4])
|
40
|
+
expect(response.mid).to eq(mid)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'TD_COAP_CORE_0{4,8}' do
|
44
|
+
mid, response = req(:delete, '/test', tt: tt)
|
45
|
+
|
46
|
+
expect(response).to be_a(CoAP::Message)
|
47
|
+
expect(response.mcode).to eq([2, 2])
|
48
|
+
expect(response.mid).to eq(mid)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'TD_COAP_CORE_09' do
|
54
|
+
pending
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'TD_COAP_CORE_10' do
|
58
|
+
token = rand(0xffffffff)
|
59
|
+
mid, response = req(:get, '/test', token: token)
|
60
|
+
|
61
|
+
expect(response).to be_a(CoAP::Message)
|
62
|
+
expect(response.mcode).to eq([2, 5])
|
63
|
+
expect(response.mid).to eq(mid)
|
64
|
+
expect(response.options[:content_format]).to eq(0)
|
65
|
+
expect(response.options[:token]).to eq(token)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'TD_COAP_CORE_11' do
|
69
|
+
mid, response = req(:get, '/test')
|
70
|
+
|
71
|
+
expect(response).to be_a(CoAP::Message)
|
72
|
+
expect(response.mcode).to eq([2, 5])
|
73
|
+
expect(response.mid).to eq(mid)
|
74
|
+
expect(response.options[:content_format]).to eq(0)
|
75
|
+
expect(response.options[:token]).to eq(0)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'TD_COAP_CORE_12' do
|
79
|
+
mid, response = req(:get, '/seg1/seg2/seg3')
|
80
|
+
|
81
|
+
expect(response).to be_a(CoAP::Message)
|
82
|
+
expect(response.mcode).to eq([2, 5])
|
83
|
+
expect(response.mid).to eq(mid)
|
84
|
+
expect(response.options[:content_format]).to eq(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'TD_COAP_CORE_13' do
|
88
|
+
mid, response = req(:get, '/query', uri_query: ['foo=1', 'bar=2'])
|
89
|
+
|
90
|
+
expect(response).to be_a(CoAP::Message)
|
91
|
+
expect(response.mcode).to eq([2, 5])
|
92
|
+
expect(response.mid).to eq(mid)
|
93
|
+
expect(response.options[:content_format]).to eq(0)
|
94
|
+
end
|
95
|
+
|
96
|
+
after do
|
97
|
+
server.terminate
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|