From db75acbcd816a600dbf60d32bf6811080178856f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 1 Mar 2024 09:42:49 +1100 Subject: [PATCH] Add query summary matching This has been copied from the openfoodnetwork repository and adapted slightly. It also needed one little fix. This approach is quite different to our previous one and I'm wondering if we use `User` and `Create` instead of `users` and `insert` to stay on the higher level of Active Record. But for now this is can be a simple replacement for the openfoodnetwork spec helper and we can evolve it from there. --- README.md | 10 +++++++- lib/rspec/sql.rb | 8 ++++++ lib/rspec/sql/query_summary.rb | 47 ++++++++++++++++++++++++++++++++++ rspec-sql.gemspec | 2 +- spec/lib/rspec/sql_spec.rb | 9 +++++++ 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 lib/rspec/sql/query_summary.rb diff --git a/README.md b/README.md index 45823dc..5e9b4c8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ expect { nil }.to_not query_database expect { User.last }.to query_database 1 expect { User.create }.to query_database 3.times -# Assert specific queries: +# Assert a specific query list: expect { User.last }.to query_database ["User Load"] expect { User.create!.update(name: "Jane") }.to query_database [ @@ -33,6 +33,14 @@ expect { User.create!.update(name: "Jane") }.to query_database [ "User Update", "TRANSACTION", ] + +# Assert a specific query summary: +expect { User.create!.update(name: "Jane") }.to query_database( + { + insert: { users: 1 }, + update: { users: 1 }, + } +) ``` ## Alternatives diff --git a/lib/rspec/sql.rb b/lib/rspec/sql.rb index b384bb6..4602e13 100644 --- a/lib/rspec/sql.rb +++ b/lib/rspec/sql.rb @@ -3,6 +3,8 @@ require "active_support" require "rspec" +require_relative "sql/query_summary" + module RSpec module Sql; end @@ -18,6 +20,8 @@ module Sql; end @queries.size == expected.size elsif expected.is_a?(Array) query_names == expected + elsif expected.is_a?(Hash) + query_summary == expected else raise "What are you expecting?" end @@ -52,6 +56,10 @@ def query_descriptions @queries.map { |q| "#{q[:name]} #{q[:sql]}" } end + def query_summary + Sql::QuerySummary.new(@queries).summary + end + def scribe_queries(&) queries = [] diff --git a/lib/rspec/sql/query_summary.rb b/lib/rspec/sql/query_summary.rb new file mode 100644 index 0000000..9d6d038 --- /dev/null +++ b/lib/rspec/sql/query_summary.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RSpec + module Sql + class QuerySummary + QUERY_TYPES = [:delete, :insert, :select, :update].freeze + + attr_reader :summary + + def initialize(queries) + @summary = {} + queries.each do |payload| + type = get_type(payload[:sql]) + next if QUERY_TYPES.exclude?(type) || pg_query?(payload[:sql]) + + table = get_table(payload[:sql]) + @summary[type] ||= {} + @summary[type][table] ||= 0 + @summary[type][table] += 1 + end + end + + private + + def get_table(sql) + sql_parts = sql.split + case get_type(sql) + when :insert + sql_parts[2] + when :update + sql_parts[1] + else + table_index = sql_parts.index("FROM") + sql_parts[table_index + 1] + end.gsub(/(\\|")/, "").to_sym + end + + def get_type(sql) + sql.split[0].downcase.to_sym + end + + def pg_query?(sql) + sql.include?("SELECT a.attname") || sql.include?("pg_attribute") + end + end + end +end diff --git a/rspec-sql.gemspec b/rspec-sql.gemspec index 83fbd1b..59e0455 100644 --- a/rspec-sql.gemspec +++ b/rspec-sql.gemspec @@ -3,7 +3,7 @@ Gem::Specification.new do |s| s.version = "0.0.0" s.summary = "RSpec::Sql matcher" s.description = "RSpec matcher for database queries." - s.authors = ["Maikel Linke"] + s.authors = ["Maikel Linke", "Open Food Network contributors"] s.email = "maikel@openfoodnetwork.org.au" s.files = Dir["lib/**/*.rb"] s.homepage = "https://github.com/openfoodfoundation/rspec-sql" diff --git a/spec/lib/rspec/sql_spec.rb b/spec/lib/rspec/sql_spec.rb index 7178458..ab1110c 100644 --- a/spec/lib/rspec/sql_spec.rb +++ b/spec/lib/rspec/sql_spec.rb @@ -57,4 +57,13 @@ "TRANSACTION", ] end + + it "expects a summary of queries" do + expect { User.create!.update(name: "Jane") }.to query_database( + { + insert: { users: 1 }, + update: { users: 1 }, + } + ) + end end