flapjack 0.6.61 → 0.7.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.
Files changed (38) hide show
  1. data/Gemfile +2 -1
  2. data/README.md +8 -4
  3. data/features/events.feature +269 -146
  4. data/features/notification_rules.feature +93 -0
  5. data/features/steps/events_steps.rb +162 -21
  6. data/features/steps/notifications_steps.rb +1 -1
  7. data/features/steps/time_travel_steps.rb +30 -19
  8. data/features/support/env.rb +71 -1
  9. data/flapjack.gemspec +3 -0
  10. data/lib/flapjack/data/contact.rb +256 -57
  11. data/lib/flapjack/data/entity.rb +2 -1
  12. data/lib/flapjack/data/entity_check.rb +22 -7
  13. data/lib/flapjack/data/global.rb +1 -0
  14. data/lib/flapjack/data/message.rb +2 -0
  15. data/lib/flapjack/data/notification_rule.rb +172 -0
  16. data/lib/flapjack/data/tag.rb +7 -2
  17. data/lib/flapjack/data/tag_set.rb +16 -0
  18. data/lib/flapjack/executive.rb +147 -13
  19. data/lib/flapjack/filters/delays.rb +21 -9
  20. data/lib/flapjack/gateways/api.rb +407 -27
  21. data/lib/flapjack/gateways/pagerduty.rb +1 -1
  22. data/lib/flapjack/gateways/web.rb +50 -22
  23. data/lib/flapjack/gateways/web/views/self_stats.haml +2 -0
  24. data/lib/flapjack/utility.rb +10 -0
  25. data/lib/flapjack/version.rb +1 -1
  26. data/spec/lib/flapjack/data/contact_spec.rb +103 -6
  27. data/spec/lib/flapjack/data/global_spec.rb +2 -0
  28. data/spec/lib/flapjack/data/message_spec.rb +6 -0
  29. data/spec/lib/flapjack/data/notification_rule_spec.rb +22 -0
  30. data/spec/lib/flapjack/data/notification_spec.rb +6 -0
  31. data/spec/lib/flapjack/gateways/api_spec.rb +727 -4
  32. data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -0
  33. data/spec/lib/flapjack/gateways/web_spec.rb +11 -1
  34. data/spec/spec_helper.rb +10 -0
  35. data/tmp/notification_rules.rb +73 -0
  36. data/tmp/test_json_post.rb +16 -0
  37. data/tmp/test_notification_rules_api.rb +170 -0
  38. metadata +59 -2
@@ -37,7 +37,7 @@ module Flapjack
37
37
 
38
38
  def start
39
39
  @logger.info("starting")
40
- while not test_pagerduty_connection do
40
+ while not test_pagerduty_connection and not @should_quit do
41
41
  @logger.error("Can't connect to the pagerduty API, retrying after 10 seconds")
42
42
  EM::Synchrony.sleep(10)
43
43
  end
@@ -19,28 +19,21 @@ module Flapjack
19
19
 
20
20
  class Web < Sinatra::Base
21
21
 
22
- if defined?(FLAPJACK_ENV) && 'test'.eql?(FLAPJACK_ENV)
23
- # expose test errors properly
24
- set :raise_errors, true
25
- set :show_exceptions, false
26
- else
27
- rescue_exception = Proc.new do |env, e|
28
- if settings.show_exceptions?
29
- # ensure the sinatra error page shows properly
30
- request = Sinatra::Request.new(env)
31
- printer = Sinatra::ShowExceptions.new(proc{ raise e })
32
- s, h, b = printer.call(env)
33
- [s, h, b]
34
- else
35
- @logger.error e.message
36
- @logger.error e.backtrace.join("\n")
37
- [503, {}, ""]
38
- end
22
+ rescue_exception = Proc.new do |env, e|
23
+ if settings.show_exceptions?
24
+ # ensure the sinatra error page shows properly
25
+ request = Sinatra::Request.new(env)
26
+ printer = Sinatra::ShowExceptions.new(proc{ raise e })
27
+ s, h, b = printer.call(env)
28
+ [s, h, b]
29
+ else
30
+ @logger.error e.message
31
+ @logger.error e.backtrace.join("\n")
32
+ [503, {}, ""]
39
33
  end
40
-
41
- # doesn't work with Rack::Test unless we wrap tests in EM.synchrony blocks
42
- use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
43
34
  end
35
+ use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
36
+
44
37
  use Rack::MethodOverride
45
38
 
46
39
  class << self
@@ -49,7 +42,13 @@ module Flapjack
49
42
 
50
43
  @logger.info "starting web - class"
51
44
 
52
- if @config && @config['access_log']
45
+ if accesslog = (@config && @config['access_log'])
46
+ if not File.directory?(File.dirname(accesslog))
47
+ puts "Parent directory for log file #{accesslog} doesn't exist"
48
+ puts "Exiting!"
49
+ exit
50
+ end
51
+
53
52
  access_logger = Flapjack::AsyncLogger.new(@config['access_log'])
54
53
  use Flapjack::CommonLogger, access_logger
55
54
  end
@@ -95,6 +94,35 @@ module Flapjack
95
94
  haml :self_stats
96
95
  end
97
96
 
97
+ get '/self_stats.json' do
98
+ self_stats
99
+
100
+ {
101
+ 'events_queued' => @events_queued,
102
+ 'failing_services' => @count,
103
+ 'processed_events' => {
104
+ 'all_time' => {
105
+ 'total' => @event_counters['all'],
106
+ 'ok' => @event_counters['ok'],
107
+ 'failure' => @event_counters['failure'],
108
+ 'action' => @event_counters['action'],
109
+ },
110
+ 'instance' => {
111
+ 'total' => @event_counters_instance['all'],
112
+ 'ok' => @event_counters_instance['ok'],
113
+ 'failure' => @event_counters_instance['failure'],
114
+ 'action' => @event_counters_instance['action'],
115
+ 'average' => @event_rate_all,
116
+ }
117
+ },
118
+ 'total_keys' => @keys.length,
119
+ 'uptime' => @uptime_string,
120
+ 'boottime' => @boot_time,
121
+ 'current_time' => Time.now,
122
+ 'executive_instances' => @executive_instances,
123
+ }.to_json
124
+ end
125
+
98
126
  get '/check' do
99
127
  @entity = params[:entity]
