dor-workflow-service 1.3.3

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d6fc3e584fe89a00011d92d20c3808bdbb7a9fe
4
+ data.tar.gz: 1f9c905de65222b5b1bcf85faa9a0caeb4b03054
5
+ SHA512:
6
+ metadata.gz: a93219741feb1dfed55872d75a97c6822debb2781b3b7aedf90daeb03bd28155baca5ec823d223fde63f5ce76aa0684505df5c35f90d750b815a0380b682897e
7
+ data.tar.gz: 2165204dc1a3b3c14dc9fa8c555e7959a6bcbc134d752bdae29023828fdd97c8fce9ce384b1facdc4c0b4716cd6824271151d2a4e2d774f286c415371a13143f
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .ruby-version
20
+ .ruby-gemset
21
+
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ notifications:
2
+ email: false
3
+
4
+ rvm:
5
+ - 1.8.7
6
+ - 1.9.3
7
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dor-workflow-service.gemspec
4
+ gemspec
5
+
6
+ if RUBY_VERSION < '1.9'
7
+ gem 'activesupport', "< 4.0"
8
+ end
9
+
10
+ group :development do
11
+ gem "debugger", :platform => :ruby_19
12
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Willy Mene
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # dor-workflow-service gem
2
+
3
+ Provides Ruby convenience methods to work with the DOR Workflow REST Service. The REST API is defined here:
4
+ https://consul.stanford.edu/display/DOR/DOR+services#DORservices-initializeworkflow
5
+
6
+ ## Usage
7
+
8
+ To initialize usage of the service, you need to call Dor::WorkflowService.configure, like in a bootup or startup method, e.g.:
9
+
10
+ Dor::WorkflowService.configure('https://test-server.edu/workflow/')
11
+
12
+ If you plan to archive workflows, then you need to set the URL to the Dor REST service:
13
+
14
+ Dor::WorkflowService.configure('https://test-server.edu/workflow/', :dor_services_url => 'https://sul-lyberservices-dev.stanford.edu/dor')
15
+
16
+ There's no need to call Dor::WorkflowService.configure if using the dor-services gem and using the Dor::Config object. The latest versions of dor-services will configure the workflow service for you.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/setup"
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ task :default => [:spec]
7
+
8
+ RSpec::Core::RakeTask.new(:spec) do |spec|
9
+ spec.pattern = 'spec/**/*_spec.rb', 'test/**/*.rb'
10
+ end
11
+
12
+ require 'yard'
13
+ YARD::Rake::YardocTask.new
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'irb'
5
+
6
+ project_root = File.expand_path(File.dirname(__FILE__) + '/..')
7
+
8
+ # Load config for current environment.
9
+ $LOAD_PATH.unshift(project_root + '/lib')
10
+
11
+ require 'dor-workflow-service'
12
+
13
+ IRB.start
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dor/workflow_version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "dor-workflow-service"
8
+ gem.version = Dor::Workflow::Service::VERSION
9
+ gem.authors = ["Willy Mene"]
10
+ gem.email = ["wmene@stanford.edu"]
11
+ gem.description = "Enables Ruby manipulation of the DOR Workflow Service via its REST API"
12
+ gem.summary = "Provides convenience methods to work with the DOR Workflow Service"
13
+ gem.homepage = "https://consul.stanford.edu/display/DOR/DOR+services#DORservices-initializeworkflow"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency "activesupport"
21
+ gem.add_dependency "nokogiri"
22
+ gem.add_dependency "rest-client"
23
+ gem.add_dependency "confstruct"
24
+
25
+ gem.add_development_dependency "rake"
26
+ gem.add_development_dependency "rspec"
27
+ gem.add_development_dependency "yard"
28
+ gem.add_development_dependency "redcarpet"
29
+ gem.add_development_dependency "equivalent-xml"
30
+ end
@@ -0,0 +1,2 @@
1
+ require "dor/workflow_version"
2
+ require 'dor/services/workflow_service'
@@ -0,0 +1,281 @@
1
+ require 'rest-client'
2
+ require 'active_support/core_ext'
3
+ require 'nokogiri'
4
+
5
+ module Dor
6
+
7
+ # Methods to create and update workflow
8
+ module WorkflowService
9
+ class << self
10
+
11
+ @@resource = nil
12
+ @@dor_services_url = nil
13
+
14
+ # Creates a workflow for a given object in the repository. If this particular workflow for this objects exists,
15
+ # it will replace the old workflow with wf_xml passed to this method. You have the option of creating a datastream or not.
16
+ # Returns true on success. Caller must handle any exceptions
17
+ #
18
+ # == Parameters
19
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
20
+ # @param [String] druid The id of the object
21
+ # @param [String] workflow_name The name of the workflow you want to create
22
+ # @param [String] wf_xml The xml that represents the workflow
23
+ # @param [Hash] opts optional params
24
+ # @option opts [Boolean] :create_ds if true, a workflow datastream will be created in Fedora. Set to false if you do not want a datastream to be created
25
+ # If you do not pass in an <b>opts</b> Hash, then :create_ds is set to true by default
26
+ # @option opts [Integer] :priority adds priority to all process elements in the wf_xml workflow xml
27
+ #
28
+ def create_workflow(repo, druid, workflow_name, wf_xml, opts = {:create_ds => true})
29
+ xml = wf_xml
30
+ xml = add_priority_to_workflow_xml(opts[:priority], wf_xml) if(opts[:priority])
31
+ workflow_resource["#{repo}/objects/#{druid}/workflows/#{workflow_name}"].put(xml, :content_type => 'application/xml',
32
+ :params => {'create-ds' => opts[:create_ds] })
33
+ return true
34
+ end
35
+
36
+ # Adds priority attributes to each process of workflow xml
37
+ #
38
+ # @param [Integer] priority value to add to each process element
39
+ # @param [String] wf_xml the workflow xml
40
+ # @return [String] wf_xml with priority attributes
41
+ def add_priority_to_workflow_xml(priority, wf_xml)
42
+ return wf_xml if(priority.to_i == 0)
43
+ doc = Nokogiri::XML(wf_xml)
44
+ doc.xpath('/workflow/process').each { |proc| proc['priority'] = priority }
45
+ doc.to_xml
46
+ end
47
+
48
+ # Updates the status of one step in a workflow.
49
+ # Returns true on success. Caller must handle any exceptions
50
+ #
51
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
52
+ # @param [String] druid The id of the object
53
+ # @param [String] workflow The name of the workflow
54
+ # @param [String] status The status that you want to set. Typical statuses are 'waiting', 'completed', 'error', but could be any string
55
+ # @param [Hash] opts optional values for the workflow step
56
+ # @option opts [Float] :elapsed The number of seconds it took to complete this step. Can have a decimal. Is set to 0 if not passed in.
57
+ # @option opts [String] :lifecycle Bookeeping label for this particular workflow step. Examples are: 'registered', 'shelved'
58
+ # @option opts [String] :note Any kind of string annotation that you want to attach to the workflow
59
+ # @option opts [Integer] :priority Processing priority, 0-100, 100 being the highest priority. Workflow queues are returned in order of highest to lowest priority. Stored in the system as 0 by default
60
+ # == Http Call
61
+ # The method does an HTTP PUT to the URL defined in Dor::WF_URI. As an example:
62
+ # PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert"
63
+ # <process name=\"convert\" status=\"completed\" />"
64
+ def update_workflow_status(repo, druid, workflow, process, status, opts = {})
65
+ opts = {:elapsed => 0, :lifecycle => nil, :note => nil}.merge!(opts)
66
+ opts[:elapsed] = opts[:elapsed].to_s
67
+ xml = create_process_xml({:name => process, :status => status}.merge!(opts))
68
+ workflow_resource["#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}"].put(xml, :content_type => 'application/xml')
69
+ return true
70
+ end
71
+
72
+ #
73
+ # Retrieves the process status of the given workflow for the given object identifier
74
+ #
75
+ def get_workflow_status(repo, druid, workflow, process)
76
+ workflow_md = workflow_resource["#{repo}/objects/#{druid}/workflows/#{workflow}"].get
77
+ doc = Nokogiri::XML(workflow_md)
78
+ raise Exception.new("Unable to parse response:\n#{workflow_md}") if(doc.root.nil?)
79
+
80
+ status = doc.root.at_xpath("//process[@name='#{process}']/@status")
81
+ if status
82
+ status=status.content
83
+ end
84
+ return status
85
+ end
86
+
87
+ def get_workflow_xml(repo, druid, workflow)
88
+ workflow_resource["#{repo}/objects/#{druid}/workflows/#{workflow}"].get
89
+ end
90
+
91
+ # Get workflow names into an array for given PID
92
+ # This method only works when this gem is used in a project that is configured to connect to DOR
93
+ #
94
+ # @param [string] pid of druid
95
+ #
96
+ # @return [array] list of worklows
97
+ # e.g.
98
+ # Dor::WorkflowService.get_workflows('druid:sr100hp0609')
99
+ # => ["accessionWF", "assemblyWF", "disseminationWF"]
100
+ def get_workflows(pid)
101
+ xml_doc=Nokogiri::XML(get_workflow_xml('dor',pid,''))
102
+ return xml_doc.xpath('//workflow').collect {|workflow| workflow['id']}
103
+ end
104
+
105
+ # Updates the status of one step in a workflow to error.
106
+ # Returns true on success. Caller must handle any exceptions
107
+ #
108
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
109
+ # @param [String] druid The id of the object
110
+ # @param [String] workflow The name of the workflow
111
+ # @param [String] error_msg The error message. Ideally, this is a brief message describing the error
112
+ # @param [Hash] opts optional values for the workflow step
113
+ # @option opts [String] :error_txt A slot to hold more information about the error, like a full stacktrace
114
+ #
115
+ # == Http Call
116
+ # The method does an HTTP PUT to the URL defined in Dor::WF_URI. As an example:
117
+ # PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert"
118
+ # <process name=\"convert\" status=\"error\" />"
119
+ def update_workflow_error_status(repo, druid, workflow, process, error_msg, opts = {})
120
+ opts = {:error_txt => nil}.merge!(opts)
121
+ xml = create_process_xml({:name => process, :status => 'error', :errorMessage => error_msg}.merge!(opts))
122
+ workflow_resource["#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}"].put(xml, :content_type => 'application/xml')
123
+ return true
124
+ end
125
+
126
+ # Deletes a workflow from a particular repository and druid
127
+ # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
128
+ # @param [String] druid The id of the object to delete the workflow from
129
+ # @param [String] workflow The name of the workflow to be deleted
130
+ def delete_workflow(repo, druid, workflow)
131
+ workflow_resource["#{repo}/objects/#{druid}/workflows/#{workflow}"].delete
132
+ return true
133
+ end
134
+
135
+ # Returns the Date for a requested milestone from workflow lifecycle
136
+ # @param [String] repo epository name
137
+ # @param [String] druid object id
138
+ # @param [String] milestone name of the milestone being queried for
139
+ # @return [Time] when the milestone was achieved. Returns nil if the milestone does not exist
140
+ # @example An example lifecycle xml from the workflow service.
141
+ # <lifecycle objectId="druid:ct011cv6501">
142
+ # <milestone date="2010-04-27T11:34:17-0700">registered</milestone>
143
+ # <milestone date="2010-04-29T10:12:51-0700">inprocess</milestone>
144
+ # <milestone date="2010-06-15T16:08:58-0700">released</milestone>
145
+ # </lifecycle>
146
+ def get_lifecycle(repo, druid, milestone)
147
+ doc = self.query_lifecycle(repo, druid)
148
+ milestone = doc.at_xpath("//lifecycle/milestone[text() = '#{milestone}']")
149
+ if(milestone)
150
+ return Time.parse(milestone['date'])
151
+ end
152
+
153
+ nil
154
+ end
155
+
156
+ # Returns the Date for a requested milestone ONLY FROM THE ACTIVE workflow table
157
+ # @param [String] repo epository name
158
+ # @param [String] druid object id
159
+ # @param [String] milestone name of the milestone being queried for
160
+ # @return [Time] when the milestone was achieved. Returns nil if the milestone does not exist
161
+ # @example An example lifecycle xml from the workflow service.
162
+ # <lifecycle objectId="druid:ct011cv6501">
163
+ # <milestone date="2010-04-27T11:34:17-0700">registered</milestone>
164
+ # <milestone date="2010-04-29T10:12:51-0700">inprocess</milestone>
165
+ # <milestone date="2010-06-15T16:08:58-0700">released</milestone>
166
+ # </lifecycle>
167
+ def get_active_lifecycle(repo, druid, milestone)
168
+ doc = self.query_lifecycle(repo, druid, true)
169
+ milestone = doc.at_xpath("//lifecycle/milestone[text() = '#{milestone}']")
170
+ if(milestone)
171
+ return Time.parse(milestone['date'])
172
+ end
173
+
174
+ nil
175
+ end
176
+
177
+ def get_milestones(repo, druid)
178
+ doc = self.query_lifecycle(repo, druid)
179
+ doc.xpath("//lifecycle/milestone").collect do |node|
180
+ { :milestone => node.text, :at => Time.parse(node['date']), :version => node['version'] }
181
+ end
182
+ end
183
+
184
+ def qualify_step(default_repository, default_workflow, step)
185
+ current = step.split(/:/,3)
186
+ current.unshift(default_workflow) if current.length < 3
187
+ current.unshift(default_repository) if current.length < 3
188
+ current.join(':')
189
+ end
190
+
191
+
192
+ # Returns a list of druids from the WorkflowService that meet the criteria of the passed in completed and waiting params
193
+ #
194
+ # @param [Array<String>, String] completed An array or single String of the completed steps, should use the qualified format:
195
+ # repository:workflow:step-name
196
+ # @param [String] waiting name of the waiting step
197
+ # @param [String] repository default repository to use if it isn't passed in the qualified-step-name
198
+ # @param [String] workflow default workflow to use if it isn't passed in the qualified-step-name
199
+ def get_objects_for_workstep completed, waiting, repository=nil, workflow=nil
200
+ result = nil
201
+ if(completed)
202
+ uri_string = "workflow_queue?waiting=#{qualify_step(repository,workflow,waiting)}"
203
+ Array(completed).each do |step|
204
+ uri_string << "&completed=#{qualify_step(repository,workflow,step)}"
205
+ end
206
+ else
207
+ uri_string = "workflow_queue?waiting=#{qualify_step(repository,workflow,waiting)}"
208
+ end
209
+ workflow_resource.options[:timeout] = 5 * 60 unless(workflow_resource.options.include?(:timeout))
210
+ resp = workflow_resource[uri_string].get
211
+ result = Nokogiri::XML(resp).xpath('//object[@id]').collect { |node| node['id'] }
212
+
213
+ result || []
214
+ end
215
+
216
+ # Get a list of druids that have errored out in a particular workflow and step
217
+ #
218
+ # @param [string] workflow name
219
+ # @param [string] step name
220
+ # @param [string] repository -- optional, default=dor
221
+ #
222
+ # @return [hash] hash of results, with key has a druid, and value as the error message
223
+ # e.g.
224
+ # Dor::WorkflowService.get_errored_objects_for_workstep('accessionWF','content-metadata')
225
+ # => {"druid:qd556jq0580"=>"druid:qd556jq0580 - Item error; caused by #<Rubydora::FedoraInvalidRequest: Error modifying datastream contentMetadata for druid:qd556jq0580. See logger for details>"}
226
+ def get_errored_objects_for_workstep workflow, step, repository='dor'
227
+ result = {}
228
+ uri_string = "workflow_queue?repository=#{repository}&workflow=#{workflow}&error=#{step}"
229
+ resp = workflow_resource[uri_string].get
230
+ objs = Nokogiri::XML(resp).xpath('//object').collect do |node|
231
+ result.merge!(node['id'] => node['errorMessage'])
232
+ end
233
+ result
234
+ end
235
+
236
+ def create_process_xml(params)
237
+ builder = Nokogiri::XML::Builder.new do |xml|
238
+ attrs = params.reject { |k,v| v.nil? }
239
+ xml.process(attrs)
240
+ end
241
+ return builder.to_xml
242
+ end
243
+
244
+ def query_lifecycle(repo, druid, active_only = false)
245
+ req = "#{repo}/objects/#{druid}/lifecycle"
246
+ req << '?active-only=true' if active_only
247
+ lifecycle_xml = workflow_resource[req].get
248
+ return Nokogiri::XML(lifecycle_xml)
249
+ end
250
+
251
+ def archive_workflow(repo, druid, wf_name, version_num=nil)
252
+ raise "Please call Dor::WorkflowService.configure(workflow_service_url, :dor_services_url => DOR_SERVIES_URL) once before archiving workflow" if(@@dor_services_url.nil?)
253
+
254
+ dor_services = RestClient::Resource.new(@@dor_services_url)
255
+ url = "/v1/objects/#{druid}/workflows/#{wf_name}/archive"
256
+ url << "/#{version_num}" if(version_num)
257
+ dor_services[url].post ''
258
+ end
259
+
260
+ def workflow_resource
261
+ raise "Please call Dor::WorkflowService.configure(url) once before calling any WorkflowService methods" if(@@resource.nil?)
262
+ @@resource
263
+ end
264
+
265
+ # @param [String] url points to the workflow service
266
+ # @param [Hash] opts optional params
267
+ # @option opts [String] :client_cert_file path to an SSL client certificate
268
+ # @option opts [String] :client_key_file path to an SSL key file
269
+ # @option opts [String] :client_key_pass password for the key file
270
+ # @option opts [String] :dor_services_uri uri to the DOR REST service
271
+ def configure(url, opts={})
272
+ params = {}
273
+ @@dor_services_url = opts[:dor_services_url] if opts[:dor_services_url]
274
+ #params[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(opts[:client_cert_file])) if opts[:client_cert_file]
275
+ #params[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(opts[:client_key_file]), opts[:client_key_pass]) if opts[:client_key_file]
276
+ @@resource = RestClient::Resource.new(url, params)
277
+ end
278
+
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,7 @@
1
+ module Dor
2
+ module Workflow
3
+ module Service
4
+ VERSION = "1.3.3"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Bundler.require(:default, :development)
5
+
6
+ RSpec.configure do |conf|
7
+
8
+ end
9
+
10
+ Rails = Object.new unless defined? Rails
@@ -0,0 +1,245 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'dor-workflow-service'
3
+ require 'equivalent-xml'
4
+
5
+ describe Dor::WorkflowService do
6
+
7
+ let(:wf_xml) { <<-EOXML
8
+ <workflow id="etdSubmitWF">
9
+ <process name="register-object" status="completed" attempts="1" />
10
+ <process name="submit" status="waiting" />
11
+ <process name="reader-approval" status="waiting" />
12
+ <process name="registrar-approval" status="waiting" />
13
+ <process name="start-accession" status="waiting" />
14
+ </workflow>
15
+ EOXML
16
+ }
17
+
18
+ before(:each) do
19
+ @repo = 'dor'
20
+ @druid = 'druid:123'
21
+
22
+ @mock_logger = double('logger').as_null_object
23
+ Rails.stub(:logger).and_return(@mock_logger)
24
+
25
+ @mock_resource = double('mock_rest_client_resource')
26
+ @mock_resource.stub(:[]).and_return(@mock_resource)
27
+ @mock_resource.stub(:options).and_return( {} )
28
+ RestClient::Resource.stub(:new).and_return(@mock_resource)
29
+ Dor::WorkflowService.configure 'https://dortest.stanford.edu/workflow'
30
+ end
31
+
32
+ describe "#create_workflow" do
33
+ it "should pass workflow xml to the DOR workflow service and return the URL to the workflow" do
34
+ @mock_resource.should_receive(:put).with(wf_xml, anything()).and_return('')
35
+ Dor::WorkflowService.create_workflow(@repo, @druid, 'etdSubmitWF', wf_xml)
36
+ end
37
+
38
+ it "should log an error and return false if the PUT to the DOR workflow service throws an exception" do
39
+ ex = Exception.new("exception thrown")
40
+ @mock_resource.should_receive(:put).and_raise(ex)
41
+ lambda{ Dor::WorkflowService.create_workflow(@repo, @druid, 'etdSubmitWF', wf_xml) }.should raise_error(Exception, "exception thrown")
42
+ end
43
+
44
+ it "sets the create-ds param to the value of the passed in options hash" do
45
+ @mock_resource.should_receive(:put).with(wf_xml, :content_type => 'application/xml',
46
+ :params => {'create-ds' => false}).and_return('')
47
+ Dor::WorkflowService.create_workflow(@repo, @druid, 'etdSubmitWF', wf_xml, :create_ds => false)
48
+ end
49
+
50
+ it "adds priority attributes to all steps if passed in as an option" do
51
+
52
+ end
53
+
54
+ end
55
+
56
+ describe "#add_priority_to_workflow_xml" do
57
+
58
+ it "adds priority attributes to all process elements" do
59
+ expected = <<-XML
60
+ <workflow id="etdSubmitWF">
61
+ <process name="register-object" status="completed" attempts="1" priority="50"/>
62
+ <process name="submit" status="waiting" priority="50"/>
63
+ <process name="reader-approval" status="waiting" priority="50"/>
64
+ <process name="registrar-approval" status="waiting" priority="50"/>
65
+ <process name="start-accession" status="waiting" priority="50"/>
66
+ </workflow>
67
+ XML
68
+
69
+ Dor::WorkflowService.add_priority_to_workflow_xml(50, wf_xml).should be_equivalent_to(expected)
70
+ end
71
+ end
72
+
73
+ describe "#update_workflow_status" do
74
+ before(:each) do
75
+ @xml_re = /name="reader-approval"/
76
+ end
77
+
78
+ it "should update workflow status and return true if successful" do
79
+ @mock_resource.should_receive(:put).with(@xml_re, { :content_type => 'application/xml' }).and_return('')
80
+ Dor::WorkflowService.update_workflow_status(@repo, @druid, "etdSubmitWF", "reader-approval", "completed", :version => 2, :note => 'annotation', :priority => 34).should be_true
81
+ end
82
+
83
+ it "should return false if the PUT to the DOR workflow service throws an exception" do
84
+ ex = Exception.new("exception thrown")
85
+ @mock_resource.should_receive(:put).with(@xml_re, { :content_type => 'application/xml' }).and_raise(ex)
86
+ lambda{ Dor::WorkflowService.update_workflow_status(@repo, @druid, "etdSubmitWF", "reader-approval", "completed") }.should raise_error(Exception, "exception thrown")
87
+ end
88
+ end
89
+
90
+ describe "#update_workflow_error_status" do
91
+ it "should update workflow status to error and return true if successful" do
92
+ @mock_resource.should_receive(:put).with(/status="error"/, { :content_type => 'application/xml' }).and_return('')
93
+ Dor::WorkflowService.update_workflow_error_status(@repo, @druid, "etdSubmitWF", "reader-approval", "Some exception", :error_txt =>"The optional stacktrace")
94
+ end
95
+
96
+ it "should return false if the PUT to the DOR workflow service throws an exception" do
97
+ ex = Exception.new("exception thrown")
98
+ @mock_resource.should_receive(:put).with(/status="completed"/, { :content_type => 'application/xml' }).and_raise(ex)
99
+ lambda{ Dor::WorkflowService.update_workflow_status(@repo, @druid, "etdSubmitWF", "reader-approval", "completed") }.should raise_error(Exception, "exception thrown")
100
+ end
101
+ end
102
+
103
+ describe "#get_workflow_status" do
104
+ it "parses workflow xml and returns status as a string" do
105
+ @mock_resource.should_receive(:get).and_return('<process name="registrar-approval" status="completed" />')
106
+ Dor::WorkflowService.get_workflow_status('dor', 'druid:123', 'etdSubmitWF', 'registrar-approval').should == 'completed'
107
+ end
108
+
109
+ it "should throw an exception if it fails for any reason" do
110
+ ex = Exception.new("exception thrown")
111
+ @mock_resource.should_receive(:get).and_raise(ex)
112
+
113
+ lambda{ Dor::WorkflowService.get_workflow_status('dor', 'druid:123', 'etdSubmitWF', 'registrar-approval') }.should raise_error(Exception, "exception thrown")
114
+ end
115
+
116
+ it "should throw an exception if it cannot parse the response" do
117
+ @mock_resource.should_receive(:get).and_return('something not xml')
118
+ lambda{ Dor::WorkflowService.get_workflow_status('dor', 'druid:123', 'etdSubmitWF', 'registrar-approval') }.should raise_error(Exception, "Unable to parse response:\nsomething not xml")
119
+ end
120
+ it "should return nil if the workflow/process combination doesnt exist" do
121
+ @mock_resource.should_receive(:get).and_return('<process name="registrar-approval" status="completed" />')
122
+ Dor::WorkflowService.get_workflow_status('dor', 'druid:123', 'accessionWF', 'publish').should == nil
123
+ end
124
+
125
+ end
126
+
127
+ describe "#get_workflow_xml" do
128
+ it "returns the xml for a given repository, druid, and workflow" do
129
+ xml = '<workflow id="etdSubmitWF"><process name="registrar-approval" status="completed" /></workflow>'
130
+ @mock_resource.should_receive(:get).and_return(xml)
131
+ Dor::WorkflowService.get_workflow_xml('dor', 'druid:123', 'etdSubmitWF').should == xml
132
+ end
133
+ end
134
+
135
+ describe "#get_lifecycle" do
136
+ it "returns a Time object reprenting when the milestone was reached" do
137
+ xml = <<-EOXML
138
+ <lifecycle objectId="druid:ct011cv6501">
139
+ <milestone date="2010-04-27T11:34:17-0700">registered</milestone>
140
+ <milestone date="2010-04-29T10:12:51-0700">inprocess</milestone>
141
+ <milestone date="2010-06-15T16:08:58-0700">released</milestone>
142
+ </lifecycle>
143
+ EOXML
144
+ @mock_resource.should_receive(:get).and_return(xml)
145
+ Dor::WorkflowService.get_lifecycle('dor', 'druid:123', 'released').beginning_of_day.should == Time.parse('2010-06-15T16:08:58-0700').beginning_of_day
146
+ end
147
+
148
+ it "returns nil if the milestone hasn't been reached yet" do
149
+ @mock_resource.should_receive(:get).and_return('<lifecycle/>')
150
+ Dor::WorkflowService.get_lifecycle('dor', 'druid:abc', 'inprocess').should be_nil
151
+ end
152
+
153
+ end
154
+
155
+ describe "#get_objects_for_workstep" do
156
+ before :each do
157
+ @repository = "dor"
158
+ @workflow = "googleScannedBookWF"
159
+ @completed = "google-download"
160
+ @waiting = "process-content"
161
+ end
162
+
163
+ context "a query with one step completed and one waiting" do
164
+ it "creates the URI string with only the one completed step" do
165
+ @mock_resource.should_receive(:[]).with("workflow_queue?waiting=#{@repository}:#{@workflow}:#{@waiting}&completed=#{@repository}:#{@workflow}:#{@completed}")
166
+ @mock_resource.should_receive(:get).and_return(%{<objects count="1"><object id="druid:ab123de4567"/><object id="druid:ab123de9012"/></objects>})
167
+ Dor::WorkflowService.get_objects_for_workstep(@completed, @waiting, @repository, @workflow).should == ['druid:ab123de4567','druid:ab123de9012']
168
+ end
169
+ end
170
+
171
+ context "a query with TWO steps completed and one waiting" do
172
+ it "creates the URI string with the two completed steps correctly" do
173
+ second_completed="google-convert"
174
+ @mock_resource.should_receive(:[]).with("workflow_queue?waiting=#{@repository}:#{@workflow}:#{@waiting}&completed=#{@repository}:#{@workflow}:#{@completed}&completed=#{@repository}:#{@workflow}:#{second_completed}")
175
+ @mock_resource.should_receive(:get).and_return(%{<objects count="1"><object id="druid:ab123de4567"/><object id="druid:ab123de9012"/></objects>})
176
+ Dor::WorkflowService.get_objects_for_workstep([@completed,second_completed], @waiting, @repository, @workflow).should == ['druid:ab123de4567','druid:ab123de9012']
177
+ end
178
+ end
179
+
180
+ context "a query using qualified workflow names for completed and waiting" do
181
+ it "creates the URI string with the two completed steps across repositories correctly" do
182
+ qualified_waiting = "#{@repository}:#{@workflow}:#{@waiting}"
183
+ qualified_completed = "#{@repository}:#{@workflow}:#{@completed}"
184
+ repo2 = "sdr"
185
+ workflow2 = "sdrIngestWF"
186
+ completed2="complete-deposit"
187
+ completed3="ingest-transfer"
188
+ qualified_completed2 = "#{repo2}:#{workflow2}:#{completed2}"
189
+ qualified_completed3 = "#{repo2}:#{workflow2}:#{completed3}"
190
+ @mock_resource.should_receive(:[]).with("workflow_queue?waiting=#{qualified_waiting}&completed=#{qualified_completed}&completed=#{qualified_completed2}&completed=#{qualified_completed3}")
191
+ @mock_resource.should_receive(:get).and_return(%{<objects count="2"><object id="druid:ab123de4567" priority="0"/><object id="druid:ab123de9012" priority="0"/></objects>})
192
+ Dor::WorkflowService.get_objects_for_workstep([qualified_completed, qualified_completed2, qualified_completed3], qualified_waiting).should == ['druid:ab123de4567', 'druid:ab123de9012']
193
+ end
194
+
195
+ it "creates the URI string with only one completed step passed in as a String" do
196
+ qualified_waiting = "#{@repository}:#{@workflow}:#{@waiting}"
197
+ qualified_completed = "#{@repository}:#{@workflow}:#{@completed}"
198
+ repo2 = "sdr"
199
+
200
+ @mock_resource.should_receive(:[]).with("workflow_queue?waiting=#{qualified_waiting}&completed=#{qualified_completed}")
201
+ @mock_resource.should_receive(:get).and_return(%{<objects count="1"><object id="druid:ab123de4567"/></objects>})
202
+ Dor::WorkflowService.get_objects_for_workstep(qualified_completed, qualified_waiting).should == ['druid:ab123de4567']
203
+ end
204
+
205
+ it "creates the URI string without any completed steps, only waiting" do
206
+ qualified_waiting = "#{@repository}:#{@workflow}:#{@waiting}"
207
+
208
+ @mock_resource.should_receive(:[]).with("workflow_queue?waiting=#{qualified_waiting}")
209
+ @mock_resource.should_receive(:get).and_return(%{<objects count="1"><object id="druid:ab123de4567"/></objects>})
210
+ Dor::WorkflowService.get_objects_for_workstep(nil, qualified_waiting).should == ['druid:ab123de4567']
211
+ end
212
+ end
213
+ end
214
+
215
+ context "get empty workflow queue" do
216
+ it "returns an empty list if it encounters an empty workflow queue" do
217
+ repository = "dor"
218
+ workflow = "googleScannedBookWF"
219
+ completed = "google-download"
220
+ waiting = "process-content"
221
+ @mock_resource.should_receive(:[]).with("workflow_queue?waiting=#{repository}:#{workflow}:#{waiting}&completed=#{repository}:#{workflow}:#{completed}")
222
+ @mock_resource.should_receive(:get).and_return(%{<objects count="0"/>})
223
+ Dor::WorkflowService.get_objects_for_workstep(completed, waiting, repository, workflow).should == []
224
+ end
225
+ end
226
+
227
+ describe "#delete_workflow" do
228
+ it "sends a delete request to the workflow service" do
229
+ @mock_resource.should_receive(:[]).with("#{@repo}/objects/#{@druid}/workflows/accessionWF")
230
+ @mock_resource.should_receive(:delete)
231
+ Dor::WorkflowService.delete_workflow(@repo, @druid, 'accessionWF')
232
+ end
233
+ end
234
+ describe 'get_milestones' do
235
+ it 'should include the version in with the milestones' do
236
+ xml='<?xml version="1.0" encoding="UTF-8"?><lifecycle objectId="druid:gv054hp4128"><milestone date="2012-01-26T21:06:54-0800" version="2">published</milestone></lifecycle>'
237
+ xml=Nokogiri::XML(xml)
238
+ Dor::WorkflowService.stub(:query_lifecycle).and_return(xml)
239
+ milestones=Dor::WorkflowService.get_milestones(@repo, @druid)
240
+ milestones.first[:milestone].should == "published"
241
+ milestones.first[:version].should == "2"
242
+ end
243
+ end
244
+
245
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dor-workflow-service
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.3
5
+ platform: ruby
6
+ authors:
7
+ - Willy Mene
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rest-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: confstruct
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: redcarpet
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: equivalent-xml
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: Enables Ruby manipulation of the DOR Workflow Service via its REST API
140
+ email:
141
+ - wmene@stanford.edu
142
+ executables:
143
+ - console
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - .gitignore
148
+ - .travis.yml
149
+ - Gemfile
150
+ - LICENSE.txt
151
+ - README.md
152
+ - Rakefile
153
+ - bin/console
154
+ - dor-workflow-service.gemspec
155
+ - lib/dor-workflow-service.rb
156
+ - lib/dor/services/workflow_service.rb
157
+ - lib/dor/workflow_version.rb
158
+ - spec/spec_helper.rb
159
+ - spec/workflow_service_spec.rb
160
+ homepage: https://consul.stanford.edu/display/DOR/DOR+services#DORservices-initializeworkflow
161
+ licenses: []
162
+ metadata: {}
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubyforge_project:
179
+ rubygems_version: 2.0.3
180
+ signing_key:
181
+ specification_version: 4
182
+ summary: Provides convenience methods to work with the DOR Workflow Service
183
+ test_files:
184
+ - spec/spec_helper.rb
185
+ - spec/workflow_service_spec.rb
186
+ has_rdoc: