diff --git a/.rubocop.yml b/.rubocop.yml index 0def896e..065e92d7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -97,6 +97,7 @@ Elegant/GoodMethodName: - search_commits - search_issues - sort_by + - sorted_values - timeout_unaware - total_commits - total_commits_pushed diff --git a/lib/fbe/iterate.rb b/lib/fbe/iterate.rb index d702a652..54a0e6df 100644 --- a/lib/fbe/iterate.rb +++ b/lib/fbe/iterate.rb @@ -203,6 +203,9 @@ def sort_by(prop) raise(Fbe::Error, 'Cannot set sort field to nil') if prop.nil? raise(Fbe::Error, 'Sort field must be a String') unless prop.is_a?(String) raise(Fbe::Error, 'Sort field cannot be empty') if prop.empty? + unless prop.match?(/\A[_a-z][a-zA-Z0-9_]*\z/) + raise(Fbe::Error, "Wrong sort field format '#{prop}', use [_a-z][a-zA-Z0-9_]*") + end @sorting = prop end @@ -316,9 +319,7 @@ def over # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metric end nxt = if @sorting - values[repo] ||= @fb.query(@query).each( - @fb, before: before[repo], repository: repo - ).filter_map { _1[@sorting]&.first }.uniq.sort!.each + values[repo] ||= sorted_values(repo, before[repo]) begin values[repo].next rescue StopIteration @@ -379,4 +380,21 @@ def over # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metric end end end + + private + + # Fetches and sorts the values of the +sort_by+ field for one repository. + # + # @param [Integer] repo The repository ID + # @param [Object] since The +$before+ value to run the query with + # @return [Enumerator] The sorted values, ready for +.next+ + # @raise [Fbe::Error] If matched facts exist but none of them carry the sort field + def sorted_values(repo, since) + raw = @fb.query(@query).each(@fb, before: since, repository: repo).to_a + sorted = raw.filter_map { _1[@sorting]&.first }.uniq + if sorted.empty? && !raw.empty? + raise(Fbe::Error, "None of the #{raw.size} matched facts in ##{repo} carry the '#{@sorting}' property") + end + sorted.sort!.each + end end diff --git a/test/fbe/test_iterate.rb b/test/fbe/test_iterate.rb index 3abbdf5c..257eea4f 100644 --- a/test/fbe/test_iterate.rb +++ b/test/fbe/test_iterate.rb @@ -579,6 +579,42 @@ def test_sort_by_configuration # rubocop:disable Metrics/AbcSize assert_equal(25, fb.query('(eq what "iterate")').each.to_a.first.marker) end + def test_sort_by_rejects_a_bad_format + ex = + assert_raises(Fbe::Error) do + Fbe.iterate( + fb: Fbe.fb(fb: Factbase.new, global: {}, options: Judges::Options.new, loog: Loog::NULL), + loog: Loog::NULL, options: Judges::Options.new, global: {}, epoch: Time.now, kickoff: Time.now + ) do + sort_by('9number') + end + end + assert_match(/Wrong sort field format/, ex.message) + end + + def test_sort_by_reports_a_misspelled_field_instead_of_looking_exhausted + opts = Judges::Options.new(['repositories=foo/bar', 'testing=true']) + global = {} + fb = Fbe.fb(fb: Factbase.new, global:, options: opts, loog: Loog::NULL) + fb.insert.then do |f| + f.where = 'github' + f.what = 'judge' + f.repository = 3_861_188_962 + f.number = 1 + end + ex = + assert_raises(Fbe::Error) do + Fbe.iterate(fb:, loog: Loog::NULL, options: opts, global:, epoch: Time.now, kickoff: Time.now) do + as('marker') + sort_by('numero') + by("(and (eq repository $repository) (eq where 'github') (eq what 'judge'))") + repeats(1) + over { |_repository, n| n } + end + end + assert_match(/carry the 'numero' property/, ex.message) + end + def test_custom_since opts = Judges::Options.new(['repositories=foo/bar', 'testing=true']) fb = Fbe.fb(fb: Factbase.new, global: {}, options: opts, loog: Loog::NULL)