100
128
  @check = params[:check]
@@ -217,7 +245,7 @@ module Flapjack
217
245
  @pagerduty_credentials = @contact.pagerduty_credentials
218
246
  end
219
247
 
220
- @entities_and_checks = @contact.entities_and_checks.sort_by {|ec|
248
+ @entities_and_checks = @contact.entities(:checks => true).sort_by {|ec|
221
249
  ec[:entity].name
222
250
  }
223
251
 
@@ -23,3 +23,5 @@
23
23
  %p Boot time: #{@boot_time}
24
24
  %p Current time: #{Time.now}
25
25
  %p Executive Instances: #{@executive_instances.inspect}
26
+ %p
27
+ %a{:href => 'self_stats.json'} Instrument as JSON
@@ -42,5 +42,15 @@ module Flapjack
42
42
  "#{tzname} (UTC#{tzoffset})"
43
43
  end
44
44
 
45
+ def remove_utc_offset(time)
46
+ Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec)
47
+ end
48
+
49
+ def symbolize(obj)
50
+ return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash
51
+ return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array
52
+ return obj
53
+ end
54
+
45
55
  end
46
56
  end
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.6.61"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -1,10 +1,28 @@
1
1
  require 'spec_helper'
2
2
 
3
+ require 'active_support/time_with_zone'
4
+ require 'ice_cube'
5
+
3
6
  require 'flapjack/data/contact'
4
7
  require 'flapjack/data/entity_check'
8
+ require 'flapjack/data/notification_rule'
5
9
 
6
10
  describe Flapjack::Data::Contact, :redis => true do
7
11
 
12
+ weekdays_8_18 = IceCube::Schedule.new(Time.local(2013,2,1,8,0,0), :duration => 60 * 60 * 10)
13
+ weekdays_8_18.add_recurrence_rule(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday, :thursday, :friday))
14
+
15
+ let(:notification_rule_data) {
16
+ {:entity_tags => ["database","physical"],
17
+ :entities => ["foo-app-01.example.com"],
18
+ :time_restrictions => [ weekdays_8_18.to_hash ],
19
+ :warning_media => ["email"],
20
+ :critical_media => ["sms", "email"],
21
+ :warning_blackhole => false,
22
+ :critical_blackhole => false
23
+ }
24
+ }
25
+
8
26
  before(:each) do
