pushpop 0.2 → 0.3.1
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 +4 -4
- data/README.md +59 -1
- data/lib/pushpop.rb +41 -0
- data/lib/pushpop/cli.rb +23 -19
- data/lib/pushpop/job.rb +19 -9
- data/lib/pushpop/step.rb +1 -1
- data/lib/pushpop/version.rb +1 -1
- data/lib/pushpop/web.rb +48 -0
- data/pushpop.gemspec +1 -0
- data/spec/pushpop/job_spec.rb +37 -1
- data/spec/pushpop/web_spec.rb +54 -0
- data/spec/pushpop_spec.rb +30 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d33b1c0d4e1f844e67237d4a11a2869fbf459974
|
4
|
+
data.tar.gz: f96dfd05743df179f3105cc1c514bff5996b2565
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 809afc53fb27b310504568f9a4456a62c73b4d1c71fd73f5cecb60cceec53b414b8c3c5e6778d09211c8a51685713594aea773588a02c28cbe7484c9cf8c2ee0
|
7
|
+
data.tar.gz: 9a115b8c0a544ee3654ff2a3bd3f3a677d03e97b52281089e011b240833d62068a59af711a90af0f36b99be51f8ca1ca8da867194a8a62add7dbe4e28b9973cb
|
data/README.md
CHANGED
@@ -196,6 +196,26 @@ every 1.week, at: 'Monday 12:30'
|
|
196
196
|
|
197
197
|
See the full range of possibilities on the [Clockwork README](https://github.com/tomykaira/clockwork#event-parameters).
|
198
198
|
|
199
|
+
##### Webhooks
|
200
|
+
|
201
|
+
Jobs can also be triggered via a webhook, instead of scheduling via `every`. Simply use `webhook` instead of `every`, and pass in a path that should trigger that job. `webhook` also accepts a block, which becomes the first step of the job.
|
202
|
+
|
203
|
+
``` ruby
|
204
|
+
webhook '/trigger' do
|
205
|
+
if params[:secret] == '12345'
|
206
|
+
params[:name]
|
207
|
+
else
|
208
|
+
false # Returning false cancels the job
|
209
|
+
end
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
The webhooks are simply a [Sinatra](http://www.sinatrarb.com) app under the hood, so you can reuse a lot of the features that are built-in to Sinatra. Their [routing features](http://www.sinatrarb.com/intro.html#Routes) work out of the box. The webhook block that gets called for every request is also run in the [Sinatra request scope](http://www.sinatrarb.com/intro.html#Request/Instance%20Scope), so you can access the full app (via `settings`), and request params via `params`.
|
214
|
+
|
215
|
+
The return value of the webhook block will be passed in as `response` for the first step, and will be stored under `step_responses['webhook']` for all future steps.
|
216
|
+
|
217
|
+
*You may want to read about running a [custom HTTP server](#custom-http-server-for-webhooks) if you're going to be using webhooks in production.*
|
218
|
+
|
199
219
|
##### Job workflow
|
200
220
|
|
201
221
|
When a job kicks off, steps are run serially in the order they are specified. Each step is invoked with 2
|
@@ -217,7 +237,7 @@ job do
|
|
217
237
|
end
|
218
238
|
step 'add previous steps' do |response, step_responses|
|
219
239
|
puts response # prints 6
|
220
|
-
puts step_responses['one'] + step_responses['two'] # prints
|
240
|
+
puts step_responses['one'] + step_responses['two'] # prints 7
|
221
241
|
end
|
222
242
|
end
|
223
243
|
```
|
@@ -267,6 +287,7 @@ end
|
|
267
287
|
|
268
288
|
In this example, the `twilio` step will only be ran if the `keen` step returned a count greater than 0.
|
269
289
|
|
290
|
+
|
270
291
|
#### Steps
|
271
292
|
|
272
293
|
Steps have the following attributes:
|
@@ -300,6 +321,43 @@ Here's a very simple template that uses the `response` variable in context:
|
|
300
321
|
<p>We got <%= response %> new users today!</p>
|
301
322
|
```
|
302
323
|
|
324
|
+
## Custom HTTP Server for Webhooks
|
325
|
+
|
326
|
+
If you're running Pushpop locally, you can continue to use the CLI for running jobs - `jobs:run` will start the Sinatra app internally if you have any webhooks configured. However, running the app with the CLI in a production environment may not scale well. If your webhooks are going to be hit rapidly in production, you may want to use a beefier HTTP server than the default [WEBrick](http://ruby-doc.org/stdlib-1.9.3/libdoc/webrick/rdoc/WEBrick.html) built in to Ruby.
|
327
|
+
|
328
|
+
Here's an example of getting Pushpop running on [Unicorn](http://unicorn.bogomips.org/)
|
329
|
+
|
330
|
+
**unicorn.rb**
|
331
|
+
|
332
|
+
``` ruby
|
333
|
+
require 'pushpop'
|
334
|
+
|
335
|
+
# Set this to whatever you want.
|
336
|
+
worker_processes 2
|
337
|
+
|
338
|
+
# This loads all of the job files in /jobs
|
339
|
+
Pushpop.load_jobs
|
340
|
+
|
341
|
+
# This configures Clockwork for any scheduled jobs you have.
|
342
|
+
# You can omit this if all you are using is Webhooks
|
343
|
+
Pushpop.schedule
|
344
|
+
|
345
|
+
# This tells Clockwork to actually start running jobs
|
346
|
+
# You can omit this if all you are using is Webhooks
|
347
|
+
Pushpop.start_clock
|
348
|
+
```
|
349
|
+
|
350
|
+
**config.ru**
|
351
|
+
``` ruby
|
352
|
+
# This tells Unicorn what to run whenever it starts up a worker.. which is the Pushpop web app
|
353
|
+
run Pushpop.web.app
|
354
|
+
```
|
355
|
+
|
356
|
+
And then to run it, just do:
|
357
|
+
``` bash
|
358
|
+
unicorn -c unicorn.rb
|
359
|
+
```
|
360
|
+
|
303
361
|
## Recipes
|
304
362
|
|
305
363
|
The community-driven [pushpop-recipes](https://github.com/pushpop-project/pushpop-recipes) repository contains jobs and templates
|
data/lib/pushpop.rb
CHANGED
@@ -4,6 +4,7 @@ require 'pushpop/version'
|
|
4
4
|
require 'pushpop/job'
|
5
5
|
require 'pushpop/step'
|
6
6
|
require 'pushpop/cli'
|
7
|
+
require 'pushpop/web'
|
7
8
|
|
8
9
|
module Pushpop
|
9
10
|
class << self
|
@@ -30,6 +31,23 @@ module Pushpop
|
|
30
31
|
@@jobs
|
31
32
|
end
|
32
33
|
|
34
|
+
def web
|
35
|
+
@web ||= Web.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_webserver
|
39
|
+
# If we start this thread with no routes, it will throw off the all_waits listener
|
40
|
+
# and we don't want to start the web server willy nilly, because it looks weird
|
41
|
+
# on the CLI interface
|
42
|
+
if web.routes.length > 0
|
43
|
+
Thread.new do
|
44
|
+
@web.app.run!
|
45
|
+
end
|
46
|
+
else
|
47
|
+
false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
33
51
|
# for jobs and steps
|
34
52
|
def random_name
|
35
53
|
(0...8).map { (65 + rand(26)).chr }.join
|
@@ -48,6 +66,29 @@ module Pushpop
|
|
48
66
|
self.jobs.map &:schedule
|
49
67
|
end
|
50
68
|
|
69
|
+
def start_clock
|
70
|
+
Thread.new do
|
71
|
+
Clockwork.manager.run
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def require_file(file = nil)
|
76
|
+
if file
|
77
|
+
if File.directory?(file)
|
78
|
+
Dir.glob("#{file}/**/*.rb").each { |file|
|
79
|
+
load "#{Dir.pwd}/#{file}"
|
80
|
+
}
|
81
|
+
else
|
82
|
+
load file
|
83
|
+
end
|
84
|
+
else
|
85
|
+
Dir.glob("#{Dir.pwd}/jobs/**/*.rb").each { |file|
|
86
|
+
load file
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
alias :load_jobs :require_file
|
91
|
+
|
51
92
|
def load_plugin(name)
|
52
93
|
load "#{File.expand_path("../plugins/#{name}", __FILE__)}.rb"
|
53
94
|
end
|
data/lib/pushpop/cli.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'dotenv'
|
3
3
|
require 'pushpop'
|
4
|
+
require 'thwait'
|
4
5
|
|
5
6
|
module Pushpop
|
6
7
|
class CLI < Thor
|
@@ -24,7 +25,7 @@ module Pushpop
|
|
24
25
|
|
25
26
|
def describe_jobs
|
26
27
|
Dotenv.load
|
27
|
-
require_file(options[:file])
|
28
|
+
Pushpop.require_file(options[:file])
|
28
29
|
Pushpop.jobs.tap do |jobs|
|
29
30
|
jobs.each do |job|
|
30
31
|
puts job.name
|
@@ -38,7 +39,7 @@ module Pushpop
|
|
38
39
|
|
39
40
|
def run_jobs_once
|
40
41
|
Dotenv.load
|
41
|
-
require_file(options[:file])
|
42
|
+
Pushpop.require_file(options[:file])
|
42
43
|
Pushpop.run
|
43
44
|
end
|
44
45
|
|
@@ -48,29 +49,32 @@ module Pushpop
|
|
48
49
|
|
49
50
|
def run_jobs
|
50
51
|
Dotenv.load
|
51
|
-
require_file(options[:file])
|
52
|
+
Pushpop.require_file(options[:file])
|
52
53
|
Pushpop.schedule
|
53
|
-
Clockwork.manager.run
|
54
|
-
end
|
55
54
|
|
56
|
-
|
55
|
+
threads = []
|
56
|
+
threads << Pushpop.start_clock
|
57
|
+
|
58
|
+
Pushpop.web.app.traps = false
|
59
|
+
web_thread = Pushpop.start_webserver
|
60
|
+
threads << web_thread if web_thread
|
57
61
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
else
|
65
|
-
load file
|
62
|
+
# Listen to exit signals, so the CLI doesn't hang infinitely on clock
|
63
|
+
[:INT, :TERM].each do |signal|
|
64
|
+
trap(signal) do
|
65
|
+
threads.each do |thread|
|
66
|
+
thread.exit
|
67
|
+
end
|
66
68
|
end
|
67
|
-
else
|
68
|
-
Dir.glob("#{Dir.pwd}/jobs/**/*.rb").each { |file|
|
69
|
-
load file
|
70
|
-
}
|
71
69
|
end
|
72
|
-
end
|
73
70
|
|
71
|
+
# Wait for both the clock thread and the sinatra thread to close before exiting
|
72
|
+
ThreadsWait.all_waits(threads) do
|
73
|
+
threads.each do |thread|
|
74
|
+
thread.exit
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
data/lib/pushpop/job.rb
CHANGED
@@ -19,6 +19,8 @@ module Pushpop
|
|
19
19
|
|
20
20
|
attr_accessor :name
|
21
21
|
attr_accessor :period
|
22
|
+
attr_accessor :webhook_url
|
23
|
+
attr_accessor :webhook_proc
|
22
24
|
attr_accessor :every_options
|
23
25
|
attr_accessor :steps
|
24
26
|
|
@@ -34,6 +36,16 @@ module Pushpop
|
|
34
36
|
self.every_options = options
|
35
37
|
end
|
36
38
|
|
39
|
+
def webhook(url, &block)
|
40
|
+
raise 'Webhook is already set' if @webhook_url
|
41
|
+
raise 'Webhook must be set before steps' if self.steps.length > 0
|
42
|
+
|
43
|
+
self.webhook_url = url
|
44
|
+
self.webhook_proc = block
|
45
|
+
|
46
|
+
Pushpop.web.add_route url, self
|
47
|
+
end
|
48
|
+
|
37
49
|
def step(name=nil, plugin=nil, &block)
|
38
50
|
if plugin
|
39
51
|
|
@@ -51,18 +63,16 @@ module Pushpop
|
|
51
63
|
end
|
52
64
|
|
53
65
|
def schedule
|
54
|
-
raise 'Set job period via "every"' unless self.period
|
55
|
-
|
56
|
-
|
66
|
+
raise 'Set job period via "every"' unless self.period || @webhook_url
|
67
|
+
|
68
|
+
if self.period
|
69
|
+
Clockwork.manager.every(period, name, every_options) do
|
70
|
+
run
|
71
|
+
end
|
57
72
|
end
|
58
73
|
end
|
59
74
|
|
60
|
-
def run
|
61
|
-
|
62
|
-
# track the last response, and all responses
|
63
|
-
last_response = nil
|
64
|
-
step_responses = {}
|
65
|
-
|
75
|
+
def run(last_response = nil, step_responses = {})
|
66
76
|
self.steps.each do |step|
|
67
77
|
|
68
78
|
# track the last_response and all responses
|
data/lib/pushpop/step.rb
CHANGED
data/lib/pushpop/version.rb
CHANGED
data/lib/pushpop/web.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Pushpop
|
5
|
+
class Web
|
6
|
+
|
7
|
+
def app
|
8
|
+
Sinatra::Application
|
9
|
+
end
|
10
|
+
|
11
|
+
def routes
|
12
|
+
@routes ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_route(url, job)
|
16
|
+
|
17
|
+
if url[0] != '/'
|
18
|
+
url = "/#{url}"
|
19
|
+
end
|
20
|
+
|
21
|
+
raise "Route #{url} is already set up as a webhook" if routes.include?(url)
|
22
|
+
|
23
|
+
runner = lambda do
|
24
|
+
response = self.instance_eval(&job.webhook_proc)
|
25
|
+
|
26
|
+
if response
|
27
|
+
{
|
28
|
+
status: 'success',
|
29
|
+
job: job.name
|
30
|
+
}.to_json
|
31
|
+
else
|
32
|
+
{
|
33
|
+
status: 'failed',
|
34
|
+
job: job.name,
|
35
|
+
message: 'webhook step did not pass'
|
36
|
+
}.to_json
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Sinatra::Application.get url, &runner
|
41
|
+
Sinatra::Application.post url, &runner
|
42
|
+
Sinatra::Application.put url, &runner
|
43
|
+
|
44
|
+
routes.push(url)
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/pushpop.gemspec
CHANGED
data/spec/pushpop/job_spec.rb
CHANGED
@@ -37,6 +37,29 @@ describe Pushpop::Job do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
describe '#webhook' do
|
41
|
+
it 'sets the webhook url and proc' do
|
42
|
+
job = empty_job
|
43
|
+
empty_proc = Proc.new{}
|
44
|
+
job.webhook('/test', &empty_proc)
|
45
|
+
expect(job.webhook_url).to eq('/test')
|
46
|
+
expect(job.webhook_proc.class).to be(Proc)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'raises an error if webhook is already set' do
|
50
|
+
job = empty_job
|
51
|
+
job.webhook('/test1')
|
52
|
+
expect{ job.webhook('/test2') }.to raise_error
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'raises an error if any steps have been created' do
|
56
|
+
job = empty_job
|
57
|
+
empty_proc = Proc.new{}
|
58
|
+
job.step('test', &empty_proc)
|
59
|
+
expect{job.webhook('/test')}.to raise_error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
40
63
|
describe '#step' do
|
41
64
|
it 'adds the step to the internal list of steps' do
|
42
65
|
empty_proc = Proc.new {}
|
@@ -108,11 +131,24 @@ describe Pushpop::Job do
|
|
108
131
|
expect(simple_job.run.first).to eq(4)
|
109
132
|
end
|
110
133
|
|
111
|
-
it 'fails if
|
134
|
+
it 'fails if neither period nor webhook was not specified' do
|
112
135
|
simple_job = Pushpop::Job.new('foo') do end
|
113
136
|
expect {
|
114
137
|
simple_job.schedule
|
115
138
|
}.to raise_error
|
139
|
+
|
140
|
+
simple_job.period = 5.seconds
|
141
|
+
|
142
|
+
expect {
|
143
|
+
simple_job.schedule
|
144
|
+
}.not_to raise_error
|
145
|
+
|
146
|
+
simple_job.period = nil
|
147
|
+
simple_job.webhook_url = '/test'
|
148
|
+
|
149
|
+
expect {
|
150
|
+
simple_job.schedule
|
151
|
+
}.not_to raise_error
|
116
152
|
end
|
117
153
|
end
|
118
154
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pushpop::Web do
|
4
|
+
web = nil
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
web = Pushpop::Web.new
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns a Sinatra Application' do
|
11
|
+
expect(web.app).to equal(Sinatra::Application)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'routes' do
|
15
|
+
it 'is an array' do
|
16
|
+
expect(web.routes.class).to equal(Array)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is empty by default' do
|
20
|
+
expect(web.routes.length).to equal(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'gets filled with new routes' do
|
24
|
+
web.add_route('/test', Proc.new{})
|
25
|
+
|
26
|
+
expect(web.routes.length).to equal(1)
|
27
|
+
expect(web.routes[0]).to eq('/test')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'add_route' do
|
32
|
+
|
33
|
+
before(:each) do
|
34
|
+
Sinatra::Application.reset!
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'raises an error for duplicate routes' do
|
38
|
+
empty_proc = Proc.new{}
|
39
|
+
|
40
|
+
web.add_route('/test', empty_proc)
|
41
|
+
expect{web.add_route('/test', empty_proc)}.to raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'creates GET, POST, and PUT endpoints' do
|
45
|
+
web.add_route('/test', Proc.new{})
|
46
|
+
|
47
|
+
['GET', 'POST', 'PUT'].each do |method|
|
48
|
+
expect(web.app.routes.include?(method)).to be_truthy
|
49
|
+
expect(web.app.routes[method].length).to equal(1)
|
50
|
+
expect(web.app.routes[method][0][0].match('/test').length).to equal(1)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/pushpop_spec.rb
CHANGED
@@ -8,6 +8,9 @@ describe 'job' do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe Pushpop do
|
11
|
+
before(:each) do
|
12
|
+
Pushpop.web.instance_variable_set(:@routes, [])
|
13
|
+
end
|
11
14
|
|
12
15
|
describe 'add_job' do
|
13
16
|
it 'adds a job to the list' do
|
@@ -23,4 +26,31 @@ describe Pushpop do
|
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
29
|
+
describe 'clock' do
|
30
|
+
it 'starts clock in a thread' do
|
31
|
+
t = Pushpop.start_clock
|
32
|
+
expect(t.class).to be(Thread)
|
33
|
+
|
34
|
+
t.exit
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'web' do
|
39
|
+
it 'gets or creates an instance of Web' do
|
40
|
+
expect(Pushpop.web.class).to be(Pushpop::Web)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not start the web app if no routes are defined' do
|
44
|
+
expect(Pushpop.start_webserver).to be_falsey
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'starts the web app in a thread' do
|
48
|
+
Pushpop.web.add_route('/test', Proc.new{})
|
49
|
+
t = Pushpop.start_webserver
|
50
|
+
expect(t.class).to be(Thread)
|
51
|
+
|
52
|
+
t.exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
26
56
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pushpop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josh Dzielak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: clockwork
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sinatra
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: dotenv
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,11 +86,13 @@ files:
|
|
72
86
|
- lib/pushpop/job.rb
|
73
87
|
- lib/pushpop/step.rb
|
74
88
|
- lib/pushpop/version.rb
|
89
|
+
- lib/pushpop/web.rb
|
75
90
|
- pushpop.gemspec
|
76
91
|
- spec/jobs/simple_job.rb
|
77
92
|
- spec/pushpop/cli_spec.rb
|
78
93
|
- spec/pushpop/job_spec.rb
|
79
94
|
- spec/pushpop/step_spec.rb
|
95
|
+
- spec/pushpop/web_spec.rb
|
80
96
|
- spec/pushpop_spec.rb
|
81
97
|
- spec/simple_job_spec.rb
|
82
98
|
- spec/spec_helper.rb
|
@@ -109,6 +125,7 @@ test_files:
|
|
109
125
|
- spec/pushpop/cli_spec.rb
|
110
126
|
- spec/pushpop/job_spec.rb
|
111
127
|
- spec/pushpop/step_spec.rb
|
128
|
+
- spec/pushpop/web_spec.rb
|
112
129
|
- spec/pushpop_spec.rb
|
113
130
|
- spec/simple_job_spec.rb
|
114
131
|
- spec/spec_helper.rb
|