require 'holepicker/vulnerability'
require 'spec_helper'

module HolePicker
  describe Vulnerability do
    let(:url) { "http://google.com" }
    let(:date) { "2013-02-11T18:40Z" }
    let(:note) { "Buggy release" }
    let(:gems) {{}}

    let(:json) {{
      'url' => url,
      'date' => date,
      'gems' => gems,
      'note' => note
    }}

    def build(json)
      Vulnerability.new(json)
    end

    describe "#id" do
      before { Vulnerability.class_eval { remove_class_variable("@@count") }}

      it "should start at 1" do
        build(json).id.should == 1
      end

      it "should increase by 1 for every new object" do
        build(json)
        build(json).id.should == 2
        build(json).id.should == 3
      end
    end

    describe "#url" do
      it "should be taken from the json" do
        build(json).url.should == url
      end
    end

    describe "#note" do
      it "should be taken from the json" do
        build(json).note.should == note
      end
    end

    describe "#gems" do
      let(:gems) {{
        'rails' => ['3.2.11', '3.0.5', '2.3.7'],
        'json' => ['1.1.1', '1.3.2']
      }}

      subject { build(json).gems }

      it { should be_a(Hash) }

      it "should have keys corresponding to gem names" do
        subject.keys.should =~ ['rails', 'json']
      end

      it "should have values storing lists of gem versions" do
        subject['rails'].should be_an(Array)
        subject['rails'].should have(3).elements

        subject['json'].should be_an(Array)
        subject['json'].should have(2).elements
      end

      it "should store gem versions as Gem::Version" do
        subject.values.flatten.each { |g| g.should be_a(::Gem::Version) }
      end

      it "should match the versions in the json file" do
        subject.each do |k, v|
          v.map(&:to_s).should =~ gems[k]
        end
      end
    end

    describe "#date" do
      it "should be a Time object built from a json field" do
        date = build(json).date

        date.should be_a(Time)
        date.strftime("%Y-%m-%d %H:%M:%S").should == '2013-02-11 18:40:00'
      end
    end

    describe "#day" do
      it "should return formatted date without time" do
        build(json).day.should == '2013-02-11'
      end
    end

    describe "#recent?" do
      subject { build(json).recent? }

      context "if vulnerability is newer than #{Vulnerability::NEW_VULNERABILITY_DAYS} days" do
        let(:date) { (Time.now - 5 * 3600).to_s }

        it { should be_true }
      end

      context "if vulnerability is older than #{Vulnerability::NEW_VULNERABILITY_DAYS} days" do
        let(:date) { (Time.now - Vulnerability::NEW_VULNERABILITY_DAYS * 86400 - 3600).to_s }

        it { should be_false }
      end
    end

    describe "#tag" do
      before { Vulnerability.stubs(:next_id => 78) }

      it "should return '#id'" do
        build(json).tag.should == '#78'
      end
    end

    describe "#gem_names" do
      let(:gems) {{ 'rails' => [], 'json' => [] }}

      it "should return list of gem names" do
        build(json).gem_names.should =~ ['rails', 'json']
      end
    end

    describe "#gem_vulnerable?" do
      let(:vulnerability) { build(json) }
      let(:gem) { stub }

      subject { vulnerability.gem_vulnerable?(gem) }

      context "if gem_safe? returns true" do
        before { vulnerability.expects(:gem_safe?).with(gem).returns(true) }

        it { should be_false }
      end

      context "if gem_safe? returns false" do
        before { vulnerability.expects(:gem_safe?).with(gem).returns(false) }

        it { should be_true }
      end
    end

    describe "#gem_safe?" do
      let(:gems) {{
        'rails' => ['3.2.12', '3.1.11', '2.3.17']
      }}

      let(:v) { build(json) }

      subject { v.gem_safe?(gem) }

      def gem_version(name, version)
        Gem.new("#{name} (#{version})")
      end

      context "if the exact gem version is listed as a fixed version" do
        it "should return true" do
          v.gem_safe?(gem_version(:rails, '3.2.12')).should be_true
          v.gem_safe?(gem_version(:rails, '3.1.11')).should be_true
          v.gem_safe?(gem_version(:rails, '2.3.17')).should be_true
        end
      end

      context "if there are no fixes listed for that gem" do
        let(:gem) { gem_version(:json, '1.2.3') }

        it { should be_true }
      end

      context "if given version has the same minor version number as a fixed one" do
        context "if the patchlevel is higher than in the fixed one" do
          let(:gem) { gem_version(:rails, '3.1.14') }

          it { should be_true }
        end

        context "if the patchlevel is lower than in the fixed one" do
          let(:gem) { gem_version(:rails, '3.1.10') }

          it { should be_false }
        end
      end

      context "if none of the fixes has the same minor version number" do
        context "if given version has higher minor version number than all of the fixes" do
          # we're assuming the version was either released after the fix or didn't include the bug at all
          it "should return true" do
            v.gem_safe?(gem_version(:rails, '3.3')).should be_true
            v.gem_safe?(gem_version(:rails, '3.3.1')).should be_true
            v.gem_safe?(gem_version(:rails, '3.4.5')).should be_true
            v.gem_safe?(gem_version(:rails, '4.0.1')).should be_true
          end
        end

        context "if given version has lower minor version number than all of the fixes" do
          # we're assuming the version is not maintained anymore and didn't get the fix
          it "should return false" do
            v.gem_safe?(gem_version(:rails, '2.2.24')).should be_false
            v.gem_safe?(gem_version(:rails, '2.1')).should be_false
            v.gem_safe?(gem_version(:rails, '1.8.20')).should be_false
          end
        end

        context "if given version has lower minor version than some fixes and higher than some others" do
          # we're assuming the version is not maintained anymore and didn't get the fix
          it "should return false" do
            v.gem_safe?(gem_version(:rails, '2.4.15')).should be_false
            v.gem_safe?(gem_version(:rails, '3.0.20')).should be_false
          end
        end
      end
    end
  end
end
