require "./spec_helper"

private def handle(request, fallthrough = true, decompress = true)
  io = IO::Memory.new
  response = HTTP::Server::Response.new(io)
  context = HTTP::Server::Context.new(request, response)
  handler = Kemal::StaticFileHandler.new "#{__DIR__}/static", fallthrough
  handler.call context
  response.close
  io.rewind
  HTTP::Client::Response.from_io(io, decompress: decompress)
end

describe Kemal::StaticFileHandler do
  file = File.open "#{__DIR__}/static/dir/test.txt"
  file_size = file.size

  it "should serve a file with content type and etag" do
    response = handle HTTP::Request.new("GET", "/dir/test.txt")
    response.status_code.should eq(200)
    response.headers["Content-Type"].should eq "text/plain"
    response.headers["Etag"].should contain "W/\""
    response.body.should eq(File.read("#{__DIR__}/static/dir/test.txt"))
  end

  it "should serve the 'index.html' file when a directory is requested and index serving is enabled" do
    serve_static({"dir_index" => true})
    response = handle HTTP::Request.new("GET", "/dir/")
    response.status_code.should eq(200)
    response.headers["Content-Type"].should eq "text/html"
    response.headers["Etag"].should contain "W/\""
    response.body.should eq(File.read("#{__DIR__}/static/dir/index.html"))
  end

  it "should respond with 304 if file has not changed" do
    response = handle HTTP::Request.new("GET", "/dir/test.txt")
    response.status_code.should eq(200)
    etag = response.headers["Etag"]

    headers = HTTP::Headers{"If-None-Match" => etag}
    response = handle HTTP::Request.new("GET", "/dir/test.txt", headers)
    response.headers["Content-Type"]?.should be_nil
    response.status_code.should eq(304)
    response.body.should eq ""
  end

  it "should not list directory's entries" do
    serve_static({"gzip" => true, "dir_listing" => false})
    response = handle HTTP::Request.new("GET", "/dir/")
    response.status_code.should eq(404)
  end

  it "should list directory's entries when config is set" do
    serve_static({"gzip" => true, "dir_listing" => true})
    response = handle HTTP::Request.new("GET", "/dir/")
    response.status_code.should eq(200)
    response.body.should match(/test.txt/)
  end

  it "should gzip a file if config is true, headers accept gzip and file is > 880 bytes" do
    serve_static({"gzip" => true, "dir_listing" => true})
    headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"}
    response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers), decompress: false
    response.status_code.should eq(200)
    response.headers["Content-Encoding"].should eq "gzip"
  end

  it "should not gzip a file if config is true, headers accept gzip and file is < 880 bytes" do
    serve_static({"gzip" => true, "dir_listing" => true})
    headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"}
    response = handle HTTP::Request.new("GET", "/dir/test.txt", headers), decompress: false
    response.status_code.should eq(200)
    response.headers["Content-Encoding"]?.should be_nil
  end

  it "should not gzip a file if config is false, headers accept gzip and file is > 880 bytes" do
    serve_static({"gzip" => false, "dir_listing" => true})
    headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"}
    response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers), decompress: false
    response.status_code.should eq(200)
    response.headers["Content-Encoding"]?.should be_nil
  end

  it "should not serve a not found file" do
    response = handle HTTP::Request.new("GET", "/not_found_file.txt")
    response.status_code.should eq(404)
  end

  it "should not serve a not found directory" do
    response = handle HTTP::Request.new("GET", "/not_found_dir/")
    response.status_code.should eq(404)
  end

  it "should not serve a file as directory" do
    response = handle HTTP::Request.new("GET", "/dir/test.txt/")
    response.status_code.should eq(404)
  end

  it "should handle only GET and HEAD method" do
    %w(GET HEAD).each do |method|
      response = handle HTTP::Request.new(method, "/dir/test.txt")
      response.status_code.should eq(200)
    end

    %w(POST PUT DELETE).each do |method|
      response = handle HTTP::Request.new(method, "/dir/test.txt")
      response.status_code.should eq(404)
      response = handle HTTP::Request.new(method, "/dir/test.txt"), false
      response.status_code.should eq(405)
      response.headers["Allow"].should eq("GET, HEAD")
    end
  end

  it "should send part of files when requested (RFC7233)" do
    %w(POST PUT DELETE HEAD).each do |method|
      headers = HTTP::Headers{"Range" => "0-100"}
      response = handle HTTP::Request.new(method, "/dir/test.txt", headers)
      response.status_code.should_not eq(206)
      response.headers.has_key?("Content-Range").should eq(false)
    end

    %w(GET).each do |method|
      headers = HTTP::Headers{"Range" => "0-100"}
      response = handle HTTP::Request.new(method, "/dir/test.txt", headers)
      response.status_code.should eq(206 || 200)
      if response.status_code == 206
        response.headers.has_key?("Content-Range").should eq true
        match = response.headers["Content-Range"].match(/bytes (\d+)-(\d+)\/(\d+)/)
        match.should_not be_nil
        if match
          start_range = match[1].to_i { 0 }
          end_range = match[2].to_i { 0 }
          range_size = match[3].to_i { 0 }

          range_size.should eq file_size
          (end_range < file_size).should eq true
          (start_range < end_range).should eq true
        end
      end
    end
  end

  it "should handle setting custom headers" do
    headers = Proc(HTTP::Server::Response, String, File::Info, Void).new do |response, path, stat|
      if path =~ /\.html$/
        response.headers.add("Access-Control-Allow-Origin", "*")
      end
      response.headers.add("Content-Size", stat.size.to_s)
    end

    static_headers(&headers)

    response = handle HTTP::Request.new("GET", "/dir/test.txt")
    response.headers.has_key?("Access-Control-Allow-Origin").should be_false
    response.headers["Content-Size"].should eq(
      File.info("#{__DIR__}/static/dir/test.txt").size.to_s
    )

    response = handle HTTP::Request.new("GET", "/dir/index.html")
    response.headers["Access-Control-Allow-Origin"].should eq("*")
  end
end
