dca 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.
- data/.document +5 -0
- data/.rspec +2 -0
- data/Gemfile +48 -0
- data/Gemfile.lock +126 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/bin/dca +5 -0
- data/dca.gemspec +160 -0
- data/lib/dca.rb +64 -0
- data/lib/dca/cli.rb +32 -0
- data/lib/dca/commands/area.rb +133 -0
- data/lib/dca/commands/templates/area/analyzer.rb.erb +34 -0
- data/lib/dca/commands/templates/area/area.rb.erb +2 -0
- data/lib/dca/commands/templates/area/models.rb.erb +2 -0
- data/lib/dca/commands/templates/area/page.rb.erb +17 -0
- data/lib/dca/commands/templates/area/position.rb.erb +8 -0
- data/lib/dca/commands/templates/config.yml.erb +38 -0
- data/lib/dca/commands/templates/spec/analyzer_spec.rb.erb +15 -0
- data/lib/dca/commands/templates/spec/spec_helper.rb.erb +2 -0
- data/lib/dca/config.rb +20 -0
- data/lib/dca/helpers.rb +2 -0
- data/lib/dca/helpers/logger.rb +50 -0
- data/lib/dca/jobs.rb +3 -0
- data/lib/dca/jobs/analyzer_job.rb +119 -0
- data/lib/dca/jobs/job.rb +62 -0
- data/lib/dca/models.rb +5 -0
- data/lib/dca/models/base_model.rb +73 -0
- data/lib/dca/models/binder.rb +68 -0
- data/lib/dca/models/binder_helper.rb +48 -0
- data/lib/dca/models/nokogiri_binder.rb +43 -0
- data/lib/dca/models/position.rb +15 -0
- data/lib/dca/net.rb +1 -0
- data/lib/dca/net/browser_helper.rb +20 -0
- data/lib/dca/notifier.rb +2 -0
- data/lib/dca/notifier/notifier.rb +11 -0
- data/lib/dca/notifier/redis/models/analyze_notify.rb +12 -0
- data/lib/dca/notifier/redis/models/failure_notify.rb +8 -0
- data/lib/dca/notifier/redis/models/fetch_notify.rb +15 -0
- data/lib/dca/notifier/redis/models/session.rb +52 -0
- data/lib/dca/notifier/redis/notifier.rb +25 -0
- data/lib/dca/notifier/redis_notifier.rb +9 -0
- data/lib/dca/storage.rb +3 -0
- data/lib/dca/storage/elasticsearch_storage.rb +80 -0
- data/lib/dca/storage/mongo_storage.rb +51 -0
- data/lib/dca/storage/storage.rb +55 -0
- data/spec/analyzer_spec.rb +64 -0
- data/spec/area_task_spec.rb +45 -0
- data/spec/base_model_spec.rb +34 -0
- data/spec/binder_spec.rb +69 -0
- data/spec/config.yml +18 -0
- data/spec/elasticsearch_storage_spec.rb +28 -0
- data/spec/fixtures/page.html +12 -0
- data/spec/fixtures/positions.yml +13 -0
- data/spec/fixtures/positions_with_error.yml +14 -0
- data/spec/fixtures/states.yml +3 -0
- data/spec/job_spec.rb +31 -0
- data/spec/mock/analyzer_job.rb +30 -0
- data/spec/mock/file_storage.rb +28 -0
- data/spec/mock/notify_object.rb +13 -0
- data/spec/mock/page.rb +13 -0
- data/spec/mock/position.rb +40 -0
- data/spec/mock/web_notifier.rb +30 -0
- data/spec/mongo_storage_spec.rb +20 -0
- data/spec/redis_notifier_spec.rb +98 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/storage_examples.rb +103 -0
- metadata +408 -0
data/spec/config.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
test:
|
2
|
+
elascticseach_db:
|
3
|
+
driver: ElasticSearch
|
4
|
+
host: localhost
|
5
|
+
port: 9200
|
6
|
+
index: test
|
7
|
+
mongo_db:
|
8
|
+
driver: Mongo
|
9
|
+
host: localhost
|
10
|
+
port: 27017
|
11
|
+
collection: test
|
12
|
+
db:
|
13
|
+
driver: Mock::File
|
14
|
+
notifier:
|
15
|
+
driver: Mock::Web
|
16
|
+
logger: false
|
17
|
+
|
18
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require File.expand_path('../mock/position', __FILE__)
|
3
|
+
|
4
|
+
include DCA::Mock
|
5
|
+
|
6
|
+
describe 'ElasticSearch storage' do
|
7
|
+
let(:connection) { @connection ||= DCA::ElasticSearchStorage.establish_connection APP_CONFIG[:elascticseach_db] }
|
8
|
+
let(:position) { ElasticSearchPosition.new :base_id => '0', :checksum => '0'}
|
9
|
+
let(:storage) { @storage ||= DCA::ElasticSearchStorage.new connection, position.class, :index => 'test' }
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
connection
|
13
|
+
storage.index do
|
14
|
+
create
|
15
|
+
store :type => 'position', :base_id => '1', :checksum => '1'
|
16
|
+
refresh
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after :all do
|
21
|
+
storage.index do
|
22
|
+
delete
|
23
|
+
refresh
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it_behaves_like 'storage'
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Page</title>
|
4
|
+
</head>
|
5
|
+
<body>
|
6
|
+
<ul>
|
7
|
+
<li><a href="/positions/1">Position 1</a><span class="description">Description 1</span><span class="date">12.10.2012</span></li>
|
8
|
+
<li><a href="/positions/2">Position 2</a><span class="description">Description 2</span><span class="date">13.10.2012</span></li>
|
9
|
+
<li><a href="/positions/3">Position 3</a><span class="description">Description 3</span><span class="date">14.10.2012</span></li>
|
10
|
+
</ul>
|
11
|
+
</body>
|
12
|
+
</html>
|
data/spec/job_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require File.expand_path('../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
module TestModule
|
5
|
+
class TestJob < DCA::Jobs::Job
|
6
|
+
end
|
7
|
+
|
8
|
+
class LoopJob < DCA::Jobs::Job
|
9
|
+
def perform
|
10
|
+
loop do
|
11
|
+
sleep 1
|
12
|
+
break if shutdown?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'Job' do
|
19
|
+
it 'should get queue name from module name' do
|
20
|
+
TestModule::TestJob.queue.should == 'TestModule'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should shutdown when QUIT signal is happened' do
|
24
|
+
job_pid = Process.fork { TestModule::LoopJob.create }
|
25
|
+
sleep 1
|
26
|
+
Process.kill 'QUIT', job_pid
|
27
|
+
Timeout::timeout(1) {
|
28
|
+
Process.waitpid job_pid
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module DCA
|
2
|
+
module Mock
|
3
|
+
class AnalyzerJob < DCA::Jobs::AnalyzerJob
|
4
|
+
|
5
|
+
def positions(&block)
|
6
|
+
positions = YAML.load_file(options[:fixture])
|
7
|
+
positions.each { |position| block.call Position.new position.symbolize_keys }
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch(position)
|
11
|
+
raise Exception if position.raise
|
12
|
+
position.failed ? nil : position
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Areas
|
18
|
+
module Mock
|
19
|
+
class AnalyzerJob < DCA::Jobs::AnalyzerJob
|
20
|
+
def perform
|
21
|
+
loop do
|
22
|
+
break if shutdown?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DCA
|
2
|
+
module Mock
|
3
|
+
class FileStorage
|
4
|
+
|
5
|
+
attr_reader :collection
|
6
|
+
|
7
|
+
def initialize(connection, context, options = {})
|
8
|
+
@collection = DCA.project_name
|
9
|
+
@states = YAML.load_file('./spec/fixtures/states.yml')
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.establish_connection(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def state(position)
|
16
|
+
@states[position.id].to_sym
|
17
|
+
end
|
18
|
+
|
19
|
+
def refresh(position, state)
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def context object
|
24
|
+
self.clone
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/spec/mock/page.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module DCA
|
2
|
+
module Mock
|
3
|
+
class Page < DCA::Models::BaseModel
|
4
|
+
has_many :positions, ExtPosition, :selector => 'li'
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
class PageExt < DCA::Models::BaseModel
|
9
|
+
attr_accessor :category
|
10
|
+
has_many :positions, :selector => 'li', :polymorphic => :category, :append => true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module DCA
|
2
|
+
module Mock
|
3
|
+
class MongoSearchPosition < DCA::Models::Position
|
4
|
+
establish_connection :mongo_db
|
5
|
+
end
|
6
|
+
|
7
|
+
class ElasticSearchPosition < DCA::Models::Position
|
8
|
+
establish_connection :elascticseach_db
|
9
|
+
end
|
10
|
+
|
11
|
+
class Position < DCA::Models::Position
|
12
|
+
attr_accessor :raise, :failed, :title
|
13
|
+
|
14
|
+
has_one :base_id, :integer, :selector => 'a', :attribute => :href, :regex => /(\d+)$/
|
15
|
+
has_one :title, :string, :selector => 'a'
|
16
|
+
end
|
17
|
+
|
18
|
+
class ExtPosition < Position
|
19
|
+
has_one :description, :string, :selector => 'span.description'
|
20
|
+
has_one :date, :datetime, :selector => 'span.date'
|
21
|
+
end
|
22
|
+
|
23
|
+
class FullPosition < Position
|
24
|
+
has_one :base_id, :string, :selector => 'a'
|
25
|
+
end
|
26
|
+
|
27
|
+
class ChildPosition < DCA::Models::Position
|
28
|
+
attr_reader :name, :test
|
29
|
+
end
|
30
|
+
|
31
|
+
class RootPosition < DCA::Models::Position
|
32
|
+
has_one :one_child, :child_position
|
33
|
+
has_many :child_position
|
34
|
+
end
|
35
|
+
|
36
|
+
class PositionWithoutState < DCA::Models::BaseModel
|
37
|
+
attr_reader :name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module DCA
|
2
|
+
module Mock
|
3
|
+
class WebNotifier
|
4
|
+
def initialize config
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.queue
|
9
|
+
@queue ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.clean
|
13
|
+
@queue = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def push(object, event, options)
|
17
|
+
if event == :fetch && options[:result] == false
|
18
|
+
failed_queue = Mock::WebNotifier.queue[:failed] ||= {}
|
19
|
+
failed_queue[options[:state]] ||= 0
|
20
|
+
failed_queue[options[:state]] += 1
|
21
|
+
end
|
22
|
+
if [:analyze, :fetch].include? event
|
23
|
+
queue = Mock::WebNotifier.queue[event] ||= {}
|
24
|
+
queue[options[:state]] ||= 0
|
25
|
+
queue[options[:state]] += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require File.expand_path('../mock/position', __FILE__)
|
3
|
+
|
4
|
+
include DCA::Mock
|
5
|
+
|
6
|
+
describe DCA::MongoStorage do
|
7
|
+
let(:connection) { @connection ||= DCA::MongoStorage.establish_connection APP_CONFIG[:mongo_db] }
|
8
|
+
let(:position) { MongoSearchPosition.new :base_id => '0', :checksum => '0'}
|
9
|
+
let(:storage) { @storage ||= DCA::MongoStorage.new connection, position.class, :collection => 'test' }
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
connection
|
13
|
+
end
|
14
|
+
|
15
|
+
after :all do
|
16
|
+
connection.drop_database storage.database.name
|
17
|
+
end
|
18
|
+
|
19
|
+
it_behaves_like 'storage'
|
20
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require File.expand_path('../mock/notify_object', __FILE__)
|
3
|
+
require File.expand_path('../mock/position', __FILE__)
|
4
|
+
require File.expand_path('../mock/file_storage', __FILE__)
|
5
|
+
require File.expand_path('../mock/analyzer_job', __FILE__)
|
6
|
+
|
7
|
+
include DCA
|
8
|
+
|
9
|
+
describe 'Redis Notifier' do
|
10
|
+
|
11
|
+
it 'should connect to redis' do
|
12
|
+
DCA::Notifier.create :driver => 'Redis', :host => 'localhost', :port => '6379'
|
13
|
+
Ohm.redis.info
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'Instance' do
|
17
|
+
before :all do
|
18
|
+
DCA::Notifier.create :driver => 'Redis', :host => 'localhost', :port => '6379'
|
19
|
+
end
|
20
|
+
|
21
|
+
before :each do
|
22
|
+
DCA::Redis::Session.all.each { |session| session.delete }
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should push analyze notify' do
|
26
|
+
notify_object = Mock::NotifyObject.new
|
27
|
+
DCA::Notifier.push notify_object, :analyze, :state => :create
|
28
|
+
session = DCA::Redis::Session.find(:project => 'DCA', :area => 'test_queue', :uid => 'test_session').first
|
29
|
+
session.analyze_state(:create).count.should equal 1
|
30
|
+
|
31
|
+
DCA::Notifier.push notify_object, :analyze, :state => :create
|
32
|
+
session.analyze_state(:create).count.should equal 2
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should push analyze notify with different state' do
|
36
|
+
notify_object = Mock::NotifyObject.new
|
37
|
+
DCA::Notifier.push notify_object, :analyze, :state => :create
|
38
|
+
DCA::Notifier.push notify_object, :analyze, :state => :update
|
39
|
+
DCA::Notifier.push notify_object, :analyze, :state => :remove
|
40
|
+
DCA::Notifier.push notify_object, :analyze, :state => :unmodified
|
41
|
+
|
42
|
+
session = DCA::Redis::Session.find(:project => 'DCA', :area => 'test_queue', :uid => 'test_session').first
|
43
|
+
session.analyze_state(:create).count.should equal 1
|
44
|
+
session.analyze_state(:update).count.should equal 1
|
45
|
+
session.analyze_state(:remove).count.should equal 1
|
46
|
+
session.analyze_state(:unmodified).count.should equal 1
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should push fetch notify' do
|
50
|
+
notify_object = Mock::NotifyObject.new
|
51
|
+
DCA::Notifier.push notify_object, :fetch, :state => :create, :result => true
|
52
|
+
session = DCA::Redis::Session.find(:project => 'DCA', :area => 'test_queue', :uid => 'test_session').first
|
53
|
+
session.fetch_state(:create).success.should equal 1
|
54
|
+
|
55
|
+
DCA::Notifier.push notify_object, :fetch, :state => :create, :result => true
|
56
|
+
session.fetch_state(:create).success.should equal 2
|
57
|
+
|
58
|
+
DCA::Notifier.push notify_object, :fetch, :state => :create, :result => false
|
59
|
+
session.fetch_state(:create).failure.should equal 1
|
60
|
+
|
61
|
+
DCA::Notifier.push notify_object, :fetch, :state => :create, :result => false
|
62
|
+
session.fetch_state(:create).failure.should equal 2
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should push analyze notify with different state' do
|
66
|
+
notify_object = Mock::NotifyObject.new
|
67
|
+
DCA::Notifier.push notify_object, :fetch, :state => :create, :result => true
|
68
|
+
DCA::Notifier.push notify_object, :fetch, :state => :update, :result => true
|
69
|
+
|
70
|
+
session = DCA::Redis::Session.find(:project => 'DCA', :area => 'test_queue', :uid => 'test_session').first
|
71
|
+
session.fetch_state(:create).success.should equal 1
|
72
|
+
session.fetch_state(:update).success.should equal 1
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should notify failure' do
|
76
|
+
notify_object = Mock::NotifyObject.new
|
77
|
+
|
78
|
+
begin
|
79
|
+
raise "Test exception"
|
80
|
+
rescue Exception => exception
|
81
|
+
DCA::Notifier.push notify_object, :failure, :exception => exception
|
82
|
+
end
|
83
|
+
|
84
|
+
session = DCA::Redis::Session.find(:project => 'DCA', :area => 'test_queue', :uid => 'test_session').first
|
85
|
+
session.failures.count.should equal 1
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should work with analyze job' do
|
89
|
+
Mock::AnalyzerJob.create :fixture => './spec/fixtures/positions.yml'
|
90
|
+
session = DCA::Redis::Session.all.first
|
91
|
+
session.analyze_state(:create).count.should equal 2
|
92
|
+
session.analyze_state(:unmodified).count.should equal 1
|
93
|
+
|
94
|
+
session.fetch_state(:create).success.should equal 1
|
95
|
+
session.fetch_state(:create).failure.should equal 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
require 'rake'
|
9
|
+
require 'rspec'
|
10
|
+
require 'hashr'
|
11
|
+
|
12
|
+
SYS_ENV = 'test'
|
13
|
+
APP_CONFIG = YAML.load_file('./spec/config.yml')[SYS_ENV].deep_symbolize_keys
|
14
|
+
|
15
|
+
require './lib/dca'
|
16
|
+
|
17
|
+
|
18
|
+
# Set resque call perform method inline, without putting into redis queue
|
19
|
+
Resque.inline = true
|
20
|
+
|
21
|
+
# Requires supporting files with custom matchers and macros, etc,
|
22
|
+
# in ./support/ and its subdirectories.
|
23
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
shared_examples_for 'storage' do
|
2
|
+
context '.establish_connection' do
|
3
|
+
it 'should connect to storage' do
|
4
|
+
storage.should_not be_nil
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#state' do
|
9
|
+
context 'when new position' do
|
10
|
+
subject { position.state }
|
11
|
+
it { should equal :create}
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when modify position' do
|
15
|
+
before do
|
16
|
+
position.save
|
17
|
+
position.checksum = 1
|
18
|
+
end
|
19
|
+
|
20
|
+
after { position.destroy }
|
21
|
+
|
22
|
+
subject { position.state }
|
23
|
+
it { should equal :update}
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when exist position' do
|
27
|
+
before { position.save }
|
28
|
+
after { position.destroy }
|
29
|
+
subject { position.state }
|
30
|
+
it { should equal :unmodified}
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when position without state' do
|
34
|
+
let(:position) { PositionWithoutState.new name: 'test'}
|
35
|
+
subject { position.state }
|
36
|
+
it { should equal :create}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#refresh' do
|
41
|
+
def refresh state
|
42
|
+
storage.should_receive(state).with(position)
|
43
|
+
storage.refresh(position, state)
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when new position' do
|
47
|
+
it 'then create it' do
|
48
|
+
refresh :create
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when modify position' do
|
53
|
+
it 'then update it' do
|
54
|
+
refresh :update
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when old position' do
|
59
|
+
it 'then delete it' do
|
60
|
+
refresh :remove
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#create' do
|
66
|
+
before { position.save }
|
67
|
+
after { position.destroy }
|
68
|
+
|
69
|
+
it 'sould create position' do
|
70
|
+
storage.find(position).should_not be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'set position id' do
|
74
|
+
position.id.should_not be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#update' do
|
79
|
+
before do
|
80
|
+
position.save
|
81
|
+
position.checksum = '1'
|
82
|
+
position.save
|
83
|
+
end
|
84
|
+
after { position.destroy }
|
85
|
+
|
86
|
+
it 'should update position' do
|
87
|
+
storage.find(position)['checksum'].should eql '1'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#remove' do
|
92
|
+
before do
|
93
|
+
position.save
|
94
|
+
position.destroy
|
95
|
+
end
|
96
|
+
|
97
|
+
after { position.destroy }
|
98
|
+
|
99
|
+
it 'should remove position' do
|
100
|
+
storage.find(position).should be_nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|