1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
require_relative "../../test_helper"

require "sequel"
require "tmpdir"

require "sequel/extensions/sole"

class SoleTest < Minitest::Test
  def with_rows(ids)
    Dir.mktmpdir do |dir|
      database = Sequel.sqlite("#{dir}/t.db")
      database.extension(:sole)
      database.create_table(:things) do
        String :id
        String :body
      end
      ids.each do |id|
        database[:things].insert(id:, body: "body of #{id}")
      end
      yield database[:things]
    end
  end

  def test_the_one_row_comes_back
    with_rows(%w[a b]) do |things|
      assert_equal "body of a", things.where(id: "a").sole.fetch(:body)
    end
  end

  # Sequel's own `first!` raises this for an empty dataset, so a caller
  # for whom no row is ordinary has one error to name.
  def test_no_rows_raises
    with_rows(%w[a]) do |things|
      assert_raises(Sequel::NoMatchingRow) { things.where(id: "nobody").sole }
    end
  end

  # The half `first` gets wrong: it would answer with one of the two.
  def test_more_than_one_row_raises
    with_rows(%w[a a]) do |things|
      assert_raises(Sequel::Sole::TooManyRows) { things.where(id: "a").sole }
    end
  end

  # An exception message is one of the things that reaches Sentry, and a
  # filter can carry card content, so the message names the table only.
  def test_the_message_names_the_table_and_not_the_query
    with_rows(%w[a a]) do |things|
      error = assert_raises(Sequel::Sole::TooManyRows) { things.where(body: "body of a").sole }

      assert_equal "more than one row in things", error.message
      refute_includes error.message, "body of a"
    end
  end

  def test_it_is_a_sequel_error
    assert_operator Sequel::Sole::TooManyRows, :<, Sequel::Error
  end

  # Everything Sequel logs, which is how the query itself is inspected.
  class QueryLog
    attr_reader :queries

    def initialize
      @queries = []
    end

    def info(message)
      @queries << message
    end
    alias_method :error, :info
  end

  # A filter that turned out to match every row must not drag them all
  # back to prove it.
  def test_it_reads_two_rows_at_most
    with_rows(%w[a a a a a]) do |things|
      log = QueryLog.new
      things.db.loggers << log

      assert_raises(Sequel::Sole::TooManyRows) { things.sole }
      assert(log.queries.any? { it.include?("LIMIT 2") }, log.queries.inspect)
    end
  end
end