dragnet 5.2.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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/release.yml +45 -0
  3. data/.github/workflows/ruby-linters.yml +41 -0
  4. data/.github/workflows/ruby-tests.yml +38 -0
  5. data/.github/workflows/sphinx-doc.yml +79 -0
  6. data/.gitignore +13 -0
  7. data/.reek.yml +15 -0
  8. data/.rspec +3 -0
  9. data/.rubocop.yml +14 -0
  10. data/.ruby-version +1 -0
  11. data/.travis.yml +6 -0
  12. data/CHANGELOG.md +178 -0
  13. data/Gemfile +18 -0
  14. data/README.md +119 -0
  15. data/Rakefile +8 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/default.reek +7 -0
  19. data/dragnet.gemspec +39 -0
  20. data/exe/dragnet +9 -0
  21. data/lib/dragnet/base_repository.rb +69 -0
  22. data/lib/dragnet/cli/base.rb +72 -0
  23. data/lib/dragnet/cli/logger.rb +72 -0
  24. data/lib/dragnet/cli/master.rb +173 -0
  25. data/lib/dragnet/cli.rb +8 -0
  26. data/lib/dragnet/errors/error.rb +8 -0
  27. data/lib/dragnet/errors/file_not_found_error.rb +11 -0
  28. data/lib/dragnet/errors/incompatible_repository_error.rb +10 -0
  29. data/lib/dragnet/errors/missing_timestamp_attribute_error.rb +11 -0
  30. data/lib/dragnet/errors/no_mtr_files_found_error.rb +11 -0
  31. data/lib/dragnet/errors/not_a_repository_error.rb +11 -0
  32. data/lib/dragnet/errors/repo_path_not_found_error.rb +10 -0
  33. data/lib/dragnet/errors/unable_to_write_report_error.rb +11 -0
  34. data/lib/dragnet/errors/unknown_export_format_error.rb +11 -0
  35. data/lib/dragnet/errors/validation_error.rb +11 -0
  36. data/lib/dragnet/errors/yaml_format_error.rb +9 -0
  37. data/lib/dragnet/errors.rb +9 -0
  38. data/lib/dragnet/explorer.rb +103 -0
  39. data/lib/dragnet/exporter.rb +130 -0
  40. data/lib/dragnet/exporters/exporter.rb +29 -0
  41. data/lib/dragnet/exporters/html_exporter.rb +158 -0
  42. data/lib/dragnet/exporters/id_generator.rb +35 -0
  43. data/lib/dragnet/exporters/json_exporter.rb +34 -0
  44. data/lib/dragnet/exporters/serializers/repo_serializer.rb +40 -0
  45. data/lib/dragnet/exporters/serializers/test_record_serializer.rb +101 -0
  46. data/lib/dragnet/exporters/serializers/verification_result_serializer.rb +34 -0
  47. data/lib/dragnet/exporters/serializers.rb +12 -0
  48. data/lib/dragnet/exporters/templates/template.html.erb +518 -0
  49. data/lib/dragnet/exporters.rb +13 -0
  50. data/lib/dragnet/helpers/repository_helper.rb +27 -0
  51. data/lib/dragnet/multi_repository.rb +48 -0
  52. data/lib/dragnet/repo.rb +31 -0
  53. data/lib/dragnet/repository.rb +77 -0
  54. data/lib/dragnet/test_record.rb +91 -0
  55. data/lib/dragnet/validator.rb +79 -0
  56. data/lib/dragnet/validators/data_validator.rb +75 -0
  57. data/lib/dragnet/validators/entities/repo_validator.rb +31 -0
  58. data/lib/dragnet/validators/entities/test_record_validator.rb +93 -0
  59. data/lib/dragnet/validators/entities.rb +12 -0
  60. data/lib/dragnet/validators/fields/description_validator.rb +24 -0
  61. data/lib/dragnet/validators/fields/field_validator.rb +69 -0
  62. data/lib/dragnet/validators/fields/files_validator.rb +29 -0
  63. data/lib/dragnet/validators/fields/id_validator.rb +36 -0
  64. data/lib/dragnet/validators/fields/meta_data_field_validator.rb +36 -0
  65. data/lib/dragnet/validators/fields/path_validator.rb +22 -0
  66. data/lib/dragnet/validators/fields/repos_validator.rb +62 -0
  67. data/lib/dragnet/validators/fields/result_validator.rb +33 -0
  68. data/lib/dragnet/validators/fields/sha1_validator.rb +42 -0
  69. data/lib/dragnet/validators/fields.rb +17 -0
  70. data/lib/dragnet/validators/files_validator.rb +85 -0
  71. data/lib/dragnet/validators/repos_validator.rb +74 -0
  72. data/lib/dragnet/validators/validator.rb +20 -0
  73. data/lib/dragnet/validators.rb +12 -0
  74. data/lib/dragnet/verification_result.rb +125 -0
  75. data/lib/dragnet/verifier.rb +64 -0
  76. data/lib/dragnet/verifiers/changes_verifier.rb +70 -0
  77. data/lib/dragnet/verifiers/files_verifier.rb +51 -0
  78. data/lib/dragnet/verifiers/repos_verifier.rb +92 -0
  79. data/lib/dragnet/verifiers/repository_verifier.rb +27 -0
  80. data/lib/dragnet/verifiers/result_verifier.rb +32 -0
  81. data/lib/dragnet/verifiers/test_record_verifier.rb +85 -0
  82. data/lib/dragnet/verifiers/verifier.rb +37 -0
  83. data/lib/dragnet/verifiers.rb +8 -0
  84. data/lib/dragnet/version.rb +5 -0
  85. data/lib/dragnet.rb +18 -0
  86. metadata +190 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'base_repository'
6
+
7
+ module Dragnet
8
+ # A small wrapper around a Git Repository object. It provides some useful
9
+ # methods needed during the verify process as well as for reporting.
10
+ class Repository < Dragnet::BaseRepository
11
+ extend Forwardable
12
+
13
+ attr_reader :git
14
+
15
+ def_delegators :@git, :branch, :branches, :diff
16
+
17
+ # Creates a new instance of the class. Tries to open the given path as a Git
18
+ # repository.
19
+ # @param [Pathname] path The path where the root of the repository is located.
20
+ # @raise [ArgumentError] If the given path is not a valid git repository.
21
+ def initialize(path:)
22
+ super
23
+ @git = Git.open(path)
24
+ end
25
+
26
+ # @return [Git::Object::Commit] The +Commit+ object at the +HEAD+ of the
27
+ # repository.
28
+ def head
29
+ @head ||= git.object('HEAD')
30
+ end
31
+
32
+ # Returns the URI path of the repository (extracted from its first remote
33
+ # [assumed to be the origin]). Example:
34
+ #
35
+ # ssh://jenkins@gerrit.int.esrlabs.com:29418/tools/dragnet -> /tools/dragnet
36
+ #
37
+ # @return [String] The URI path of the repository
38
+ def remote_uri_path
39
+ URI.parse(git.remotes.first.url).path
40
+ end
41
+
42
+ # @return [FalseClass] It always returns false
43
+ def multi?
44
+ false
45
+ end
46
+
47
+ # Returns an array of all the branches that include the given commit.
48
+ # @param [String] commit The SHA1 of the commit to look for.
49
+ # @return [Array<Git::Branch>] An array with all the branches that contain
50
+ # the given commit.
51
+ def branches_with(commit)
52
+ branches.select { |branch| branch.contains?(commit) }
53
+ end
54
+
55
+ # Returns an array of all the branches that include the current HEAD.
56
+ # @return [Array<Git::Branch>] An array with all the branches that contain
57
+ # the current HEAD.
58
+ def branches_with_head
59
+ @branches_with_head ||= branches_with(head.sha)
60
+ end
61
+
62
+ private
63
+
64
+ # @param [Symbol] method_name The name of the method that was invoked.
65
+ # @raise [Dragnet::Errors::IncompatibleRepositoryError] Is always raised
66
+ # with a description of the method that was invoked and a possible cause
67
+ # for the failure.
68
+ def incompatible_repository(method_name)
69
+ super(
70
+ "Failed to perform the action '#{method_name}' on '#{path}'."\
71
+ ' The path was not set-up as a multi-repo path. If you are running'\
72
+ ' without the --multi-repo command line switch make sure that none of'\
73
+ " your MTRs have a 'repos' attribute or run with the --multi-repo switch"
74
+ )
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validators/entities/test_record_validator'
4
+
5
+ module Dragnet
6
+ # Represents a Manual Test Record loaded from a MTR file.
7
+ class TestRecord
8
+ PASSED_RESULT = 'passed'
9
+ REVIEWED_STATUS = 'reviewed'
10
+ NO_FINDINGS = 'no findings'
11
+
12
+ # :reek:Attribute (This is an entity class)
13
+
14
+ attr_accessor :id, :result, :sha1, :name, :description, :files, :repos,
15
+ :review_status, :review_comments, :findings, :test_method,
16
+ :tc_derivation_method, :source_file, :verification_result
17
+
18
+ # rubocop:disable Metrics/AbcSize (There isn't much that can be done here,
19
+ # those are the attributes an MTR has).
20
+ # :reek:FeatureEnvy (Refers to args as much as it refers to itself)
21
+
22
+ # Creates a new instance of the class.
23
+ # @param [Hash] args The data for the Manual Test Record
24
+ # @option args [String] :id The ID of the MTR
25
+ # @option args [String] :result The result of the Manual Test.
26
+ # @option args [String] :sha1 The SHA1 of the commit in which the Manual
27
+ # Test was performed.
28
+ # @option args [String, Array<String>, nil] :name The name of the person who
29
+ # performed the Manual Test.
30
+ # @option args [String, nil] :description The description of the Manual
31
+ # Test, normally which actions were performed and what it was mean to
32
+ # test.
33
+ # @option args [String, Array<String>, nil] :files The files involved in the
34
+ # MTR, these are the files which will be checked for changes when
35
+ # evaluating the validity of the MTR.
36
+ # @option args [Array<Hash>, nil] :repos An array of +Hash+es with the
37
+ # information about the repositories that are involved in the MTR, these
38
+ # repositories will be checked for changes during the evaluation of the
39
+ # MTR.
40
+ # @option args [String, nil] :review_status or :reviewstatus The review
41
+ # status of the MTR. (Normally changed when someone other than the tester
42
+ # verifies the result of the Manual Test)
43
+ # @option args [String, nil] :review_comments or :reviewcomments The
44
+ # comments left by the person who performed the review of the Manual Test.
45
+ # @option args [String, nil] :findings The findings that the reviewer
46
+ # collected during the review process (if any).
47
+ # @option args [String, Array<String>, nil] :test_method The method(s) used
48
+ # to carry out the test.
49
+ # @option args [String, Array<String>, nil] :tc_derivation_method: The
50
+ # method(s) used to derive the test case,
51
+ # @note Either +:files+ or +:repos+ should be present, not both.
52
+ def initialize(args)
53
+ @id = args[:id]
54
+ @result = args[:result]
55
+ @sha1 = args[:sha1]
56
+ @name = args[:name]
57
+ @description = args[:description]
58
+ @files = args[:files]
59
+ @repos = args[:repos]
60
+ @review_status = args[:review_status] || args[:reviewstatus]
61
+ @review_comments = args[:review_comments] || args[:reviewcomments]
62
+ @findings = args[:findings]
63
+ @test_method = args[:test_method]
64
+ @tc_derivation_method = args[:tc_derivation_method]
65
+ end
66
+ # rubocop:enable Metrics/AbcSize
67
+
68
+ # Validates the MTR's fields
69
+ # @raise [Dragnet::Errors::ValidationError] If the validation fails.
70
+ def validate
71
+ Dragnet::Validators::Entities::TestRecordValidator.new(self).validate
72
+ end
73
+
74
+ # @return [Boolean] True if the Manual Test passed, false otherwise.
75
+ def passed?
76
+ result == PASSED_RESULT
77
+ end
78
+
79
+ # @return [Boolean] True if the Manual Test Record has been reviewed, false
80
+ # otherwise.
81
+ def reviewed?
82
+ review_status == REVIEWED_STATUS
83
+ end
84
+
85
+ # @return [Boolean] True if the Manual Test Record has findings (problems
86
+ # annotated during the review), false otherwise.
87
+ def findings?
88
+ !(findings.nil? || findings.strip.empty? || findings.downcase == NO_FINDINGS)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+
5
+ require_relative 'validators/data_validator'
6
+ require_relative 'validators/files_validator'
7
+ require_relative 'validators/repos_validator'
8
+
9
+ module Dragnet
10
+ # Validates a set of Manual Test Record files. That means, checking that they
11
+ # can be read, that they are valid YAML files, that they have the expected
12
+ # keys and that these keys have sensible values.
13
+ class Validator
14
+ attr_reader :files, :path, :logger, :errors, :valid_files
15
+
16
+ # Creates a new instance of the class.
17
+ # @param [Array<Pathname>] files An array with the MTR files to validate.
18
+ # @param [Pathname] path The path where the MTR files are located.
19
+ # @param [#info, #error] logger A logger object to use for output.
20
+ def initialize(files:, path:, logger:)
21
+ @files = files
22
+ @path = path
23
+ @logger = logger
24
+ end
25
+
26
+ # Validates the given files.
27
+ # @return [Array<Dragnet::TestRecord>] An array of +TestRecord+s, one for
28
+ # each valid MTR file (invalid files will be added to the +errors+ array).
29
+ # The returned hash has the following structure:
30
+ def validate
31
+ logger.info('Validating MTR Files...')
32
+
33
+ @errors = []
34
+ @valid_files = files.map { |file| validate_file(file) }.compact
35
+ end
36
+
37
+ private
38
+
39
+ # Validates the given file
40
+ # @param [Pathname] file The file to be validated.
41
+ # @return [Dragnet::TestRecord, nil] A +TestRecord+ object or +nil+ if the
42
+ # file is invalid.
43
+ # rubocop:disable Metrics/AbcSize (because of logging).
44
+ def validate_file(file)
45
+ logger.info "Validating #{file}..."
46
+ data = YAML.safe_load(File.read(file))
47
+ test_record = Dragnet::Validators::DataValidator.new(data, file).validate
48
+ Dragnet::Validators::FilesValidator.new(test_record, path).validate
49
+ Dragnet::Validators::ReposValidator.new(test_record, path).validate
50
+
51
+ logger.info "#{'✔ SUCCESS'.colorize(:light_green)} #{file} Successfully loaded"
52
+ test_record
53
+ rescue SystemCallError => e
54
+ push_error(file, 'IO Error: Cannot read the specified file', e)
55
+ rescue Psych::Exception => e
56
+ push_error(file, 'YAML Parsing Error', e)
57
+ rescue Dragnet::Errors::YAMLFormatError => e
58
+ push_error(file, 'YAML Formatting Error', e)
59
+ rescue Dragnet::Errors::FileNotFoundError => e
60
+ push_error(file, 'Referenced file not found in repository', e)
61
+ rescue Dragnet::Errors::RepoPathNotFoundError => e
62
+ push_error(file, 'Referenced repository not found', e)
63
+ end
64
+ # rubocop:enable Metrics/AbcSize
65
+
66
+ # Pushes an entry into the +errors+ array.
67
+ # @param [Pathname] file The file that contains the error.
68
+ # @param [String] message A general description of the message.
69
+ # @param [Exception] exception The raised exception (through which the file
70
+ # was branded invalid)
71
+ # @return [nil] Returns nil so that +validate_file+ can return nil for
72
+ # invalid files.
73
+ def push_error(file, message, exception)
74
+ errors << { file: file, message: message, exception: exception }
75
+ logger.error "#{'✘ FAILED'.colorize(:light_red)} #{file} Failed: #{message} - #{exception.message}"
76
+ nil
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/hash/keys'
5
+
6
+ require_relative '../test_record'
7
+ require_relative 'validator'
8
+
9
+ module Dragnet
10
+ module Validators
11
+ # Validates the data (key-value pairs) inside an MTR file. Verifies the
12
+ # structure, the required keys and their types.
13
+ class DataValidator < Dragnet::Validators::Validator
14
+ attr_reader :data, :source_file
15
+
16
+ # Creates a new instance of the class
17
+ # @param [Hash] data The data inside the YAML (after parsing)
18
+ # @param [Pathname] source_file The path to the file from which the MTR
19
+ # data was loaded.
20
+ def initialize(data, source_file)
21
+ @data = data
22
+ @source_file = source_file
23
+ end
24
+
25
+ # Validates the given data
26
+ # @return [Dragnet::TestRecord] A +TestRecord+ object created
27
+ # from the given data (if the data was valid).
28
+ # @raise [Dragnet::Errors::YAMLFormatError] If the data is invalid. The
29
+ # raised exceptions contains a message specifying why the data is
30
+ # invalid.
31
+ def validate
32
+ yaml_format_error("Incompatible data structure. Expecting a Hash, got a #{data.class}") unless data.is_a?(Hash)
33
+ data.deep_symbolize_keys!
34
+
35
+ # A call to chomp for strings is needed because the following YAML
36
+ # syntax:
37
+ #
38
+ # findings: >
39
+ # no findings
40
+ #
41
+ # causes the string values to end with a newline ("\n"):
42
+ data.transform_values! { |value| value.is_a?(String) ? value.chomp : value }
43
+ test_record = create_mtr(data)
44
+ validate_mtr(test_record)
45
+ end
46
+
47
+ private
48
+
49
+ # @param [Hash] data A hash with the data for the +TestRecord+
50
+ # @see Dragnet::TestRecord#initialize
51
+ def create_mtr(data)
52
+ Dragnet::TestRecord.new(data).tap do |test_record|
53
+ test_record.source_file = source_file
54
+ end
55
+ end
56
+
57
+ # Creates a +Dragnet::TestRecord+ with the given data and runs its
58
+ # validation. If the validation is successful the +TestRecord+
59
+ # object is returned, if the validation fails an error is raised.
60
+ # @param [Dragnet::TestRecord] test_record The +TestRecord+ object to
61
+ # validate.
62
+ # @return [Dragnet::TestRecord] The given +TestRecord+ object if the
63
+ # validation passed.
64
+ # @raise [Dragnet::Errors::YAMLFormatError] If the data is invalid. The
65
+ # raised exceptions contains a message specifying why the data is
66
+ # invalid.
67
+ def validate_mtr(test_record)
68
+ test_record.validate
69
+ test_record
70
+ rescue Dragnet::Errors::ValidationError => e
71
+ yaml_format_error(e.message)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../fields/sha1_validator'
4
+ require_relative '../fields/files_validator'
5
+ require_relative '../fields/path_validator'
6
+
7
+ module Dragnet
8
+ module Validators
9
+ module Entities
10
+ # Validates a +Dragnet::Repo+ object, by checking its attributes.
11
+ class RepoValidator
12
+ attr_reader :repo
13
+
14
+ # @param [Dragnet::Repo] repo An instance of +Dragnet::Repo+ to validate.
15
+ def initialize(repo)
16
+ @repo = repo
17
+ end
18
+
19
+ # Validates the instance of the +Dragnet::Repo+ object by checking each
20
+ # of its attributes.
21
+ # @raise [Dragnet::Errors::ValidationError] If any of the fields in the
22
+ # given +Dragnet::Repo+ object fails the validation.
23
+ def validate
24
+ Dragnet::Validators::Fields::SHA1Validator.new.validate('repos[sha1]', repo.sha1)
25
+ Dragnet::Validators::Fields::PathValidator.new.validate('repos[path]', repo.path)
26
+ repo.files = Dragnet::Validators::Fields::FilesValidator.new.validate('repos[files]', repo.files)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../fields/description_validator'
4
+ require_relative '../fields/files_validator'
5
+ require_relative '../fields/id_validator'
6
+ require_relative '../fields/meta_data_field_validator'
7
+ require_relative '../fields/repos_validator'
8
+ require_relative '../fields/result_validator'
9
+ require_relative '../fields/sha1_validator'
10
+
11
+ require_relative '../../errors/validation_error'
12
+
13
+ module Dragnet
14
+ module Validators
15
+ module Entities
16
+ # Validates a MTR object
17
+ class TestRecordValidator
18
+ attr_reader :test_record
19
+
20
+ # Creates a new instance of the class.
21
+ # @param [Dragnet::TestRecord] test_record The test record to validate.
22
+ def initialize(test_record)
23
+ @test_record = test_record
24
+ end
25
+
26
+ # Validates the given test record
27
+ # @raise [Dragnet::Errors::ValidationError] If the validation fails.
28
+ def validate
29
+ repos_xor_files
30
+ repos_xor_sha1
31
+
32
+ Dragnet::Validators::Fields::IDValidator.new.validate('id', test_record.id)
33
+ Dragnet::Validators::Fields::DescriptionValidator.new.validate('description', test_record.description)
34
+ validate_meta_data_fields
35
+
36
+ test_record.files = Dragnet::Validators::Fields::FilesValidator.new.validate('files', test_record.files)
37
+ test_record.result = Dragnet::Validators::Fields::ResultValidator.new.validate('result', test_record.result)
38
+ end
39
+
40
+ private
41
+
42
+ # @raise [Dragnet::Errors::ValidationError] If the MTR has both a
43
+ # +files+ and a +repos+ attribute.
44
+ def repos_xor_files
45
+ return unless test_record.files && test_record.repos
46
+
47
+ raise Dragnet::Errors::ValidationError,
48
+ "Invalid MTR: #{test_record.id}. Either 'files' or 'repos' should be provided, not both"
49
+ end
50
+
51
+ # Executes the validation over the +repos+ attribute and then verifies
52
+ # if the +sha1+ attribute was also given. If it was, an error is
53
+ # raised. If +repos+ is not present, then the +sha1+ attribute is
54
+ # validated.
55
+ #
56
+ # This happens in this order to leverage the fact that the
57
+ # +ReposValidator+ returns +nil+ for empty +Array+s. So if +repos+ is
58
+ # given as en empty +Array+ the MTR will still be considered valid
59
+ # (provided it has a SHA1).
60
+ #
61
+ # @raise [Dragnet::Errors::ValidationError] If the validation of the
62
+ # +repos+ attribute fails, if both +repos+ and +sha1+ are present or
63
+ # if the validation of the +sha1+ attribute fails.
64
+ def repos_xor_sha1
65
+ test_record.repos = Dragnet::Validators::Fields::ReposValidator.new.validate('repos', test_record.repos)
66
+
67
+ unless test_record.repos
68
+ Dragnet::Validators::Fields::SHA1Validator.new.validate('sha1', test_record.sha1)
69
+ return
70
+ end
71
+
72
+ return unless test_record.sha1
73
+
74
+ raise Dragnet::Errors::ValidationError,
75
+ "Invalid MTR: #{test_record.id}. Either 'repos' or 'sha1' should be provided, not both"
76
+ end
77
+
78
+ # Validates the meta-data fields of the Test Record.
79
+ # @raise [Dragnet::Errors::ValidationError] If any of the meta-data
80
+ # fields fail the validation.
81
+ def validate_meta_data_fields
82
+ meta_data_validator = Dragnet::Validators::Fields::MetaDataFieldValidator.new
83
+
84
+ test_record.name = meta_data_validator.validate('name', test_record.name)
85
+ test_record.test_method = meta_data_validator.validate('test_method', test_record.test_method)
86
+ test_record.tc_derivation_method = meta_data_validator.validate(
87
+ 'tc_derivation_method', test_record.tc_derivation_method
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entities/repo_validator'
4
+ require_relative 'entities/test_record_validator'
5
+
6
+ module Dragnet
7
+ module Validators
8
+ # Namespace for Dragnet's Entity Validators (validators that deal with data
9
+ # objects / models).
10
+ module Entities; end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'field_validator'
4
+
5
+ module Dragnet
6
+ module Validators
7
+ module Fields
8
+ # Validates the +description+ field for a MTR.
9
+ class DescriptionValidator < Dragnet::Validators::Fields::FieldValidator
10
+ # Validates a MTR's description
11
+ # @param [String] key The name of the key
12
+ # @param [Object] value The value of the key
13
+ # @raise [Dragnet::Errors::ValidationError] If the description contains
14
+ # anything but a +String+ or +nil+.
15
+ # :reek:NilCheck (Only +nil+ is allowed, +false+ should be considered invalid).
16
+ def validate(key, value)
17
+ return if value.nil?
18
+
19
+ validate_type(key, value, String)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../errors/validation_error'
4
+
5
+ module Dragnet
6
+ module Validators
7
+ module Fields
8
+ # Base class for all the validators used to validate individual fields
9
+ # inside entities.
10
+ class FieldValidator
11
+ def validate(_key, _value)
12
+ raise NotImplementedError, "#validate method not implemented in #{self.class}"
13
+ end
14
+
15
+ private
16
+
17
+ # Validates the presence of a value
18
+ # @param [String, Symbol] key The key associated with the value.
19
+ # @param [Object] value The value to validate.
20
+ # @raise [Dragnet::Errors::ValidationError] If the given value is not
21
+ # present (i.e. is +nil+)
22
+ def validate_presence(key, value)
23
+ validation_error("Missing required key: #{key}") if value.nil?
24
+ end
25
+
26
+ # Validates the type of the given value.
27
+ # @param [String, Symbol] key The key associated with the value.
28
+ # @param [Object] value The value to validate.
29
+ # @param [Array<Class>] expected_types The allowed types for the given
30
+ # value.
31
+ # @raise [Dragnet::Errors::ValidationError] If the given value has a type
32
+ # which is not in the given array of expected types.
33
+ def validate_type(key, value, *expected_types)
34
+ return if expected_types.include?(value.class)
35
+
36
+ validation_error(
37
+ "Incompatible type for key #{key}: "\
38
+ "Expected #{expected_types.join(', ')} got #{value.class} instead"
39
+ )
40
+ end
41
+
42
+ # Raises a +Dragnet::Errors::ValidationError+ with the given message.
43
+ # @param [String] message The message for the error.
44
+ # @raise [Dragnet::Errors::ValidationError] Is always raised.
45
+ def validation_error(message)
46
+ raise Dragnet::Errors::ValidationError, message
47
+ end
48
+
49
+ # Validates that all elements inside the given array are of the
50
+ # expected type
51
+ # @param [String, Symbol] key The key associated with the array.
52
+ # @param [Array] array The array whose types should be checked.
53
+ # @param [Class] expected_type The type the elements inside the array
54
+ # should have.
55
+ # @raise [Dragnet::Errors::ValidationError] If any of the elements inside
56
+ # the given array is of a different type.
57
+ def validate_array_types(key, array, expected_type)
58
+ incompatible_value = array.find { |val| !val.is_a?(expected_type) }
59
+ return unless incompatible_value
60
+
61
+ validation_error(
62
+ "Incompatible type for key #{key}: Expected a Array<#{expected_type}>. "\
63
+ "Found a(n) #{incompatible_value.class} inside the array"
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'field_validator'
4
+
5
+ module Dragnet
6
+ module Validators
7
+ module Fields
8
+ # Validates the files field on a Manual Test Record
9
+ class FilesValidator < Dragnet::Validators::Fields::FieldValidator
10
+ # Validates the MTR's +files+ array.
11
+ # @param [String] key The name of the key
12
+ # @param [Object] value The value of the key
13
+ # @return [Array<String>, nil] If +files+ is an Array or a String then
14
+ # an array is returned, if +files+ is +nil+ then +nil+ is returned.
15
+ # @raise [Dragnet::Errors::ValidationError] If the +files+ key is not a
16
+ # +String+ or an +Array+ of +String+s.
17
+ def validate(key, value)
18
+ return unless value
19
+
20
+ validate_type(key, value, String, Array)
21
+ value = *value
22
+ validate_array_types(key, value, String)
23
+
24
+ value
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'field_validator'
4
+
5
+ module Dragnet
6
+ module Validators
7
+ module Fields
8
+ # Validates the ID Field for Manual Test Records
9
+ class IDValidator < Dragnet::Validators::Fields::FieldValidator
10
+ # Validates the Requirement ID(s) of the MTR
11
+ # @param [String] key The name of the key
12
+ # @param [Object] value The value of the key
13
+ # @raise [Dragnet::Errors::ValidationError] If the Requirement ID(s) are
14
+ # missing, they are not a String or an Array of Strings if they contain
15
+ # a disallowed character or (in the case of an Array) any of its
16
+ # elements is not a String.
17
+ def validate(key, value)
18
+ validate_presence(key, value)
19
+ validate_type(key, value, String, Array)
20
+
21
+ if value.is_a?(String)
22
+ match = value.match(/,|\s/)
23
+ return unless match
24
+
25
+ validation_error(
26
+ "Disallowed character '#{match}' found in the value for key #{key}. "\
27
+ 'To use multiple requirement IDs please put them into an array'
28
+ )
29
+ else
30
+ validate_array_types(key, value, String)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'field_validator'
4
+ require_relative '../../errors/validation_error'
5
+
6
+ module Dragnet
7
+ module Validators
8
+ module Fields
9
+ # Base class to validate the fields that are part of the meta-data group.
10
+ # This means: Either +String+ +Array<String>+ or +nil+ as value.
11
+ class MetaDataFieldValidator < Dragnet::Validators::Fields::FieldValidator
12
+ # Validates the specified attribute as a meta-data field.
13
+ # @param [String] key The name of the key
14
+ # @param [Object] value The value of the key
15
+ # @raise [Dragnet::Errors::ValidationError] If the attribute fails the
16
+ # validation.
17
+ # @return [nil] If +value+ is +nil+ or an empty array.
18
+ # @return [Array<String>] If +value+ is a +String+ or an +Arry<String>+
19
+ def validate(key, value)
20
+ return unless value
21
+
22
+ validate_type(key, value, String, Array)
23
+
24
+ if value.is_a?(Array)
25
+ return if value.empty?
26
+
27
+ validate_array_types(key, value, String)
28
+ value
29
+ else
30
+ [value]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'field_validator'
4
+
5
+ module Dragnet
6
+ module Validators
7
+ module Fields
8
+ # Validates the +path+ attribute of a +Repo+ object.
9
+ class PathValidator < Dragnet::Validators::Fields::FieldValidator
10
+ # Validates the Path of the repository.
11
+ # @param [String] key The name of the key
12
+ # @param [Object] value The value of the key
13
+ # @raise [Dragnet::Errors::ValidationError] If the path is missing, or
14
+ # it isn't a String.
15
+ def validate(key, value)
16
+ validate_presence(key, value)
17
+ validate_type(key, value, String)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end