require "webrick"
require "mercenary"
require "helper"
require "openssl"

class TestCommandsServe < JekyllUnitTest
  def custom_opts(what)
    @cmd.send(
      :webrick_opts, what
    )
  end

  context "with a program" do
    setup do
      @merc = nil
      @cmd = Jekyll::Commands::Serve
      Mercenary.program(:jekyll) do |p|
        @merc = @cmd.init_with_program(
          p
        )
      end
      Jekyll.sites.clear
      allow(SafeYAML).to receive(:load_file).and_return({})
      allow(Jekyll::Commands::Build).to receive(:build).and_return("")
    end
    teardown do
      Jekyll.sites.clear
    end

    should "label itself" do
      assert_equal(
        @merc.name, :serve
      )
    end

    should "have aliases" do
      assert_includes @merc.aliases, :s
      assert_includes @merc.aliases, :server
    end

    should "have a description" do
      refute_nil(
        @merc.description
      )
    end

    should "have an action" do
      refute_empty(
        @merc.actions
      )
    end

    should "not have an empty options set" do
      refute_empty(
        @merc.options
      )
    end

    context "with custom options" do
      should "create a default set of mimetypes" do
        refute_nil custom_opts({})[
          :MimeTypes
        ]
      end

      should "use user destinations" do
        assert_equal "foo", custom_opts({ "destination" => "foo" })[
          :DocumentRoot
        ]
      end

      should "use user port" do
        # WHAT?!?!1 Over 9000? That's impossible.
        assert_equal 9001, custom_opts({ "port" => 9001 })[
          :Port
        ]
      end

      should "use empty directory index list when show_dir_listing is true" do
        opts = { "show_dir_listing" => true }
        assert custom_opts(opts)[:DirectoryIndex].empty?
      end

      should "keep config between build and serve" do
        custom_options = {
          "config"  => %w(_config.yml _development.yml),
          "serving" => true,
          "watch"   => false, # for not having guard output when running the tests
          "url"     => "http://localhost:4000"
        }

        expect(Jekyll::Commands::Serve).to receive(:process).with(custom_options)
        @merc.execute(:serve, { "config" => %w(_config.yml _development.yml),
                                "watch"  => false })
      end

      context "in development environment" do
        setup do
          expect(Jekyll).to receive(:env).and_return("development")
          expect(Jekyll::Commands::Serve).to receive(:start_up_webrick)
        end
        should "set the site url by default to `http://localhost:4000`" do
          @merc.execute(:serve, { "watch" => false, "url" => "https://jekyllrb.com/" })

          assert_equal 1, Jekyll.sites.count
          assert_equal "http://localhost:4000", Jekyll.sites.first.config["url"]
        end

        should "take `host`, `port` and `ssl` into consideration if set" do
          @merc.execute(:serve, {
            "watch"    => false,
            "host"     => "example.com",
            "port"     => "9999",
            "url"      => "https://jekyllrb.com/",
            "ssl_cert" => "foo",
            "ssl_key"  => "bar"
          })

          assert_equal 1, Jekyll.sites.count
          assert_equal "https://example.com:9999", Jekyll.sites.first.config["url"]
        end
      end

      context "not in development environment" do
        should "not update the site url" do
          expect(Jekyll).to receive(:env).and_return("production")
          expect(Jekyll::Commands::Serve).to receive(:start_up_webrick)
          @merc.execute(:serve, { "watch" => false, "url" => "https://jekyllrb.com/" })

          assert_equal 1, Jekyll.sites.count
          assert_equal "https://jekyllrb.com/", Jekyll.sites.first.config["url"]
        end
      end

      context "verbose" do
        should "debug when verbose" do
          assert_equal custom_opts({ "verbose" => true })[:Logger].level, 5
        end

        should "warn when not verbose" do
          assert_equal custom_opts({})[:Logger].level, 3
        end
      end

      context "enabling SSL" do
        should "raise if enabling without key or cert" do
          assert_raises RuntimeError do
            custom_opts({
              "ssl_key" => "foo"
            })
          end

          assert_raises RuntimeError do
            custom_opts({
              "ssl_key" => "foo"
            })
          end
        end

        should "allow SSL with a key and cert" do
          expect(OpenSSL::PKey::RSA).to receive(:new).and_return("c2")
          expect(OpenSSL::X509::Certificate).to receive(:new).and_return("c1")
          allow(File).to receive(:read).and_return("foo")

          result = custom_opts({
            "ssl_cert"   => "foo",
            "source"     => "bar",
            "enable_ssl" => true,
            "ssl_key"    => "bar"
          })

          assert result[:SSLEnable]
          assert_equal result[:SSLPrivateKey ], "c2"
          assert_equal result[:SSLCertificate], "c1"
        end
      end
    end
  end
end
