require "spec_helper"

describe Solve::RubySolver do
  describe "ClassMethods" do
    describe "::timeout" do
      subject { described_class.timeout }

      it "returns 30,000 by default" do
        expect(subject).to eql(30_000)
      end

      context "when the SOLVE_TIMEOUT env variable is set" do
        before { ENV.stub(:[]).with("SOLVE_TIMEOUT") { "30" } }

        it "returns the value multiplied by a thousand" do
          expect(subject).to eql(30_000)
        end
      end
    end

    describe "::activate" do

      it "is a no-op" do
        described_class.activate
      end

    end
  end

  let(:graph) { double(Solve::Graph) }
  let(:demands) { [["mysql"], ["nginx"]] }
  subject(:solver) { described_class.new(graph, demands, dependency_source: "Berksfile") }

  it "has a list of demands as ruby literals" do
    solver.demands_array.should == demands
  end

  it "has a list of demands as model objects" do
    expected = [
      Solve::Demand.new(solver, "mysql"),
      Solve::Demand.new(solver, "nginx"),
    ]
    solver.demands.should == expected
  end

  it "has a graph" do
    solver.graph.should == graph
  end

  describe "when the constraints are solvable" do
    let(:graph) do
      graph = Solve::Graph.new
      graph.artifact("A", "1.0.0")
      graph.artifact("B", "1.0.0").depends("A")
      graph
    end

    let(:demands) { [["A"], ["B"]] }

    it "gives the solution as a Hash" do
      solver.resolve.should == { "A" => "1.0.0", "B" => "1.0.0" }
    end

    it "gives the solution in sorted form" do
      solver.resolve(sorted: true).should == [["A", "1.0.0"], ["B", "1.0.0"]]
    end
  end

  describe "when the constraints are not solvable" do
    let(:error) do
      begin
        solver.resolve
      rescue => e
        e
      else
        raise "Expected resolve to cause an error"
      end
    end

    context "and molinillo identifies missing artifacts" do
      let(:graph) do
        graph = Solve::Graph.new
        graph.artifact("A", "1.0.0")
        graph
      end

      let(:demands) { [ ["Z"] ] }

      it "raises an error detailing the missing artifacts" do
        expect(error).to be_a_kind_of(Solve::Errors::NoSolutionError)
        expected_error = <<~ERROR_MESSAGE
          Unable to satisfy the following requirements:

          - `Z (>= 0.0.0)` required by `Berksfile`
        ERROR_MESSAGE
        expect(error.to_s).to eq(expected_error)
      end
    end

    context "and molinillo identifies constraints that exclude all known versions" do
      let(:graph) do
        graph = Solve::Graph.new
        graph.artifact("A", "1.0.0")
        graph
      end

      let(:demands) { [ ["A", "> 1.0.0"] ] }

      it "raises an error detailing the missing artifacts" do
        expect(error).to be_a_kind_of(Solve::Errors::NoSolutionError)
        expected_error = <<~ERROR_MESSAGE
          Unable to satisfy the following requirements:

          - `A (> 1.0.0)` required by `Berksfile`
        ERROR_MESSAGE
        expect(error.to_s).to eq(expected_error)
      end
    end

    context "and molinillo identifies dependency conflicts" do
      let(:graph) do
        graph = Solve::Graph.new
        graph.artifact("A", "1.0.0").depends("B").depends("C")
        graph.artifact("B", "1.0.0").depends("D", "= 1.0.0")
        graph.artifact("C", "1.0.0").depends("D", "= 2.0.0")
        graph.artifact("D", "1.0.0")
        graph.artifact("D", "2.0.0")
        graph
      end

      let(:demands) { [ [ "A" ] ] }

      it "raises an error detailing the missing artifacts" do
        expect(error).to be_a_kind_of(Solve::Errors::NoSolutionError)
        expected_error = <<~ERROR_MESSAGE
          Unable to satisfy the following requirements:

          - `D (= 1.0.0)` required by `B-1.0.0`
          - `D (= 2.0.0)` required by `C-1.0.0`
        ERROR_MESSAGE
        expect(error.to_s).to eq(expected_error)
      end
    end
  end

  describe "supporting Serializer interface" do
    let(:serializer) { Solve::Solver::Serializer.new }

    before do
      graph.stub(:artifacts).and_return([])
    end

    it "implements the required interface" do
      problem = Solve::Problem.from_solver(solver)
      json_string = serializer.serialize(problem)
      problem_data = JSON.parse(json_string)
      expected_demands = [
        { "name" => "mysql", "constraint" => ">= 0.0.0" },
        { "name" => "nginx", "constraint" => ">= 0.0.0" },
      ]

      problem_data["demands"].should =~ expected_demands
    end
  end
end
