• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MushroomObserver / mushroom-observer / 13391390877

18 Feb 2025 01:12PM UTC coverage: 93.454% (+0.01%) from 93.444%
13391390877

push

github

web-flow
Merge pull request #2702 from MushroomObserver/jdc-2460-inat-tracker-stimulus-all-updates

InatImportJobTracker auto updates

23 of 29 new or added lines in 4 files covered. (79.31%)

15 existing lines in 4 files now uncovered.

27427 of 29348 relevant lines covered (93.45%)

582.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/app/classes/query/modules/validation.rb
1
# frozen_string_literal: true
2

3
# validation of Query parameters
4
module Query::Modules::Validation
1✔
5
  attr_accessor :params, :params_cache
1✔
6

7
  def validate_params
1✔
8
    old_params = @params.dup.compact.symbolize_keys
2,937✔
9
    new_params = {}
2,937✔
10
    permitted_params = parameter_declarations.slice(*old_params.keys)
2,937✔
11
    permitted_params.each do |param, param_type|
2,937✔
12
      val = old_params[param]
4,985✔
13
      val = validate_value(param_type, param, val) if val.present?
4,985✔
14
      new_params[param] = val
4,965✔
15
    end
16
    check_for_unexpected_params(old_params)
2,917✔
17
    @params = new_params
2,916✔
18
  end
19

20
  def check_for_unexpected_params(old_params)
1✔
21
    unexpected_params = old_params.except(*parameter_declarations.keys)
2,917✔
22
    return if unexpected_params.keys.empty?
2,917✔
23

24
    str = unexpected_params.keys.map(&:to_s).join("', '")
1✔
25
    raise("Unexpected parameter(s) '#{str}' for #{model} query.")
1✔
26
  end
27

28
  def validate_value(param_type, param, val)
1✔
29
    if param_type.is_a?(Array)
4,877✔
30
      result = array_validate(param, val, param_type.first).flatten
1,544✔
31
      result = result.uniq if positive_integers?(result)
1,539✔
32
      result
1,539✔
33
    else
34
      val = scalar_validate(param, val, param_type)
3,333✔
35
      val = val.first if val.is_a?(Array)
3,318✔
36
      val
3,318✔
37
    end
38
  end
39

40
  def positive_integers?(list)
1✔
41
    list.all? { |item| item.is_a?(Integer) && item.positive? }
3,262✔
42
  end
43

44
  def array_validate(param, val, param_type)
1✔
45
    case val
1,544✔
46
    when Array
47
      val[0, MO.query_max_array].map! do |val2|
465✔
48
        scalar_validate(param, val2, param_type)
722✔
49
      end
50
    when ::API2::OrderedRange
51
      [scalar_validate(param, val.begin, param_type),
39✔
52
       scalar_validate(param, val.end, param_type)]
53
    else
54
      [scalar_validate(param, val, param_type)]
1,040✔
55
    end
56
  end
57

58
  def scalar_validate(param, val, param_type)
1✔
59
    case param_type
5,452✔
60
    when Symbol
61
      send(:"validate_#{param_type}", param, val)
3,479✔
62
    when Class
63
      validate_class_param(param, val, param_type)
1,777✔
64
    when Hash
65
      validate_hash_param(param, val, param_type)
196✔
66
    else
UNCOV
67
      raise("Invalid declaration of :#{param} for #{model} " \
×
68
            "query! (invalid type: #{param_type.class.name})")
69
    end
70
  end
71

72
  def validate_class_param(param, val, param_type)
1✔
73
    if param_type.respond_to?(:descends_from_active_record?)
1,777✔
74
      validate_record(param, val, param_type)
1,777✔
75
    else
UNCOV
76
      raise(
×
77
        "Don't know how to parse #{param_type} :#{param} for #{model} query."
78
      )
79
    end
80
  end
81

82
  def validate_hash_param(param, val, param_type)
1✔
83
    if [:string, :boolean].include?(param_type.keys.first)
196✔
84
      validate_enum(param, val, param_type)
165✔
85
    else
86
      validate_nested_params(param, val, param_type)
31✔
87
    end
88
  end
89

90
  def validate_nested_params(_param, val, hash)
1✔
91
    val2 = {}
31✔
92
    hash.each do |key, arg_type|
31✔
93
      val2[key] = scalar_validate(key, val[key], arg_type)
115✔
94
    end
95
    val2
28✔
96
  end
97

98
  def validate_enum(param, val, hash)
1✔
99
    if hash.keys.length != 1
165✔
UNCOV
100
      raise(
×
101
        "Invalid enum declaration for :#{param} for #{model} " \
102
        "query! (wrong number of keys in hash)"
103
      )
104
    end
105

106
    arg_type = hash.keys.first
165✔
107
    set = hash.values.first
165✔
108
    unless set.is_a?(Array)
165✔
UNCOV
109
      raise(
×
110
        "Invalid enum declaration for :#{param} for #{model} " \
111
        "query! (expected value to be an array of allowed values)"
112
      )
113
    end
114

115
    val2 = scalar_validate(param, val, arg_type)
165✔
116
    if (arg_type == :string) && set.include?(val2.to_sym)
164✔
117
      val2 = val2.to_sym
89✔
118
    elsif set.exclude?(val2)
75✔
119
      raise("Value for :#{param} should be one of the following: " \
2✔
120
            "#{set.inspect}.")
121
    end
122
    val2
162✔
123
  end
124

125
  def validate_boolean(param, val)
1✔
126
    case val
1,089✔
127
    # Disable cop because we do mean to symbols with boolean names
128
    # rubocop:disable Lint/BooleanSymbol
129
    when :true, :yes, :on, "true", "yes", "on", "1", 1, true
130
      true
1,085✔
131
    when :false, :no, :off, "false", "no", "off", "0", 0, false, nil
132
      false
4✔
133
    # rubocop:enable Lint/BooleanSymbol
134
    else
UNCOV
135
      raise("Value for :#{param} should be boolean, got: #{val.inspect}")
×
136
    end
137
  end
138

139
  # def validate_integer(param, val)
140
  #   if val.is_a?(Integer) || val.is_a?(String) && val.match(/^-?\d+$/)
141
  #     val.to_i
142
  #   elsif val.blank?
143
  #     nil
144
  #   else
145
  #     raise("Value for :#{param} should be an integer, got: #{val.inspect}")
146
  #   end
147
  # end
148

149
  def validate_float(param, val)
1✔
150
    if val.is_a?(Integer) || val.is_a?(Float) ||
142✔
151
       (val.is_a?(String) && val.match(/^-?(\d+(\.\d+)?|\.\d+)$/))
7✔
152
      val.to_f
140✔
153
    else
154
      raise("Value for :#{param} should be a float, got: #{val.inspect}")
2✔
155
    end
156
  end
157

158
  # This type of param accepts instances, ids, or strings. When the query is
159
  # executed, the string will be sent to the appropriate `Lookup` subclass.
160
  def validate_record(param, val, type = ActiveRecord::Base)
1✔
161
    if val.is_a?(type)
1,777✔
162
      raise("Value for :#{param} is an unsaved #{type} instance.") unless val.id
335✔
163

164
      set_cached_parameter_instance(param, val)
335✔
165
      val.id
335✔
166
    elsif could_be_record_id?(param, val)
1,442✔
167
      val.to_i
1,330✔
168
    elsif val.is_a?(String) && param != :ids
112✔
169
      val
105✔
170
    else
171
      raise("Value for :#{param} should be id, string " \
7✔
172
            "or #{type} instance, got: #{val.inspect}")
173
    end
174
  end
175

176
  def validate_string(param, val)
1✔
177
    if val.is_any?(Integer, Float, String, Symbol)
2,044✔
178
      val.to_s
