CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/support/matchers/query_the_database.rb
Views: 11778
1
module Shoulda # :nodoc:
2
module Matchers
3
module ActiveRecord # :nodoc:
4
5
# Ensures that the number of database queries is known. Rails 3.1 or greater is required.
6
#
7
# Options:
8
# * <tt>when_calling</tt> - Required, the name of the method to examine.
9
# * <tt>with</tt> - Used in conjunction with <tt>when_calling</tt> to pass parameters to the method to examine.
10
# * <tt>or_less</tt> - Pass if the database is queried no more than the number of times specified, as opposed to exactly that number of times.
11
#
12
# Examples:
13
# it { is_expected.to query_the_database(4.times).when_calling(:complicated_counting_method)
14
# it { is_expected.to query_the_database(4.times).or_less.when_calling(:generate_big_report)
15
# it { is_expected.not_to query_the_database.when_calling(:cached_count)
16
#
17
def query_the_database(times = nil)
18
QueryTheDatabaseMatcher.new(times)
19
end
20
21
class QueryTheDatabaseMatcher # :nodoc:
22
def initialize(times)
23
@queries = []
24
@options = {}
25
26
if times.respond_to?(:count)
27
@options[:expected_query_count] = times.count
28
else
29
@options[:expected_query_count] = times
30
end
31
end
32
33
def when_calling(method_name)
34
@options[:method_name] = method_name
35
self
36
end
37
38
def with(*method_arguments)
39
@options[:method_arguments] = method_arguments
40
self
41
end
42
43
def or_less
44
@options[:expected_count_is_maximum] = true
45
self
46
end
47
48
def matches?(subject)
49
subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |name, started, finished, id, payload|
50
@queries << payload unless filter_query(payload)
51
end
52
53
if @options[:method_arguments]
54
subject.send(@options[:method_name], *@options[:method_arguments])
55
else
56
subject.send(@options[:method_name])
57
end
58
59
ActiveSupport::Notifications.unsubscribe(subscriber)
60
61
if @options[:expected_count_is_maximum]
62
@queries.length <= @options[:expected_query_count]
63
elsif @options[:expected_query_count].present?
64
@queries.length == @options[:expected_query_count]
65
else
66
@queries.length > 0
67
end
68
end
69
70
def failure_message_for_should
71
if @options.key?(:expected_query_count)
72
"Expected ##{@options[:method_name]} to cause #{@options[:expected_query_count]} database queries but it actually caused #{@queries.length} queries:" + friendly_queries
73
else
74
"Expected ##{@options[:method_name]} to query the database but it actually caused #{@queries.length} queries:" + friendly_queries
75
end
76
end
77
78
def failure_message_for_should_not
79
if @options[:expected_query_count]
80
"Expected ##{@options[:method_name]} to not cause #{@options[:expected_query_count]} database queries but it actually caused #{@queries.length} queries:" + friendly_queries
81
else
82
"Expected ##{@options[:method_name]} to not query the database but it actually caused #{@queries.length} queries:" + friendly_queries
83
end
84
end
85
86
private
87
88
def friendly_queries
89
@queries.map do |query|
90
"\n (#{query[:name]}) #{query[:sql]}"
91
end.join
92
end
93
94
def filter_query(query)
95
query[:name] == 'SCHEMA' || looks_like_schema?(query[:sql])
96
end
97
98
def schema_terms
99
['FROM sqlite_master', 'PRAGMA', 'SHOW TABLES', 'SHOW KEYS FROM', 'SHOW FIELDS FROM', 'begin transaction', 'commit transaction']
100
end
101
102
def looks_like_schema?(sql)
103
schema_terms.any? { |term| sql.include?(term) }
104
end
105
end
106
end
107
end
108
end
109
110