psc 0.0.1

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 (47) hide show
  1. data/.gitignore +13 -0
  2. data/.rvmrc +2 -0
  3. data/.yardopts +4 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +20 -0
  6. data/LICENSE +20 -0
  7. data/README.md +163 -0
  8. data/Rakefile +69 -0
  9. data/cucumber.yml +13 -0
  10. data/features/step_definitions/eval_steps.rb +14 -0
  11. data/features/step_definitions/setup_steps.rb +7 -0
  12. data/features/studies.feature +21 -0
  13. data/features/support/env.rb +33 -0
  14. data/features/support/int_psc.rb +165 -0
  15. data/int-psc/README-int-psc.md +57 -0
  16. data/int-psc/hsqldb/baseline.log +197 -0
  17. data/int-psc/hsqldb/baseline.properties +17 -0
  18. data/int-psc/hsqldb/baseline.script +295 -0
  19. data/int-psc/hsqldb/datasource.properties +18 -0
  20. data/int-psc/hsqldb/datasource.script +2205 -0
  21. data/int-psc/jetty/jetty-runner-7.4.0.v20110414.jar +0 -0
  22. data/int-psc/state/ABC 1200.xml +115 -0
  23. data/int-psc/state/int-psc-state.xml +64 -0
  24. data/lib/psc.rb +47 -0
  25. data/lib/psc/client.rb +35 -0
  26. data/lib/psc/connection.rb +96 -0
  27. data/lib/psc/faraday.rb +11 -0
  28. data/lib/psc/faraday/http_basic.rb +35 -0
  29. data/lib/psc/faraday/psc_token.rb +42 -0
  30. data/lib/psc/faraday/string_is_xml.rb +25 -0
  31. data/lib/psc/version.rb +3 -0
  32. data/meta.rakefile +30 -0
  33. data/psc.gemspec +33 -0
  34. data/spec/fixtures/studies-json.http +37 -0
  35. data/spec/middleware_helper.rb +18 -0
  36. data/spec/psc/client_spec.rb +39 -0
  37. data/spec/psc/connection_spec.rb +156 -0
  38. data/spec/psc/faraday/http_basic_spec.rb +15 -0
  39. data/spec/psc/faraday/psc_token_spec.rb +38 -0
  40. data/spec/psc/faraday/string_is_xml_spec.rb +49 -0
  41. data/spec/psc/version_spec.rb +11 -0
  42. data/spec/psc_spec.rb +16 -0
  43. data/spec/spec_helper.rb +15 -0
  44. data/tasks/int-psc.rake +84 -0
  45. data/tasks/psc/TODO +3 -0
  46. data/tasks/psc/state.rb +393 -0
  47. metadata +311 -0
