daf 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe DAF::Configurable do
4
+ # Test class to verify Configurable functionality
5
+ class MockClass
6
+ include DAF::Configurable
7
+
8
+ attr_option :test, String, :required
9
+ attr_option :test2, Integer, :optional do |val|
10
+ val > 2
11
+ end
12
+
13
+ attr_output :test_out, String
14
+ attr_output :test2_out, Integer
15
+ end
16
+
17
+ before(:each) do
18
+ @under_test = MockClass.new
19
+ end
20
+
21
+ it 'has required option' do
22
+ expect { MockClass.required_options }.to_not raise_error
23
+ expect(MockClass.required_options).not_to be_empty
24
+ expect(MockClass.required_options.length).to eq(1)
25
+ end
26
+
27
+ it 'has options' do
28
+ expect { MockClass.options }.to_not raise_error
29
+ expect(MockClass.options).not_to be_empty
30
+ expect(MockClass.options.length).to eq(2)
31
+ end
32
+
33
+ it 'has outputs' do
34
+ expect { MockClass.outputs }.to_not raise_error
35
+ expect(MockClass.outputs).not_to be_empty
36
+ expect(MockClass.outputs.length).to eq(2)
37
+ end
38
+
39
+ it 'has readable outputs' do
40
+ expect { @under_test.test2_out }.to_not raise_error
41
+ expect { @under_test.test_out }.to_not raise_error
42
+ end
43
+
44
+ it 'exposes required type for inputs' do
45
+ expect(@under_test.test.type).to eq(String)
46
+ expect(@under_test.test2.type).to eq(Integer)
47
+ end
48
+
49
+ it 'has writable inputs' do
50
+ @under_test.test.value = 'Test'
51
+ @under_test.test2.value = 40
52
+ expect(@under_test.test.value).to eq('Test')
53
+ expect(@under_test.test2.value).to eq(40)
54
+ end
55
+
56
+ it 'validates inputs' do
57
+ @under_test.test.value = 40
58
+ @under_test.test2.value = 'bad value'
59
+ expect(@under_test.test.valid?).to eq(false)
60
+ expect(@under_test.test.valid?).to eq(false)
61
+ end
62
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+ include DAF
3
+
4
+ describe 'DAF' do
5
+ context 'when start_dad is called' do
6
+ let!(:data_source) do
7
+ dup = class_double('DAF::YAMLDataSource').as_stubbed_const
8
+ allow(dup).to receive(:new)
9
+ dup
10
+ end
11
+
12
+ let!(:command) do
13
+ dup = class_double('DAF::Command').as_stubbed_const
14
+ allow(dup).to receive(:new).and_return('com')
15
+ dup
16
+ end
17
+
18
+ let!(:dir) do
19
+ dup = class_double('Dir').as_stubbed_const(
20
+ transfer_nested_constants: true)
21
+ allow(dup).to receive(:[]).and_return(%w(test1 test2))
22
+ dup
23
+ end
24
+
25
+ let!(:dad) do
26
+ dup = class_double('DAF::DynamicActionDaemon').as_stubbed_const
27
+ allow(dup).to receive(:new).and_return(idad)
28
+ dup
29
+ end
30
+
31
+ let(:idad) do
32
+ dup = double('DAF::DynamicActionDaemon')
33
+ allow(dup).to receive(:start)
34
+ dup
35
+ end
36
+
37
+ it 'should print usage if argument is not directory' do
38
+ expect(self).to receive(:print_usage)
39
+ ARGV[0] = '/dev/null'
40
+ start_dad
41
+ end
42
+
43
+ it 'should get list of files using Dir' do
44
+ expect(dir).to receive(:[]).with('//*.yaml')
45
+ ARGV[0] = '/'
46
+ start_dad
47
+ end
48
+
49
+ it 'should generate a list of commands from each file' do
50
+ expect(command).to receive(:new).twice
51
+ expect(data_source).to receive(:new).with('test1')
52
+ expect(data_source).to receive(:new).with('test2')
53
+ ARGV[0] = '/'
54
+ start_dad
55
+ end
56
+
57
+ it 'should create a new daemon with commands' do
58
+ expect(dad).to receive(:new).with(%w(com com))
59
+ ARGV[0] = '/'
60
+ start_dad
61
+ end
62
+
63
+ it 'should start the daemon' do
64
+ expect(idad).to receive(:start)
65
+ ARGV[0] = '/'
66
+ start_dad
67
+ end
68
+ end
69
+
70
+ context 'when usage information is printed' do
71
+ it 'should write to stdout' do
72
+ expect($stdout).to receive(:write).at_least(1).times
73
+ print_usage
74
+ end
75
+ end
76
+ end
77
+
78
+ describe 'DAF::DynamicActionDaemon' do
79
+ context 'when started' do
80
+ it 'should execute each command' do
81
+ command1 = double('DAF::Command')
82
+ command2 = double('DAF::Command')
83
+ expect(command1).to receive(:execute)
84
+ expect(command2).to receive(:execute)
85
+ dad = DynamicActionDaemon.new([command1, command2])
86
+ thread = Thread.new do
87
+ dad.start
88
+ end
89
+ sleep(1)
90
+ thread.kill
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe DAF::EmailAction do
4
+
5
+ before(:each) do
6
+ @server = 'mail.example.com'
7
+ @options = { 'from' => 'test@example.com',
8
+ 'to' => 'test_to@example.com',
9
+ 'subject' => 'Test Subject',
10
+ 'body' => 'Test Body',
11
+ 'server' => @server }
12
+ @action = DAF::EmailAction.new
13
+ end
14
+
15
+ it 'has five required options' do
16
+ expect { @action.class.required_options }.not_to raise_error
17
+ expect(@action.class.required_options.length).to eq(5)
18
+ end
19
+
20
+ it 'has six options' do
21
+ expect { @action.class.options }.not_to raise_error
22
+ expect(@action.class.options.length).to eq(6)
23
+ end
24
+
25
+ it 'has a port option of type Integer' do
26
+ expect(@action.class.options['port']).to eq(Integer)
27
+ end
28
+
29
+ context 'when activate is called' do
30
+ before(:each) do
31
+ @smtp_obj = double(Net::SMTP.new('mail.example.com'))
32
+ @smtp = class_double('Net::SMTP')
33
+ .as_stubbed_const(transfer_nested_constants: true)
34
+ end
35
+
36
+ it 'sends with the server and port passed in' do
37
+ @options['port'] = 333
38
+ expect(@smtp).to receive(:start).with(@server, 333)
39
+ @action.activate(@options)
40
+ end
41
+
42
+ it 'should use a default port if none is specified' do
43
+ expect(@smtp).to receive(:start).with(@server, 25)
44
+ @action.activate(@options)
45
+ end
46
+
47
+ it 'should send a message' do
48
+ target_message = <<END
49
+ From: test@example.com
50
+ To: test_to@example.com
51
+ Subject: Test Subject
52
+
53
+ Test Body
54
+ END
55
+ allow(@smtp).to receive(:start).and_yield(@smtp_obj)
56
+ expect(@smtp_obj).to receive(:send_message).with(
57
+ target_message, 'test@example.com', 'test_to@example.com')
58
+ @action.activate(@options)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'DAF::FileUpdateMonitor' do
4
+ context 'when new monitor is created' do
5
+ it 'should validate that the path exists' do
6
+ options = { 'frequency' => 2, 'path' => '/tmp/fake' }
7
+ expect { FileUpdateMonitor.new(options) }.to raise_error
8
+ end
9
+
10
+ it 'should validate that the frequency is > 1' do
11
+ options = { 'frequency' => 0, 'path' => '/' }
12
+ expect { FileUpdateMonitor.new(options) }.to raise_error
13
+ end
14
+
15
+ it 'should have a required option named path' do
16
+ expect(FileUpdateMonitor.required_options).to include('path')
17
+ end
18
+
19
+ it 'should have a required option named frequency' do
20
+ expect(FileUpdateMonitor.required_options).to include('frequency')
21
+ end
22
+ end
23
+
24
+ context 'when block_until_triggered is called' do
25
+ let(:monitor) do
26
+ options = { 'frequency' => 2, 'path' => '/' }
27
+ FileUpdateMonitor.new(options)
28
+ end
29
+
30
+ let!(:file) do
31
+ dup = class_double('File').as_stubbed_const(
32
+ transfer_nested_constants: true)
33
+ @time = 0
34
+ allow(dup).to receive(:mtime) do
35
+ @time += 1
36
+ end
37
+ allow(dup).to receive(:exist?).and_return(true)
38
+ allow(dup).to receive(:open).and_return(ifile)
39
+ dup
40
+ end
41
+
42
+ let(:ifile) do
43
+ dup = double('File')
44
+ allow(dup).to receive(:read).and_return('contents')
45
+ allow(dup).to receive(:close)
46
+ dup
47
+ end
48
+
49
+ it 'should sleep the set frequency' do
50
+ expect(monitor).to receive(:sleep).with(2)
51
+ monitor.block_until_triggered
52
+ end
53
+
54
+ it 'should record current time' do
55
+ expect(file).to receive(:mtime).twice
56
+ monitor.block_until_triggered
57
+ end
58
+
59
+ it 'should skip loop unless file modify time changes' do
60
+ expect(monitor).to receive(:sleep).with(2).exactly(3).times
61
+ @mtime = 0
62
+ allow(file).to receive(:mtime) do
63
+ @mtime += 1
64
+ if @mtime < 4
65
+ 0
66
+ else
67
+ 1
68
+ end
69
+ end
70
+ monitor.block_until_triggered
71
+ end
72
+
73
+ context 'when file is modified' do
74
+ it 'should record the time as output' do
75
+ monitor.block_until_triggered
76
+ expect(monitor.time).to eq(2)
77
+ end
78
+
79
+ it 'should record the contents of the file as output' do
80
+ monitor.block_until_triggered
81
+ expect(monitor.contents).to eq('contents')
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ # Test monitor to verify functionality
4
+ class TestMonitor < DAF::Monitor
5
+ attr_option :option, String
6
+ attr_reader :output
7
+ def block_until_triggered
8
+ @output = 123
9
+ end
10
+ end
11
+
12
+ describe DAF::Monitor do
13
+ let(:test_monitor) { TestMonitor.new('option' => 'test') }
14
+
15
+ it 'should be configurable' do
16
+ mixed_in = DAF::Monitor.ancestors.select { |o| o.class == Module }
17
+ expect(mixed_in).to include(DAF::Configurable)
18
+ end
19
+
20
+ it 'should have an on_trigger method' do
21
+ expect(test_monitor).to respond_to(:on_trigger)
22
+ end
23
+
24
+ it 'should require a block to execute' do
25
+ expect { test_monitor.on_trigger }.to raise_error(LocalJumpError)
26
+ end
27
+
28
+ it 'should call block_until_triggered' do
29
+ test_monitor.on_trigger {}
30
+ expect(test_monitor.output).to eq(123)
31
+ end
32
+
33
+ it 'should yield to a given block when triggered' do
34
+ expect { |b| test_monitor.on_trigger(&b) }
35
+ .to yield_with_no_args
36
+ end
37
+
38
+ it 'should set option values' do
39
+ expect(test_monitor.option.value).to eq('test')
40
+ end
41
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe DAF::ShellAction do
4
+ before(:each) do
5
+ @options = { 'path' => '/bin/ls' }
6
+ @action = DAF::ShellAction.new
7
+ end
8
+
9
+ context 'options' do
10
+ it 'has a required path option of type String' do
11
+ expect { @action.class.required_options }.not_to raise_error
12
+ expect(@action.class.required_options.length).to eq(1)
13
+ end
14
+
15
+ it 'validates the path is executable and exists' do
16
+ @action.path.value = '/bin/ls'
17
+ expect(@action.path.valid?).to eq(true)
18
+ @action.path.value = '/tmp/nonsense'
19
+ expect(@action.path.valid?).to eq(false)
20
+ @action.path.value = '/tmp/test1'
21
+ expect(@action.path.valid?).to eq(false)
22
+ end
23
+
24
+ it 'has an optional arguments option of type String' do
25
+ expect { @action.class.options }.not_to raise_error
26
+ expect(@action.class.options.length).to eq(2)
27
+ end
28
+ end
29
+
30
+ it 'has an output results of type String' do
31
+ expect { @action.class.outputs }.not_to raise_error
32
+ expect(@action.class.outputs.length).to eq(1)
33
+ expect(@action.class.outputs['results']).to eq(String)
34
+ end
35
+
36
+ context 'when activate is called' do
37
+ it 'executes a shell script' do
38
+ expect(@action).to receive(:`).with('/bin/ls')
39
+ @action.activate(@options)
40
+ end
41
+
42
+ it 'returns the result of shell script' do
43
+ allow(@action).to receive(:`).and_return('result!')
44
+ @action.activate(@options)
45
+ expect(@action.results).to eq('result!')
46
+ end
47
+
48
+ it 'passes arguments to the shell script' do
49
+ expect(@action).to receive(:`).with('/bin/ls test')
50
+ @options['arguments'] = 'test'
51
+ @action.activate(@options)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter '/spec/'
4
+ add_group 'Actions', 'lib/daf/actions/'
5
+ add_group 'Monitors', 'lib/daf/monitors/'
6
+ add_group 'Data Sources', 'lib/daf/datasources/'
7
+ minimum_coverage 95
8
+ refuse_coverage_drop
9
+ end
10
+
11
+ require 'daf'
12
+ require 'daf/command'
13
+ require 'daf/configurable'
14
+ require 'daf/monitor'
15
+ require 'daf/action'
16
+ require 'daf/monitors/file_update_monitor'
17
+ require 'daf/actions/email_action'
18
+ require 'daf/actions/shell_action'
19
+ require 'daf/datasources/yaml_data_source'
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ class TestMonitor < DAF::Monitor
4
+ end
5
+
6
+ class TestAction < DAF::Action
7
+ end
8
+
9
+ describe DAF::YAMLDataSource do
10
+ let!(:yaml) do
11
+ yaml = class_double('YAML').as_stubbed_const(
12
+ transfer_nested_constants: true)
13
+ allow(yaml).to receive(:load_file).and_return(
14
+ 'Monitor' => { 'Options' => {}, 'Type' => 'TestMonitor' },
15
+ 'Action' => { 'Options' => {
16
+ 'test' => '{{test}}',
17
+ 'test2' => 'thing: {{test2}}'
18
+ }, 'Type' => 'TestAction' })
19
+ yaml
20
+ end
21
+ let(:data_source) { DAF::YAMLDataSource.new('/tmp/test') }
22
+
23
+ context 'properties' do
24
+ it 'responds to #monitor' do
25
+ expect(data_source).to respond_to(:monitor)
26
+ end
27
+
28
+ it 'responds to #action' do
29
+ expect(data_source).to respond_to(:action)
30
+ end
31
+ end
32
+
33
+ context 'when new is called' do
34
+ it 'should load the file at the given path' do
35
+ expect(yaml).to receive(:load_file).with('/tmp/2')
36
+ DAF::YAMLDataSource.new('/tmp/2')
37
+ end
38
+
39
+ it 'should initialize monitor class specified' do
40
+ expect(data_source.monitor.class).to eq(TestMonitor)
41
+ end
42
+
43
+ it 'should initialize action class specified' do
44
+ expect(data_source.action.class).to eq(TestAction)
45
+ end
46
+
47
+ it 'should throw an exception if class does not exist' do
48
+ allow(yaml).to receive(:load_file).and_return(
49
+ 'Monitor' => { 'Options' => [], 'Type' => 'BadTestMonitor' },
50
+ 'Action' => { 'Options' => [], 'Type' => 'BadTestAction' })
51
+ expect { DAF::YAMLDataSource.new('/tmp/new') }.to raise_error
52
+ end
53
+ end
54
+
55
+ context 'when asked for action_options' do
56
+ let(:itest_action) do
57
+ double('TestAction')
58
+ end
59
+ let!(:test_action) do
60
+ dup = class_double('TestAction').as_stubbed_const
61
+ allow(dup).to receive(:new).and_return(itest_action)
62
+ dup
63
+ end
64
+ let(:itest_monitor) do
65
+ double('TestMonitor')
66
+ end
67
+ let!(:test_monitor) do
68
+ dup = class_double('TestMonitor').as_stubbed_const
69
+ allow(dup).to receive(:new).and_return(itest_monitor)
70
+ dup
71
+ end
72
+
73
+ it 'should return raw options if no outputs defined' do
74
+ allow(test_monitor).to receive(:outputs).and_return({})
75
+ expect(data_source.action_options).to have_key('test')
76
+ expect(data_source.action_options['test']).to eq('{{test}}')
77
+ expect(data_source.action_options).to have_key('test2')
78
+ expect(data_source.action_options['test2']).to eq('thing: {{test2}}')
79
+ end
80
+
81
+ it 'should return raw options if no outputs match outputs' do
82
+ allow(test_monitor).to receive(:outputs).and_return(
83
+ 'another_test' => String
84
+ )
85
+ allow(itest_monitor).to receive(:another_test).and_return('test_output')
86
+ expect(data_source.action_options).to have_key('test')
87
+ expect(data_source.action_options['test']).to eq('{{test}}')
88
+ expect(data_source.action_options).to have_key('test2')
89
+ expect(data_source.action_options['test2']).to eq('thing: {{test2}}')
90
+ end
91
+
92
+ it 'should substitute outputs into options' do
93
+ allow(test_monitor).to receive(:outputs).and_return(
94
+ 'test' => String
95
+ )
96
+ allow(itest_monitor).to receive(:test).and_return('test output')
97
+ expect(data_source.action_options).to have_key('test')
98
+ expect(data_source.action_options['test']).to eq('test output')
99
+ expect(data_source.action_options).to have_key('test2')
100
+ expect(data_source.action_options['test2']).to eq('thing: {{test2}}')
101
+ end
102
+
103
+ it 'should substitute multiple outputs into multiple inputs' do
104
+ allow(test_monitor).to receive(:outputs).and_return(
105
+ 'test' => String,
106
+ 'test2' => String
107
+ )
108
+ allow(itest_monitor).to receive(:test).and_return('test output')
109
+ allow(itest_monitor).to receive(:test2).and_return('aout')
110
+ expect(data_source.action_options).to have_key('test')
111
+ expect(data_source.action_options['test']).to eq('test output')
112
+ expect(data_source.action_options).to have_key('test2')
113
+ expect(data_source.action_options['test2']).to eq('thing: aout')
114
+ end
115
+
116
+ end
117
+ end