mamiya 0.0.1.alpha19 → 0.0.1.alpha20

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.
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'mamiya/agent/tasks/abstract'
3
+
4
+ describe Mamiya::Agent::Tasks::Abstract do
5
+ let(:queue) { double('task_queue') }
6
+
7
+ let(:job) { {'foo' => 'bar'} }
8
+ subject(:task) { described_class.new(queue, job) }
9
+
10
+ describe "#task" do
11
+ before do
12
+ allow(described_class).to receive(:identifier).and_return('ident')
13
+ end
14
+ it "includes job specification and class' identifier" do
15
+ expect(task.task['foo']).to eq 'bar'
16
+ expect(task.task['task']).to eq described_class.identifier
17
+ end
18
+ end
19
+
20
+ describe "#execute" do
21
+ it "calls before, then run" do
22
+ expect(task).to receive(:before).ordered
23
+ expect(task).to receive(:run).ordered
24
+ task.execute
25
+ end
26
+ it "calls after" do
27
+ expect(task).to receive(:run).ordered
28
+ expect(task).to receive(:after).ordered
29
+ expect(task).not_to receive(:error)
30
+ task.execute
31
+ end
32
+
33
+ it "handles error" do
34
+ expect(task).to receive(:after)
35
+ expect(task).to receive(:errored)
36
+
37
+ err = RuntimeError.new
38
+ allow(task).to receive(:run).and_raise(err)
39
+
40
+ task.execute
41
+
42
+ expect(task.error).to eq err
43
+ end
44
+ end
45
+
46
+ describe ".identifier" do
47
+ it "returns class name without module name" do
48
+ allow(described_class).to receive(:name).and_return('Mamiya::Agent::Tasks::Foo')
49
+ expect(described_class.identifier).to eq 'foo'
50
+ end
51
+
52
+ it "returns camelcased class name" do
53
+ allow(described_class).to receive(:name).and_return('Mamiya::Agent::Tasks::FooBar')
54
+ expect(described_class.identifier).to eq 'foo_bar'
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ require 'mamiya/agent/tasks/clean'
4
+ require 'mamiya/agent/tasks/abstract'
5
+ require 'mamiya/steps/fetch'
6
+
7
+ describe Mamiya::Agent::Tasks::Clean do
8
+ let!(:tmpdir) { Dir.mktmpdir('mamiya-agent-tasks-clean-spec') }
9
+ after { FileUtils.remove_entry_secure(tmpdir) if File.exist?(tmpdir) }
10
+
11
+ let(:config) { {packages_dir: tmpdir, keep_packages: 2} }
12
+
13
+ let(:agent) { double('agent', config: config) }
14
+ let(:task_queue) { double('task_queue') }
15
+
16
+ subject(:task) { described_class.new(task_queue, {}, agent: agent, raise_error: true) }
17
+
18
+ it 'inherits abstract task' do
19
+ expect(described_class.ancestors).to include(Mamiya::Agent::Tasks::Abstract)
20
+ end
21
+
22
+
23
+ describe "#execute" do
24
+ before do
25
+ path = Pathname.new(tmpdir)
26
+
27
+ path.join('a').mkdir
28
+ File.write path.join('a', "a.tar.gz"), "\n"
29
+ File.write path.join('a', "a.json"), "\n"
30
+ File.write path.join('a', "b.json"), "\n"
31
+ File.write path.join('a', "b.tar.gz"), "\n"
32
+ File.write path.join('a', "c.json"), "\n"
33
+ File.write path.join('a', "c.tar.gz"), "\n"
34
+ path.join('b').mkdir
35
+ File.write path.join('b', "a.tar.gz"), "\n"
36
+ File.write path.join('b', "a.json"), "\n"
37
+
38
+ path.join('c').mkdir
39
+ File.write path.join('c', "a.tar.gz"), "\n"
40
+ File.write path.join('c', "b.json"), "\n"
41
+ end
42
+
43
+ it "cleans up" do
44
+ expect(agent).to receive(:trigger).with('pkg', action: 'remove', application: 'a', package: 'a', coalesce: false)
45
+
46
+ task.execute
47
+
48
+ path = Pathname.new(tmpdir)
49
+ existences = Hash[
50
+ [
51
+ path.join('a', 'a.tar.gz'),
52
+ path.join('a', 'a.json'),
53
+ path.join('a', 'b.tar.gz'),
54
+ path.join('a', 'b.json'),
55
+ path.join('a', 'c.tar.gz'),
56
+ path.join('a', 'c.json'),
57
+ ].map { |file|
58
+ [file, file.exist?]
59
+ }
60
+ ]
61
+
62
+ expect(existences).to eq(
63
+ path.join('a', 'a.tar.gz') => false,
64
+ path.join('a', 'a.json') => false,
65
+ path.join('a', 'b.tar.gz') => true,
66
+ path.join('a', 'b.json') => true,
67
+ path.join('a', 'c.tar.gz') => true,
68
+ path.join('a', 'c.json') => true,
69
+ )
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ require 'mamiya/agent/tasks/fetch'
4
+ require 'mamiya/agent/tasks/notifyable'
5
+ require 'mamiya/steps/fetch'
6
+
7
+ describe Mamiya::Agent::Tasks::Fetch do
8
+ let(:config) { {packages_dir: File::NULL} }
9
+ let(:agent) { double('agent', config: config) }
10
+ let(:task_queue) { double('task_queue', enqueue: nil) }
11
+
12
+ let(:step) { double('step', run!: nil) }
13
+
14
+ let(:job) { {'app' => 'myapp', 'pkg' => 'mypkg'} }
15
+
16
+ subject(:task) { described_class.new(task_queue, job, agent: agent, raise_error: true) }
17
+
18
+ it 'inherits notifyable task' do
19
+ expect(described_class.ancestors).to include(Mamiya::Agent::Tasks::Notifyable)
20
+ end
21
+
22
+ describe "#execute" do
23
+ before do
24
+ allow(Mamiya::Steps::Fetch).to receive(:new).with(
25
+ application: 'myapp',
26
+ package: 'mypkg',
27
+ destination: File.join(File::NULL, 'myapp'),
28
+ config: config,
29
+ ).and_return(step)
30
+
31
+ allow(agent).to receive(:trigger)
32
+ end
33
+
34
+ it "calls fetch step" do
35
+ expect(step).to receive(:run!)
36
+
37
+ task.execute
38
+ end
39
+
40
+ it "enqueues clean task" do
41
+ expect(task_queue).to receive(:enqueue).with(:clean, {})
42
+
43
+ task.execute
44
+ end
45
+
46
+ context "when already fetched" do
47
+ it "does nothing" do
48
+ allow(step).to receive(:run!).and_raise(Mamiya::Storages::Abstract::AlreadyFetched)
49
+
50
+ expect {
51
+ task.execute
52
+ }.not_to raise_error
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+ require 'mamiya/agent/tasks/notifyable'
3
+
4
+ describe Mamiya::Agent::Tasks::Notifyable do
5
+ let(:queue) { double('task_queue') }
6
+ let(:agent) { double('agent', trigger: nil) }
7
+
8
+ # specifying 'task' key that Tasks::Abstract assigns,
9
+ # to make expecting message easier
10
+ let(:job) { {'foo' => 'bar', 'task' => 'notifyable'} }
11
+ subject(:task) { described_class.new(queue, job, agent: agent) }
12
+
13
+ describe "#execute" do
14
+ it "notifies first, then :run" do
15
+ expect(agent).to receive(:trigger).with('task', action: 'start', task: job).ordered
16
+ expect(task).to receive(:before).ordered
17
+ expect(task).to receive(:run).ordered
18
+ task.execute
19
+ end
20
+
21
+ it "calls after, then notify" do
22
+ expect(task).to receive(:run).ordered
23
+ expect(task).to receive(:after).ordered
24
+ expect(agent).to receive(:trigger).with('task', action: 'finish', task: job).ordered
25
+ task.execute
26
+ end
27
+
28
+ it "handles error" do
29
+ expect(agent).to receive(:trigger).with('task', action: 'error', task: job, error: RuntimeError.name)
30
+ err = RuntimeError.new
31
+ allow(task).to receive(:run).and_raise(err)
32
+ task.execute
33
+ expect(task.error).to eq err
34
+ end
35
+ end
36
+ end
37
+
data/spec/agent_spec.rb CHANGED
@@ -7,7 +7,7 @@ require 'villein/event'
7
7
 
