about summary refs log tree commit diff
path: root/spec/lib/request_spec.rb
blob: e555a8b5a1a286db8e958e98a400e68656abdfae (plain) (blame)
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
# frozen_string_literal: true

require 'rails_helper'
require 'securerandom'

describe Request do
  subject { Request.new(:get, 'http://example.com') }

  describe '#headers' do
    it 'returns user agent' do
      expect(subject.headers['User-Agent']).to be_present
    end

    it 'returns the date header' do
      expect(subject.headers['Date']).to be_present
    end

    it 'returns the host header' do
      expect(subject.headers['Host']).to be_present
    end

    it 'does not return virtual request-target header' do
      expect(subject.headers['(request-target)']).to be_nil
    end
  end

  describe '#on_behalf_of' do
    it 'when used, adds signature header' do
      subject.on_behalf_of(Fabricate(:account))
      expect(subject.headers['Signature']).to be_present
    end
  end

  describe '#add_headers' do
    it 'adds headers to the request' do
      subject.add_headers('Test' => 'Foo')
      expect(subject.headers['Test']).to eq 'Foo'
    end
  end

  describe '#perform' do
    context 'with valid host' do
      before { stub_request(:get, 'http://example.com') }

      it 'executes a HTTP request' do
        expect { |block| subject.perform(&block) }.to yield_control
        expect(a_request(:get, 'http://example.com')).to have_been_made.once
      end

      it 'executes a HTTP request when the first address is private' do
        resolver = double

        allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844))
        allow(resolver).to receive(:timeouts=).and_return(nil)
        allow(Resolv::DNS).to receive(:open).and_yield(resolver)

        expect { |block| subject.perform(&block) }.to yield_control
        expect(a_request(:get, 'http://example.com')).to have_been_made.once
      end

      it 'sets headers' do
        expect { |block| subject.perform(&block) }.to yield_control
        expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
      end

      it 'closes underlying connection' do
        expect_any_instance_of(HTTP::Client).to receive(:close)
        expect { |block| subject.perform(&block) }.to yield_control
      end

      it 'returns response which implements body_with_limit' do
        subject.perform do |response|
          expect(response).to respond_to :body_with_limit
        end
      end
    end

    context 'with private host' do
      around do |example|
        WebMock.disable!
        example.run
        WebMock.enable!
      end

      it 'raises Mastodon::ValidationError' do
        resolver = double

        allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face))
        allow(resolver).to receive(:timeouts=).and_return(nil)
        allow(Resolv::DNS).to receive(:open).and_yield(resolver)

        expect { subject.perform }.to raise_error Mastodon::ValidationError
      end
    end
  end

  describe "response's body_with_limit method" do
    it 'rejects body more than 1 megabyte by default' do
      stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes))
      expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError
    end

    it 'accepts body less than 1 megabyte by default' do
      stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes))
      expect { subject.perform { |response| response.body_with_limit } }.not_to raise_error
    end

    it 'rejects body by given size' do
      stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes))
      expect { subject.perform { |response| response.body_with_limit(1.kilobyte) } }.to raise_error Mastodon::LengthValidationError
    end

    it 'rejects too large chunked body' do
      stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Transfer-Encoding' => 'chunked' })
      expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError
    end

    it 'rejects too large monolithic body' do
      stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes })
      expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError
    end

    it 'truncates large monolithic body' do
      stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes })
      expect(subject.perform { |response| response.truncated_body.bytesize }).to be < 2.megabytes
    end

    it 'uses binary encoding if Content-Type does not tell encoding' do
      stub_request(:any, 'http://example.com').to_return(body: '', headers: { 'Content-Type' => 'text/html' })
      expect(subject.perform { |response| response.body_with_limit.encoding }).to eq Encoding::BINARY
    end

    it 'uses binary encoding if Content-Type tells unknown encoding' do
      stub_request(:any, 'http://example.com').to_return(body: '', headers: { 'Content-Type' => 'text/html; charset=unknown' })
      expect(subject.perform { |response| response.body_with_limit.encoding }).to eq Encoding::BINARY
    end

    it 'uses encoding specified by Content-Type' do
      stub_request(:any, 'http://example.com').to_return(body: '', headers: { 'Content-Type' => 'text/html; charset=UTF-8' })
      expect(subject.perform { |response| response.body_with_limit.encoding }).to eq Encoding::UTF_8
    end
  end
end