kanban 0.7.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f6f0f321b90145bc913fab9ee5b932908d16d2e8
4
- data.tar.gz: 8f015ee628cc53a95d702f646a15ec6d7a47188c
3
+ metadata.gz: a02deca9fb7c7a28e7e8ad7098207514dd7f7923
4
+ data.tar.gz: 86cd0848296598c34ff29804f074982335ddb7ba
5
5
  SHA512:
6
- metadata.gz: 9f51ccf582a838859c987187938554200e9a5ac471221a9928994cf46e370b8b1174ba4d168831ed6f1b3a55e77aabbf31727419bc1c2b4122830f9af62eb064
7
- data.tar.gz: 796f6ca6814a854ac62163a3142daff394bde399b12280594f9cb7d3583f45d443c76f56ba0d17cf8d970142217b29f980da3969097fcbed2a68daef2bdfe9af
6
+ metadata.gz: 7e3c12bdac55a092b22cf08e144195e6f42bb061d8f549dc2b482c9effb0002f6df083a7d28638deb96a36b2d7fce1865971dca8491420689763f51b6e65fd1d
7
+ data.tar.gz: bdc4dd6c4a1bf249771f9fba20b7640daac28ec3c53cb7c4e29616ef372765fad3f9fc2c21b096af52875b5b32fe44b46a602e519b6af085d8c53c58f1e8fbf7
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source "http://rubygems.org"
4
4
  # gem "activesupport", ">= 2.3.5"
5
5
 
6
6
  gem 'redis'
7
+ gem 'contracts'
7
8
 
8
9
  # Add dependencies to develop your gem here.
9
10
  # Include everything needed to run rake, tests, features, etc.
data/Gemfile.lock CHANGED
@@ -4,6 +4,7 @@ GEM
4
4
  addressable (2.3.8)
5
5
  autotest-standalone (4.5.11)
6
6
  builder (3.2.2)
7
+ contracts (0.9)
7
8
  descendants_tracker (0.0.4)
8
9
  thread_safe (~> 0.3, >= 0.3.1)
9
10
  diff-lcs (1.2.5)
@@ -93,6 +94,7 @@ PLATFORMS
93
94
  DEPENDENCIES
94
95
  autotest-standalone
95
96
  bundler (~> 1.0)
97
+ contracts
96
98
  jeweler (~> 2.0.1)
97
99
  rdoc (~> 3.12)
98
100
  redis
data/README.md CHANGED
@@ -1,2 +1,39 @@
1
1
  # kanban
2
2
  Because your code totally needed an Agile workflow.
3
+
4
+ Description
5
+ ===========
6
+ Kanban provides tools to model task flow in distributed apps.
7
+
8
+ Create a Backlog
9
+ ----------------
10
+ ```ruby
11
+ require 'redis'
12
+ require 'kanban'
13
+
14
+ backlog = Kanban::Backlog.new backend: Redis.new
15
+ ```
16
+
17
+ Add some tasks to your shiny new Backlog
18
+ ----------------------------------------
19
+ ```ruby
20
+ task = { 'foo' => 'bar' }
21
+ 5.times { backlog.add task }
22
+ ```
23
+
24
+ (Elsewhere) Stake a claim on a task from the backlog
25
+ ----------------------------------------------------
26
+ ```ruby
27
+ task_id = backlog.claim # Will block until there is a task, if the backlog is empty or all tasks are being worked.
28
+ details = backlog.get task_id
29
+ ```
30
+
31
+ Mark a task as complete (or unworkable)
32
+ ---------------------------------------
33
+ ```ruby
34
+ backlog.complete task_id
35
+ # or backlog.unworkable task_id
36
+ backlog.done? task_id # => true
37
+ ```
38
+
39
+ Claims expire after awhile (default 3 seconds), and become eligible to be worked by something else.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.0
1
+ 0.8.0
data/kanban.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: kanban 0.7.0 ruby lib
5
+ # stub: kanban 0.8.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "kanban"
9
- s.version = "0.7.0"
9
+ s.version = "0.8.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Chris Olstrom"]
14
- s.date = "2015-05-30"
14
+ s.date = "2015-06-02"
15
15
  s.description = "Because your code totally needed an Agile Workflow"