8
8
  require 'mamiya/version'
9
9
  require 'mamiya/agent'
10
- require 'mamiya/agent/fetcher'
10
+ require 'mamiya/agent/task_queue'
11
11
  require 'mamiya/agent/actions'
12
12
 
13
13
  require_relative './support/dummy_serf.rb'
@@ -15,12 +15,9 @@ require_relative './support/dummy_serf.rb'
15
15
  describe Mamiya::Agent do
16
16
 
17
17
  let(:serf) { DummySerf.new }
18
- let(:fetcher) do
19
- double('fetcher', start!: nil, working?: false).tap do |f|
20
- cleanup_hook = nil
21
- allow(f).to receive(:cleanup_hook=) { |_| cleanup_hook = _ }
22
- allow(f).to receive(:cleanup_hook) { cleanup_hook }
23
- end
18
+
19
+ let(:task_queue) do
20
+ double('task_queue', start!: nil)
24
21
  end
25
22
 
26
23
  let(:config) do
@@ -29,7 +26,7 @@ describe Mamiya::Agent do
29
26
 
30
27
  before do
31
28
  allow(Villein::Agent).to receive(:new).and_return(serf)
32
- allow(Mamiya::Agent::Fetcher).to receive(:new).and_return(fetcher)
29
+ allow(Mamiya::Agent::TaskQueue).to receive(:new).and_return(task_queue)
33
30
  end
34
31
 
35
32
  subject(:agent) { described_class.new(config) }
@@ -38,32 +35,47 @@ describe Mamiya::Agent do
38
35
  expect(described_class.ancestors).to include(Mamiya::Agent::Actions)
39
36
  end
40
37
 
41
- describe "fetcher" do
42
- it "sends events on cleanup hook" do
38
+ describe "#trigger" do
39
+ it "sends serf event" do
43
40
  expect(serf).to receive(:event).with(
44
- 'mamiya:fetch-result:remove',
41
+ 'mamiya:foo',
45
42
  {
46
- name: serf.name, application: 'foo', package: 'bar',
43
+ a: 'b',
44
+ name: 'my-name',
47
45
  }.to_json,
48
- coalesce: false,
46
+ coalesce: true,
49
47
  )
50
48
 
51
- agent.fetcher.cleanup_hook.call('foo', 'bar')
49
+ agent.trigger(:foo, a: 'b')
50
+ end
51
+
52
+ it "sends serf event with action" do
53
+ expect(serf).to receive(:event).with(
54
+ 'mamiya:foo:bar',
55
+ {
56
+ a: 'b',
57
+ name: 'my-name',
58
+ }.to_json,
59
+ coalesce: true,
60
+ )
61
+
62
+ agent.trigger(:foo, a: 'b', action: 'bar')
52
63
  end
53
64
  end
54
65
 
55
66
  describe "#run!" do
56
- it "starts serf and fetcher" do
67
+ it "starts serf, and task_queue" do
57
68
  begin
58
69
  flag = false
59
70
 
60
- expect(fetcher).to receive(:start!)
71
+ expect(task_queue).to receive(:start!)
61
72
  expect(serf).to receive(:start!)
62
73
  expect(serf).to receive(:auto_stop) do
63
74
  flag = true
64
75
  end
65
76
 
66
77
  th = Thread.new { agent.run! }
78
+ th.abort_on_exception = true
67
79
 
68
80
  10.times { break if flag; sleep 0.1 }
69
81
  ensure
@@ -82,18 +94,6 @@ describe Mamiya::Agent do
82
94
  end
83
95
  end
84
96
 
85
- context "when it is fetching" do
86
- before do
87
- allow(fetcher).to receive(:working?).and_return(true)
88
- end
89
-
90
- it "shows fetching" do
91
- agent.update_tags!
92
-
93
- expect(serf.tags['mamiya']).to eq ',fetching,'
94
- end
95
- end
96
-
97
97
  context "when it is running multiple jobs" do
98
98
  pending
99
99
  end
@@ -111,10 +111,8 @@ describe Mamiya::Agent do
111
111
  describe "#status" do
112
112
  before do
