require 'spec_helper'
require 'ronin/sql/injection'

describe SQL::Injection do
  describe "PLACE_HOLDERS" do
    subject { described_class::PLACE_HOLDERS }

    it { should include(integer: 1)   }
    it { should include(decimal: 1.0) }
    it { should include(string: '1')  }
    it { should include(list: [nil])  }
    it { should include(column: :id)  }
  end

  describe "#initialize" do
    context "with no arguments" do
      its(:escape)       { should == :integer }
      its(:place_holder) { should == 1 }
    end

    context "with :escape" do
      context "with no :place_holder" do
        let(:place_holders) { described_class::PLACE_HOLDERS }
        let(:escape) { :string }

        subject { described_class.new(:escape => escape) }

        it "should default the place_holder based on the :escape type" do
          subject.place_holder.should == place_holders[escape]
        end
      end
    end

    context "with :place_holder" do
      let(:data) { 'A' }

      subject { described_class.new(:place_holder => data) }

      it "should pass it to the InjectionExpr" do
        subject.expression.expression.should == data
      end
    end

    context "when a block is given" do
      subject { described_class.new { @x = 1 } }

      it "should instance_eval the block" do
        subject.instance_variable_get(:@x).should == 1
      end
    end
  end

  describe "#to_sql" do
    context "without an expression" do
      subject { described_class.new(place_holder: 1) }

      it "should still emit the place-holder value" do
        subject.to_sql.should == '1'
      end

      context "with clauses" do
        subject do
          sqli = described_class.new(place_holder: 1)
          sqli.limit(100).offset(10)
          sqli
        end

        it "should emit the clauses" do
          subject.to_sql.should == '1 LIMIT 100 OFFSET 10'
        end
      end
    end

    context "with an expression" do
      subject do
        sqli = described_class.new
        sqli.or { 1 == 1 }
        sqli
      end

      it "should emit the expression" do
        subject.to_sql.should == '1 OR 1=1'
      end

      context "with clauses" do
        subject do
          sqli = described_class.new
          sqli.or { 1 == 1 }.limit(100).offset(10)
          sqli
        end

        it "should emit the clauses" do
          subject.to_sql.should == '1 OR 1=1 LIMIT 100 OFFSET 10'
        end

        context "with :space" do
          it "should emit the clauses with custom spaces" do
            subject.to_sql(space: '/**/').should == '1/**/OR/**/1=1/**/LIMIT/**/100/**/OFFSET/**/10'
          end
        end
      end

      context "with statements" do
        subject do
          sqli = described_class.new
          sqli.or { 1 == 1 }.select(1,2,3)
          sqli
        end

        it "should emit the clauses" do
          subject.to_sql.should == '1 OR 1=1; SELECT (1,2,3)'
        end

        context "with :space" do
          it "should emit the statements with custom spaces" do
            subject.to_sql(space: '/**/').should == '1/**/OR/**/1=1;/**/SELECT/**/(1,2,3)'
          end
        end
      end
    end

    context "when escaping a string value" do
      context "when the place-holder and last operand are Strings" do
        subject do
          sqli = described_class.new(escape: :string)
          sqli.or { string(1) == string(1) }
          sqli
        end 

        it "should balance the quotes" do
          subject.to_sql.should == "1' OR '1'='1"
        end
      end

      context "when the place-holder and last operand are not both Strings" do
        subject do
          sqli = described_class.new(escape: :string)
          sqli.or { int(1) == int(1) }
          sqli
        end 

        it "should terminate the SQL statement" do
          subject.to_sql.should == "1' OR 1=1;--"
        end
      end

      context "when terminating" do
        subject do
          sqli = described_class.new(escape: :string)
          sqli.or { string(1) == string(1) }
          sqli
        end 

        it "should terminate the SQL statement" do
          subject.to_sql(terminate: true).should == "1' OR '1'='1';--"
        end
      end
    end

    context "when terminating" do
      subject do
        sqli = described_class.new(escape: :integer)
        sqli.or { 1 == 1 }
        sqli
      end 

      it "should terminate the SQL statement" do
        subject.to_sql(terminate: true).should == "1 OR 1=1;--"
      end
    end
  end
end