summaryrefslogtreecommitdiff
path: root/spec/functional/notifications_spec.rb
blob: a02fdffe5e732c18740247b163e55339a693acc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
require 'spec_helper'
require 'chef/recipe'


# The goal of these tests is to make sure that loading resources from a file creates the necessary notifications.
# Then once converge has started, both immediate and delayed notifications are called as the resources are converged.
# We want to do this WITHOUT actually converging any resources - we don't want to take time changing the system,
# we just want to make sure the run_context, the notification DSL and the converge hooks are working together
# to perform notifications.

# This test is extremely fragile since it mocks MANY different systems at once - any of them changes, this test
# breaks
describe "Notifications" do

  # We always pretend we are on OSx because that has a specific provider (HomebrewProvider) so it
  # tests the translation from Provider => HomebrewProvider
  let(:node) {
    n = Chef::Node.new
    n.override[:os] = "darwin"
    n
  }
  let(:cookbook_collection) { double("Chef::CookbookCollection").as_null_object }
  let(:events) { double("Chef::EventDispatch::Dispatcher").as_null_object }
  let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
  let(:recipe) { Chef::Recipe.new("notif", "test", run_context) }
  let(:runner) { Chef::Runner.new(run_context) }

  before do
    # By default, every provider will do nothing
    p = Chef::Provider.new(nil, run_context)
    allow_any_instance_of(Chef::Resource).to receive(:provider_for_action).and_return(p)
    allow(p).to receive(:run_action)
  end

  it "should subscribe from one resource to another" do
    log_resource = recipe.declare_resource(:log, "subscribed-log") do
      message "This is a log message"
      action :nothing
      subscribes :write, "package[vim]", :immediately
    end

    package_resource = recipe.declare_resource(:package, "vim") do
      action :install
    end

    expect(log_resource).to receive(:run_action).with(:nothing, nil, nil).and_call_original

    expect(package_resource).to receive(:run_action).with(:install, nil, nil).and_call_original
    update_action(package_resource)

    expect(log_resource).to receive(:run_action).with(:write, :immediate, package_resource).and_call_original

    runner.converge
  end

  it "should notify from one resource to another immediately" do
    log_resource = recipe.declare_resource(:log, "log") do
      message "This is a log message"
      action :write
      notifies :install, "package[vim]", :immediately
    end

    package_resource = recipe.declare_resource(:package, "vim") do
      action :nothing
    end

    expect(log_resource).to receive(:run_action).with(:write, nil, nil).and_call_original
    update_action(log_resource)

    expect(package_resource).to receive(:run_action).with(:install, :immediate, log_resource).ordered.and_call_original

    expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original

    runner.converge
  end

  it "should notify from one resource to another delayed" do
    log_resource = recipe.declare_resource(:log, "log") do
      message "This is a log message"
      action :write
      notifies :install, "package[vim]", :delayed
    end

    package_resource = recipe.declare_resource(:package, "vim") do
      action :nothing
    end

    expect(log_resource).to receive(:run_action).with(:write, nil, nil).and_call_original
    update_action(log_resource)

    expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original

    expect(package_resource).to receive(:run_action).with(:install, :delayed, nil).ordered.and_call_original

    runner.converge
  end
  
  describe "when one resource is defined lazily" do

    it "subscribes to a resource defined in a ruby block" do
      r = recipe
      t = self
      ruby_block = recipe.declare_resource(:ruby_block, "rblock") do
        block do
          log_resource = r.declare_resource(:log, "log") do
            message "This is a log message"
            action :write
          end
          t.expect(log_resource).to t.receive(:run_action).with(:write, nil, nil).and_call_original
          t.update_action(log_resource)
        end
      end

      package_resource = recipe.declare_resource(:package, "vim") do
        action :nothing
        subscribes :install, "log[log]", :delayed
      end

      # RubyBlock needs to be able to run for our lazy examples to work - and it alone cannot affect the system
      expect(ruby_block).to receive(:provider_for_action).and_call_original

      expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original

      expect(package_resource).to receive(:run_action).with(:install, :delayed, nil).ordered.and_call_original

      runner.converge
    end

    it "notifies from inside a ruby_block to a resource defined outside" do
      r = recipe
      t = self
      ruby_block = recipe.declare_resource(:ruby_block, "rblock") do
        block do
          log_resource = r.declare_resource(:log, "log") do
            message "This is a log message"
            action :write
            notifies :install, "package[vim]", :immediately
          end
          t.expect(log_resource).to t.receive(:run_action).with(:write, nil, nil).and_call_original
          t.update_action(log_resource)
        end
      end

      package_resource = recipe.declare_resource(:package, "vim") do
        action :nothing
      end

      # RubyBlock needs to be able to run for our lazy examples to work - and it alone cannot affect the system
      expect(ruby_block).to receive(:provider_for_action).and_call_original

      expect(package_resource).to receive(:run_action).with(:install, :immediate, instance_of(Chef::Resource::Log)).ordered.and_call_original

      expect(package_resource).to receive(:run_action).with(:nothing, nil, nil).ordered.and_call_original

      runner.converge
    end

  end

  # Mocks having the provider run successfully and update the resource
  def update_action(resource)
    p = Chef::Provider.new(resource, run_context)
    expect(resource).to receive(:provider_for_action).and_return(p)
    expect(p).to receive(:run_action) {
      resource.updated_by_last_action(true)
    }
  end

end