113
113
  allow(agent).to receive(:existing_packages).and_return("app" => ["pkg"])
114
- allow(fetcher).to receive(:queue_size).and_return(42)
115
- allow(fetcher).to receive(:working?).and_return(false)
116
- allow(fetcher).to receive(:current_job).and_return(nil)
117
- allow(fetcher).to receive(:pending_jobs).and_return([['app', 'pkg2', nil, nil]])
114
+
115
+ allow(task_queue).to receive(:status).and_return({a: {working: nil, queue: []}})
118
116
  end
119
117
 
120
118
  subject(:status) { agent.status }
@@ -131,28 +129,9 @@ describe Mamiya::Agent do
131
129
  expect(status[:packages]).to eq agent.existing_packages
132
130
  end
133
131
 
134
- describe "(fetcher)" do
135
- it "includes queue_size as pending" do
136
- expect(status[:fetcher][:pending]).to eq 42
137
- end
138
-
139
- it "includes pendings job" do
140
- expect(status[:fetcher][:pending_jobs]).to eq([['app', 'pkg2']])
141
- end
142
-
143
- it "shows fetching status" do
144
- expect(status[:fetcher][:fetching]).to be_nil
145
- end
146
-
147
- context "when it's fetching" do
148
- before do
149
- allow(fetcher).to receive(:working?).and_return(true)
150
- allow(fetcher).to receive(:current_job).and_return(%w(foo bar))
151
- end
152
-
153
- it "shows fetching true" do
154
- expect(status[:fetcher][:fetching]).to eq ['foo', 'bar']
155
- end
132
+ describe "(task queue)" do
133
+ it "includes task_queue" do
134
+ expect(status[:queues]).to eq({a: {working: nil, queue: []}})
156
135
  end
157
136
  end
158
137
  end
@@ -150,119 +150,205 @@ describe Mamiya::Master::AgentMonitor do
150
150
 
151
151
  subject(:new_status) { agent_monitor.statuses["a"] }
152
152
 
153
- describe "fetch-result" do
154
- describe ":ack" do
155
- let(:status) do
156
- {fetcher: {fetching: nil, pending: 0}}
157
- end
153
+ describe "task" do
154
+ describe ":start" do
155
+ context "if task is in the queue" do
156
+ let(:status) do
157
+ {queues: {
158
+ a: {queue: [{task: 'a', foo: 'bar'}], working: nil}
159
+ }}
160
+ end
158
161
 
159
- it "updates pending" do
160
- commit('mamiya:fetch-result:ack', pending: 72, application: 'foo', package: 'bar')
161
- expect(new_status["fetcher"]["pending"]).to eq 72
162
- expect(new_status["fetcher"]["pending_jobs"]).to eq [%w(foo bar)]
163
- end
164
- end
162
+ it "removes from queue, set in working" do
163
+ commit('mamiya:task:start',
164
+ task: {task: 'a', foo: 'bar'})
165
165
 
166
- describe ":start" do
167
- let(:status) do
168
- {fetcher: {fetching: nil, pending: 1, pending_jobs: [%w(app pkg)]}}
166
+ expect(new_status['queues']['a']['queue']).to be_empty
167
+ expect(new_status['queues']['a']['working']).to eq('task' => 'a', 'foo' => 'bar')
168
+ end
169
169
  end
170
170
 
171
- it "updates fetching" do
172
- commit('mamiya:fetch-result:start',
173
- application: 'app', package: 'pkg', pending: 0)
174
- expect(new_status["fetcher"]["fetching"]).to eq ['app', 'pkg']
175
- expect(new_status["fetcher"]["pending_jobs"]).to be_empty
171
+ context "if task is not in the queue" do
172
+ let(:status) do
173
+ {queues: {
174
+ a: {queue: [], working: nil}
175
+ }}
176
+ end
177
+
178
+ it "set in working" do
179
+ commit('mamiya:task:start',
180
+ task: {task: 'a', foo: 'bar'})
181
+
182
+ expect(new_status['queues']['a']['working']).to eq('task' => 'a', 'foo' => 'bar')
183
+ end
176
184
  end
177
185
  end
178
186
 
