rack.git  about / heads / tags
a modular Ruby webserver interface
blob 595debd47a1d0f9e41c0a47a3670ce25d580dafa 4387 bytes (raw)
$ git show chunk:test/spec_common_logger.rb	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
 
# frozen_string_literal: true

require_relative 'helper'
require 'logger'

separate_testing do
  require_relative '../lib/rack/common_logger'
  require_relative '../lib/rack/lint'
  require_relative '../lib/rack/mock_request'
end

describe Rack::CommonLogger do
  obj = 'foobar'
  length = obj.size

  app = Rack::Lint.new lambda { |env|
    [200,
     { "content-type" => "text/html", "content-length" => length.to_s },
     [obj]]}
  app_without_length = Rack::Lint.new lambda { |env|
    [200,
     { "content-type" => "text/html" },
     []]}
  app_with_zero_length = Rack::Lint.new lambda { |env|
    [200,
     { "content-type" => "text/html", "content-length" => "0" },
     []]}
  app_without_lint = lambda { |env|
    [200,
     { "content-type" => "text/html", "content-length" => length.to_s },
     [obj]]}

  it "log to rack.errors by default" do
    res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")

    res.errors.wont_be :empty?
    res.errors.must_match(/"GET \/ HTTP\/1\.1" 200 #{length} /)
  end

  it "log to anything with +write+" do
    log = StringIO.new
    Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")

    log.string.must_match(/"GET \/ HTTP\/1\.1" 200 #{length} /)
  end

  it "work with standard library logger" do
    logdev = StringIO.new
    log = Logger.new(logdev)
    Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")

    logdev.string.must_match(/"GET \/ HTTP\/1\.1" 200 #{length} /)
  end

  it "log - content length if header is missing" do
    res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/")

    res.errors.wont_be :empty?
    res.errors.must_match(/"GET \/ HTTP\/1\.1" 200 - /)
  end

  it "log - content length if header is zero" do
    res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/")

    res.errors.wont_be :empty?
    res.errors.must_match(/"GET \/ HTTP\/1\.1" 200 - /)
  end

  it "log - records host from X-Forwarded-For header" do
    res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/", 'HTTP_X_FORWARDED_FOR' => '203.0.113.0')

    res.errors.wont_be :empty?
    res.errors.must_match(/203\.0\.113\.0 - /)
  end

  it "log - records host from RFC 7239 forwarded for header" do
    res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/", 'HTTP_FORWARDED' => 'for=203.0.113.0')

    res.errors.wont_be :empty?
    res.errors.must_match(/203\.0\.113\.0 - /)
  end

  def with_mock_time(t = 0)
    mc = class << Time; self; end
    mc.send :alias_method, :old_now, :now
    mc.send :define_method, :now do
      at(t)
    end
    yield
  ensure
    mc.send :undef_method, :now
    mc.send :alias_method, :now, :old_now
  end

  it "log in common log format" do
    log = StringIO.new
    with_mock_time do
      Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/", 'QUERY_STRING' => 'foo=bar')
    end

    md = /- - - \[([^\]]+)\] "(\w+) \/\?foo=bar HTTP\/1\.1" (\d{3}) \d+ ([\d\.]+)/.match(log.string)
    md.wont_equal nil
    time, method, status, duration = *md.captures
    time.must_equal Time.at(0).strftime("%d/%b/%Y:%H:%M:%S %z")
    method.must_equal "GET"
    status.must_equal "200"
    (0..1).must_include duration.to_f
  end

  it "escapes non printable characters except newline" do
    logdev = StringIO.new
    log = Logger.new(logdev)
    Rack::MockRequest.new(Rack::CommonLogger.new(app_without_lint, log)).request("GET\x1f", "/hello")

    logdev.string.must_match(/GET\\x1f \/hello HTTP\/1\.1/)
  end

  it "log path with PATH_INFO" do
    logdev = StringIO.new
    log = Logger.new(logdev)
    Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/hello")

    logdev.string.must_match(/"GET \/hello HTTP\/1\.1" 200 #{length} /)
  end

  it "log path with SCRIPT_NAME" do
    logdev = StringIO.new
    log = Logger.new(logdev)
    Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/path", script_name: "/script")

    logdev.string.must_match(/"GET \/script\/path HTTP\/1\.1" 200 #{length} /)
  end

  it "log path with SERVER_PROTOCOL" do
    logdev = StringIO.new
    log = Logger.new(logdev)
    Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/path", http_version: "HTTP/1.0")

    logdev.string.must_match(/"GET \/path HTTP\/1\.0" 200 #{length} /)
  end

  def length
    123
  end

  def self.obj
    "hello world"
  end
end

git clone https://yhbt.net/rack.git