2,036✔
179
    else
180
      raise("Value for :#{param} should be a string or symbol, " \
8✔
181
            "got a #{val.class}: #{val.inspect}")
182
    end
183
  end
184

185
  def validate_date(param, val)
1✔
186
    if val.acts_like?(:date)
55✔
187
      format("%04d-%02d-%02d", val.year, val.mon, val.day)
19✔
188
    elsif /^\d\d\d\d(-\d\d?){0,2}$/i.match?(val.to_s) ||
36✔
189
          /^\d\d?(-\d\d?)?$/i.match?(val.to_s)
190
      val
36✔
UNCOV
191
    elsif val.blank? || val.to_s == "0"
×
192
      nil
193
    else
UNCOV
194
      raise("Value for :#{param} should be a date (YYYY-MM-DD or MM-DD), " \
×
195
            "got: #{val.inspect}")
196
    end
197
  end
198

199
  def validate_time(param, val)
1✔
200
    if val.acts_like?(:time)
104✔
201
      val = val.utc
62✔
202
      format("%04d-%02d-%02d-%02d-%02d-%02d",
62✔
203
             val.year, val.mon, val.day, val.hour, val.min, val.sec)
204
    elsif /^\d\d\d\d(-\d\d?){0,5}$/i.match?(val.to_s)
42✔
205
      val
42✔
UNCOV
206
    elsif val.blank? || val.to_s == "0"
×
207
      nil
208
    else
UNCOV
209
      raise(
×
210
        "Value for :#{param} should be a UTC time (YYYY-MM-DD-HH-MM-SS), " \
211
        "got: #{val.class.name}::#{val.inspect}"
212
      )
213
    end
214
  end
215

216
  def validate_query(param, val)
1✔
217
    case val
45✔
218
    when Query::Base
219
      val.record.id
9✔
220
    when Integer
221
      val
36✔
222
    else
UNCOV
223
      raise(
×
224
        "Value for :#{param} should be a Query class, got: #{val.inspect}"
225
      )
226
    end
227
  end
228

229
  def find_cached_parameter_instance(model, param)
1✔
230
    val = if could_be_record_id?(param, params[param])
279✔
231
            model.find(params[param])
278✔
232
          else
233
            lookup_record_by_name(param, params[param], model)
1✔
234
          end
235
    set_cached_parameter_instance(param, val)
279✔
236
  end
237

238
  # Cache the instance for later use, in case we both instantiate and
239
  # execute query in the same action.
240
  def set_cached_parameter_instance(param, val)
1✔
241
    @params_cache ||= {}
614✔
242
    @params_cache[param] = val
614✔
243
  end
244

245
  def could_be_record_id?(param, val)
1✔
246
    val.is_a?(Integer) ||
1,721✔
247
      val.is_a?(String) && val.match(/^[1-9]\d*$/) ||
248
      # (blasted admin user has id = 0!)
249
      val.is_a?(String) && (val == "0") && (param == :user)
109✔
250
  end
251

252
  # Requires a unique identifying string and will return [only_one_record].
253
  def lookup_record_by_name(param, val, type, **args)
1✔
254
    method = args[:method] || :instances
1✔
255
    lookup = lookup_class(param, val, type)
1✔
256

257
    results = lookup.new(val).send(method)
1✔
258
    raise("Couldn't find an id for : #{val.inspect}") unless results
1✔
259

260
    results.first
1✔
261
  end
262

263
  def lookup_class(param, val, type)
1✔
264
    # We're only validating the projects passed as the param.
265
    # Projects' species_lists will be looked up later.
266
    lookup = if param == :project_lists
1✔
UNCOV
267
               Lookup::Projects
×
268
             else
269
               "Lookup::#{type.name.pluralize}".constantize
1✔
270
             end
271
    raise("#{lookup} not defined for : #{val.inspect}") unless defined?(lookup)
1✔
272

273
    lookup
1✔
274
  end
275
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc