qa_server 0.1.99

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