qa_server 0.1.99

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 (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +71 -0
  3. data/Gemfile +55 -0
  4. data/Gemfile.lock +411 -0
  5. data/LICENSE +201 -0
  6. data/README.md +187 -0
  7. data/Rakefile +18 -0
  8. data/app/assets/images/qa_server/LD4L-bg.png +0 -0
  9. data/app/assets/images/qa_server/cornell-reduced-white.svg +160 -0
  10. data/app/assets/images/qa_server/iowa-logo.png +0 -0
  11. data/app/assets/images/qa_server/samvera-fall-font1-1000w-light.png +0 -0
  12. data/app/assets/javascripts/qa_server.js +0 -0
  13. data/app/assets/stylesheets/qa_server/_authorities.scss +0 -0
  14. data/app/assets/stylesheets/qa_server/_check-status.scss +72 -0
  15. data/app/assets/stylesheets/qa_server/_footer.scss +20 -0
  16. data/app/assets/stylesheets/qa_server/_header.scss +51 -0
  17. data/app/assets/stylesheets/qa_server/_home-page.scss +15 -0
  18. data/app/assets/stylesheets/qa_server/_monitor-status.scss +13 -0
  19. data/app/assets/stylesheets/qa_server/_qa_server.scss +36 -0
  20. data/app/assets/stylesheets/qa_server/_styles.scss +27 -0
  21. data/app/assets/stylesheets/qa_server/_usage.scss +0 -0
  22. data/app/controllers/qa_server/authority_list_controller.rb +14 -0
  23. data/app/controllers/qa_server/authority_validation_controller.rb +77 -0
  24. data/app/controllers/qa_server/check_status_controller.rb +38 -0
  25. data/app/controllers/qa_server/homepage_controller.rb +8 -0
  26. data/app/controllers/qa_server/monitor_status_controller.rb +79 -0
  27. data/app/controllers/qa_server/usage_controller.rb +8 -0
  28. data/app/loggers/qa_server/scenario_logger.rb +84 -0
  29. data/app/models/qa_server/authority_scenario.rb +35 -0
  30. data/app/models/qa_server/authority_status.rb +18 -0
  31. data/app/models/qa_server/authority_status_failure.rb +7 -0
  32. data/app/models/qa_server/scenarios.rb +71 -0
  33. data/app/models/qa_server/search_scenario.rb +58 -0
  34. data/app/models/qa_server/term_scenario.rb +50 -0
  35. data/app/presenters/qa_server/authority_list_presenter.rb +22 -0
  36. data/app/presenters/qa_server/check_status_presenter.rb +96 -0
  37. data/app/presenters/qa_server/monitor_status_presenter.rb +108 -0
  38. data/app/services/qa_server/authority_lister_service.rb +31 -0
  39. data/app/services/qa_server/authority_loader_service.rb +38 -0
  40. data/app/services/qa_server/authority_validator_service.rb +41 -0
  41. data/app/services/qa_server/database_migrator.rb +70 -0
  42. data/app/services/qa_server/scenarios_loader_service.rb +57 -0
  43. data/app/validators/qa_server/scenario_validator.rb +134 -0
  44. data/app/validators/qa_server/search_scenario_validator.rb +84 -0
  45. data/app/validators/qa_server/term_scenario_validator.rb +42 -0
  46. data/app/views/layouts/qa_server.html.erb +65 -0
  47. data/app/views/qa_server/authority_list/index.html.erb +27 -0
  48. data/app/views/qa_server/check_status/index.html.erb +104 -0
  49. data/app/views/qa_server/homepage/index.html.erb +10 -0
  50. data/app/views/qa_server/monitor_status/index.html.erb +78 -0
  51. data/app/views/qa_server/usage/index.html.erb +106 -0
  52. data/app/views/shared/_footer.html.erb +24 -0
  53. data/config/locales/qa_server.en.yml +68 -0
  54. data/config/routes.rb +12 -0
  55. data/lib/generators/qa_server/assets_generator.rb +35 -0
  56. data/lib/generators/qa_server/config_generator.rb +31 -0
  57. data/lib/generators/qa_server/install_generator.rb +60 -0
  58. data/lib/generators/qa_server/models_generator.rb +18 -0
  59. data/lib/generators/qa_server/templates/config/authorities/linked_data/agrovoc_direct.json +69 -0
  60. data/lib/generators/qa_server/templates/config/authorities/linked_data/agrovoc_ld4l_cache.json +85 -0
  61. data/lib/generators/qa_server/templates/config/authorities/linked_data/dbpedia_direct.json +36 -0
  62. data/lib/generators/qa_server/templates/config/authorities/linked_data/dbpedia_ld4l_cache.json +83 -0
  63. data/lib/generators/qa_server/templates/config/authorities/linked_data/geonames_direct.json +68 -0
  64. data/lib/generators/qa_server/templates/config/authorities/linked_data/geonames_ld4l_cache.json +102 -0
  65. data/lib/generators/qa_server/templates/config/authorities/linked_data/getty_aat_ld4l_cache.json +109 -0
  66. data/lib/generators/qa_server/templates/config/authorities/linked_data/getty_tgn_ld4l_cache.json +72 -0
  67. data/lib/generators/qa_server/templates/config/authorities/linked_data/getty_ulan_ld4l_cache.json +81 -0
  68. data/lib/generators/qa_server/templates/config/authorities/linked_data/loc_direct.json +47 -0
  69. data/lib/generators/qa_server/templates/config/authorities/linked_data/locgenres_ld4l_cache.json +99 -0
  70. data/lib/generators/qa_server/templates/config/authorities/linked_data/locnames_ld4l_cache.json +89 -0
  71. data/lib/generators/qa_server/templates/config/authorities/linked_data/locsubjects_ld4l_cache.json +82 -0
  72. data/lib/generators/qa_server/templates/config/authorities/linked_data/mesh_ld4l_cache.json +70 -0
  73. data/lib/generators/qa_server/templates/config/authorities/linked_data/nalt_direct.json +32 -0
  74. data/lib/generators/qa_server/templates/config/authorities/linked_data/nalt_ld4l_cache.json +84 -0
  75. data/lib/generators/qa_server/templates/config/authorities/linked_data/oclcfast_direct.json +81 -0
  76. data/lib/generators/qa_server/templates/config/authorities/linked_data/oclcfast_ld4l_cache.json +86 -0
  77. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/agrovoc_direct_validation.yml +9 -0
  78. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/agrovoc_ld4l_cache_validation.yml +9 -0
  79. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/agrovoc_validation.yml +9 -0
  80. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/dbpedia_direct_validation.yml +6 -0
  81. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/dbpedia_ld4l_cache_validation.yml +9 -0
  82. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/geonames_direct_validation.yml +9 -0
  83. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/geonames_ld4l_cache_validation.yml +41 -0
  84. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/getty_aat_ld4l_cache_validation.yml +115 -0
  85. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/getty_tgn_ld4l_cache_validation.yml +9 -0
  86. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/getty_ulan_ld4l_cache_validation.yml +15 -0
  87. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/loc_direct_validation.yml +25 -0
  88. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/loc_validation.yml +22 -0
  89. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/locgenres_ld4l_cache_validation.yml +99 -0
  90. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/locnames_ld4l_cache_validation.yml +27 -0
  91. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/locsubjects_ld4l_cache_validation.yml +27 -0
  92. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/mesh_ld4l_cache_validation.yml +9 -0
  93. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/nalt_direct_validation.yml +6 -0
  94. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/nalt_ld4l_cache_validation.yml +9 -0
  95. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/oclc_fast_validation.yml +58 -0
  96. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/oclcfast_direct_validation.yml +58 -0
  97. data/lib/generators/qa_server/templates/config/authorities/linked_data/scenarios/oclcfast_ld4l_cache_validation.yml +30 -0
  98. data/lib/generators/qa_server/templates/config/locales/qa_server.en.yml +9 -0
  99. data/lib/generators/qa_server/templates/db/migrate/20180313045551_create_cron_authority_status.rb.erb +9 -0
  100. data/lib/generators/qa_server/templates/db/migrate/20180508045549_rename_cron_authority_status_to_authority_status.rb.erb +5 -0
  101. data/lib/generators/qa_server/templates/db/migrate/20180508045551_create_authority_status_failure.rb.erb +15 -0
  102. data/lib/generators/qa_server/templates/qa_server.scss +5 -0
  103. data/lib/qa_server/engine.rb +19 -0
  104. data/lib/qa_server/version.rb +3 -0
  105. data/lib/qa_server.rb +6 -0
  106. data/lib/tasks/install.rake +8 -0
  107. data/lib/tasks/qa_server_tasks.rake +4 -0
  108. data/qa_server.gemspec +39 -0
  109. data/spec/.gitignore +1 -0
  110. data/spec/rails_helper.rb +2 -0
  111. data/spec/spec_helper.rb +283 -0
  112. data/spec/test_app_templates/Gemfile.extra +5 -0
  113. data/spec/test_app_templates/lib/generators/test_app_generator.rb +15 -0
  114. data/tasks/qa_server_dev.rake +16 -0
  115. metadata +275 -0
@@ -0,0 +1,71 @@
1
+ # Holds all scenarios for an authority.
2
+ module QaServer
3
+ class Scenarios
4
+
5
+ AUTHORITY_SCENARIO = 'authority'.freeze
6
+ TERM_SCENARIOS = 'term'.freeze
7
+ SEARCH_SCENARIOS = 'search'.freeze
8
+
9
+ # @return [Qa::Authorities::LinkedData::GenericAuthority] authority instance the scenarios run against
10
+ attr_reader :authority
11
+
12
+ # @return [String] name of the authority the scenarios run against (e.g. 'agrovoc_direct')
13
+ attr_reader :authority_name
14
+
15
+ # @return [Array<TermScenario>] the term scenarios to run against the authority
16
+ attr_reader :term_scenarios
17
+
18
+ # @return [Array<SearchScenario>] the search scenarios to run against the authority
19
+ attr_reader :search_scenarios
20
+
21
+ # @param authority [Qa::Authorities::LinkedData::GenericAuthority] the instance of the QA authority
22
+ # @param authoity_name [String] the name of the authority the scenario tests (e.g. "agrovoc_direct")
23
+ # @param scenarios_config [Hash] configurations from the yml file for all scenarios for an authority
24
+ def initialize(authority:, authority_name:, scenarios_config:)
25
+ @authority = authority
26
+ @authority_name = authority_name
27
+ @scenarios_config = scenarios_config
28
+ parse_term_scenarios
29
+ parse_search_scenarios
30
+ end
31
+
32
+ private
33
+
34
+ def parse_term_scenarios
35
+ @term_scenarios = []
36
+ term_scenarios_config.each do |term_scenario_config|
37
+ @term_scenarios << TermScenario.new(authority: authority,
38
+ authority_name: authority_name,
39
+ authority_scenario_config: authority_scenario_config,
40
+ scenario_config: term_scenario_config)
41
+ end
42
+ end
43
+
44
+ def parse_search_scenarios
45
+ @search_scenarios = []
46
+ search_scenarios_config.each do |search_scenario_config|
47
+ @search_scenarios << SearchScenario.new(authority: authority,
48
+ authority_name: authority_name,
49
+ authority_scenario_config: authority_scenario_config,
50
+ scenario_config: search_scenario_config)
51
+ end
52
+ end
53
+
54
+ def scenarios_config
55
+ @scenarios_config
56
+ end
57
+
58
+ def authority_scenario_config
59
+ scenarios_config[AUTHORITY_SCENARIO]
60
+ end
61
+
62
+ def term_scenarios_config
63
+ scenarios_config[TERM_SCENARIOS] || []
64
+ end
65
+
66
+ def search_scenarios_config
67
+ scenarios_config[SEARCH_SCENARIOS] || []
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,58 @@
1
+ # This class parses the search configuration from the yml file into the parts needed by the search scenario validator.
2
+ module QaServer
3
+ class SearchScenario < AuthorityScenario
4
+
5
+ # @return [String] query being executed by this scenario
6
+ attr_reader :query
7
+
8
+ # @return [Hash] replacements parameters used to construct the URL executed by this scenario
9
+ attr_reader :replacements
10
+
11
+ # @return [Integer] expected_by_position designates the maximum position in search results of subject_uri, if specified, for this scenario to be considered passing
12
+ attr_reader :expected_by_position
13
+
14
+ # @return [String] subject_uri, if specified, should be in the search results between position 1 and expected_by_position
15
+ attr_reader :subject_uri
16
+
17
+ MAX_RECORDS = '4'.freeze
18
+ DEFAULT_REPLACEMENTS = { maxRecords: MAX_RECORDS }
19
+ DEFAULT_POSITION = nil
20
+ DEFAULT_SUBJECT_URI = nil
21
+
22
+ # @param authority [Qa::Authorities::LinkedData::GenericAuthority] the instance of the QA authority
23
+ # @param authoity_name [Symbol] the name of the authority the scenario tests (e.g. :AGROVOC_DIRECT)
24
+ # @param authority_scenario_config [Hash] configurations from the yml file that pertain to all scenarios regardless of type
25
+ # @param scenario_config [Hash] configuration from the yml file that are specific to a search scenario
26
+ def initialize(authority:, authority_name:, authority_scenario_config:, scenario_config:)
27
+ super
28
+ @query = scenario_config['query']
29
+ @subauthority_name = scenario_config['subauth'] || DEFAULT_SUBAUTH
30
+ @min_result_size = scenario_config['result_size'] || MIN_EXPECTED_SIZE
31
+ @replacements = scenario_config['replacements'] || DEFAULT_REPLACEMENTS
32
+ @expected_by_position = scenario_config['position'] || DEFAULT_POSITION
33
+ @subject_uri = scenario_config['subject_uri'] || DEFAULT_SUBJECT_URI
34
+ end
35
+
36
+ # Generate an example URL that can be called in a browser or through curl
37
+ # @return [String] the example URL
38
+ def url
39
+ subauth = "/#{subauthority_name}" if subauthority?
40
+ prefix = "/#{QA_ENGINE_MOUNT}/search/linked_data/#{authority_name.downcase}#{subauth}"
41
+ "#{prefix}?q=#{query}#{url_replacements}"
42
+ end
43
+
44
+ private
45
+
46
+ # Convert replacements hash into URL parameters
47
+ def url_replacements
48
+ return "&maxRecords=#{MAX_RECORDS}" unless replacements
49
+ param_replacements = ""
50
+ replacements.each { |k, v| param_replacements += "&#{k}=#{v}" }
51
+ param_replacements
52
+ end
53
+
54
+ def subauthority?
55
+ subauthority_name.present?
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ require 'erb'
2
+
3
+ # This class parses the term configuration from the yml file into the parts needed by the term scenario validator.
4
+ module QaServer
5
+ class TermScenario < AuthorityScenario
6
+ include ERB::Util
7
+
8
+ # @return [String] id or uri of the term being fetched by this scenario
9
+ attr_reader :identifier
10
+
11
+ # @param authority [Qa::Authorities::LinkedData::GenericAuthority] the instance of the QA authority
12
+ # @param authoity_name [Symbol] the name of the authority the scenario tests (e.g. :AGROVOC_DIRECT)
13
+ # @param authority_scenario_config [Hash] configurations from the yml file that pertain to all scenarios regardless of type
14
+ # @param scenario_config [Hash] configuration from the yml file that are specific to a term scenario
15
+ def initialize(authority:, authority_name:, authority_scenario_config:, scenario_config:)
16
+ super
17
+ @identifier = scenario_config['identifier']
18
+ @subauthority_name = scenario_config['subauth'] || DEFAULT_SUBAUTH
19
+ @min_result_size = scenario_config['min_result_size'] || MIN_EXPECTED_SIZE
20
+ end
21
+
22
+ # Generate an example URL that can be called in a browser or through curl
23
+ # @return [String] the example URL
24
+ def url
25
+ subauth = "/#{subauthority_name}" if subauthority_name.present?
26
+ prefix = "/#{QA_ENGINE_MOUNT}/show/linked_data/#{authority_name.downcase}#{subauth}"
27
+ "#{prefix}/#{url_identifier}"
28
+ end
29
+
30
+ private
31
+
32
+ # Convert identifier into URL safe version with encoding if needed.
33
+ def url_identifier
34
+ return uri_encode(identifier) if encode?
35
+ identifier
36
+ end
37
+
38
+ def subauthority?
39
+ subauthority_name.present?
40
+ end
41
+
42
+ def encode?
43
+ authority.auth_config.term.term_id_expects_uri?
44
+ end
45
+
46
+ def uri_encode(uri)
47
+ url_encode(uri).gsub(".", "%2E")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ # This presenter class provides all data needed by the view that show the list of authorities.
2
+ module QaServer
3
+ class AuthorityListPresenter
4
+ def initialize(urls_data:)
5
+ @urls_data = urls_data
6
+ end
7
+
8
+ # @return [Array<Hash>] A list of status data for each scenario tested.
9
+ # @example
10
+ # { status: :PASS,
11
+ # status_label: '√',
12
+ # authority_name: 'LOCNAMES_LD4L_CACHE',
13
+ # subauthority_name: 'person',
14
+ # service: 'ld4l_cache',
15
+ # action: 'search',
16
+ # url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=4',
17
+ # err_message: '' }
18
+ def urls_data
19
+ @urls_data
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ # This presenter class provides all data needed by the view that checks the status of authorities.
2
+ module QaServer
3
+ class CheckStatusPresenter
4
+
5
+ # @param authorities_list [Array<String>] a list of all loaded authorities' names
6
+ # @param status_data [Array<Hash>] a list of status data for each scenario tested
7
+ def initialize(authorities_list:, connection_status_data:, accuracy_status_data:)
8
+ @authorities_list = authorities_list
9
+ @connection_status_data = connection_status_data
10
+ @accuracy_status_data = accuracy_status_data
11
+ end
12
+
13
+ # @return [Array<String>] A list of all loaded authorities' names
14
+ # @example ['AGROVOC_DIRECT', 'AGROVOC_LD4L_CACHE', 'LOCNAMES_LD4L_CACHE']
15
+ def authorities_list
16
+ @authorities_list
17
+ end
18
+
19
+ # @return [Array<Hash>] A list of status data for each connection scenario tested.
20
+ # @example
21
+ # [ { status: :PASS,
22
+ # status_label: '√',
23
+ # authority_name: 'LOCNAMES_LD4L_CACHE',
24
+ # subauthority_name: 'person',
25
+ # service: 'ld4l_cache',
26
+ # action: 'search',
27
+ # url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=4',
28
+ # err_message: '' }, ... ]
29
+ def connection_status_data
30
+ @connection_status_data
31
+ end
32
+
33
+ # @return [Array<Hash>] A list of status data for each accuracy scenario tested.
34
+ # @example
35
+ # [ { status: :PASS,
36
+ # status_label: '√',
37
+ # authority_name: 'LOCNAMES_LD4L_CACHE',
38
+ # subauthority_name: 'person',
39
+ # service: 'ld4l_cache',
40
+ # action: 'search',
41
+ # expected: 10,
42
+ # actual: 8,
43
+ # url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=20',
44
+ # err_message: '' }, ... ]
45
+ def accuracy_status_data
46
+ @accuracy_status_data
47
+ end
48
+
49
+ # @return [Boolean] true if status data exists; otherwise false
50
+ def connection_status_data?
51
+ @connection_status_data.present?
52
+ end
53
+
54
+ # @return [Boolean] true if status data exists; otherwise false
55
+ def accuracy_status_data?
56
+ @accuracy_status_data.present?
57
+ end
58
+
59
+ # @return [String] the name of the css style class to use for the status cell based on the status of the scenario test.
60
+ def status_style_class(status)
61
+ "status-#{status[:status].to_s}"
62
+ end
63
+
64
+ def value_all_collections
65
+ CheckStatusController::ALL_AUTHORITIES
66
+ end
67
+
68
+ def value_check_param
69
+ AuthorityValidationController::VALIDATION_TYPE_PARAM
70
+ end
71
+
72
+ def value_check_connections
73
+ AuthorityValidationController::VALIDATE_CONNECTIONS
74
+ end
75
+
76
+ def label_check_connections
77
+ "#{value_check_param}_#{value_check_connections}".downcase.to_sym
78
+ end
79
+
80
+ def value_check_accuracy
81
+ AuthorityValidationController::VALIDATE_ACCURACY
82
+ end
83
+
84
+ def label_check_accuracy
85
+ "#{value_check_param}_#{value_check_accuracy}".downcase.to_sym
86
+ end
87
+
88
+ def value_all_checks
89
+ AuthorityValidationController::ALL_VALIDATIONS
90
+ end
91
+
92
+ def label_all_checks
93
+ "#{value_check_param}_#{value_all_checks}".downcase.to_sym
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,108 @@
1
+ # This presenter class provides all data needed by the view that monitors status of authorities.
2
+ module QaServer
3
+ class MonitorStatusPresenter
4
+
5
+ # @param authority_count [Integer] number of loaded authorities
6
+ # @param authority_status [AuthorityStatus] summary status of the latest run of test scenarios
7
+ # @param current_data [Array<Hash>] current set of failures for the latest test run, if any
8
+ # @param historical_data [Array<Hash>] data for past failures
9
+ def initialize(authority_count:, authority_status:, current_data:, historical_data:)
10
+ @authority_count = authority_count
11
+ @authority_status = authority_status
12
+ @current_failures = current_data
13
+ @history = historical_data
14
+ end
15
+
16
+ # @return [String] date of last test run
17
+ def last_updated
18
+ @authority_status.dt_stamp.in_time_zone("Eastern Time (US & Canada)").strftime("%m/%d/%y - %I:%M %p")
19
+ end
20
+
21
+ # @return [String] date of first recorded test run
22
+ def first_updated
23
+ AuthorityStatus.first.dt_stamp.in_time_zone("Eastern Time (US & Canada)").strftime("%m/%d/%y - %I:%M %p")
24
+ end
25
+
26
+ # @return [Integer] number of loaded authorities
27
+ def authorities_count
28
+ @authority_count
29
+ end
30
+
31
+ # @return [Integer] number of authorities with failing tests in the latest test run
32
+ def failing_authorities_count
33
+ @current_failures.map { |f| f[:authority_name] }.uniq.count
34
+ end
35
+
36
+ # @return [String] css style class representing whether all tests passed or any failed
37
+ def authorities_count_style
38
+ failures? ? 'status-bad' : 'status-good'
39
+ end
40
+
41
+ # @return [Integer] number of tests in the latest test run
42
+ def tests_count
43
+ @authority_status.test_count
44
+ end
45
+
46
+ # @return [Integer] number of passing tests in the latest test run
47
+ def passing_tests_count
48
+ tests_count - failing_tests_count
49
+ end
50
+
51
+ # @return [Integer] number of failing tests in the latest test run
52
+ def failing_tests_count
53
+ @current_failures.count
54
+ end
55
+
56
+ # @return [String] css style class representing whether all tests passed or any failed
57
+ def failing_tests_style
58
+ failures? ? 'status-bad' : 'status-good'
59
+ end
60
+
61
+ # @return [Array<Hash>] A list of failures data in the latest test run, if any
62
+ # @example
63
+ # [ { status: :FAIL,
64
+ # status_label: 'X',
65
+ # authority_name: 'LOCNAMES_LD4L_CACHE',
66
+ # subauthority_name: 'person',
67
+ # service: 'ld4l_cache',
68
+ # action: 'search',
69
+ # url: '/qa/search/linked_data/locnames_ld4l_cache/person?q=mark twain&maxRecords=4',
70
+ # err_message: 'Exception: Something went wrong.' }, ... ]
71
+ def failures
72
+ @current_failures
73
+ end
74
+
75
+ # @return [Boolean] true if failure data exists for the latest test run; otherwise false
76
+ def failures?
77
+ failing_tests_count.positive?
78
+ end
79
+
80
+ # @return [Array<Hash>] historical test data to be displayed
81
+ # @example
82
+ # [ { days_failing: 1, authority_name: 'AGROVOC_DIRECT' },
83
+ # { days_failing: 3, authority_name: 'AGROVOC_LD4L_CACHE' },
84
+ # { days_failing: 0, authority_name: 'LOCNAMES_LD4L_CACHE' } ]
85
+ def history
86
+ # TODO: STUBED -- need to include history of past failures -- Question: How much data to save?
87
+ # Want to answer questions like...
88
+ # * # of failing days out of # of days run
89
+ # * # type of failures... can't load authority vs. exception vs. no data returned
90
+ # * for a given authority, did all tests fail or just a few?
91
+ # * are tests failing for a particular subauthority?
92
+ # * are tests failing for a particular type (search vs. term)
93
+ history = []
94
+ entry = { days_failing: 3,
95
+ authority_name: "FOO",
96
+ subauthority_name: "bar",
97
+ service: 'test',
98
+ action: 'search' }
99
+ history << entry
100
+ end
101
+
102
+ # @return [Boolean] true if historical test data exists; otherwise false
103
+ def history?
104
+ # TODO: STUBED -- need check for history data
105
+ false
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,31 @@
1
+ # Provide service methods for getting a list of all authorities and scenarios for an authority.
2
+ module QaServer
3
+ class AuthorityListerService
4
+
5
+ # Return a list of supported authorities
6
+ # @return [Array<String>] list of authorities
7
+ def self.authorities_list
8
+ LINKED_DATA_AUTHORITIES_CONFIG.keys.sort
9
+ end
10
+
11
+ # Fill in status_log with data about each scenario for an authority
12
+ # @param authority_name [String] the name of the authority
13
+ # @param status_log [ScenarioLogger] the log that will hold the data about the scenarios
14
+ def self.scenarios_list(authority_name:, status_log:)
15
+ scenarios = QaServer::ScenariosLoaderService.load(authority_name: authority_name, status_log: status_log)
16
+ return if scenarios.blank?
17
+ list_terms(scenarios, status_log)
18
+ list_searches(scenarios, status_log)
19
+ end
20
+
21
+ private
22
+
23
+ def self.list_terms(scenarios, status_log)
24
+ scenarios.term_scenarios.each { |scenario| QaServer::TermScenarioValidator.new(scenario: scenario, status_log: status_log).log_without_running }
25
+ end
26
+
27
+ def self.list_searches(scenarios, status_log)
28
+ scenarios.search_scenarios.each { |scenario| QaServer::SearchScenarioValidator.new(scenario: scenario, status_log: status_log).log_without_running }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ # This class loads an authority.
2
+ module QaServer
3
+ class AuthorityLoaderService
4
+
5
+ # Load a QA authority
6
+ # @param authority_name [String] name of the authority to load (e.g. "agrovoc_direct")
7
+ # @param status_log [ScenarioLogger] logger to hold failure information if the authority cannot be loaded
8
+ # @return [Qa::Authorities::LinkedData::GenericAuthority] the instance of the authority that can receive QA requests OR nil if fails to load
9
+ def self.load(authority_name:, status_log:)
10
+ begin
11
+ authority = load_authority(authority_name, status_log)
12
+ return nil if authority.blank?
13
+ rescue Exception => e
14
+ status_log.add(authority_name: authority_name,
15
+ status: ScenarioValidator::FAIL,
16
+ error_message: "Unable to load authority '#{authority_name}'; cause: #{e.message}")
17
+ return nil
18
+ end
19
+ authority
20
+ end
21
+
22
+ private
23
+ def self.authority_key(authority_name)
24
+ authority_name.upcase.to_sym
25
+ end
26
+
27
+ def self.load_authority(authority_name, status_log)
28
+ authority = Qa::Authorities::LinkedData::GenericAuthority.new(authority_key(authority_name))
29
+ if authority.blank?
30
+ status_log.add(authority_name: authority_name,
31
+ status: ScenarioValidator::FAIL,
32
+ error_message: "Unable to load authority '#{authority_name}'; cause: UNKNOWN") unless authority.present?
33
+ return nil
34
+ end
35
+ authority
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ # Provide service methods for running a a set of validation scenarios for an authority.
2
+ module QaServer
3
+ class AuthorityValidatorService
4
+ class_attribute :validator_class,
5
+ :term_validator_class,
6
+ :search_validator_class,
7
+ :scenarios_loader_class
8
+
9
+ self.validator_class = ScenarioValidator
10
+ self.term_validator_class = TermScenarioValidator
11
+ self.search_validator_class = SearchScenarioValidator
12
+ self.scenarios_loader_class = ScenariosLoaderService
13
+
14
+ VALIDATE_CONNECTIONS = validator_class::VALIDATE_CONNECTION
15
+ VALIDATE_ACCURACY = validator_class::VALIDATE_ACCURACY
16
+ ALL_VALIDATIONS = validator_class::ALL_VALIDATIONS
17
+ DEFAULT_VALIDATION_TYPE = validator_class::DEFAULT_VALIDATION_TYPE
18
+
19
+ # Run the set of validation scenarios for an authority logging the results
20
+ # @param authority_name [String] the name of the authority
21
+ # @param status_log [ScenarioLogger] the log that will hold the data about the scenarios and test results
22
+ # @param validation_type [Symbol] the type of scenarios to run (e.g. VALIDATE_CONNECTIONS, VALIDATE_ACCURACY, ALL_VALIDATIONS)
23
+ def self.run(authority_name:, status_log:, validation_type: DEFAULT_VALIDATION_TYPE)
24
+ scenarios = scenarios_loader_class.load(authority_name: authority_name, status_log: status_log)
25
+ return if scenarios.blank?
26
+ run_terms(scenarios, status_log, validation_type)
27
+ run_searches(scenarios, status_log, validation_type)
28
+ end
29
+
30
+
31
+ private
32
+
33
+ def self.run_terms(scenarios, status_log, validation_type)
34
+ scenarios.term_scenarios.each { |scenario| term_validator_class.new(scenario: scenario, status_log: status_log, validation_type: validation_type).run }
35
+ end
36
+
37
+ def self.run_searches(scenarios, status_log, validation_type)
38
+ scenarios.search_scenarios.each { |scenario| search_validator_class.new(scenario: scenario, status_log: status_log, validation_type: validation_type).run }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,70 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module QaServer
4
+ # QaServer::DatabaseMigrator is responsible for copying QaServer's required database
5
+ # migrations into applications. Rails engines typically use the built-in
6
+ # `{ENGINE_NAME}:install:migrations` task to handle this; instead QaServer
7
+ # follows the practice used by Devise to dynamically subclass migrations with
8
+ # the version of `ActiveRecord::Migration` corresponding to the version of
9
+ # Rails used by the application.
10
+ #
11
+ # @note QaServer::DatabaseMigrator uses Rails' generator internals to avoid
12
+ # having to re-implement code that knows how to copy migrations only if
13
+ # needed
14
+ class DatabaseMigrator < Rails::Generators::Base
15
+ # @note included to pick up AR's migration numbering algorithm
16
+ include ActiveRecord::Generators::Migration
17
+
18
+ # @note This method is required by Rails::Generators::Base
19
+ def self.source_root
20
+ migrations_dir
21
+ end
22
+
23
+ def self.migrations_dir
24
+ QaServer::Engine.root.join('lib', 'generators', 'qa_server', 'templates')
25
+ end
26
+
27
+ def self.copy
28
+ new.copy
29
+ end
30
+
31
+ # def copy
32
+ # # QA Server's migrations changed between 2.0.0 and subsequent versions, so the
33
+ # # migration comparison algorithm decides that those older migrations are a
34
+ # # source of conflict. Default Rails behavior here is to abort and
35
+ # # explicitly instruct the user to try again with either the `--skip` or
36
+ # # `--force` option. QA Server skips these conflicts.
37
+ # migrations.each do |filename|
38
+ # migration_template filename,
39
+ # "db/migrate/#{parse_basename_from(filename)}",
40
+ # migration_version: migration_version,
41
+ # skip: true
42
+ # end
43
+ # end
44
+ def copy
45
+ migrations.each do |filename|
46
+ migration_template filename,
47
+ "db/migrate/#{parse_basename_from(filename)}",
48
+ migration_version: migration_version
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def migrations
55
+ Dir.chdir(self.class.migrations_dir) { Dir.glob('db/migrate/[0-9]*_*.rb.erb') }.sort
56
+ end
57
+
58
+ def parse_basename_from(filename)
59
+ # Add engine name to filename to mimic ``ActiveRecord::Migration.copy` behavior
60
+ filename.slice(/(?<dateprefix>\d)+_(?<basename>.+)\.erb/, 'basename').sub('.', '.qa_server.')
61
+ end
62
+
63
+ def migration_version
64
+ # Don't use AR migration versioning in Rails 4
65
+ return if Rails.version < '5.0.0'
66
+ # Specify the current major.minor version of Rails for AR migrations
67
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ # This class loads scenario configuration file for an authority.
2
+ module QaServer
3
+ class ScenariosLoaderService
4
+
5
+ # Load scenarios for testing an authority
6
+ # @param authority_name [String] name of the authority to load (e.g. "agrovoc_direct")
7
+ # @param status_log [ScenarioLogger] logger to hold failure information if the scenarios cannot be loaded
8
+ # @return [Scenarios] the instance of the set of scenarios to test for the authority OR nil if fails to load
9
+ def self.load(authority_name:, status_log:)
10
+ begin
11
+ authority = load_authority(authority_name, status_log)
12
+ return nil if authority.blank?
13
+ return nil unless scenarios_exist?(authority_name, status_log)
14
+
15
+ scenarios_config = load_config(authority_name, status_log)
16
+ return nil if scenarios_config.blank?
17
+
18
+ scenarios = Scenarios.new(authority: authority, authority_name: authority_name, scenarios_config: scenarios_config)
19
+ rescue Exception => e
20
+ status_log.add(authority_name: authority_name,
21
+ status: ScenarioValidator::FAIL,
22
+ error_message: "Unable to load scenarios for authority '#{authority_name}'; cause: #{e.message}")
23
+ return nil
24
+ end
25
+ scenarios
26
+ end
27
+
28
+ private
29
+
30
+ def self.load_authority(authority_name, status_log)
31
+ AuthorityLoaderService.load(authority_name: authority_name, status_log: status_log)
32
+ end
33
+
34
+ def self.load_config(authority_name, status_log)
35
+ scenarios_config = YAML.load_file(scenario_path(authority_name))
36
+ unless scenarios_config.present?
37
+ status_log.add(authority_name: authority_name,
38
+ status: ScenarioValidator::FAIL,
39
+ error_message: "Unable to load scenarios for authority '#{authority_name}'; cause: UNKNOWN")
40
+ return nil
41
+ end
42
+ scenarios_config
43
+ end
44
+
45
+ def self.scenarios_exist?(authority_name, status_log)
46
+ return true if File.exists?(scenario_path(authority_name))
47
+ status_log.add(authority_name: authority_name,
48
+ status: ScenarioValidator::FAIL,
49
+ error_message: "Unable to load scenarios for authority '#{authority_name}'; cause: #{scenario_path} does not exist.")
50
+ false
51
+ end
52
+
53
+ def self.scenario_path(authority_name)
54
+ File.join(::Rails.root, 'config', 'authorities', 'linked_data', 'scenarios', "#{authority_name.downcase}_validation.yml")
55
+ end
56
+ end
57
+ end