179
- describe ":error" do
180
- let(:status) do
181
- {fetcher: {fetching: ['app', 'pkg'], pending: 0}}
187
+ describe ":finish" do
188
+ context "if task is working" do
189
+ let(:status) do
190
+ {queues: {
191
+ a: {queue: [], working: {task: 'a', foo: 'bar'}}
192
+ }}
193
+ end
194
+
195
+ it "removes from working" do
196
+ commit('mamiya:task:finish',
197
+ task: {task: 'a', foo: 'bar'})
198
+
199
+ expect(new_status['queues']['a']['working']).to be_nil
200
+ end
182
201
  end
183
202
 
184
- it "updates fetching" do
185
- commit('mamiya:fetch-result:error',
186
- application: 'app', package: 'pkg', pending: 0)
203
+ context "if task is in queue" do
204
+ let(:status) do
205
+ {queues: {
206
+ a: {queue: [{task: 'a', foo: 'bar'}, {task: 'a', bar: 'baz'}], working: nil}
207
+ }}
208
+ end
209
+
210
+ it "removes from queue" do
211
+ commit('mamiya:task:finish',
212
+ task: {task: 'a', foo: 'bar'})
187
213
 
188
- expect(new_status["fetcher"]["fetching"]).to eq nil
214
+ expect(new_status['queues']['a']['working']).to be_nil
215
+ expect(new_status['queues']['a']['queue']).to eq [{'task' => 'a', 'bar' => 'baz'}]
216
+ end
189
217
  end
190
218
 
191
- context "when package doesn't match with present state" do
192
- it "doesn't updates fetching" do
193
- commit('mamiya:fetch-result:error',
194
- application: 'app', package: 'pkg2', pending: 0)
219
+ context "if task is not working" do
220
+ let(:status) do
221
+ {queues: {
222
+ a: {queue: [], working: {task: 'a', foo: 'baz'}}
223
+ }}
224
+ end
225
+
226
+ it "does nothing" do
227
+ commit('mamiya:task:finish',
228
+ task: {task: 'a', foo: 'bar'})
195
229
 
196
- expect(new_status["fetcher"]["fetching"]).to \
197
- eq(['app', 'pkg'])
230
+ expect(new_status['queues']['a']['working']).to eq('task' => 'a', 'foo' => 'baz')
231
+ expect(new_status['queues']['a']['queue']).to eq []
198
232
  end
199
233
  end
200
234
  end
201
235
 
202
- describe ":success" do
203
- let(:status) do
204
- {fetcher: {fetching: ['app', 'pkg'], pending: 0},
205
- packages: {}}
206
- end
236
+ describe ":error" do
237
+ context "if task is working" do
238
+ let(:status) do
239
+ {queues: {
240
+ a: {queue: [], working: {task: 'a', foo: 'bar'}}
241
+ }}
242
+ end
207
243
 
208
- it "updates fetching" do
209
- commit('mamiya:fetch-result:success',
210
- application: 'app', package: 'pkg', pending: 0)
244
+ it "removes from working" do
245
+ commit('mamiya:task:finish',
246
+ task: {task: 'a', foo: 'bar'})
211
247
 
212
- expect(new_status["fetcher"]["fetching"]).to eq nil
248
+ expect(new_status['queues']['a']['working']).to be_nil
249
+ end
213
250
  end
214
251
 
215
- it "updates packages" do
216
- commit('mamiya:fetch-result:success',
217
- application: 'app', package: 'pkg', pending: 0)
252
+ context "if task is in queue" do
253
+ let(:status) do
254
+ {queues: {
255
+ a: {queue: [{task: 'a', foo: 'bar'}, {task: 'a', bar: 'baz'}], working: nil}
256
+ }}
257
+ end
218
258
 
219
- expect(new_status["packages"]["app"]).to eq ["pkg"]
259
+ it "removes from queue" do
260
+ commit('mamiya:task:finish',
261
+ task: {task: 'a', foo: 'bar'})
262
+
263
+ expect(new_status['queues']['a']['working']).to be_nil
264
+ expect(new_status['queues']['a']['queue']).to eq [{'task' => 'a', 'bar' => 'baz'}]
265
+ end
220
266
  end
221
267
 
222
- context "with existing packages" do
268
+ context "if task is not working" do
223
269
  let(:status) do
