libis-workflow-activerecord 0.9.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +57 -0
  3. data/.travis.yml +50 -0
  4. data/Gemfile +3 -0
  5. data/README.md +13 -0
  6. data/Rakefile +85 -0
  7. data/db/config.yml +71 -0
  8. data/db/migrate/001_create_work_items_table.rb +18 -0
  9. data/db/migrate/002_create_jobs_table.rb +27 -0
  10. data/db/schema.rb +72 -0
  11. data/db/setup_db.rb +14 -0
  12. data/db/travis.config.yml +4 -0
  13. data/lib/libis-workflow-activerecord.rb +1 -0
  14. data/lib/libis/workflow/activerecord.rb +25 -0
  15. data/lib/libis/workflow/activerecord/base.rb +79 -0
  16. data/lib/libis/workflow/activerecord/config.rb +30 -0
  17. data/lib/libis/workflow/activerecord/helpers/hash_serializer.rb +32 -0
  18. data/lib/libis/workflow/activerecord/helpers/property_helper.rb +40 -0
  19. data/lib/libis/workflow/activerecord/helpers/status_serializer.rb +30 -0
  20. data/lib/libis/workflow/activerecord/job.rb +73 -0
  21. data/lib/libis/workflow/activerecord/run.rb +99 -0
  22. data/lib/libis/workflow/activerecord/version.rb +7 -0
  23. data/lib/libis/workflow/activerecord/work_item.rb +90 -0
  24. data/lib/libis/workflow/activerecord/worker.rb +20 -0
  25. data/lib/libis/workflow/activerecord/workflow.rb +40 -0
  26. data/libis-workflow-activerecord.gemspec +42 -0
  27. data/spec/db_env.sh +5 -0
  28. data/spec/db_start.sh +5 -0
  29. data/spec/items.rb +3 -0
  30. data/spec/items/test_dir_item.rb +18 -0
  31. data/spec/items/test_file_item.rb +29 -0
  32. data/spec/items/test_item.rb +6 -0
  33. data/spec/items/test_run.rb +8 -0
  34. data/spec/job_spec.rb +10 -0
  35. data/spec/run_spec.rb +4 -0
  36. data/spec/spec_helper.rb +37 -0
  37. data/spec/tasks/camelize_name.rb +13 -0
  38. data/spec/tasks/checksum_tester.rb +34 -0
  39. data/spec/tasks/collect_files.rb +52 -0
  40. data/spec/test_job.rb +6 -0
  41. data/spec/test_workflow.rb +6 -0
  42. data/spec/test_workitem.rb +7 -0
  43. data/spec/workflow_spec.rb +208 -0
  44. data/spec/workitem_spec.rb +366 -0
  45. metadata +245 -0
@@ -0,0 +1,20 @@
1
+ require 'libis/workflow/worker'
2
+ require 'sidekiq'
3
+
4
+ module Libis
5
+ module Workflow
6
+ module ActiveRecord
7
+
8
+ class Worker < Libis::Workflow::Worker
9
+
10
+ def get_job(job_config)
11
+ job_name = job_config.delete(:name)
12
+ job = ::Libis::Workflow::ActiveRecord::Job.find(name: job_name).first
13
+ raise RuntimeError.new "Workflow #{job_name} not found" unless job.is_a? ::Libis::Workflow::ActiveRecord::Job
14
+ job
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'libis/workflow/activerecord'
2
+ require 'libis/workflow/base/workflow'
3
+ require 'libis/tools/config_file'
4
+ require 'libis/tools/extend/hash'
5
+
6
+ module Libis
7
+ module Workflow
8
+ module ActiveRecord
9
+
10
+ class Workflow < ::ActiveRecord::Base
11
+ include ::Libis::Workflow::Base::Workflow
12
+ include ::Libis::Workflow::ActiveRecord::Base
13
+
14
+ # noinspection RailsParamDefResolve
15
+ has_many :jobs,
16
+ -> {order('id')},
17
+ class_name: Libis::Workflow::ActiveRecord::Job.to_s,
18
+ foreign_key: :workflow_id,
19
+ autosave: true
20
+
21
+ def self.from_hash(hash)
22
+ self.create_from_hash(hash, [:name]) do |item, cfg|
23
+ item.configure(cfg.merge('name' => item.name))
24
+ cfg.clear
25
+ end
26
+ end
27
+
28
+ def self.load(file_or_hash)
29
+ config = Libis::Tools::ConfigFile.new
30
+ config << file_or_hash
31
+ return nil if config.empty?
32
+ workflow = self.new
33
+ workflow.configure(config.to_hash.key_symbols_to_strings(recursive: true))
34
+ workflow
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'libis/workflow/activerecord/version'
7
+ require 'date'
8
+
9
+ Gem::Specification.new do |spec|
10
+ spec.name = 'libis-workflow-activerecord'
11
+ spec.version = ::Libis::Workflow::ActiveRecord::VERSION
12
+ spec.date = Date.today.to_s
13
+
14
+ spec.summary = %q{ActiveRecord persistence for the LIBIS Workflow framework.}
15
+ spec.description = %q{Class implementations that use ActiveRecord storage for the LIBIS Workflow framework.}
16
+
17
+ spec.author = 'Kris Dekeyser'
18
+ spec.email = 'kris.dekeyser@libis.be'
19
+ spec.homepage = 'https://github.com/libis/workflow-activerecord'
20
+ spec.license = 'MIT'
21
+
22
+ spec.platform = Gem::Platform::JAVA if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
23
+
24
+ spec.files = `git ls-files -z`.split("\0")
25
+ spec.executables = spec.files.grep(%r{^bin/}).map { |f| File.basename(f) }
26
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
27
+
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_runtime_dependency 'libis-workflow', '~> 2.0'
31
+ spec.add_runtime_dependency 'activerecord', '~> 5'
32
+
33
+ spec.add_development_dependency 'bundler', '~> 1.6'
34
+ spec.add_development_dependency 'rake'
35
+ spec.add_development_dependency 'rspec'
36
+ spec.add_development_dependency 'awesome_print'
37
+ spec.add_development_dependency 'database_cleaner'
38
+ spec.add_development_dependency 'coveralls'
39
+ spec.add_development_dependency 'sqlite3'
40
+ spec.add_runtime_dependency 'pg'
41
+
42
+ end
data/spec/db_env.sh ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ export DB_HOST="kahana.mongohq.com:10053"
3
+ export DB_HOST="localhost:27017"
4
+ export DB_USER="test"
5
+ export DB_PASS="abc123"
data/spec/db_start.sh ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ cd `dirname $0`
3
+ mkdir -p ./data/db
4
+ /opt/mongodb/bin/mongod --dbpath ./data/db
5
+
data/spec/items.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'items/test_dir_item'
2
+ require_relative 'items/test_file_item'
3
+ require_relative 'items/test_run'
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'libis/workflow'
3
+ require_relative 'test_item'
4
+
5
+ class TestDirItem < TestItem
6
+
7
+ include ::Libis::Workflow::Base::DirItem
8
+
9
+ def name=(dir)
10
+ raise RuntimeError, "'#{dir}' is not a directory" unless File.directory? dir
11
+ super dir
12
+ end
13
+
14
+ def name
15
+ self.properties['name'] || super
16
+ end
17
+
18
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ require 'libis-tools'
3
+ require 'libis-workflow'
4
+
5
+ require_relative 'test_item'
6
+
7
+ class TestFileItem < TestItem
8
+
9
+ include ::Libis::Workflow::Base::FileItem
10
+
11
+ def filename=(file)
12
+ raise RuntimeError, "'#{file}' is not a file" unless File.file? file
13
+ set_checksum :SHA256, ::Libis::Tools::Checksum.hexdigest(file, :SHA256)
14
+ super file
15
+ end
16
+
17
+ def name
18
+ self.properties['name'] || super
19
+ end
20
+
21
+ def filesize
22
+ properties['size']
23
+ end
24
+
25
+ def fixity_check(checksum)
26
+ properties['checksum'] == checksum
27
+ end
28
+
29
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/workflow/activerecord/work_item'
4
+
5
+ class TestItem < Libis::Workflow::ActiveRecord::WorkItem
6
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ require 'libis-workflow-activerecord'
3
+
4
+ class TestRun <::Libis::Workflow::ActiveRecord::Run
5
+
6
+ def name; 'TestRun'; end
7
+
8
+ end
data/spec/job_spec.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+ require_relative 'spec_helper'
3
+
4
+ require 'libis-workflow-activerecord'
5
+ require_relative 'test_job'
6
+
7
+ describe 'TestJob' do
8
+
9
+ context
10
+ end
data/spec/run_spec.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'rspec'
2
+ require_relative 'spec_helper'
3
+
4
+ require 'libis-workflow-activerecord'
@@ -0,0 +1,37 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'bundler/setup'
5
+ Bundler.setup
6
+
7
+ require 'rspec'
8
+ require 'libis-workflow-activerecord'
9
+
10
+ require 'database_cleaner'
11
+ require 'stringio'
12
+
13
+ RSpec.configure do |cfg|
14
+
15
+ cfg.before :suite do
16
+ # noinspection RubyResolve
17
+ ::Libis::Workflow::ActiveRecord.configure do |cfg|
18
+ cfg.logger.appenders =
19
+ ::Logging::Appenders.string_io('StringIO', layout: ::Libis::Tools::Config.get_log_formatter)
20
+ cfg.itemdir = File.join(File.dirname(__FILE__), 'items')
21
+ cfg.taskdir = File.join(File.dirname(__FILE__), 'tasks')
22
+ cfg.workdir = File.join(File.dirname(__FILE__), 'work')
23
+ cfg.database_connect 'db/config.yml', :test
24
+ end
25
+ DatabaseCleaner.clean_with :truncation
26
+ end
27
+
28
+ cfg.before :each do
29
+ # DatabaseCleaner.strategy = :transaction
30
+ # DatabaseCleaner.start
31
+ end
32
+
33
+ cfg.after :each do
34
+ # DatabaseCleaner.clean
35
+ end
36
+
37
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ require 'backports/rails/string'
3
+ require 'libis-workflow'
4
+
5
+ class CamelizeName < ::Libis::Workflow::Task
6
+
7
+ def process(item)
8
+ return unless (item.is_a?(TestFileItem) || item.is_a?(TestDirItem))
9
+ item.properties['name'] = item.name.camelize
10
+ item.save!
11
+ end
12
+
13
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ require 'libis/tools/checksum'
3
+
4
+ require 'libis/exceptions'
5
+ require 'libis-workflow'
6
+
7
+ class ChecksumTester < ::Libis::Workflow::Task
8
+
9
+ parameter checksum_type: nil,
10
+ description: 'Checksum type to use.',
11
+ constraint: ::Libis::Tools::Checksum::CHECKSUM_TYPES.map {|x| x.to_s}
12
+ parameter checksum_file: nil, description: 'File with checksums of the files.'
13
+
14
+ def process(item)
15
+ return unless item.is_a? TestFileItem
16
+
17
+ checksum_type = parameter(:checksum_type)
18
+
19
+ if checksum_type.nil?
20
+ ::Libis::Tools::Checksum::CHECKSUM_TYPES.each do |x|
21
+ test_checksum(item, x) if item.checksum(x)
22
+ end
23
+ else
24
+ test_checksum(item, checksum_type)
25
+ end
26
+ end
27
+
28
+ def test_checksum(item, checksum_type)
29
+ checksum = ::Libis::Tools::Checksum.hexdigest(item.fullpath, checksum_type.to_sym)
30
+ return if item.checksum(checksum_type) == checksum
31
+ raise ::Libis::WorkflowError, "Checksum test #{checksum_type} failed for #{item.filepath}"
32
+ end
33
+
34
+ end
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+ require 'libis/exceptions'
3
+
4
+ require_relative '../items'
5
+
6
+ class CollectFiles < ::Libis::Workflow::Task
7
+
8
+ parameter location: '.',
9
+ description: 'Dir location to start scanning for files.'
10
+ parameter subdirs: false,
11
+ description: 'Look for files in subdirs too.'
12
+ parameter selection: nil,
13
+ description: 'Only select files that match the given regular expression. Ignored if empty.'
14
+
15
+ def process(item)
16
+ if item.is_a? TestRun
17
+ add_item(item, parameter(:location))
18
+ elsif item.is_a? TestDirItem
19
+ collect_files(item, item.fullpath)
20
+ end
21
+ end
22
+
23
+ def collect_files(item, dir)
24
+ glob_string = dir
25
+ glob_string = File.join(glob_string, '**') if parameter(:subdirs)
26
+ glob_string = File.join(glob_string, '*')
27
+
28
+ Dir.glob(glob_string).select do |x|
29
+ parameter(:selection) && !parameter(:selection).empty? ? x =~ Regexp.new(parameter(:selection)) : true
30
+ end.sort.each do |file|
31
+ next if %w'. ..'.include? file
32
+ add_item(item, file)
33
+ end
34
+ end
35
+
36
+ def add_item(item, file)
37
+ child = if File.file?(file)
38
+ TestFileItem.new
39
+ elsif File.directory?(file)
40
+ TestDirItem.new
41
+ else
42
+ error 'Bad file type encountered: %s', file
43
+ nil
44
+ end
45
+ unless child
46
+ return
47
+ end
48
+ child.filename = file
49
+ item << child
50
+ end
51
+
52
+ end
data/spec/test_job.rb ADDED
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/workflow/activerecord/job'
4
+
5
+ class TestJob < ::Libis::Workflow::ActiveRecord::Job
6
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/workflow/activerecord/workflow'
4
+
5
+ class TestWorkflow < ::Libis::Workflow::ActiveRecord::Workflow
6
+ end
@@ -0,0 +1,7 @@
1
+ require 'libis/workflow/activerecord/work_item'
2
+
3
+ class TestWorkItem < ::Libis::Workflow::ActiveRecord::WorkItem
4
+ property_field :abc,
5
+ reader: lambda {|value| value.blank? ? :xyz : value.to_sym},
6
+ writer: lambda {|value| value.blank? ? nil : value.to_s}
7
+ end
@@ -0,0 +1,208 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rspec'
4
+ require 'stringio'
5
+
6
+ require 'libis-workflow-activerecord'
7
+
8
+ require_relative 'spec_helper'
9
+ require_relative 'test_job'
10
+ require_relative 'test_workflow'
11
+ require_relative 'items'
12
+
13
+ require 'awesome_print'
14
+
15
+ describe 'TestWorkflow' do
16
+
17
+ before :each do
18
+ ::Libis::Workflow::ActiveRecord::Config.logger.appenders.first.clear
19
+ end
20
+
21
+ dirname = File.absolute_path(File.join(File.dirname(__FILE__), 'items'))
22
+
23
+ let(:logoutput) { ::Libis::Workflow::ActiveRecord::Config.logger.appenders.first.sio }
24
+
25
+ let(:workflow) {
26
+ wf = TestWorkflow.find_or_initialize_by(name: 'TestWorkflow')
27
+ wf.configure(
28
+ 'name' => 'TestWorkflow',
29
+ 'description' => 'Workflow for testing',
30
+ 'tasks' => [
31
+ {'class' => 'CollectFiles', 'recursive' => true},
32
+ {
33
+ 'name' => 'ProcessFiles',
34
+ 'subitems' => true,
35
+ 'tasks' => [
36
+ {'class' => 'ChecksumTester', 'recursive' => true},
37
+ {'class' => 'CamelizeName', 'recursive' => true}
38
+ ]
39
+ }
40
+ ],
41
+ 'input' => {
42
+ 'dirname' => {'default' => '.', 'propagate_to' => 'CollectFiles#location'},
43
+ 'checksum_type' => {'default' => 'SHA1', 'propagate_to' => 'ProcessFiles/ChecksumTester'}
44
+ }
45
+ )
46
+ wf.save!
47
+ wf
48
+ }
49
+ let(:job) {
50
+ job = TestJob.from_hash(
51
+ 'name' => 'TestJob',
52
+ 'description' => 'Job for testing',
53
+ 'workflow' => 'TestWorkflow',
54
+ 'run_object' => 'TestRun',
55
+ 'input' => {'dirname' => dirname, 'checksum_type' => 'SHA256'},
56
+ 'log_to_file' => false,
57
+ 'log_each_run' => false
58
+ )
59
+
60
+ # job = TestJob.find_or_initialize_by(name: 'TestJob')
61
+ # job.configure(
62
+ # 'name' => 'TestJob',
63
+ # 'description' => 'Job for testing',
64
+ # 'workflow' => workflow,
65
+ # 'run_object' => 'TestRun',
66
+ # 'input' => {'dirname' => dirname, 'checksum_type' => 'SHA256'},
67
+ # 'log_to_file' => false
68
+ # )
69
+
70
+ # noinspection RubyResolve
71
+ job.runs.each { |run| run.destroy! }
72
+ job.save!
73
+ job
74
+ }
75
+
76
+ let(:run) { job.execute }
77
+
78
+ it 'should contain three tasks' do
79
+
80
+ expect(workflow.config['tasks'].size).to eq 2
81
+ expect(workflow.config['tasks'].first['class']).to eq 'CollectFiles'
82
+ expect(workflow.config['tasks'].last['name']).to eq 'ProcessFiles'
83
+
84
+ end
85
+
86
+ it 'find workflow' do
87
+ workflow
88
+ wf = TestWorkflow.first
89
+ expect(wf.nil?).to eq false
90
+ expect(wf.name).to eq 'TestWorkflow'
91
+ expect(wf.description).to eq 'Workflow for testing'
92
+ expect(wf.input.count).to eq 2
93
+ expect(wf.input[:dirname][:default]).to eq '.'
94
+ expect(wf.config['tasks'].count).to eq 2
95
+ expect(wf.config['tasks'][0]['class']).to eq 'CollectFiles'
96
+ expect(wf.config['tasks'][0]['recursive']).to eq true
97
+ expect(wf.config['tasks'][1]['name']).to eq 'ProcessFiles'
98
+ expect(wf.config['tasks'][1]['subitems']).to eq true
99
+ expect(wf.config['tasks'][1]['tasks'].count).to eq 2
100
+ expect(wf.config['tasks'][1]['tasks'][0]['class']).to eq 'ChecksumTester'
101
+ expect(wf.config['tasks'][1]['tasks'][0]['recursive']).to eq true
102
+ expect(wf.config['tasks'][1]['tasks'][1]['class']).to eq 'CamelizeName'
103
+ expect(wf.config['tasks'][1]['tasks'][1]['recursive']).to eq true
104
+ end
105
+
106
+ it 'should camelize the workitem name' do
107
+
108
+ expect(run.options['CollectFiles']['location']).to eq dirname
109
+
110
+ expect(run.items.size).to eq 1
111
+ expect(run.items.count).to eq 1
112
+ expect(run.size).to eq 1
113
+ expect(run.count).to eq 1
114
+ expect(run.items.first.class).to eq TestDirItem
115
+ expect(run.first.class).to eq TestDirItem
116
+ expect(run.first.items.size).to eq 4
117
+ expect(run.first.items.count).to eq 4
118
+ expect(run.first.size).to eq 4
119
+ expect(run.first.count).to eq 4
120
+ expect(run.first.items.first.class).to eq TestFileItem
121
+ expect(run.first.first.class).to eq TestFileItem
122
+
123
+ run.items.first.each_with_index do |x, i|
124
+ expect(x.name).to eq %w'TestDirItem.rb TestFileItem.rb TestItem.rb TestRun.rb'[i]
125
+ end
126
+ end
127
+
128
+ it 'should return expected debug output' do
129
+
130
+ run
131
+
132
+ sample_out = <<STR
133
+ Run - TestRun : Ingest run started.
134
+ Run - TestRun : Running subtask (1/2): CollectFiles
135
+ CollectFiles - TestRun : Processing subitem (1/1): items
136
+ CollectFiles - items : Processing subitem (1/4): test_dir_item.rb
137
+ CollectFiles - items : Processing subitem (2/4): test_file_item.rb
138
+ CollectFiles - items : Processing subitem (3/4): test_item.rb
139
+ CollectFiles - items : Processing subitem (4/4): test_run.rb
140
+ CollectFiles - items : 4 of 4 subitems passed
141
+ CollectFiles - TestRun : 1 of 1 subitems passed
142
+ Run - TestRun : Running subtask (2/2): ProcessFiles
143
+ ProcessFiles - TestRun : Running subtask (1/2): ChecksumTester
144
+ ProcessFiles/ChecksumTester - TestRun : Processing subitem (1/1): items
145
+ ProcessFiles/ChecksumTester - items : Processing subitem (1/4): test_dir_item.rb
146
+ ProcessFiles/ChecksumTester - items : Processing subitem (2/4): test_file_item.rb
147
+ ProcessFiles/ChecksumTester - items : Processing subitem (3/4): test_item.rb
148
+ ProcessFiles/ChecksumTester - items : Processing subitem (4/4): test_run.rb
149
+ ProcessFiles/ChecksumTester - items : 4 of 4 subitems passed
150
+ ProcessFiles/ChecksumTester - TestRun : 1 of 1 subitems passed
151
+ ProcessFiles - TestRun : Running subtask (2/2): CamelizeName
152
+ ProcessFiles/CamelizeName - TestRun : Processing subitem (1/1): items
153
+ ProcessFiles/CamelizeName - Items : Processing subitem (1/4): test_dir_item.rb
154
+ ProcessFiles/CamelizeName - Items : Processing subitem (2/4): test_file_item.rb
155
+ ProcessFiles/CamelizeName - Items : Processing subitem (3/4): test_item.rb
156
+ ProcessFiles/CamelizeName - Items : Processing subitem (4/4): test_run.rb
157
+ ProcessFiles/CamelizeName - Items : 4 of 4 subitems passed
158
+ ProcessFiles/CamelizeName - TestRun : 1 of 1 subitems passed
159
+ ProcessFiles - TestRun : Done
160
+ Run - TestRun : Done
161
+ STR
162
+
163
+ sample_out = sample_out.lines.to_a
164
+ output = logoutput.string.lines
165
+
166
+ # puts output
167
+
168
+ expect(run.status_log.count).to eq 5
169
+ item = run.items.first
170
+ expect(item.status_log.count).to eq 3
171
+
172
+ expect(output.count).to eq sample_out.count
173
+ output.each_with_index do |o, i|
174
+ expect(o.strip).to match(/#{Regexp.escape sample_out[i].strip}$/)
175
+ end
176
+
177
+ end
178
+
179
+ # noinspection RubyResolve
180
+ it 'find run' do
181
+ run
182
+ my_job = TestJob.first
183
+ expect(my_job).to eq job
184
+ expect(my_job.runs.all.count).to eq 1
185
+ my_run = my_job.runs.all.first
186
+ expect(my_run).to eq run
187
+ expect(my_run.status).to eq :DONE
188
+ end
189
+
190
+ # noinspection RubyResolve
191
+ it 'find first item' do
192
+ item = run.items.first
193
+ expect(item.nil?).to eq false
194
+ expect(item.is_a? TestDirItem).to eq true
195
+ expect(item.properties['name']).to eq 'Items'
196
+ end
197
+
198
+ it 'move item in relation' do
199
+ item = run.items.first
200
+ sub_item = item.items.first
201
+ expect(run.size).to eq 1
202
+ expect(item.size).to eq 4
203
+ run.move_item(sub_item)
204
+ expect(run.size).to eq 2
205
+ expect(item.size).to eq 3
206
+ end
207
+
208
+ end