@@ -0,0 +1,13 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ reports
6
+
7
+ int-psc/downloads
8
+ int-psc/bin/*
9
+ int-psc/deploy-base
10
+ int-psc/baseline.properties
11
+ int-psc/datasource.properties
12
+
13
+ .yardoc
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm_gemset_create_on_use_flag=1; rvm use @psc-rb
2
+
@@ -0,0 +1,4 @@
1
+ --no-private
2
+ --markup markdown
3
+ --hide-void-return
4
+ --files CHANGELOG.md,int-psc/README-int-psc.md
@@ -0,0 +1,4 @@
1
+ 0.0.1
2
+ =====
3
+
4
+ - Initial version
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org/"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ # For yard's markdown support
7
+ platforms :jruby do
8
+ gem 'maruku'
9
+ end
10
+
11
+ platforms :ruby_18, :ruby_19 do
12
+ gem 'rdiscount'
13
+ end
14
+
15
+ # To prevent jruby problems in development
16
+ platforms :jruby do
17
+ gem 'jruby-openssl' # when using webmock
18
+ gem 'ffi-ncurses' # when loading highline
19
+ end
20
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Rhett Sutphin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,163 @@
1
+ (Beware: [Readme-driven development in
2
+ progress](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html). This
3
+ is documentation for expected features, not for the current state of
4
+ the library.)
5
+
6
+ # psc.rb
7
+
8
+ `psc.rb` is a ruby client for [Patient Study Calendar][psc]'s RESTful
9
+ HTTP API. It provides assistance with authentication to PSC's API and
10
+ with executing common tasks. It also provides a lower-level interface
11
+ (`Psc::Connection`) to allow for making HTTP requests against the
12
+ configured PSC's API directly.
13
+
14
+ By design, the client provides a very thin abstraction over the API
15
+ itself. Please be familiar with the API (whose documentation is
16
+ available in your PSC instance at `api/v1/docs` or [on the demo
17
+ site][demo-docs]) before using this library.
18
+
19
+ [psc]: https://code.bioinformatics.northwestern.edu/issues/wiki/psc
20
+ [demo-docs]: https://demos.nubic.northwestern.edu/psc/api/v1/docs
21
+
22
+ ## Overview
23
+
24
+ require 'psc'
25
+ require 'pp'
26
+
27
+ psc = Psc::Client.new(
28
+ 'https://demos.nubic.northwestern.edu/psc',
29
+ :authenticator => { :basic => ['superuser', 'superuser'] }
30
+ )
31
+
32
+ pp psc.studies
33
+
34
+ (This code will run if you have the psc gem installed; try it and see.)
35
+
36
+ ## Installing
37
+
38
+ `psc.rb` is available as a rubygem:
39
+
40
+ $ gem install psc
41
+
42
+ ## Authentication
43
+
44
+ PSC supports two forms of authentication for API calls: HTTP Basic
45
+ (i.e., username & password) and psc_token. (Which forms are supported
46
+ in your PSC instance will depend on its authentication system
47
+ configuration.)
48
+
49
+ A particular client instance will only use one authentication
50
+ mechanism. There are three options.
51
+
52
+ ### HTTP Basic
53
+
54
+ PSC Client allows you to specify a username and password to use for
55
+ all requests. Include the `:authenticator` key like so:
56
+
57
+ :authenticator => { :basic => %w(alice password) }
58
+
59
+ => Authorization: Basic YWxpY2U6cGFzc3dvcmQ=
60
+
61
+ ### Static token
62
+
63
+ Alternatively, you can provide a token to use in all requests:
64
+
65
+ :authenticator => { :token => 'The raven flies at midnight' }
66
+
67
+ => Authorization: psc_token The raven flies at midnight
68
+
69
+ ### Dynamic token
70
+
71
+ Finally, you can provide a callable object which will be invoked for
72
+ each request and whose return value will be used for the PSC token:
73
+
74
+ :authenticator => { :token => lambda { cas_client.get_proxy_ticket } }
75
+
76
+ => Authorization: psc_token PT-133-236H522
77
+
78
+ The callable will be called with no arguments.
79
+
80
+ ## High-level interface
81
+
82
+ {Psc::Client} provides a high-level interface to some of PSC's API
83
+ capabilities.
84
+
85
+ ## Low-level interface
86
+
87
+ `psc.rb` is based on [Faraday][], a modular ruby HTTP
88
+ client. {Psc::Connection} is a Faraday connection configured
89
+ for access to a particular PSC instance. You can create a
90
+ `Psc::Connection` directly:
91
+
92
+ conn = Psc::Connection.new(
93
+ 'https://demos.nubic.northwestern.edu/psc',
94
+ :authenticator => { :basic => %w(superuser superuser) })
95
+
96
+ Or you can get an instance from the {Psc::Client} high-level
97
+ interface:
98
+
99
+ client = Psc::Client.new(
100
+ 'https://demos.nubic.northwestern.edu/psc',
101
+ :authenticator => { :basic => %w(superuser superuser) })
102
+ conn = client.connection
103
+
104
+ The connection is set up to automatically parse JSON reponses into
105
+ appropriate ruby primitives and XML responses into [Nokogiri][]
106
+ documents.
107
+
108
+ studies_json = conn.get('studies.json')
109
+ first_study_name =
110
+ studies_json.body['studies'].first['assigned_identifier']
111
+
112
+ sites_xml = conn.get('sites.xml')
113
+ first_site_name =
114
+ sites_xml.body.xpath('//psc:site', Psc.xml_namespace).first.attr('site-name')
115
+
116
+ Similarly, for PUT and POST it will encode a `Hash` or
117
+ `Array` entity as JSON and will assume that a `String` entity is XML.
118
+
119
+ [Faraday]: https://github.com/technoweenie/faraday
120
+ [Nokogiri]: http://nokogiri.org/
121
+
122
+ ### URLs
123
+
124
+ PSC's API resources all start with `api/v1`. To help you DRY things
125
+ up, `PSC::Connection` automatically adds this to the base URL on
126
+ construction. You don't need to include it when constructing
127
+ relative URLs.
128
+
129
+ ### Middleware
130
+
131
+ Faraday connections are built up from middleware. `Psc::Connection`
132
+ uses a combination of off-the-shelf and custom middleware classes. The
133
+ custom classes are in the {Psc::Faraday} module.
134
+
135
+ ## Project info
136
+
137
+ * [Source code][github]
138
+ * [API Documentation][rubydoc]
139
+ * [Bug reports and feature requests][issues]
140
+ * [Continuous integration][ci]
141
+ * Version policy: [semantic versioning][semver]
142
+
143
+ [rubydoc]: http://rubydoc.info/gems/psc
144
+ [issues]: https://github.com/NUBIC/psc.rb/issues
145
+ [github]: https://github.com/NUBIC/psc.rb
146
+ [ci]: https://ctms-ci.nubic.northwestern.edu/hudson/jobs/psc.rb
147
+ [semver]: http://semver.org/
148
+
149
+ ### Running the tests
150
+
151
+ `psc.rb` has an rspec suite for unit tests and a set of cucumber
152
+ features for integration tests. Before you can run the cucumber
153
+ features you will need to either
154
+
155
+ * Execute `rake int-psc:war` to download a copy of PSC to use
156
+ * Copy a PSC war into `int-psc/bin`
157
+
158
+ ### Patches
159
+
160
+ Patches with tests are happily considered. Please use a [pull
161
+ request][].
162
+
163
+ [pull request]: http://help.github.com/pull-requests/
@@ -0,0 +1,69 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'ci/reporter/rake/rspec'
6
+ require 'cucumber/rake/task'
7
+ require 'yard'
8
+
9
+ Dir[File.expand_path('../tasks/*.rake', __FILE__)].each { |f| load f }
10
+
11
+ desc "Run all specs"
12
+ RSpec::Core::RakeTask.new('spec') do |t|
13
+ t.pattern = 'spec/**/*_spec.rb'
14
+ t.verbose = true
15
+ t.rspec_opts = ['--format', 'html', '--out', 'reports/spec.html', '--format', 'p']
16
+ end
17
+
18
+ task :cucumber => 'cucumber:ok'
19
+
20
+ namespace :cucumber do
21
+ Cucumber::Rake::Task.new(:ok, "Run features that should pass") do |t|
22
+ t.fork = true
23
+ t.profile = "default"
24
+ end
25
+
26
+ Cucumber::Rake::Task.new(:wip, "Run features that are being worked on") do |t|
27
+ t.fork = true
28
+ t.profile = "wip"
29
+ end
30
+
31
+ Cucumber::Rake::Task.new(
32
+ :wip_platform, "Run features that are flagged as failing on the current platform"
33
+ ) do |t|
34
+ t.fork = true
35
+ t.profile = "wip_platform"
36
+ end
37
+
38
+ desc "Run all features"
39
+ task :all => [:ok, :wip, :wip_platform]
40
+ end
41
+
42
+ task :yard => ['yard:auto']
43
+
44
+ namespace :yard do
45
+ desc "Run a server which will rebuild documentation as the source changes"
46
+ task :auto do
47
+ system("bundle exec yard server --reload")
48
+ end
49
+
50
+ desc "Build API documentation with yard"
51
+ YARD::Rake::YardocTask.new("once") do |t|
52
+ t.options = ["--title", "psc.rb #{Psc::VERSION}"]
53
+ end
54
+ end
55
+
56
+ namespace :ci do
57
+ ENV["CI_REPORTS"] = "reports/spec-xml"
58
+
59
+ desc "Run specs for CI"
60
+ task :spec => ['ci:setup:rspec', 'rake:spec']
61
+
62
+ Cucumber::Rake::Task.new(:cucumber, 'Run features using the ci profile') do |t|
63
+ t.fork = true
64
+ t.profile = 'ci'
65
+ end
66
+
67
+ # currently bypassing ci_reporter due to https://github.com/nicksieger/ci_reporter/issues/5
68
+ task :build => %w(int-psc:war int-psc:clean_logs rake:spec cucumber)
69
+ end
@@ -0,0 +1,13 @@
1
+ <% platform =
2
+ if RUBY_PLATFORM == 'java'
3
+ 'jruby'
4
+ elsif RUBY_VERSION =~ /^1.9/
5
+ '19'
6
+ else
7
+ '18'
8
+ end
9
+ %>
10
+ default: --strict --tags ~@wip --tags ~@no_<%= platform %> features
11
+ wip: --tags @wip:3 --wip --tags ~@no_<%= platform %> features
12
+ ci: --tags ~@wip --tags ~@badci --tags ~@no_<%= platform %> --format html --out reports/cucumber.html --format junit --out reports/cucumber-xml --format progress --strict features
13
+ wip_platform: --tags @no_<%= platform %> --wip features
@@ -0,0 +1,14 @@
1
+ When /^I evaluate the following code:$/ do |string|
2
+ int_psc.wait_for
3
+ @captured_out = StringIO.new
4
+ begin
5
+ $stdout = @captured_out
6
+ eval(string)
7
+ ensure
8
+ $stdout = STDOUT
9
+ end
10
+ end
11
+
12
+ Then /^I should see this output:$/ do |string|
13
+ @captured_out.string.strip.should == string
14
+ end
@@ -0,0 +1,7 @@
1
+ Given /^that PSC is deployed$/ do
2
+ int_psc.boot
3
+ end
4
+
5
+ Given /^I have a PSC::Client instance$/ do
6
+ init_client
7
+ end
@@ -0,0 +1,21 @@
1
+ Feature: List studies
2
+
3
+ In order to understand what templates are available
4
+ A developer should be able to use Psc::Client to list the studies in a PSC instance
5
+
6
+ Background:
7
+ Given that PSC is deployed
8
+ And I have a PSC::Client instance
9
+
10
+ Scenario: List studies
11
+ When I evaluate the following code:
12
+ """
13
+ s = client.studies
14
+ puts "Study count: #{s.size}"
15
+ puts "First study: #{s.first['assigned_identifier']}"
16
+ """
17
+ Then I should see this output:
18
+ """
19
+ Study count: 1
20
+ First study: ABC 1200
21
+ """
@@ -0,0 +1,33 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'rspec'
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
7
+
8
+ require 'psc'
9
+
10
+ module Psc::Cucumber
11
+ include ::RSpec::Matchers
12
+
13
+ class World
14
+ def int_psc
15
+ @int_psc ||= IntPsc.new
16
+ end
17
+
18
+ def init_client
19
+ @client = Psc::Client.new(
20
+ File.join(IntPsc.url, 'api/v1'),
21
+ :authenticator => { :basic => [ 'superuser' ] * 2 }
22
+ )
23
+ end
24
+
25
+ def client
26
+ @client
27
+ end
28
+ end
29
+ end
30
+
31
+ World do
32
+ Psc::Cucumber::World.new
33
+ end
@@ -0,0 +1,165 @@
1
+ # Utilities for starting and stopping a PSC instance for integration testing
2
+
3
+ require 'childprocess'
4
+ require 'open-uri'
5
+ require 'timeout'
6
+ require 'forwardable'
7
+ require 'fileutils'
8
+
9
+ class IntPsc
10
+ DEFAULT_CONFIGURATION_NAME='datasource'
11
+
12
+ extend Forwardable
13
+ include FileUtils
14
+
15
+ def self.path(*bits)
16
+ File.join(File.expand_path('../../..', __FILE__), 'int-psc', *bits)
17
+ end
18
+ def_delegator self, :path
19
+
20
+ def self.port
21
+ ENV['INT_PSC_PORT'] ||= '7209'
22
+ end
23
+ def_delegator self, :port
24
+
25
+ def self.url
26
+ "http://localhost:#{port}/"
27
+ end
28
+ def_delegator self, :url
29
+
30
+ def self.warfile
31
+ path('bin', 'psc.war')
32
+ end
33
+ def_delegator self, :warfile
34
+
35
+ def self.expanded_war_directory
36
+ path('deploy-base', 'webapps', 'ROOT')
37
+ end
38
+ def_delegator self, :expanded_war_directory
39
+
40
+ def self.run(configuration=DEFAULT_CONFIGURATION_NAME)
41
+ psc = self.new(configuration)
42
+ psc.boot
43
+ puts "Waiting for PSC's API to become available"
44
+ psc.wait_for
45
+ yield psc
46
+ psc.stop
47
+ end
48
+
49
+ attr_reader :configuration_name
50
+
51
+ def initialize(configuration=DEFAULT_CONFIGURATION_NAME)
52
+ @configuration_name = configuration
53
+ end
54
+
55
+ def boot
56
+ expand_if_necessary
57
+ create_hsql_psc_configuration
58
+
59
+ cmd = [
60
+ 'java',
61
+ ENV['JAVA_OPTS'],
62
+ "-Dpsc.config.datasource=#{configuration_name}",
63
+ "-Dpsc.config.path=#{path}",
64
+ '-Dpsc.logging.debug=true',
65
+ "-Dcatalina.base=#{path 'deploy-base'}",
66
+
67
+ '-jar',
68
+ # jetty-runner
69
+ path('jetty', 'jetty-runner-7.4.0.v20110414.jar'),
70
+ "--port", port,
71
+ expanded_war_directory
72
+ ].compact
73
+
74
+ @running_psc = ChildProcess.build(*cmd)
75
+ @running_psc.duplex = true
76
+
77
+ @output_capture = rolled_log('jetty.out')
78
+ @running_psc.io.stdout = @output_capture
79
+ @running_psc.io.stderr = @output_capture
80
+
81
+ at_exit { self.stop }
82
+ @running_psc.start
83
+ @running_psc.io.stdin.close # to turn off ShellTUI for the OSGi layer
84
+ end
85
+
86
+ def stop
87
+ if @running_psc && @running_psc.alive?
88
+ @running_psc.stop
89
+ @output_capture.close
90
+ end
91
+ end
92
+
93
+ def wait_for
94
+ Timeout.timeout(90) do
95
+ while true
96
+ begin
97
+ open(File.join(self.url, '/api/v1/docs')) { |f| f.read }
98
+ break
99
+ rescue RuntimeError
100
+ sleep(1)
101
+ rescue
102
+ sleep(1)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def create_hsql_psc_configuration
109
+ File.open(path("#{configuration_name}.properties"), 'w') do |f|
110
+ f.puts [
111
+ "datasource.url=jdbc:hsqldb:file:#{path('hsqldb', configuration_name)};shutdown=true",
112
+ 'datasource.username=sa',
113
+ 'datasource.password=',
114
+ 'datasource.driver=org.hsqldb.jdbcDriver'
115
+ ].join("\n")
116
+ end
117
+ end
118
+
119
+ def apply_state_and_mark_readonly
120
+ client = Faraday.new(File.join(url, '/api/v1')) do |builder|
121
+ builder.adapter :net_http
122
+ end
123
+ client.basic_auth('superuser', 'superuser')
124
+ Psc::State.from_file(path('state/int-psc-state.xml')).apply(client)
125
+
126
+ File.open(path('hsqldb', "#{configuration_name}.properties"), 'a') do |f|
127
+ f.puts 'hsqldb.files_readonly=true'
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def expand_if_necessary
134
+ dirtime = File.directory?(expanded_war_directory) ?
135
+ File.mtime(expanded_war_directory) :
136
+ Time.at(0)
137
+ wartime = File.mtime(warfile)
138
+ if wartime > dirtime
139
+ rm_rf expanded_war_directory
140
+ mkdir_p expanded_war_directory
141
+ cd expanded_war_directory do
142
+ system("jar xf '#{warfile}'")
143
+ end
144
+ end
145
+ end
146
+
147
+ def rolled_log(name)
148
+ log = path('deploy-base', 'logs', name)
149
+ mkdir_p File.dirname(log)
150
+ if File.exist?(log)
151
+ now_s = date_str_from_time(Time.new)
152
+ existing_s = date_str_from_time(File.mtime(log))
153
+ if now_s != existing_s
154
+ rolled_log_name = log.sub(/\.([^\.]+)$/, ".#{existing_s}.\\1")
155
+ mv log, rolled_log_name
156
+ system("gzip -N '#{rolled_log_name}'")
157
+ end
158
+ end
159
+ File.open(log, 'a')
160
+ end
161
+
162
+ def date_str_from_time(t)
163
+ t.iso8601.split('T').first
164
+ end
165
+ end