224
- {fetcher: {fetching: ['app', 'pkg2'], pending: 0},
225
- packages: {"app" => ['pkg1']}}
270
+ {queues: {
271
+ a: {queue: [], working: {task: 'a', foo: 'baz'}}
272
+ }}
226
273
  end
227
274
 
228
- it "updates packages" do
229
- commit('mamiya:fetch-result:success',
230
- application: 'app', package: 'pkg2', pending: 0)
275
+ it "does nothing" do
276
+ commit('mamiya:task:finish',
277
+ task: {task: 'a', foo: 'bar'})
231
278
 
232
- expect(new_status["packages"]["app"]).to eq %w(pkg1 pkg2)
279
+ expect(new_status['queues']['a']['working']).to eq('task' => 'a', 'foo' => 'baz')
280
+ expect(new_status['queues']['a']['queue']).to eq []
233
281
  end
234
282
  end
283
+ end
284
+ end
235
285
 
236
- context "when package doesn't match with present state" do
237
- it "doesn't updates fetching" do
238
- commit('mamiya:fetch-result:success',
239
- application: 'app', package: 'pkg2', pending: 0)
286
+ describe "(task handling)" do
287
+ describe "pkg" do
288
+ describe ":remove" do
289
+ let(:status) do
290
+ {packages: {'myapp' => ['pkg1']}}
291
+ end
292
+
293
+ it "removes removed package from packages" do
294
+ commit('mamiya:pkg:remove',
295
+ application: 'myapp', package: 'pkg1')
240
296
 
241
- expect(agent_monitor.statuses["a"]["fetcher"]["fetching"]).to \
242
- eq(['app', 'pkg'])
297
+ expect(new_status["packages"]['myapp']).to eq []
243
298
  end
244
299
 
245
- it "updates packages" do
246
- commit('mamiya:fetch-result:success',
247
- application: 'app', package: 'pkg', pending: 0)
300
+ context "with existing packages" do
301
+ let(:status) do
302
+ {packages: {'myapp' => ['pkg1', 'pkg2']}}
303
+ end
304
+
305
+ it "removes removed package from packages" do
306
+ commit('mamiya:pkg:remove',
307
+ application: 'myapp', package: 'pkg1')
308
+
309
+ expect(new_status["packages"]['myapp']).to eq ['pkg2']
310
+ end
311
+ end
248
312
 
249
- expect(new_status["packages"]["app"]).to eq ["pkg"]
313
+ context "with inexist package" do
314
+ let(:status) do
315
+ {packages: {'myapp' => ['pkg1', 'pkg3']}}
316
+ end
317
+
318
+ it "removes removed package from packages" do
319
+ commit('mamiya:pkg:remove',
320
+ application: 'myapp', package: 'pkg2')
321
+
322
+ expect(new_status["packages"]['myapp']).to eq ['pkg1', 'pkg3']
323
+ end
250
324
  end
251
325
  end
252
326
  end
253
327
 
254
- describe ":remove" do
255
- context "with existing packages" do
328
+ describe "fetch" do
329
+ describe "success" do
256
330
  let(:status) do
257
- {fetcher: {fetching: ['app', 'pkg2'], pending: 0},
258
- packages: {"app" => ['pkg1']}}
331
+ {packages: {}}
259
332
  end
260
333
 
261
334
  it "updates packages" do
262
- commit('mamiya:fetch-result:remove',
263
- application: 'app', package: 'pkg1', pending: 0)
335
+ commit('mamiya:task:finish',
336
+ task: {task: 'fetch', app: 'myapp', pkg: 'pkg'})
337
+
338
+ expect(new_status["packages"]['myapp']).to eq ["pkg"]
339
+ end
340
+
341
+ context "with existing packages" do
342
+ let(:status) do
343
+ {packages: {'myapp' => ['pkg1']}}
344
+ end
345
+
346
+ it "updates packages" do
347
+ commit('mamiya:task:finish',
348
+ task: {task: 'fetch', app: 'myapp', pkg: 'pkg2'})
264
349
 
265
- expect(new_status["packages"]["app"]).to eq []
350
+ expect(new_status["packages"]['myapp']).to eq %w(pkg1 pkg2)
351
+ end
266
352
  end
267
353
  end
268
354
  end