daf 0.3.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.
@@ -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