require "spec_helper"
require "sql-parser"

RSpec.describe ActiveRecord::SqlAnalyzer::RedactedLogger do
  let(:tmp_dir) { Dir.mktmpdir }
  let(:parser) { SQLParser::Parser.new }
  after { FileUtils.remove_entry(tmp_dir) }

  context "#filter_event" do
    let(:event) { {} }

    subject(:filter_event) do
      instance = described_class.new(tmp_dir, "foo")
      instance.filter_event(event)
      event
    end

    before do
      ActiveRecord::SqlAnalyzer.configure do |c|
        c.add_backtrace_redactors [
          [/erb_erb_[0-9]+_[0-9]+/, ""]
        ]
      end

      # All raw SQL should be valid :)
      event_sql_list = event[:calls].map { |call| call[:sql] }
      expect { event_sql_list.map { |sql| parser.scan_str sql } }.not_to raise_exception if event_sql_list.any?(&:present?)
    end

    after do
      # All redacted SQL should be valid
      expect { parser.scan_str(filter_event[:sql]) }.not_to raise_exception if filter_event[:sql].present?
    end

    context "ambiguous backtraces" do
      let(:event) do
        {
          calls: [{
            caller: %w(ambiguous foo bar),
            sql: "",
          }]
        }
      end

      before do
        ActiveRecord::SqlAnalyzer.configure do |c|
          c.add_ambiguous_tracers [/ambiguous/]
        end
      end

      it "switches to ambiguous backtrace logging" do
        expect(filter_event[:caller]).to eq("ambiguous, foo, bar")
      end
    end

    context "backtrace" do
      let(:event) do
        {
          calls: [{
            caller: %w(foo-bar-erb_erb_1_5),
            sql: "",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:caller]).to eq("foo-bar-")
      end
    end

    context "sql quoted" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name = 'hello\\'s name'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name = '[REDACTED]'")
      end
    end

    context "sql quoted multiple WHERE" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name = 'hello\\'s name' AND age = '21'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name = '[REDACTED]' AND age = '[REDACTED]'")
      end
    end

    context "sql escaped and quoted" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name = 'hello\\\'s name'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name = '[REDACTED]'")
      end
    end

    context "sql case insensitivity" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name lIkE 'hello'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name LIKE '[REDACTED]'")
      end
    end

    context "sql" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE id = 1234",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE id = '[REDACTED]'")
      end
    end

    context "like quoted" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: %{SELECT * FROM foo WHERE name LIKE 'A \\'quoted\\' value.' OR name LIKE "another ""quoted"" \\"value\\""},
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name LIKE '[REDACTED]' OR name LIKE '[REDACTED]'")
      end
    end

    context "like escaped and quoted" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name LIKE 'A \\\'quoted\\\' value.'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name LIKE '[REDACTED]'")
      end
    end

    context "in quoted" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name IN ('A ''quoted'' value.')",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name IN ('[REDACTED]')")
      end
    end

    context "in escaped and quoted" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: %{SELECT * FROM foo WHERE name IN ('A ''quoted'' value.', "another ""quoted"" \\"value\\"")},
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name IN ('[REDACTED]')")
      end
    end

    context "between strings" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name BETWEEN 'A value.' AND 'Another value'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name BETWEEN '[REDACTED]' AND '[REDACTED]'")
      end
    end

    context "between strings with escaped quotes" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name BETWEEN 'A ''quoted'' value.' AND 'Another \\'value\\''",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name BETWEEN '[REDACTED]' AND '[REDACTED]'")
      end
    end

    context "in with = and other where clauses" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "SELECT * FROM foo WHERE name IN ('value=') AND name = 'value'",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("SELECT * FROM foo WHERE name IN ('[REDACTED]') AND name = '[REDACTED]'")
      end
    end

    context "insert" do
      let(:event) do
        {
          calls: [{
            caller: [""],
            sql: "INSERT INTO `boom` (`bam`, `foo`) VALUES ('howdy', 'dowdy')",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:sql]).to eq("INSERT INTO `boom` (REDACTED_COLUMNS) VALUES ('[REDACTED]')")
      end
    end

    context "empty callstack" do
      let(:event) do
        {
          calls: [{
            caller: [],
            sql: "INSERT INTO `boom` (`bam`, `foo`) VALUES ('howdy', 'dowdy')",
          }]
        }
      end

      it "redacts" do
        expect(filter_event[:caller]).to eq("")
      end
    end
  end
end
