dor-workflow-service 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
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: