mcollective-client 1.3.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mcollective-client might be problematic. Click here for more details.

Files changed (103) hide show
  1. data/bin/mc-call-agent +54 -0
  2. data/bin/mco +27 -0
  3. data/lib/mcollective.rb +70 -0
  4. data/lib/mcollective/agents.rb +160 -0
  5. data/lib/mcollective/application.rb +354 -0
  6. data/lib/mcollective/applications.rb +145 -0
  7. data/lib/mcollective/client.rb +292 -0
  8. data/lib/mcollective/config.rb +202 -0
  9. data/lib/mcollective/connector.rb +18 -0
  10. data/lib/mcollective/connector/base.rb +24 -0
  11. data/lib/mcollective/facts.rb +39 -0
  12. data/lib/mcollective/facts/base.rb +86 -0
  13. data/lib/mcollective/log.rb +103 -0
  14. data/lib/mcollective/logger.rb +5 -0
  15. data/lib/mcollective/logger/base.rb +73 -0
  16. data/lib/mcollective/logger/console_logger.rb +61 -0
  17. data/lib/mcollective/logger/file_logger.rb +46 -0
  18. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  19. data/lib/mcollective/matcher.rb +16 -0
  20. data/lib/mcollective/matcher/parser.rb +93 -0
  21. data/lib/mcollective/matcher/scanner.rb +123 -0
  22. data/lib/mcollective/message.rb +201 -0
  23. data/lib/mcollective/monkey_patches.rb +104 -0
  24. data/lib/mcollective/optionparser.rb +164 -0
  25. data/lib/mcollective/pluginmanager.rb +180 -0
  26. data/lib/mcollective/pluginpackager.rb +26 -0
  27. data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
  28. data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
  29. data/lib/mcollective/registration.rb +16 -0
  30. data/lib/mcollective/registration/base.rb +75 -0
  31. data/lib/mcollective/rpc.rb +188 -0
  32. data/lib/mcollective/rpc/actionrunner.rb +142 -0
  33. data/lib/mcollective/rpc/agent.rb +441 -0
  34. data/lib/mcollective/rpc/audit.rb +38 -0
  35. data/lib/mcollective/rpc/client.rb +793 -0
  36. data/lib/mcollective/rpc/ddl.rb +258 -0
  37. data/lib/mcollective/rpc/helpers.rb +339 -0
  38. data/lib/mcollective/rpc/progress.rb +63 -0
  39. data/lib/mcollective/rpc/reply.rb +61 -0
  40. data/lib/mcollective/rpc/request.rb +51 -0
  41. data/lib/mcollective/rpc/result.rb +41 -0
  42. data/lib/mcollective/rpc/stats.rb +185 -0
  43. data/lib/mcollective/runnerstats.rb +90 -0
  44. data/lib/mcollective/security.rb +26 -0
  45. data/lib/mcollective/security/base.rb +237 -0
  46. data/lib/mcollective/shell.rb +87 -0
  47. data/lib/mcollective/ssl.rb +246 -0
  48. data/lib/mcollective/unix_daemon.rb +37 -0
  49. data/lib/mcollective/util.rb +274 -0
  50. data/lib/mcollective/vendor.rb +41 -0
  51. data/lib/mcollective/vendor/require_vendored.rb +2 -0
  52. data/lib/mcollective/windows_daemon.rb +25 -0
  53. data/spec/Rakefile +16 -0
  54. data/spec/fixtures/application/test.rb +7 -0
  55. data/spec/fixtures/test-cert.pem +15 -0
  56. data/spec/fixtures/test-private.pem +15 -0
  57. data/spec/fixtures/test-public.pem +6 -0
  58. data/spec/monkey_patches/instance_variable_defined.rb +7 -0
  59. data/spec/spec.opts +1 -0
  60. data/spec/spec_helper.rb +25 -0
  61. data/spec/unit/agents_spec.rb +280 -0
  62. data/spec/unit/application_spec.rb +636 -0
  63. data/spec/unit/applications_spec.rb +155 -0
  64. data/spec/unit/array.rb +30 -0
  65. data/spec/unit/config_spec.rb +148 -0
  66. data/spec/unit/facts/base_spec.rb +118 -0
  67. data/spec/unit/facts_spec.rb +39 -0
  68. data/spec/unit/log_spec.rb +71 -0
  69. data/spec/unit/logger/base_spec.rb +110 -0
  70. data/spec/unit/logger/syslog_logger_spec.rb +86 -0
  71. data/spec/unit/matcher/parser_spec.rb +106 -0
  72. data/spec/unit/matcher/scanner_spec.rb +71 -0
  73. data/spec/unit/message_spec.rb +401 -0
  74. data/spec/unit/optionparser_spec.rb +113 -0
  75. data/spec/unit/pluginmanager_spec.rb +173 -0
  76. data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
  77. data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
  78. data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
  79. data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
  80. data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
  81. data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
  82. data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
  83. data/spec/unit/registration/base_spec.rb +77 -0
  84. data/spec/unit/rpc/actionrunner_spec.rb +213 -0
  85. data/spec/unit/rpc/agent_spec.rb +155 -0
  86. data/spec/unit/rpc/client_spec.rb +523 -0
  87. data/spec/unit/rpc/ddl_spec.rb +388 -0
  88. data/spec/unit/rpc/helpers_spec.rb +55 -0
  89. data/spec/unit/rpc/reply_spec.rb +143 -0
  90. data/spec/unit/rpc/request_spec.rb +115 -0
  91. data/spec/unit/rpc/result_spec.rb +66 -0
  92. data/spec/unit/rpc/stats_spec.rb +288 -0
  93. data/spec/unit/runnerstats_spec.rb +40 -0
  94. data/spec/unit/security/base_spec.rb +279 -0
  95. data/spec/unit/shell_spec.rb +144 -0
  96. data/spec/unit/ssl_spec.rb +244 -0
  97. data/spec/unit/symbol.rb +11 -0
  98. data/spec/unit/unix_daemon.rb +41 -0
  99. data/spec/unit/util_spec.rb +342 -0
  100. data/spec/unit/vendor_spec.rb +34 -0
  101. data/spec/unit/windows_daemon.rb +43 -0
  102. data/spec/windows_spec.opts +1 -0
  103. metadata +242 -0
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ describe Applications do
7
+ before do
8
+ tmpfile = Tempfile.new("mc_applications_spec")
9
+ path = tmpfile.path
10
+ tmpfile.close!
11
+
12
+ @tmpdir = FileUtils.mkdir_p(path)
13
+ @tmpdir = @tmpdir[0] if @tmpdir.is_a?(Array) # ruby 1.9.2
14
+ $LOAD_PATH << @tmpdir
15
+
16
+ @appsdir = File.join([@tmpdir, "mcollective", "application"])
17
+ FileUtils.mkdir_p(@appsdir)
18
+
19
+ FileUtils.cp(File.join([File.dirname(__FILE__), "..", "fixtures", "application", "test.rb"]), @appsdir)
20
+ end
21
+
22
+ after do
23
+ FileUtils.rm_r(@tmpdir)
24
+ end
25
+
26
+ describe "[]" do
27
+ it "should load the config" do
28
+ Applications.expects(:load_config).once
29
+ PluginManager.expects("[]").once
30
+ Applications["test"]
31
+ end
32
+
33
+ it "should return the correct stored application" do
34
+ app = mock("app")
35
+ app.stubs(:run)
36
+
37
+ Applications.expects(:load_config).once
38
+ PluginManager.expects("[]").with("test_application").once.returns(app)
39
+ Applications["test"].should == app
40
+ end
41
+ end
42
+
43
+ describe "#run" do
44
+ it "should load the configuration" do
45
+ app = mock("app")
46
+ app.stubs(:run)
47
+
48
+ Applications.expects(:load_config).once
49
+ Applications.expects(:load_application).once
50
+ PluginManager.expects("[]").once.returns(app)
51
+
52
+ Applications.run("test")
53
+ end
54
+
55
+ it "should load the application" do
56
+ app = mock("app")
57
+ app.stubs(:run)
58
+
59
+ Applications.expects(:load_config).once
60
+ Applications.expects(:load_application).with("test").once
61
+ PluginManager.expects("[]").once.returns(app)
62
+
63
+ Applications.run("test")
64
+ end
65
+
66
+ it "should invoke the application run method" do
67
+ app = mock("app")
68
+ app.stubs(:run).returns("hello world")
69
+
70
+ Applications.expects(:load_config).once
71
+ Applications.expects(:load_application)
72
+ PluginManager.expects("[]").once.returns(app)
73
+
74
+ Applications.run("test").should == "hello world"
75
+ end
76
+ end
77
+
78
+ describe "#load_application" do
79
+ it "should return the existing application if already loaded" do
80
+ app = mock("app")
81
+ app.stubs(:run)
82
+
83
+ PluginManager << {:type => "test_application", :class => app}
84
+
85
+ Applications.expects("load_config").never
86
+
87
+ Applications.load_application("test")
88
+ end
89
+
90
+ it "should load the config" do
91
+ Applications.expects("load_config").returns(true).once
92
+ Applications.load_application("test")
93
+ end
94
+
95
+ it "should load the correct class from disk" do
96
+ PluginManager.expects("loadclass").with("MCollective::Application::Test")
97
+ Applications.expects("load_config").returns(true).once
98
+
99
+ Applications.load_application("test")
100
+ end
101
+
102
+ it "should add the class to the plugin manager" do
103
+ Applications.expects("load_config").returns(true).once
104
+
105
+ PluginManager.expects("<<").with({:type => "test_application", :class => "MCollective::Application::Test"})
106
+
107
+ Applications.load_application("test")
108
+ end
109
+ end
110
+
111
+ describe "#list" do
112
+ it "should load the configuration" do
113
+ Applications.expects("load_config").returns(true).once
114
+ Config.any_instance.expects("libdir").returns([@tmpdir])
115
+ Applications.list
116
+ end
117
+
118
+ it "should add found applications to the list" do
119
+ Applications.expects("load_config").returns(true).once
120
+ Config.any_instance.expects("libdir").returns([@tmpdir])
121
+
122
+ Applications.list.should == ["test"]
123
+ end
124
+
125
+ it "should print a friendly error and exit on failure" do
126
+ Applications.expects("load_config").raises(Exception)
127
+ IO.any_instance.expects(:puts).with(regexp_matches(/Failed to generate application list/)).once
128
+
129
+ expect {
130
+ Applications.list.should
131
+ }.to raise_error(SystemExit)
132
+ end
133
+ end
134
+
135
+ describe "#filter_extra_options" do
136
+ it "should parse --config=x" do
137
+ ["--config=x --foo=bar -f -f bar", "--foo=bar --config=x -f -f bar"].each do |t|
138
+ Applications.filter_extra_options(t).should == "--config=x"
139
+ end
140
+ end
141
+
142
+ it "should parse --config x" do
143
+ ["--config x --foo=bar -f -f bar", "--foo=bar --config x -f -f bar"].each do |t|
144
+ Applications.filter_extra_options(t).should == "--config=x"
145
+ end
146
+ end
147
+
148
+ it "should parse -c x" do
149
+ ["-c x --foo=bar -f -f bar", "--foo=bar -c x -f -f bar"].each do |t|
150
+ Applications.filter_extra_options(t).should == "--config=x"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ class Array
6
+ describe "#in_groups_of" do
7
+ it "should correctly group array members" do
8
+ [1,2,3,4,5,6,7,8,9,10].in_groups_of(5).should == [[1,2,3,4,5], [6,7,8,9,10]]
9
+ end
10
+
11
+ it "should padd missing data with correctly" do
12
+ arr = [1,2,3,4,5,6,7,8,9,10]
13
+
14
+ arr.in_groups_of(3).should == [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, nil, nil]]
15
+ arr.in_groups_of(3, 0).should == [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 0, 0]]
16
+ arr.in_groups_of(11).should == [[1,2,3,4,5, 6,7,8,9,10, nil]]
17
+ arr.in_groups_of(11, 0).should == [[1,2,3,4,5, 6,7,8,9,10, 0]]
18
+ end
19
+
20
+ it "should indicate when the last abtched was reached" do
21
+ arr = [1,2,3,4,5,6,7,8,9,10]
22
+
23
+ ctr = 0
24
+
25
+ [1,2,3,4,5,6,7,8,9,10].in_groups_of(3) {|a, last_batch| ctr += 1 unless last_batch}
26
+
27
+ ctr.should == 3
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective
6
+ describe Config do
7
+ describe "#loadconfig" do
8
+ it "should not allow any path like construct for identities" do
9
+ # Taken from puppet test cases
10
+ ['../foo', '..\\foo', './../foo', '.\\..\\foo',
11
+ '/foo', '//foo', '\\foo', '\\\\goo',
12
+ "test\0/../bar", "test\0\\..\\bar",
13
+ "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar",
14
+ " / bar", " /../ bar", " \\..\\ bar",
15
+ "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar",
16
+ "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar",
17
+ "//?/c:/foo"
18
+ ].each do |input|
19
+ File.expects(:open).with("/nonexisting", "r").returns(StringIO.new("identity = #{input}"))
20
+ File.expects(:exists?).with("/nonexisting").returns(true)
21
+ File.expects(:exists?).with(File.join(File.dirname("/nonexisting"), "rpc-help.erb")).returns(true)
22
+
23
+ expect {
24
+ Config.instance.loadconfig("/nonexisting")
25
+ }.to raise_error('Identities can only match /\w\.\-/')
26
+ end
27
+ end
28
+
29
+ it "should allow valid identities" do
30
+ ["foo", "foo_bar", "foo-bar", "foo-bar-123", "foo.bar", "foo_bar_123"].each do |input|
31
+ File.expects(:open).with("/nonexisting", "r").returns(StringIO.new("identity = #{input}"))
32
+ File.expects(:exists?).with("/nonexisting").returns(true)
33
+ File.expects(:exists?).with(File.join(File.dirname("/nonexisting"), "rpc-help.erb")).returns(true)
34
+ PluginManager.stubs(:loadclass)
35
+ PluginManager.stubs("<<")
36
+
37
+ Config.instance.loadconfig("/nonexisting")
38
+ end
39
+ end
40
+
41
+ it "should not allow the syslog logger type on windows" do
42
+ Util.expects("windows?").returns(true).twice
43
+ File.expects(:open).with("/nonexisting", "r").returns(StringIO.new("logger_type = syslog"))
44
+ File.expects(:exists?).with("/nonexisting").returns(true)
45
+ File.expects(:exists?).with(File.join(File.dirname("/nonexisting"), "rpc-help.erb")).returns(true)
46
+ PluginManager.stubs(:loadclass)
47
+ PluginManager.stubs("<<")
48
+
49
+ expect { Config.instance.loadconfig("/nonexisting") }.to raise_error("The sylog logger is not usable on the Windows platform")
50
+ end
51
+
52
+ it "should default to finding the help template in the same dir as the config file" do
53
+ path = File.join(File.dirname("/nonexisting"), "rpc-help.erb")
54
+
55
+ File.expects(:open).with("/nonexisting", "r").returns(StringIO.new(""))
56
+ File.expects(:exists?).with("/nonexisting").returns(true)
57
+ PluginManager.stubs(:loadclass)
58
+ PluginManager.stubs("<<")
59
+
60
+ File.expects(:exists?).with(path).returns(true)
61
+
62
+ Config.instance.loadconfig("/nonexisting")
63
+ Config.instance.rpchelptemplate.should == path
64
+ end
65
+
66
+ it "should fall back to old behavior if the help template file does not exist in the config dir" do
67
+ path = File.join(File.dirname("/nonexisting"), "rpc-help.erb")
68
+
69
+ File.expects(:open).with("/nonexisting", "r").returns(StringIO.new(""))
70
+ File.expects(:exists?).with("/nonexisting").returns(true)
71
+ File.expects(:exists?).with(path).returns(false)
72
+ PluginManager.stubs(:loadclass)
73
+ PluginManager.stubs("<<")
74
+
75
+ Config.instance.loadconfig("/nonexisting")
76
+ Config.instance.rpchelptemplate.should == "/etc/mcollective/rpc-help.erb"
77
+ end
78
+ end
79
+
80
+ describe "#read_plugin_config_dir" do
81
+ before do
82
+ tmpfile = Tempfile.new("mc_config_spec")
83
+ path = tmpfile.path
84
+ tmpfile.close!
85
+
86
+ @tmpdir = FileUtils.mkdir_p(path)
87
+ @tmpdir = @tmpdir[0] if @tmpdir.is_a?(Array) # ruby 1.9.2
88
+
89
+ @plugindir = File.join([@tmpdir, "plugin.d"])
90
+ FileUtils.mkdir(@plugindir)
91
+
92
+ Config.instance.set_config_defaults("")
93
+ end
94
+
95
+ it "should not fail if the supplied directory is missing" do
96
+ Config.instance.read_plugin_config_dir("/nonexisting")
97
+ Config.instance.pluginconf.should == {}
98
+ end
99
+
100
+ it "should skip files that do not match the expected filename pattern" do
101
+ File.open(File.join([@tmpdir, "plugin.d", "foo.txt"]), "w") { }
102
+ IO.expects(:open).with(File.join([@tmpdir, "plugin.d", "foo.txt"])).never
103
+
104
+ Config.instance.read_plugin_config_dir(@plugindir)
105
+ end
106
+
107
+ it "should load the config files" do
108
+ configfile = File.join([@tmpdir, "plugin.d", "foo.cfg"])
109
+ File.open(configfile, "w") { }
110
+
111
+ File.expects(:open).with(configfile, "r").returns([]).once
112
+ Config.instance.read_plugin_config_dir(@plugindir)
113
+ end
114
+
115
+ it "should set config parameters correctly" do
116
+ configfile = File.join([@tmpdir, "plugin.d", "foo.cfg"])
117
+
118
+ File.open(configfile, "w") do |f|
119
+ f.puts "bar = baz"
120
+ end
121
+
122
+ Config.instance.set_config_defaults(@tmpdir)
123
+ Config.instance.read_plugin_config_dir(@plugindir)
124
+ Config.instance.pluginconf.should == {"foo.bar" => "baz"}
125
+ end
126
+
127
+ it "should override main config file" do
128
+ configfile = File.join([@tmpdir, "plugin.d", "foo.cfg"])
129
+ servercfg = File.join(@tmpdir, "server.cfg")
130
+
131
+ File.open(servercfg, "w") {|f| f.puts "plugin.foo.bar = foo"}
132
+
133
+ PluginManager.stubs(:loadclass)
134
+
135
+ Config.instance.loadconfig(servercfg)
136
+ Config.instance.pluginconf.should == {"foo.bar" => "foo"}
137
+
138
+ File.open(configfile, "w") do |f|
139
+ f.puts "bar = baz"
140
+ end
141
+
142
+ PluginManager.delete("global_stats")
143
+ Config.instance.loadconfig(servercfg)
144
+ Config.instance.pluginconf.should == {"foo.bar" => "baz"}
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env rspec
2
+
3
+ require 'spec_helper'
4
+
5
+ module MCollective::Facts
6
+ describe Base do
7
+ before do
8
+ class Testfacts<Base; end
9
+
10
+ MCollective::PluginManager.delete("facts_plugin")
11
+ MCollective::PluginManager << {:type => "facts_plugin", :class => "MCollective::Facts::Testfacts"}
12
+ end
13
+
14
+ describe "#inherited" do
15
+ it "should add classes to the plugin manager" do
16
+ MCollective::PluginManager.stubs("<<").with({:type => "facts_plugin", :class => "MCollective::Facts::Bar"})
17
+
18
+ class Bar<Base; end
19
+ end
20
+
21
+ it "should be available in the PluginManager" do
22
+ MCollective::PluginManager["facts_plugin"].class.should == MCollective::Facts::Testfacts
23
+ end
24
+ end
25
+
26
+ describe "#get_fact" do
27
+ it "should call the fact provider #load_facts_from_source" do
28
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar"}).once
29
+
30
+ f = Testfacts.new
31
+ f.get_fact("foo")
32
+ end
33
+
34
+ it "should honor the cache timeout" do
35
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar"}).once
36
+
37
+ f = Testfacts.new
38
+ f.get_fact("foo")
39
+ f.get_fact("foo")
40
+ end
41
+
42
+ it "should detect empty facts" do
43
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({})
44
+ MCollective::Log.expects("error").with("Failed to load facts: RuntimeError: Got empty facts").once
45
+
46
+ f = Testfacts.new
47
+ f.get_fact("foo")
48
+ end
49
+
50
+ it "should convert non string facts to strings" do
51
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({:foo => "bar"})
52
+
53
+ f = Testfacts.new
54
+ f.get_fact("foo").should == "bar"
55
+ end
56
+
57
+ it "should not create duplicate facts while converting to strings" do
58
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({:foo => "bar"})
59
+
60
+ f = Testfacts.new
61
+ f.get_fact(nil).include?(:foo).should == false
62
+ end
63
+
64
+ it "should update last_facts_load on success" do
65
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar"}).once
66
+
67
+ f = Testfacts.new
68
+ f.get_fact("foo")
69
+
70
+ f.instance_variable_get("@last_facts_load").should_not == 0
71
+ end
72
+
73
+ it "should restore last known good facts on failure" do
74
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({}).once
75
+ MCollective::Log.expects("error").with("Failed to load facts: RuntimeError: Got empty facts").once
76
+
77
+ f = Testfacts.new
78
+ f.instance_variable_set("@last_good_facts", {"foo" => "bar"})
79
+
80
+ f.get_fact("foo").should == "bar"
81
+ end
82
+
83
+ it "should return all facts for nil parameter" do
84
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar", "bar" => "baz"})
85
+
86
+ f = Testfacts.new
87
+ f.get_fact(nil).keys.size.should == 2
88
+ end
89
+
90
+ it "should return a specific fact when specified" do
91
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar", "bar" => "baz"})
92
+
93
+ f = Testfacts.new
94
+ f.get_fact("bar").should == "baz"
95
+ end
96
+ end
97
+
98
+ describe "#get_facts" do
99
+ it "should load and return all facts" do
100
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar", "bar" => "baz"})
101
+
102
+ f = Testfacts.new
103
+ f.get_facts.should == {"foo" => "bar", "bar" => "baz"}
104
+ end
105
+ end
106
+
107
+ describe "#has_fact?" do
108
+ it "should correctly report fact presense" do
109
+ Testfacts.any_instance.stubs("load_facts_from_source").returns({"foo" => "bar"})
110
+
111
+ f = Testfacts.new
112
+ f.has_fact?("foo").should == true
113
+ f.has_fact?("bar").should == false
114
+ end
115
+ end
116
+
117
+ end
118
+ end