cli
1# frozen_string_literal: true
2
3RSpec.describe Scim::Kit::Http do
4 subject { described_class.new }
5
6 let(:uri) { URI(FFaker::Internet.uri('https')) }
7
8 describe 'request logging' do
9 let(:log) { StringIO.new }
10 let(:server) { TCPServer.new('127.0.0.1', 0) }
11
12 around do |example|
13 WebMock.disable!
14 original = Scim::Kit.logger
15 Scim::Kit.logger = Logger.new(log)
16 described_class.instance_variable_set(:@default_driver, nil)
17 example.run
18 Scim::Kit.logger = original
19 described_class.instance_variable_set(:@default_driver, nil)
20 WebMock.enable!
21 server.close
22 end
23
24 def respond_once
25 Thread.new do
26 socket = server.accept
27 loop { break if socket.gets.to_s.strip.empty? }
28 socket.print("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\n{}")
29 socket.close
30 end
31 end
32
33 def fetch_with_credentials
34 responder = respond_once
35 described_class.new(retries: 0).fetch(
36 URI("http://127.0.0.1:#{server.addr[1]}/Users"),
37 headers: { 'Authorization' => 'Bearer s3cret' }
38 )
39 responder.join
40 end
41
42 it 'keeps credentials out of the log' do
43 fetch_with_credentials
44
45 expect(log.string).not_to include('s3cret')
46 end
47 end
48
49 describe '#fetch' do
50 context 'when the response is successful' do
51 let(:body) { { id: '123' } }
52
53 before { stub_request(:get, uri).to_return(status: 200, body: body.to_json) }
54
55 specify { expect(subject.fetch(uri)).to be_ok }
56 specify { expect(subject.fetch(uri).status).to be(200) }
57 specify { expect(subject.fetch(uri).body).to eql(body) }
58 end
59
60 context 'when the response is a scim error' do
61 let(:error_body) { { detail: 'Resource not found', status: '404' } }
62
63 before { stub_request(:get, uri).to_return(status: 404, body: error_body.to_json) }
64
65 specify { expect(subject.fetch(uri)).not_to be_ok }
66 specify { expect(subject.fetch(uri).status).to be(404) }
67 specify { expect(subject.fetch(uri).body).to eql(error_body) }
68 end
69
70 context 'when the response body is not json' do
71 before { stub_request(:get, uri).to_return(status: 500, body: 'boom') }
72
73 specify { expect(subject.fetch(uri)).not_to be_ok }
74 specify { expect(subject.fetch(uri).body).to eql(detail: 'boom') }
75 end
76
77 context 'when a successful response body is not json' do
78 before { stub_request(:get, uri).to_return(status: 200, body: '<html>') }
79
80 specify { expect(subject.fetch(uri)).not_to be_ok }
81 specify { expect(subject.fetch(uri).body).to eql(detail: '<html>') }
82 end
83
84 context 'when the response has no body' do
85 before { stub_request(:get, uri).to_return(status: 204, body: nil) }
86
87 specify { expect(subject.fetch(uri)).to be_ok }
88 specify { expect(subject.fetch(uri).body).to eql({}) }
89 end
90
91 context 'when the connection fails' do
92 subject { described_class.new(retries: 0) }
93
94 before { stub_request(:get, uri).to_raise(Errno::ECONNREFUSED) }
95
96 specify { expect(subject.fetch(uri)).not_to be_ok }
97 specify { expect(subject.fetch(uri).status).to be_nil }
98 specify { expect(subject.fetch(uri).body[:detail]).to include('Connection refused') }
99 end
100
101 context 'when headers are provided' do
102 before do
103 stub_request(:get, uri)
104 .with(headers: { 'X-Test' => 'value' })
105 .to_return(status: 200, body: '{}')
106 end
107
108 specify { expect(subject.fetch(uri, headers: { 'X-Test' => 'value' })).to be_ok }
109 end
110
111 context 'when the response redirects to the same origin' do
112 let(:credentials) { { 'Authorization' => 'Bearer xyz' } }
113 let(:redirect_uri) { URI.join(uri, '/v2/Users') }
114
115 before do
116 stub_request(:get, uri)
117 .to_return(status: 301, headers: { 'Location' => redirect_uri.to_s })
118 stub_request(:get, redirect_uri)
119 .with(headers: credentials).to_return(status: 200, body: '{}')
120 end
121
122 specify { expect(subject.fetch(uri, headers: credentials)).to be_ok }
123 end
124
125 context 'when the response redirects to another origin' do
126 let(:credentials) { { 'Authorization' => 'Bearer xyz' } }
127 let(:redirect_uri) { URI('https://elsewhere.example.com/Users') }
128
129 before do
130 stub_request(:get, uri)
131 .to_return(status: 301, headers: { 'Location' => redirect_uri.to_s })
132 stub_request(:get, redirect_uri).to_return(status: 200, body: '{}')
133 end
134
135 it 'does not forward the credentials' do
136 subject.fetch(uri, headers: credentials)
137
138 expect(a_request(:get, redirect_uri)
139 .with(headers: credentials)).not_to have_been_made
140 end
141 end
142 end
143
144 describe '#get' do
145 context 'when the response is successful' do
146 before { stub_request(:get, uri).to_return(status: 200, body: '{"a":1}') }
147
148 specify { expect(subject.get(uri)).to eql(a: 1) }
149 end
150
151 context 'when a successful response body is not json' do
152 before { stub_request(:get, uri).to_return(status: 200, body: '<html>') }
153
154 it 'does not hand the unparsed body to the caller' do
155 expect(subject.get(uri)).to eql({})
156 end
157 end
158 end
159end