9
27
  Flapjack::Data::Contact.add({'id' => '362',
10
28
  'first_name' => 'John',
@@ -42,14 +60,93 @@ describe Flapjack::Data::Contact, :redis => true do
42
60
  contact.name.should == "John Johnson"
43
61
  end
44
62
 
45
- it "deletes all contacts" do
46
- Flapjack::Data::Contact.delete_all(:redis => @redis)
47
- contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
48
- contact.should be_nil
63
+ it "adds a contact with the same id as an existing one, clears notification rules" do
49
64
  contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
50
- contact.should be_nil
65
+ contact.should_not be_nil
66
+
67
+ contact.add_notification_rule(notification_rule_data)
68
+
69
+ nr = contact.notification_rules
70
+ nr.should_not be_nil
71
+ nr.should have(1).notification_rule
72
+
73
+ Flapjack::Data::Contact.add({'id' => '363',
74
+ 'first_name' => 'Smithy',
75
+ 'last_name' => 'Smith',
76
+ 'email' => 'smithys@example.com'},
77
+ :redis => @redis)
78
+
79
+ contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
80
+ contact.should_not be_nil
81
+ contact.name.should == 'Smithy Smith'
82
+
83
+ nr = contact.notification_rules
84
+ nr.should_not be_nil
85
+ nr.should be_empty
51
86
  end
52
87
 
88
+ it "updates a contact, does not clear notification rules" do
89
+ contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
90
+ contact.should_not be_nil
91
+
92
+ contact.add_notification_rule(notification_rule_data)
93
+
94
+ nr = contact.notification_rules
95
+ nr.should_not be_nil
96
+ nr.should have(1).notification_rule
97
+
98
+ contact.update('first_name' => 'John',
99
+ 'last_name' => 'Smith',
100
+ 'email' => 'johns@example.com')
101
+ contact.name.should == 'John Smith'
102
+
103
+ nr = contact.notification_rules
104
+ nr.should_not be_nil
105
+ nr.should have(1).notification_rule
106
+ end
107
+
108
+ it "adds a notification rule for a contact" do
109
+ contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
110
+ contact.should_not be_nil
111
+
112
+ expect {
113
+ contact.add_notification_rule(notification_rule_data)
114
+ }.to change { contact.notification_rules.size }.from(0).to(1)
115
+ end
116
+
117
+ it "removes a notification rule from a contact" do
118
+ contact = Flapjack::Data::Contact.find_by_id('363', :redis => @redis)
119
+ contact.should_not be_nil
120
+
121
+ rule = contact.add_notification_rule(notification_rule_data)
122
+
123
+ expect {
124
+ contact.delete_notification_rule(rule)
125
+ }.to change { contact.notification_rules.size }.from(1).to(0)
126
+ end
127
+
128
+ it "deletes a contact by id, including linked entities, checks, tags and notification rules" do
129
+ contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
130
+ contact.add_tags('admin')
131
+
132
+ entity_name = 'abc-123'
133
+
134
+ entity = Flapjack::Data::Entity.add({'id' => '5000',
135
+ 'name' => entity_name,
136
+ 'contacts' => ['362']},
137
+ :redis => @redis)
138
+
139
+ expect {
140
+ expect {
141
+ expect {
142
+ contact.delete!
143
+ }.to change { Flapjack::Data::Contact.all(:redis => @redis).size }.by(-1)
144
+ }.to change { @redis.smembers('contact_tag:admin').size }.by(-1)
145
+ }.to change { entity.contacts.size }.by(-1)
146
+ end
147
+
148
+ it "deletes all contacts"
149
+
53
150
  it "returns a list of entities and their checks for a contact" do
54
151
  entity_name = 'abc-123'
55
152
 
@@ -63,7 +160,7 @@ describe Flapjack::Data::Contact, :redis => true do
63
160
  ec.update_state('ok', :timestamp => t, :summary => 'a')
64
161
 
65
162
  contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
66
- eandcs = contact.entities_and_checks
163
+ eandcs = contact.entities(:checks => true)
67
164
  eandcs.should_not be_nil
68
165
  eandcs.should be_an(Array)
69
166
  eandcs.should have(1).entity_and_checks
@@ -3,4 +3,6 @@ require 'flapjack/data/global'
3
3
 
4
4
  describe Flapjack::Data::Global do
5
5
 
6
+ it "returns unacknowledged failing checks"
7
+
6
8
  end
@@ -3,4 +3,10 @@ require 'flapjack/data/message'
3
3
 
4
4
  describe Flapjack::Data::Message do
5
5
 
6
+ it "is generated for a contact"
7
+
8
+ it "assigns itself an ID"
9
+
10
+ it "returns its contained data"
11
+
6
12
  end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'flapjack/data/notification_rule'
3
+
4
+ describe Flapjack::Data::NotificationRule do
5
+
6
+ it "checks that a notification rule exists"
7
+
8
+ it "returns a notification rule if it exists"
9
+
10
+ it "does not return a notification rule if it does not exist"
11
+
12
+ it "updates a notification rule"
13
+
14
+ it "checks whether tag or entity names match"
15
+
16
+ it "checks whether times match"
17
+
18
+ it "checks if blackhole settings for a rule match a severity level"
19
+
20
+ it "returns the media settings for a rule's severity level"
21
+
22
+ end
@@ -3,4 +3,10 @@ require 'flapjack/data/notification'
3
3
 
4
4
  describe Flapjack::Data::Notification do
5
5
 
6
+ it "generates a notification for an event"
7
+
8
+ it "generates messages for contacts"
9
+
10
+ it "returns its contained data"
11
+
6
12
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'flapjack/gateways/api'
3
3
 
4
- describe 'Flapjack::Gateways::API', :sinatra => true, :logger => true do
4
+ describe 'Flapjack::Gateways::API', :sinatra => true, :logger => true, :json => true do
5
5
 
6
6
  def app
7
7
  Flapjack::Gateways::API
@@ -14,11 +14,58 @@ describe 'Flapjack::Gateways::API', :sinatra => true, :logger => true do
14
14
  let(:entity_name_esc) { URI.escape(entity_name) }
15
15
  let(:check) { 'ping' }
16
16
 
17
+ let(:contact) { mock(Flapjack::Data::Contact, :id => '21') }
18
+ let(:contact_core) {
19
+ {'id' => contact.id,
20
+ 'first_name' => "Ada",
21
+ 'last_name' => "Lovelace",
22
+ 'email' => "ada@example.com",
23
+ 'tags' => ["legend", "first computer programmer"]
24
+ }
25
+ }
26
+
27
+ let(:media) {
28
+ {'email' => 'ada@example.com',
29
+ 'sms' => '04123456789'
30
+ }
31
+ }
32
+
33
+ let(:media_intervals) {
34
+ {'email' => 500,
35
+ 'sms' => 300
36
+ }
37
+ }
38
+
17
39
  let(:entity_presenter) { mock(Flapjack::Gateways::API::EntityPresenter) }
18
40
  let(:entity_check_presenter) { mock(Flapjack::Gateways::API::EntityCheckPresenter) }
19
41
 
20
42
  let(:redis) { mock(::Redis) }
21
43
 
44
+ let(:notification_rule) {
45
+ mock(Flapjack::Data::NotificationRule, :id => '1', :contact_id => '21')
46
+ }
47
+
48
+ let(:notification_rule_data) {
49
+ {"contact_id" => "21",
50
+ "entity_tags" => ["database","physical"],
51
+ "entities" => ["foo-app-01.example.com"],
52
+ "time_restrictions" => nil,
53
+ "warning_media" => ["email"],
54
+ "critical_media" => ["sms", "email"],
55
+ "warning_blackhole" => false,
56
+ "critical_blackhole" => false
57
+ }
58
+ }
59
+
60
+ before(:all) do
61
+ Flapjack::Gateways::API.class_eval {
62
+ set :raise_errors, true
63
+ }
64
+ Flapjack::Gateways::API.instance_variable_get('@middleware').delete_if {|m|
65
+ m[0] == Rack::FiberPool
66
+ }
67
+ end
68
+
22
69
  before(:each) do
23
70
  Flapjack::RedisPool.should_receive(:new).and_return(redis)
24
71
  Flapjack::Gateways::API.instance_variable_set('@config', {})
@@ -290,7 +337,7 @@ describe 'Flapjack::Gateways::API', :sinatra => true, :logger => true do
290
337
  ]
291
338
  }
292
339
 
293
- Flapjack::Data::Contact.should_receive(:delete_all)
340
+ Flapjack::Data::Contact.should_receive(:all).with(:redis => redis).and_return([])
294
341
  Flapjack::Data::Contact.should_receive(:add).twice
295
342
 
296
343
  post "/contacts", contacts.to_json, {'CONTENT_TYPE' => 'application/json'}
@@ -298,7 +345,6 @@ describe 'Flapjack::Gateways::API', :sinatra => true, :logger => true do
298
345
  end
299
346
 
300
347
  it "does not create contacts if the data is improperly formatted" do
301
- Flapjack::Data::Contact.should_not_receive(:delete_all)
302
348
  Flapjack::Data::Contact.should_not_receive(:add)
303
349
 
304
350
  post "/contacts", {'contacts' => ["Hello", "again"]}.to_json,
@@ -321,11 +367,688 @@ describe 'Flapjack::Gateways::API', :sinatra => true, :logger => true do
321
367
  ]
322
368
  }
323
369
 
324
- Flapjack::Data::Contact.should_receive(:delete_all)
370
+ Flapjack::Data::Contact.should_receive(:all).with(:redis => redis).and_return([])
325
371
  Flapjack::Data::Contact.should_receive(:add)
326
372
 
327
373
  post "/contacts", contacts.to_json, {'CONTENT_TYPE' => 'application/json'}
328
374
  last_response.status.should == 200
329
375
  end
330
376
 
377
+ it "updates a contact if it is already present" do
378
+ contacts = {'contacts' =>
379
+ [{"id" => "0362",
380
+ "first_name" => "John",
381
+ "last_name" => "Smith",
382
+ "email" => "johns@example.dom",
383
+ "media" => {"email" => "johns@example.dom",
384
+ "jabber" => "johns@conference.localhost"}},
385
+ {"id" => "0363",
386
+ "first_name" => "Jane",
387
+ "last_name" => "Jones",
388
+ "email" => "jane@example.dom",
389
+ "media" => {"email" => "jane@example.dom"}}
390
+ ]
391
+ }
392
+
393
+ existing = mock(Flapjack::Data::Contact)
394
+ existing.should_receive(:id).and_return("0363")
395
+ existing.should_receive(:update).with(contacts['contacts'][1])
396
+
397
+ Flapjack::Data::Contact.should_receive(:all).with(:redis => redis).and_return([existing])
398
+ Flapjack::Data::Contact.should_receive(:add).with(contacts['contacts'][0], :redis => redis)
399
+
400
+ post "/contacts", contacts.to_json, {'CONTENT_TYPE' => 'application/json'}
401
+ last_response.status.should == 200
402
+ end
403
+
404
+ it "deletes a contact not found in a bulk update list" do
405
+ contacts = {'contacts' =>
406
+ [{"id" => "0363",
407
+ "first_name" => "Jane",
408
+ "last_name" => "Jones",
409
+ "email" => "jane@example.dom",
410
+ "media" => {"email" => "jane@example.dom"}}
411
+ ]
412
+ }
413
+
414
+ existing = mock(Flapjack::Data::Contact)
415
+ existing.should_receive(:id).twice.and_return("0362")
416
+ existing.should_receive(:delete!)
417
+
418
+ Flapjack::Data::Contact.should_receive(:all).with(:redis => redis).and_return([existing])
419
+ Flapjack::Data::Contact.should_receive(:add).with(contacts['contacts'][0], :redis => redis)
420
+
421
+ post "/contacts", contacts.to_json, {'CONTENT_TYPE' => 'application/json'}
422
+ last_response.status.should == 200
423
+ end
424
+
425
+ it "returns all the contacts" do
426
+ contact.should_receive(:to_json).and_return(contact_core.to_json)
427
+ Flapjack::Data::Contact.should_receive(:all).with(:redis => redis).
428
+ and_return([contact])
429
+
430
+ get '/contacts'
431
+ last_response.should be_ok
432
+ last_response.body.should be_json_eql([contact_core].to_json)
433
+ end
434
+
435
+ it "returns the core information of a specified contact" do
436
+ contact.should_receive(:to_json).and_return(contact_core.to_json)
437
+ Flapjack::Data::Contact.should_receive(:find_by_id).
438
+ with(contact.id, :redis => redis).and_return(contact)
439
+
440
+ get "/contacts/#{contact.id}"
441
+ last_response.should be_ok
442
+ last_response.body.should be_json_eql(contact_core.to_json)
443
+ end
444
+
445
+ it "does not return information for a contact that does not exist" do
446
+ Flapjack::Data::Contact.should_receive(:find_by_id).
447
+ with(contact.id, :redis => redis).and_return(nil)
448
+
449
+ get "/contacts/#{contact.id}"
450
+ last_response.should be_not_found
451
+ end
452
+
453
+ it "lists a contact's notification rules" do
454
+ notification_rule_2 = mock(Flapjack::Data::NotificationRule, :id => '2', :contact_id => '21')
455
+ notification_rule.should_receive(:to_json).and_return('"rule_1"')
456
+ notification_rule_2.should_receive(:to_json).and_return('"rule_2"')
457
+ notification_rules = [ notification_rule, notification_rule_2 ]
458
+
459
+ contact.should_receive(:notification_rules).and_return(notification_rules)
460
+ Flapjack::Data::Contact.should_receive(:find_by_id).
461
+ with(contact.id, :redis => redis).and_return(contact)
462
+
463
+ get "/contacts/#{contact.id}/notification_rules"
464
+ last_response.should be_ok
465
+ last_response.body.should be_json_eql( '["rule_1", "rule_2"]' )
466
+ end
467
+
468
+ it "does not list notification rules for a contact that does not exist" do
469
+ Flapjack::Data::Contact.should_receive(:find_by_id).
470
+ with(contact.id, :redis => redis).and_return(nil)
471
+
472
+ get "/contacts/#{contact.id}/notification_rules"
473
+ last_response.should be_not_found
474
+ end
475
+
476
+ it "returns a specified notification rule" do
477
+ notification_rule.should_receive(:to_json).and_return('"rule_1"')
478
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
479
+ with(notification_rule.id, :redis => redis).and_return(notification_rule)
480
+
481
+ get "/notification_rules/#{notification_rule.id}"
482
+ last_response.should be_ok
483
+ last_response.body.should be_json_eql('"rule_1"')
484
+ end
485
+
486
+ it "does not return a notification rule that does not exist" do
487
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
488
+ with(notification_rule.id, :redis => redis).and_return(nil)
489
+
490
+ get "/notification_rules/#{notification_rule.id}"
491
+ last_response.should be_not_found
492
+ end
493
+
494
+ # POST /notification_rules
495
+ it "creates a new notification rule" do
496
+ Flapjack::Data::Contact.should_receive(:find_by_id).
497
+ with(contact.id, :redis => redis).and_return(contact)
498
+ notification_rule.should_receive(:to_json).and_return('"rule_1"')
499
+
500
+ # symbolize the keys
501
+ notification_rule_data_sym = notification_rule_data.inject({}){|memo,(k,v)|
502
+ memo[k.to_sym] = v; memo
503
+ }
504
+ notification_rule_data_sym.delete(:contact_id)
505
+
506
+ contact.should_receive(:add_notification_rule).
507
+ with(notification_rule_data_sym).and_return(notification_rule)
508
+
509
+ post "/notification_rules", notification_rule_data.to_json,
510
+ {'CONTENT_TYPE' => 'application/json'}
511
+ last_response.should be_ok
512
+ last_response.body.should be_json_eql('"rule_1"')
513
+ end
514
+
515
+ it "does not create a notification_rule for a contact that's not present" do
516
+ Flapjack::Data::Contact.should_receive(:find_by_id).
517
+ with(contact.id, :redis => redis).and_return(nil)
518
+
519
+ post "/notification_rules", notification_rule_data.to_json,
520
+ {'CONTENT_TYPE' => 'application/json'}
521
+ last_response.should be_not_found
522
+ end
523
+
524
+ it "does not create a notification_rule if a rule id is provided" do
525
+ contact.should_not_receive(:add_notification_rule)
526
+ Flapjack::Data::Contact.should_receive(:find_by_id).
527
+ with(contact.id, :redis => redis).and_return(contact)
528
+
529
+ post "/notification_rules", notification_rule_data.merge(:id => 1).to_json,
530
+ {'CONTENT_TYPE' => 'application/json'}
531
+ last_response.status.should == 403
532
+ end
533
+
534
+ # PUT /notification_rules/RULE_ID
535
+ it "updates a notification rule" do
536
+ Flapjack::Data::Contact.should_receive(:find_by_id).
537
+ with(contact.id, :redis => redis).and_return(contact)
538
+ notification_rule.should_receive(:to_json).and_return('"rule_1"')
539
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
540
+ with(notification_rule.id, :redis => redis).and_return(notification_rule)
541
+
542
+ # symbolize the keys
543
+ notification_rule_data_sym = notification_rule_data.inject({}){|memo,(k,v)|
544
+ memo[k.to_sym] = v; memo
545
+ }
546
+ notification_rule_data_sym.delete(:contact_id)
547
+
548
+ notification_rule.should_receive(:update).with(notification_rule_data_sym)
549
+
550
+ put "/notification_rules/#{notification_rule.id}", notification_rule_data.to_json,
551
+ {'CONTENT_TYPE' => 'application/json'}
552
+ last_response.should be_ok
553
+ last_response.body.should be_json_eql('"rule_1"')
554
+ end
555
+
556
+ it "does not update a notification rule that's not present" do
557
+ Flapjack::Data::Contact.should_receive(:find_by_id).
558
+ with(contact.id, :redis => redis).and_return(contact)
559
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
560
+ with(notification_rule.id, :redis => redis).and_return(nil)
561
+
562
+ put "/notification_rules/#{notification_rule.id}", notification_rule_data
563
+ last_response.should be_not_found
564
+ end
565
+
566
+ it "does not update a notification_rule for a contact that's not present" do
567
+ Flapjack::Data::Contact.should_receive(:find_by_id).
568
+ with(contact.id, :redis => redis).and_return(nil)
569
+
570
+ put "/notification_rules/#{notification_rule.id}", notification_rule_data.to_json,
571
+ {'CONTENT_TYPE' => 'application/json'}
572
+ last_response.should be_not_found
573
+ end
574
+
575
+ # DELETE /notification_rules/RULE_ID
576
+ it "deletes a notification rule" do
577
+ notification_rule.should_receive(:contact_id).and_return(contact.id)
578
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
579
+ with(notification_rule.id, :redis => redis).and_return(notification_rule)
580
+ contact.should_receive(:delete_notification_rule).with(notification_rule)
581
+ Flapjack::Data::Contact.should_receive(:find_by_id).
582
+ with(contact.id, :redis => redis).and_return(contact)
583
+
584
+ delete "/notification_rules/#{notification_rule.id}"
585
+ last_response.status.should == 204
586
+ end
587
+
588
+ it "does not delete a notification rule that's not present" do
589
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
590
+ with(notification_rule.id, :redis => redis).and_return(nil)
591
+
592
+ delete "/notification_rules/#{notification_rule.id}"
593
+ last_response.should be_not_found
594
+ end
595
+
596
+ it "does not delete a notification rule if the contact is not present" do
597
+ notification_rule.should_receive(:contact_id).and_return(contact.id)
598
+ Flapjack::Data::NotificationRule.should_receive(:find_by_id).
599
+ with(notification_rule.id, :redis => redis).and_return(notification_rule)
600
+ Flapjack::Data::Contact.should_receive(:find_by_id).
601
+ with(contact.id, :redis => redis).and_return(nil)
602
+
603
+ delete "/notification_rules/#{notification_rule.id}"
604
+ last_response.should be_not_found
605
+ end
606
+
607
+ # GET /contacts/CONTACT_ID/media
608
+ it "returns the media of a contact" do
609
+ contact.should_receive(:media).and_return(media)
610
+ contact.should_receive(:media_intervals).and_return(media_intervals)
611
+ Flapjack::Data::Contact.should_receive(:find_by_id).
612
+ with(contact.id, :redis => redis).and_return(contact)
613
+ result = Hash[ *(media.keys.collect {|m|
614
+ [m, {'address' => media[m],
615
+ 'interval' => media_intervals[m] }]
616
+ }).flatten(1)].to_json
617
+
618
+ get "/contacts/#{contact.id}/media"
619
+ last_response.should be_ok
620
+ last_response.body.should be_json_eql(result)
621
+ end
622
+
623
+ it "does not return the media of a contact if the contact is not present" do
624
+ Flapjack::Data::Contact.should_receive(:find_by_id).
625
+ with(contact.id, :redis => redis).and_return(nil)
626
+
627
+ get "/contacts/#{contact.id}/media"
628
+ last_response.should be_not_found
629
+ end
630
+
631
+ # GET /contacts/CONTACT_ID/media/MEDIA
632
+ it "returns the specified media of a contact" do
633
+ contact.should_receive(:media).twice.and_return(media)
634
+ contact.should_receive(:media_intervals).and_return(media_intervals)
635
+ Flapjack::Data::Contact.should_receive(:find_by_id).
636
+ with(contact.id, :redis => redis).and_return(contact)
637
+
638
+ result = {'address' => media['sms'], 'interval' => media_intervals['sms']}
639
+
640
+ get "/contacts/#{contact.id}/media/sms"
641
+ last_response.should be_ok
642
+ last_response.body.should be_json_eql(result.to_json)
643
+ end
644
+
645
+ it "does not return the media of a contact if the contact is not present" do
646
+ Flapjack::Data::Contact.should_receive(:find_by_id).
647
+ with(contact.id, :redis => redis).and_return(nil)
648
+
649
+ get "/contacts/#{contact.id}/media/sms"
650
+ last_response.should be_not_found
651
+ end
652
+
653
+ it "does not return the media of a contact if the media is not present" do
654
+ contact.should_receive(:media).and_return(media)
655
+ Flapjack::Data::Contact.should_receive(:find_by_id).
656
+ with(contact.id, :redis => redis).and_return(contact)
657
+
658
+ get "/contacts/#{contact.id}/media/telepathy"
659
+ last_response.should be_not_found
660
+ end
661
+
662
+ # PUT, DELETE /contacts/CONTACT_ID/media/MEDIA
663
+ it "creates/updates a media of a contact" do
664
+ # as far as API is concerned these are the same -- contact.rb spec test
665
+ # may distinguish between them
666
+ alt_media = media.merge('sms' => '04987654321')
667
+ alt_media_intervals = media_intervals.merge('sms' => '200')
668
+
669
+ contact.should_receive(:set_address_for_media).with('sms', '04987654321')
670
+ contact.should_receive(:set_interval_for_media).with('sms', '200')
671
+ contact.should_receive(:media).and_return(alt_media)
672
+ contact.should_receive(:media_intervals).and_return(alt_media_intervals)
673
+ Flapjack::Data::Contact.should_receive(:find_by_id).
674
+ with(contact.id, :redis => redis).and_return(contact)
675
+
676
+ result = {'address' => alt_media['sms'], 'interval' => alt_media_intervals['sms']}
677
+
678
+ put "/contacts/#{contact.id}/media/sms", {:address => '04987654321', :interval => '200'}
679
+ last_response.should be_ok
680
+ last_response.body.should be_json_eql(result.to_json)
681
+ end
682
+
683
+ it "does not create a media of a contact that's not present" do
684
+ Flapjack::Data::Contact.should_receive(:find_by_id).
685
+ with(contact.id, :redis => redis).and_return(nil)
686
+
687
+ put "/contacts/#{contact.id}/media/sms", {:address => '04987654321', :interval => '200'}
688
+ last_response.should be_not_found
689
+ end
690
+
691
+ it "does not create a media of a contact if no address is provided" do
692
+ Flapjack::Data::Contact.should_receive(:find_by_id).
693
+ with(contact.id, :redis => redis).and_return(contact)
694
+
695
+ put "/contacts/#{contact.id}/media/sms", {:interval => '200'}
696
+ last_response.should be_forbidden
697
+ end
698
+
699
+ it "does not create a media of a contact if no interval is provided" do
700
+ Flapjack::Data::Contact.should_receive(:find_by_id).
701
+ with(contact.id, :redis => redis).and_return(contact)
702
+
703
+ put "/contacts/#{contact.id}/media/sms", {:address => '04987654321'}
704
+ last_response.should be_forbidden
705
+ end
706
+
707
+ it "deletes a media of a contact" do
708
+ contact.should_receive(:remove_media).with('sms')
709
+ Flapjack::Data::Contact.should_receive(:find_by_id).
710
+ with(contact.id, :redis => redis).and_return(contact)
711
+
712
+ delete "/contacts/#{contact.id}/media/sms"
713
+ last_response.status.should == 204
714
+ end
715
+
716
+ it "does not delete a media of a contact that's not present" do
717
+ Flapjack::Data::Contact.should_receive(:find_by_id).
718
+ with(contact.id, :redis => redis).and_return(nil)
719
+
720
+ delete "/contacts/#{contact.id}/media/sms"
721
+ last_response.should be_not_found
722
+ end
723
+
724
+ # GET /contacts/CONTACT_ID/timezone
725
+ it "returns the timezone of a contact" do
726
+ contact.should_receive(:timezone).and_return(::ActiveSupport::TimeZone.new('Australia/Sydney'))
727
+ Flapjack::Data::Contact.should_receive(:find_by_id).
728
+ with(contact.id, :redis => redis).and_return(contact)
729
+
730
+ get "/contacts/#{contact.id}/timezone"
731
+ last_response.should be_ok
732
+ last_response.body.should be_json_eql('"Australia/Sydney"')
733
+ end
734
+
735
+ it "doesn't get the timezone of a contact that doesn't exist" do
736
+ Flapjack::Data::Contact.should_receive(:find_by_id).
737
+ with(contact.id, :redis => redis).and_return(nil)
738
+
739
+ get "/contacts/#{contact.id}/timezone"
740
+ last_response.should be_not_found
741
+ end
742
+
743
+ # PUT /contacts/CONTACT_ID/timezone
744
+ it "sets the timezone of a contact" do
745
+ contact.should_receive(:timezone=).with('Australia/Perth')
746
+ contact.should_receive(:timezone).and_return(ActiveSupport::TimeZone.new('Australia/Perth'))
747
+ Flapjack::Data::Contact.should_receive(:find_by_id).
748
+ with(contact.id, :redis => redis).and_return(contact)
749
+
750
+ put "/contacts/#{contact.id}/timezone", {:timezone => 'Australia/Perth'}
751
+ last_response.should be_ok
752
+ end
753
+
754
+ it "doesn't set the timezone of a contact who can't be found" do
755
+ Flapjack::Data::Contact.should_receive(:find_by_id).
756
+ with(contact.id, :redis => redis).and_return(nil)
757
+
758
+ put "/contacts/#{contact.id}/timezone", {:timezone => 'Australia/Perth'}
759
+ last_response.should be_not_found
760
+ end
761
+
762
+ # DELETE /contacts/CONTACT_ID/timezone
763
+ it "deletes the timezone of a contact" do
764
+ contact.should_receive(:timezone=).with(nil)
765
+ Flapjack::Data::Contact.should_receive(:find_by_id).
766
+ with(contact.id, :redis => redis).and_return(contact)
767
+
768
+ delete "/contacts/#{contact.id}/timezone"
769
+ last_response.status.should == 204
770
+ end
771
+
772
+ it "does not delete the timezone of a contact that's not present" do
773
+ Flapjack::Data::Contact.should_receive(:find_by_id).
774
+ with(contact.id, :redis => redis).and_return(nil)
775
+
776
+ delete "/contacts/#{contact.id}/timezone"
777
+ last_response.should be_not_found
778
+ end
779
+
780
+
781
+
782
+ it "sets a single tag on an entity and returns current tags" do
783
+ entity.should_receive(:add_tags).with('web')
784
+ entity.should_receive(:tags).and_return(['web'])
785
+ Flapjack::Data::Entity.should_receive(:find_by_name).
786
+ with(entity_name, :redis => redis).and_return(entity)
787
+
788
+ post "entities/#{entity_name}/tags", :tag => 'web'
789
+ last_response.should be_ok
790
+ last_response.body.should be_json_eql( ['web'].to_json )
791
+ end
792
+
793
+ it "does not set a single tag on an entity that's not found" do
794
+ Flapjack::Data::Entity.should_receive(:find_by_name).
795
+ with(entity_name, :redis => redis).and_return(nil)
796
+
797
+ post "entities/#{entity_name}/tags", :tag => 'web'
798
+ last_response.should be_not_found
799
+ end
800
+
801
+ it "sets multiple tags on an entity and returns current tags" do
802
+ entity.should_receive(:add_tags).with('web', 'app')
803
+ entity.should_receive(:tags).and_return(['web', 'app'])
804
+ Flapjack::Data::Entity.should_receive(:find_by_name).
805
+ with(entity_name, :redis => redis).and_return(entity)
806
+
807
+ # NB submitted at a lower level as tag[]=web&tag[]=app
808
+ post "entities/#{entity_name}/tags", :tag => ['web', 'app']
809
+ last_response.should be_ok
810
+ last_response.body.should be_json_eql( ['web', 'app'].to_json )
811
+ end
812
+
813
+ it "does not set multiple tags on an entity that's not found" do
814
+ Flapjack::Data::Entity.should_receive(:find_by_name).
815
+ with(entity_name, :redis => redis).and_return(nil)
816
+
817
+ post "entities/#{entity_name}/tags", :tag => ['web', 'app']
818
+ last_response.should be_not_found
819
+ end
820
+
821
+ it "removes a single tag from an entity" do
822
+ entity.should_receive(:delete_tags).with('web')
823
+ Flapjack::Data::Entity.should_receive(:find_by_name).
824
+ with(entity_name, :redis => redis).and_return(entity)
825
+
826
+ delete "entities/#{entity_name}/tags", :tag => 'web'
827
+ last_response.status.should == 204
828
+ end
829
+
830
+ it "does not remove a single tag from an entity that's not found" do
831
+ Flapjack::Data::Entity.should_receive(:find_by_name).
832
+ with(entity_name, :redis => redis).and_return(nil)
833
+
834
+ delete "entities/#{entity_name}/tags", :tag => 'web'
835
+ last_response.should be_not_found
836
+ end
837
+
838
+ it "removes multiple tags from an entity" do
839
+ entity.should_receive(:delete_tags).with('web', 'app')
840
+ Flapjack::Data::Entity.should_receive(:find_by_name).
841
+ with(entity_name, :redis => redis).and_return(entity)
842
+
843
+ delete "entities/#{entity_name}/tags", :tag => ['web', 'app']
844
+ last_response.status.should == 204
845
+ end
846
+
847
+ it "does not remove multiple tags from an entity that's not found" do
848
+ Flapjack::Data::Entity.should_receive(:find_by_name).
849
+ with(entity_name, :redis => redis).and_return(nil)
850
+
851
+ delete "entities/#{entity_name}/tags", :tag => ['web', 'app']
852
+ last_response.should be_not_found
853
+ end
854
+
855
+ it "gets all tags on an entity" do
856
+ entity.should_receive(:tags).and_return(['web', 'app'])
857
+ Flapjack::Data::Entity.should_receive(:find_by_name).
858
+ with(entity_name, :redis => redis).and_return(entity)
859
+
860
+ get "entities/#{entity_name}/tags"
861
+ last_response.should be_ok
862
+ last_response.body.should be_json_eql( ['web', 'app'].to_json )
863
+ end
864
+
865
+ it "does not get all tags on an entity that's not found" do
866
+ Flapjack::Data::Entity.should_receive(:find_by_name).
867
+ with(entity_name, :redis => redis).and_return(nil)
868
+
869
+ get "entities/#{entity_name}/tags"
870
+ last_response.should be_not_found
871
+ end
872
+
873
+
874
+ it "sets a single tag on a contact and returns current tags" do
875
+ contact.should_receive(:add_tags).with('web')
876
+ contact.should_receive(:tags).and_return(['web'])
877
+ Flapjack::Data::Contact.should_receive(:find_by_id).
878
+ with(contact.id, :redis => redis).and_return(contact)
879
+
880
+ post "contacts/#{contact.id}/tags", :tag => 'web'
881
+ last_response.should be_ok
882
+ last_response.body.should be_json_eql( ['web'].to_json )
883
+ end
884
+
885
+ it "does not set a single tag on a contact that's not found" do
886
+ Flapjack::Data::Contact.should_receive(:find_by_id).
887
+ with(contact.id, :redis => redis).and_return(nil)
888
+
889
+ post "contacts/#{contact.id}/tags", :tag => 'web'
890
+ last_response.should be_not_found
891
+ end
892
+
893
+ it "sets multiple tags on a contact and returns current tags" do
894
+ contact.should_receive(:add_tags).with('web', 'app')
895
+ contact.should_receive(:tags).and_return(['web', 'app'])
896
+ Flapjack::Data::Contact.should_receive(:find_by_id).
897
+ with(contact.id, :redis => redis).and_return(contact)
898
+
899
+ post "contacts/#{contact.id}/tags", :tag => ['web', 'app']
900
+ last_response.should be_ok
901
+ last_response.body.should be_json_eql( ['web', 'app'].to_json )
902
+ end
903
+
904
+ it "does not set multiple tags on a contact that's not found" do
905
+ Flapjack::Data::Contact.should_receive(:find_by_id).
906
+ with(contact.id, :redis => redis).and_return(nil)
907
+
908
+ post "contacts/#{contact.id}/tags", :tag => ['web', 'app']
909
+ last_response.should be_not_found
910
+ end
911
+
912
+ it "removes a single tag from a contact" do
913
+ contact.should_receive(:delete_tags).with('web')
914
+ Flapjack::Data::Contact.should_receive(:find_by_id).
915
+ with(contact.id, :redis => redis).and_return(contact)
916
+
917
+ delete "contacts/#{contact.id}/tags", :tag => 'web'
918
+ last_response.status.should == 204
919
+ end
920
+
921
+ it "does not remove a single tag from a contact that's not found" do
922
+ Flapjack::Data::Contact.should_receive(:find_by_id).
923
+ with(contact.id, :redis => redis).and_return(nil)
924
+
925
+ delete "contacts/#{contact.id}/tags", :tag => 'web'
926
+ last_response.should be_not_found
927
+ end
928
+
929
+ it "removes multiple tags from a contact" do
930
+ contact.should_receive(:delete_tags).with('web', 'app')
931
+ Flapjack::Data::Contact.should_receive(:find_by_id).
932
+ with(contact.id, :redis => redis).and_return(contact)
933
+
934
+ delete "contacts/#{contact.id}/tags", :tag => ['web', 'app']
935
+ last_response.status.should == 204
936
+ end
937
+
938
+ it "does not remove multiple tags from a contact that's not found" do
939
+ Flapjack::Data::Contact.should_receive(:find_by_id).
940
+ with(contact.id, :redis => redis).and_return(nil)
941
+
942
+ delete "contacts/#{contact.id}/tags", :tag => ['web', 'app']
943
+ last_response.should be_not_found
944
+ end
945
+
946
+ it "gets all tags on a contact" do
947
+ contact.should_receive(:tags).and_return(['web', 'app'])
948
+ Flapjack::Data::Contact.should_receive(:find_by_id).
949
+ with(contact.id, :redis => redis).and_return(contact)
950
+
951
+ get "contacts/#{contact.id}/tags"
952
+ last_response.should be_ok
953
+ last_response.body.should be_json_eql( ['web', 'app'].to_json )
954
+ end
955
+
956
+ it "does not get all tags on a contact that's not found" do
957
+ Flapjack::Data::Contact.should_receive(:find_by_id).
958
+ with(contact.id, :redis => redis).and_return(nil)
959
+
960
+ get "contacts/#{contact.id}/tags"
961
+ last_response.should be_not_found
962
+ end
963
+
964
+ it "gets all entity tags for a contact" do
965
+ entity_1 = mock(Flapjack::Data::Entity)
966
+ entity_1.should_receive(:name).and_return('entity_1')
967
+ entity_2 = mock(Flapjack::Data::Entity)
968
+ entity_2.should_receive(:name).and_return('entity_2')
969
+ tag_data = [{:entity => entity_1, :tags => ['web']},
970
+ {:entity => entity_2, :tags => ['app']}]
971
+ contact.should_receive(:entities).with(:tags => true).
972
+ and_return(tag_data)
973
+
974
+ Flapjack::Data::Contact.should_receive(:find_by_id).
975
+ with(contact.id, :redis => redis).and_return(contact)
976
+
977
+ get "contacts/#{contact.id}/entity_tags"
978
+ last_response.should be_ok
979
+ tag_response = {'entity_1' => ['web'],
980
+ 'entity_2' => ['app']}
981
+ last_response.body.should be_json_eql( tag_response.to_json )
982
+ end
983
+
984
+ it "does not get all entity tags for a contact that's not found" do
985
+ Flapjack::Data::Contact.should_receive(:find_by_id).
986
+ with(contact.id, :redis => redis).and_return(nil)
987
+
988
+ get "contacts/#{contact.id}/entity_tags"
989
+ last_response.should be_not_found
990
+ end
991
+
992
+ it "adds tags to multiple entities for a contact" do
993
+ entity_1 = mock(Flapjack::Data::Entity)
994
+ entity_1.should_receive(:name).twice.and_return('entity_1')
995
+ entity_1.should_receive(:add_tags).with('web')
996
+ entity_2 = mock(Flapjack::Data::Entity)
997
+ entity_2.should_receive(:name).twice.and_return('entity_2')
998
+ entity_2.should_receive(:add_tags).with('app')
999
+
1000
+ entities = [{:entity => entity_1}, {:entity => entity_2}]
1001
+ contact.should_receive(:entities).and_return(entities)
1002
+ tag_data = [{:entity => entity_1, :tags => ['web']},
1003
+ {:entity => entity_2, :tags => ['app']}]
1004
+ contact.should_receive(:entities).with(:tags => true).and_return(tag_data)
1005
+
1006
+ Flapjack::Data::Contact.should_receive(:find_by_id).
1007
+ with(contact.id, :redis => redis).and_return(contact)
1008
+
1009
+ post "contacts/#{contact.id}/entity_tags",
1010
+ :entity => {'entity_1' => ['web'], 'entity_2' => ['app']}
1011
+ last_response.should be_ok
1012
+ tag_response = {'entity_1' => ['web'],
1013
+ 'entity_2' => ['app']}
1014
+ last_response.body.should be_json_eql( tag_response.to_json )
1015
+ end
1016
+
1017
+ it "does not add tags to multiple entities for a contact that's not found" do
1018
+ Flapjack::Data::Contact.should_receive(:find_by_id).
1019
+ with(contact.id, :redis => redis).and_return(nil)
1020
+
1021
+ post "contacts/#{contact.id}/entity_tags",
1022
+ :entity => {'entity_1' => ['web'], 'entity_2' => ['app']}
1023
+ last_response.should be_not_found
1024
+ end
1025
+
1026
+ it "deletes tags from multiple entities for a contact" do
1027
+ entity_1 = mock(Flapjack::Data::Entity)
1028
+ entity_1.should_receive(:name).and_return('entity_1')
1029
+ entity_1.should_receive(:delete_tags).with('web')
1030
+ entity_2 = mock(Flapjack::Data::Entity)
1031
+ entity_2.should_receive(:name).and_return('entity_2')
1032
+ entity_2.should_receive(:delete_tags).with('app')
1033
+
1034
+ entities = [{:entity => entity_1}, {:entity => entity_2}]
1035
+ contact.should_receive(:entities).and_return(entities)
1036
+
1037
+ Flapjack::Data::Contact.should_receive(:find_by_id).
1038
+ with(contact.id, :redis => redis).and_return(contact)
1039
+
1040
+ delete "contacts/#{contact.id}/entity_tags",
1041
+ :entity => {'entity_1' => ['web'], 'entity_2' => ['app']}
1042
+ last_response.status.should == 204
1043
+ end
1044
+
1045
+ it "does not delete tags from multiple entities for a contact that's not found" do
1046
+ Flapjack::Data::Contact.should_receive(:find_by_id).
1047
+ with(contact.id, :redis => redis).and_return(nil)
1048
+
1049
+ delete "contacts/#{contact.id}/entity_tags",
1050
+ :entity => {'entity_1' => ['web'], 'entity_2' => ['app']}
1051
+ last_response.should be_not_found
1052
+ end
1053
+
331
1054
  end