16
16
  s.email = "chris@olstrom.com"
17
17
  s.extra_rdoc_files = [
@@ -48,6 +48,7 @@ Gem::Specification.new do |s|
48
48
 
49
49
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
50
  s.add_runtime_dependency(%q<redis>, [">= 0"])
51
+ s.add_runtime_dependency(%q<contracts>, [">= 0"])
51
52
  s.add_development_dependency(%q<rspec>, ["~> 3.2"])
52
53
  s.add_development_dependency(%q<yard>, ["~> 0.7"])
53
54
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
@@ -60,6 +61,7 @@ Gem::Specification.new do |s|
60
61
  s.add_development_dependency(%q<rspec-autotest>, [">= 0"])
61
62
  else
62
63
  s.add_dependency(%q<redis>, [">= 0"])
64
+ s.add_dependency(%q<contracts>, [">= 0"])
63
65
  s.add_dependency(%q<rspec>, ["~> 3.2"])
64
66
  s.add_dependency(%q<yard>, ["~> 0.7"])
65
67
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
@@ -73,6 +75,7 @@ Gem::Specification.new do |s|
73
75
  end
74
76
  else
75
77
  s.add_dependency(%q<redis>, [">= 0"])
78
+ s.add_dependency(%q<contracts>, [">= 0"])
76
79
  s.add_dependency(%q<rspec>, ["~> 3.2"])
77
80
  s.add_dependency(%q<yard>, ["~> 0.7"])
78
81
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
data/lib/kanban.rb CHANGED
@@ -1,9 +1,11 @@
1
1
  require 'redis'
2
+ require 'contracts'
2
3
  require_relative 'kanban/hash_safety'
3
4
 
4
5
  module Kanban
5
6
  class Backlog
6
7
  using HashSafety
8
+ include Contracts
7
9
 
8
10
  attr_reader :namespace, :queue, :item
9
11
 
@@ -14,77 +16,105 @@ module Kanban
14
16
  @backend = backend
15
17
  end
16
18
 
19
+ Contract Num => Hash
17
20
  def get(id)
18
21
  @backend.hgetall "#{@item}:#{id}"
19
22
  end
20
23
 
24
+ Contract None => Num
21
25
  def next_id
22
26
  @backend.incr "#{@queue}:id"
23
27
  end
24
28
 
29
+ Contract None => ArrayOf[Num]
25
30
  def todo
26
31
  @backend.lrange("#{@queue}:todo", 0, -1).map(&:to_i)
27
32
  end
28
33
 
34
+ Contract HashOf[String, Any] => Num
29
35
  def add(task)
30
- fail TypeError if task.keys_contain_symbols?
31
36
  id = next_id
32
37
  @backend.hmset "#{@item}:#{id}", *task.to_a
33
38
  @backend.lpush "#{@queue}:todo", id
34
39
  id
35
40
  end
36
41
 
42
+ Contract Hash => Num
37
43
  def add!(task)
38
44
  safe = task.with_string_keys
39
45
  add(safe)
40
46
  end
41
47
 
48
+ Contract Maybe[({ duration: Num })] => Num
42
49
  def claim(duration: 3)
43
50
  id = @backend.brpoplpush("#{@queue}:todo", "#{@queue}:doing")
44
51
  @backend.set "#{@item}:#{id}:claimed", true, ex: duration
45
52
  id.to_i
46
53
  end
47
54
 
55
+ Contract Num => Bool
48
56
  def claimed?(id)
49
57
  @backend.exists "#{@item}:#{id}:claimed"
50
58
  end
51
59
 
60
+ Contract None => ArrayOf[Num]
52
61
  def doing
53
62
  @backend.lrange("#{@queue}:doing", 0, -1).map(&:to_i)
54
63
  end
55
64
 
65
+ Contract Num => Bool
56
66
  def complete(id)
57
67
  @backend.setbit("#{@queue}:completed", id, 1).zero?
58
68
  end
59
69
 
70
+ Contract Num => Bool
60
71
  def completed?(id)
61
72
  @backend.getbit("#{@queue}:completed", id) == 1
62
73
  end
63
74
 
75
+ Contract Num => Bool
64
76
  def unworkable(id)
65
77
  @backend.setbit("#{@queue}:unworkable", id, 1).zero?
66
78
  end
67
79
 
80
+ Contract Num => Bool
68
81
  def unworkable?(id)
69
82
  @backend.getbit("#{@queue}:unworkable", id) == 1
70
83
  end
71
84
 
85
+ Contract Num => Bool
72
86
  def done?(id)
73
87
  completed?(id) || unworkable?(id)
74
88
  end
75
89
 
90
+ Contract Num => Bool
76
91
  def release(id)
77
92
  expire_claim id
78
93
  @backend.lrem("#{@queue}:doing", 0, id) > 0
79
94
  end
80
95
 
96
+ Contract Num => Bool
81
97
  def expire_claim(id)
82
98
  @backend.expire "#{@item}:#{id}:claimed", 0
83
99
  end
84
100
 
101
+ Contract Num => Bool
85
102
  def requeue(id)
86
103
  release id
87
104
  @backend.lpush("#{@queue}:todo", id) > 0
88
105
  end
106
+
107
+ Contract None => ArrayOf[Num]
108
+ def groom
109
+ doing.map do |id|
110
+ # puts "#{id} has active claim" if claimed? id
111
+ next if claimed? id
112
+ if done? id
113
+ id if release id
114
+ else
115
+ id if requeue id
116
+ end
117
+ end.compact
118
+ end
89
119
  end
90
120
  end
data/spec/kanban_spec.rb CHANGED
@@ -2,10 +2,12 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
  require 'timeout'
3
3
 
4
4
  describe 'Backlog' do
5
+ let(:redis) { Redis.new }
6
+ let(:backlog) { Kanban::Backlog.new backend: redis, namespace: 'kanban:test' }
7
+
5
8
  before do
6
- @backlog = Kanban::Backlog.new backend: Redis.new, namespace: 'kanban:test'
7
9
  task = { 'test' => 'data' }
8
- 5.times { @backlog.add(task) }
10
+ 5.times { backlog.add(task) }
9
11
  end
10
12
 
11
13
  after(:all) do
@@ -15,159 +17,363 @@ describe 'Backlog' do
15
17
  end
16
18
  end
17
19
 
18
- it 'instantiates without error' do
19
- expect(@backlog).to be_an_instance_of(Kanban::Backlog)
20
- end
20
+ describe '#new' do
21
+ it 'should require a backend' do
22
+ expect { Kanban::Backlog.new }.to raise_error(ArgumentError)
23
+ end
21
24
 
22
- it 'should allow namespace configuration at initialization' do
23
- expect(@backlog.namespace).to eq 'kanban:test'
24
- end
25
+ subject { backlog }
26
+ it { is_expected.to be_an_instance_of Kanban::Backlog }
25
27
 
26
- it 'should prefix queue keys with the namespace' do
27
- expect(@backlog.queue).to start_with('kanban:test')
28
- end
28
+ context 'when no optional parameters are given' do
29
+ let(:backlog) { Kanban::Backlog.new backend: redis }
29
30
 
30
- it 'should prefix item keys with the namespace' do
31
- expect(@backlog.item).to start_with('kanban:test')
32
- end
31
+ describe '.namespace' do
32
+ subject { backlog.namespace }
33
+ it { is_expected.to eq 'default' }
34
+ end
33
35
 
34
- it 'should require a backend' do
35
- expect { Kanban::Backlog.new }.to raise_error(ArgumentError)
36
- end
36
+ describe '.queue' do
37
+ subject { backlog.queue }
38
+ it { is_expected.to eq 'default:tasks' }
39
+ end
37
40
 
38
- it 'should be able to get a task' do
39
- expect(@backlog.get(0)).to be_a Hash
40
- end
41
+ describe '.item' do
42
+ subject { backlog.item }
43
+ it { is_expected.to eq 'default:task' }
44
+ end
45
+ end
41
46
 
42
- it 'shoud provide the next ID to assign to a task' do
43
- expect(@backlog.next_id).to be_a Fixnum
44
- end
47
+ context 'when :namespace is "testing"' do
48
+ let(:backlog) { Kanban::Backlog.new backend: redis, namespace: 'testing' }
45
49
 
46
- it 'should not reuse IDs' do
47
- expect(@backlog.next_id).to eq (@backlog.next_id - 1)
48
- end
50
+ describe '.namespace' do
51
+ subject { backlog.namespace }
52
+ it { is_expected.to eq 'testing' }
53
+ end
54
+ end
49
55
 
50
- it 'should have a list of tasks waiting to be done' do
51
- expect(@backlog.todo).to be_an Array
52
- end
56
+ context 'when :queue is "tests"' do
57
+ let(:backlog) { Kanban::Backlog.new backend: redis, queue: 'tests' }
58
+ describe '.queue' do
59
+ subject { backlog.queue }
60
+ it { is_expected.to eq 'default:tests' }
61
+ end
62
+ end
53
63
 
54
- it 'should throw a TypeError if passed a Hash with Symbol keys' do
55
- task = { foo: 'bar' }
56
- expect { @backlog.add task }.to raise_error(TypeError)
64
+ context 'when :item is "test"' do
65
+ let(:backlog) { Kanban::Backlog.new backend: redis, item: 'test' }
66
+ describe '.item' do
67
+ subject { backlog.item }
68
+ it { is_expected.to eq 'default:test' }
69
+ end
70
+ end
57
71
  end
58
72
 
59
- it 'should return the ID of a newly added task' do
60
- task = { 'foo' => 'bar' }
61
- expect(@backlog.add(task)).to be_a Fixnum
62
- end
73
+ describe '#get' do
74
+ context 'when the task does not exist' do
75
+ subject { backlog.get 0 }
76
+ it { is_expected.to be_empty }
77
+ end
63
78
 
64
- it 'should allow Symbol keys with add! method' do
65
- task = { foo: 'bar' }
66
- expect(@backlog.add!(task)).to be_a Fixnum
79
+ context 'when the task is {"test"=>"data"}' do
80
+ let(:task) { ({ 'test' => 'data' }) }
81
+ let(:id) { backlog.add task }
82
+ subject { backlog.get id }
83
+ it { is_expected.to eq task }
84
+ end
67
85
  end
68
86
 
69
- it 'should preserve the task details' do
70
- task = { 'foo' => 'bar' }
71
- expect(@backlog.get(@backlog.add(task))).to eq task
87
+ describe '#next_id' do
88
+ describe 'should return incrementing values' do
89
+ let!(:last_id) { backlog.next_id }
90
+ subject { backlog.next_id }
91
+ it { is_expected.to be > last_id }
92
+ end
72
93
  end
73
94
 
74
- it 'should add new tasks to the list of tasks waiting to be done' do
75
- task = { 'foo' => 'bar' }
76
- id = @backlog.add(task)
77
- expect(@backlog.todo).to include(id)
95
+ describe '#todo' do
96
+ context 'when there are no tasks pending' do
97
+ before { redis.del "#{backlog.queue}:todo" }
98
+ subject { backlog.todo }
99
+ it { is_expected.to be_empty }
100
+ end
101
+
102
+ context 'when there are tasks pending' do
103
+ let(:task) { ({ 'test' => 'data' }) }
104
+ let!(:id) { backlog.add task }
105
+ subject { backlog.todo }
106
+ it { is_expected.to_not be_empty }
107
+ it { is_expected.to include id }
108
+ end
109
+
110
+ context 'when a task is requeued' do
111
+ let(:id) { backlog.claim }
112
+ before { backlog.requeue id }
113
+ subject { backlog.todo }
114
+ it { is_expected.to include id }
115
+ end
78
116
  end
79
117
 
80
- it 'should allow a task to be claimed' do
81
- expect(@backlog.claim).to be_a Fixnum
118
+ describe '#add' do
119
+ context 'when task is a hash with symbol keys' do
120
+ let(:task) { ({ foo: 'bar' }) }
121
+ it 'should raise a ParamContractError' do
122
+ expect { backlog.add task }.to raise_error(ParamContractError)
123
+ end
124
+ end
125
+
126
+ context 'when task is a hash with string keys' do
127
+ let(:task) { ({ 'test' => 'data' }) }
128
+ let!(:id) { backlog.next_id + 1}
129
+ subject { backlog.add task }
130
+ it { is_expected.to eq id }
131
+ end
82
132
  end
83
133
 
84
- it 'should track the claim separately from the queue it is in' do
85
- id = @backlog.claim
86
- expect(@backlog.claimed?(id)).to be true
134
+ describe '#add!' do
135
+ context 'when task is a hash with symbol keys' do
136
+ let(:task) { ({ test: 'data' }) }
137
+ let!(:id) { backlog.next_id + 1 }
138
+ subject { backlog.add! task }
139
+ it { is_expected.to eq id }
140
+ end
87
141
  end
88
142
 
89
- it 'should allow claims to expire' do
90
- id = @backlog.claim(duration: 1)
91
- sleep 1.1
92
- expect(@backlog.claimed?(id)).to be false
143
+ describe '#claimed?' do
144
+ context 'when a claim does not exist' do
145
+ subject { backlog.claimed? 0 }
146
+ it { is_expected.to be false }
147
+ end
148
+
149
+ context 'when a claim exists' do
150
+ let(:id) { backlog.claim }
151
+ subject { backlog.claimed? id }
152
+ it { is_expected.to be true }
153
+ end
154
+
155
+ context 'when a claim has expired' do
156
+ let!(:id) { backlog.claim duration: 1 }
157
+ before { sleep 1.1 }
158
+ subject { backlog.claimed? id }
159
+ it { is_expected.to be false }
160
+ end
161
+
162
+ context 'when a claim has been forcibly expired' do
163
+ let(:id) { backlog.claim }
164
+ before { backlog.expire_claim id }
165
+ subject { backlog.claimed? id }
166
+ it { is_expected.to be false }
167
+ end
168
+
169
+ context 'when a task has been released' do
170
+ let(:id) { backlog.claim }
171
+ before { backlog.release id }
172
+ subject { backlog.claimed? id }
173
+ it { is_expected.to be false }
174
+ end
93
175
  end
94
176
 
95
- it 'should block if there are no pending tasks' do
96
- redis = Redis.new
97
- redis.del "#{@backlog.queue}:todo"
98
- expect do
99
- Timeout.timeout(0.1) do
100
- @backlog.claim
177
+ describe '#claim' do
178
+ context 'when there are no pending tasks' do
179
+ before { redis.del "#{backlog.queue}:todo" }
180
+ it 'should block' do
181
+ expect do
182
+ Timeout.timeout(0.1) do
183
+ backlog.claim
184
+ end
185
+ end.to raise_error(Timeout::Error)
101
186
  end
102
- end.to raise_error(Timeout::Error)
103
- end
187
+ end
104
188
 
105
- it 'should report if a task is claimed' do
106
- id = @backlog.claim
107
- expect(@backlog.claimed?(id)).to be true
108
- expect(@backlog.claimed?(0)).to be false
189
+ context 'when there are pending tasks' do
190
+ before { backlog.add ({ 'test' => 'data' }) }
191
+ subject { backlog.claim }
192
+ it { is_expected.to be_a Fixnum }
193
+ end
109
194
  end
110
195
 
111
- it 'should have a list of tasks being worked on' do
112
- id = @backlog.claim
113
- expect(@backlog.doing).to include(id)
114
- end
196
+ describe '#doing' do
197
+ let!(:id) { backlog.claim }
198
+ subject { backlog.doing }
199
+ it { is_expected.to include id }
115
200
 
116
- it 'should allow indicating completion of a task only once' do
117
- expect(@backlog.complete(1)).to be true
118
- expect(@backlog.complete(1)).to be false
201
+ context 'when a task is released' do
202
+ before { backlog.release id }
203
+ subject { backlog.doing }
204
+ it { is_expected.to_not include id }
205
+ end
206
+
207
+ context 'when a task is requeued' do
208
+ before { backlog.requeue id }
209
+ subject { backlog.doing }
210
+ it { is_expected.to_not include id }
211
+ end
119
212
  end
120
213
 
121
- it 'should check if a task is completed' do
122
- expect(@backlog.completed?(2)).to be false
123
- @backlog.complete 2
124
- expect(@backlog.completed?(2)).to be true
214
+ describe '#complete' do
215
+ context 'when task has not been marked complete' do
216
+ subject { backlog.complete 1 }
217
+ it { is_expected.to be true }
218
+ end
219
+
220
+ context 'when task has been marked complete' do
221
+ before { backlog.complete 1 }
222
+ subject { backlog.complete 1 }
223
+ it { is_expected.to be false }
224
+ end
125
225
  end
126
226
 
127
- it 'should allow indicating a task should not be retried' do
128
- expect(@backlog.unworkable(3)).to be true
129
- expect(@backlog.unworkable(3)).to be false
227
+ describe '#completed?' do
228
+ context 'when task has not been marked complete' do
229
+ subject { backlog.completed? 2 }
230
+ it { is_expected.to be false }
231
+ end
232
+
233
+ context 'when task has been marked complete' do
234
+ before { backlog.complete 3 }
235
+ subject { backlog.completed? 3 }
236
+ it { is_expected.to be true }
237
+ end
130
238
  end
131
239
 
132
- it 'should check if a task is unworkable' do
133
- expect(@backlog.unworkable?(4)).to be false
134
- @backlog.unworkable 4
135
- expect(@backlog.unworkable?(4)).to be true
240
+ describe '#unworkable' do
241
+ context 'when task has not been marked unworkable' do
242
+ subject { backlog.unworkable 1 }
243
+ it { is_expected.to be true }
244
+ end
245
+
246
+ context 'when task has been marked unworkable' do
247
+ before { backlog.unworkable 1 }
248
+ subject { backlog.unworkable 1 }
249
+ it { is_expected.to be false }
250
+ end
136
251
  end
137
252
 
138
- it 'should consider a task that is completed or unworkable to be done' do
139
- expect(@backlog.done?(0)).to be false
140
- @backlog.complete(5)
141
- expect(@backlog.done?(5)).to be true
142
- @backlog.unworkable(6)
143
- expect(@backlog.done?(6)).to be true
253
+ describe '#unworkable?' do
254
+ context 'when task has not been marked unworkable' do
255
+ subject { backlog.unworkable? 2 }
256
+ it { is_expected.to be false }
257
+ end
258
+
259
+ context 'when task has been marked unworkable' do
260
+ before { backlog.unworkable 3 }
261
+ subject { backlog.unworkable? 3 }
262
+ it { is_expected.to be true }
263
+ end
144
264
  end
145
265
 
146
- it 'should be able to release a task from being in progress' do
147
- id = @backlog.claim
148
- expect(@backlog.release(id)).to be true
149
- expect(@backlog.release(id)).to be false
150
- expect(@backlog.doing).to_not include(id)
266
+ describe '#done?' do
267
+ context 'when task has not been marked either complete or unworkable' do
268
+ subject { backlog.done? 0 }
269
+ it { is_expected.to be false }
270
+ end
271
+
272
+ context 'when task has been marked complete' do
273
+ before { backlog.complete 5 }
274
+ subject { backlog.done? 5 }
275
+ it { is_expected.to be true }
276
+ end
277
+
278
+ context 'when task has been marked unworkable' do
279
+ before { backlog.unworkable 6 }
280
+ subject { backlog.done? 6 }
281
+ it { is_expected.to be true }
282
+ end
151
283
  end
152
284
 
153
- it 'should be able to forcibly expire a claim' do
154
- expect(@backlog.expire_claim(0)).to be false
155
- id = @backlog.claim
156
- expect(@backlog.expire_claim(id)).to be true
157
- expect(@backlog.claimed?(id)).to be false
285
+ describe '#release' do
286
+ context 'when task is claimed' do
287
+ let(:id) { backlog.claim }
288
+ subject { backlog.release id }
289
+ it { is_expected.to be true }
290
+ end
291
+
292
+ context 'when task was not claimed' do
293
+ subject { backlog.release 0 }
294
+ it { is_expected.to be false }
295
+ end
158
296
  end
159
297
 
160
- it 'should expire any active claims when a task is released' do
161
- id = @backlog.claim
162
- expect(@backlog.claimed?(id)).to be true
163
- @backlog.release(id)
164
- expect(@backlog.claimed?(id)).to be false
298
+ describe '#expire_claim' do
299
+ context 'when task was not claimed' do
300
+ subject { backlog.expire_claim 0 }
301
+ it { is_expected.to be false }
302
+ end
303
+
304
+ context 'when task was claimed' do
305
+ let(:id) { backlog.claim }
306
+ subject { backlog.expire_claim id }
307
+ it { is_expected.to be true }
308
+ end
165
309
  end
166
310
 
167
- it 'should be able to requeue a task' do
168
- id = @backlog.claim
169
- expect(@backlog.requeue(id)).to be true
170
- expect(@backlog.todo).to include(id)
171
- expect(@backlog.doing).to_not include(id)
311
+ describe '#groom' do
312
+ context 'when a task is claimed' do
313
+ let(:id) { backlog.claim }
314
+
315
+ context 'and the claim has expired' do
316
+ before { backlog.expire_claim id }
317
+ subject { backlog.groom }
318
+ it { is_expected.to include id }
319
+
320
+ context 'when #groom is called' do
321
+ before { backlog.groom }
322
+
323
+ describe '#todo' do
324
+ subject { backlog.todo }
325
+ it { is_expected.to include id }
326
+ end
327
+
328
+ describe '#doing' do
329
+ subject { backlog.doing }
330
+ it { is_expected.to_not include id }
331
+ end
332
+ end
333
+ end
334
+
335
+ context 'and the task is done' do
336
+ before { backlog.complete id }
337
+
338
+ context 'and the claim has expired' do
339
+ before { backlog.expire_claim id }
340
+
341
+ subject { backlog.groom }
342
+ it { is_expected.to include id }
343
+
344
+ context 'when #groom is called' do
345
+ before { backlog.groom }
346
+ describe '#todo' do
347
+ subject { backlog.todo }
348
+ it { is_expected.to_not include id }
349
+ end
350
+
351
+ describe '#doing' do
352
+ subject { backlog.doing }
353
+ it { is_expected.to_not include id }
354
+ end
355
+ end
356
+ end
357
+
358
+ context 'and the claim has not expired' do
359
+ subject { backlog.groom }
360
+ it { is_expected.to_not include id }
361
+
362
+ context 'when #groom is called' do
363
+ before { backlog.groom }
364
+
365
+ describe '#todo' do
366
+ subject { backlog.todo }
367
+ it { is_expected.to_not include id }
368
+ end
369
+
370
+ describe '#doing' do
371
+ subject { backlog.doing }
372
+ it { is_expected.to include id }
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end
172
378
  end
173
379
  end
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,8 @@ end
15
15
 
16
16
  ENV['COVERAGE'] && SimpleCov.start do
17
17
  add_filter '/.rvm/'
18
+ add_filter '/.rubies/'
19
+ add_filter '/vendor/'
18
20
  end
19
21
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
20
22
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -25,3 +27,9 @@ require 'kanban'
25
27
  # Requires supporting files with custom matchers and macros, etc,
26
28
  # in ./support/ and its subdirectories.
27
29
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
30
+
31
+ RSpec.configure do |config|
32
+ config.expect_with :rspec do |c|
33
+ c.syntax = :expect
34
+ end
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kanban
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Olstrom
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-30 00:00:00.000000000 Z
11
+ date: 2015-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: contracts
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement