|
68 | 68 | end |
69 | 69 |
|
70 | 70 | context 'when Rails routing is present' do |
71 | | - context 'when route has format suffix' do |
72 | | - before do |
73 | | - allow(request).to receive(:env).and_return({'action_dispatch.route_uri_pattern' => '/users/:id(.:format)'}) |
| 71 | + context 'when action_dispatch.route_uri_pattern is set' do |
| 72 | + context 'when route has format suffix' do |
| 73 | + before do |
| 74 | + allow(request).to receive(:env).and_return({'action_dispatch.route_uri_pattern' => '/users/:id(.:format)'}) |
| 75 | + end |
| 76 | + |
| 77 | + it { expect(described_class.route_pattern(request)).to eq('/users/:id') } |
74 | 78 | end |
75 | 79 |
|
76 | | - it { expect(described_class.route_pattern(request)).to eq('/users/:id') } |
77 | | - end |
| 80 | + context 'when route has no format suffix' do |
| 81 | + before { allow(request).to receive(:env).and_return({'action_dispatch.route_uri_pattern' => '/users/:id'}) } |
78 | 82 |
|
79 | | - context 'when route has no format suffix' do |
80 | | - before { allow(request).to receive(:env).and_return({'action_dispatch.route_uri_pattern' => '/users/:id'}) } |
| 83 | + it { expect(described_class.route_pattern(request)).to eq('/users/:id') } |
| 84 | + end |
81 | 85 |
|
82 | | - it { expect(described_class.route_pattern(request)).to eq('/users/:id') } |
| 86 | + context 'when route has nested path' do |
| 87 | + before do |
| 88 | + allow(request).to receive(:env).and_return( |
| 89 | + {'action_dispatch.route_uri_pattern' => '/api/v1/users/:id/posts/:post_id(.:format)'} |
| 90 | + ) |
| 91 | + end |
| 92 | + |
| 93 | + it { expect(described_class.route_pattern(request)).to eq('/api/v1/users/:id/posts/:post_id') } |
| 94 | + end |
83 | 95 | end |
84 | 96 |
|
85 | | - context 'when route has nested path' do |
86 | | - before do |
87 | | - allow(request).to receive(:env).and_return( |
88 | | - {'action_dispatch.route_uri_pattern' => '/api/v1/users/:id/posts/:post_id(.:format)'} |
89 | | - ) |
| 97 | + context 'when action_dispatch.route is set (Rails 8.1.1 and up)' do |
| 98 | + context 'when route has format suffix' do |
| 99 | + let(:route_double) do |
| 100 | + spec_double = instance_double('ActionDispatch::Journey::Nodes::Cat', to_s: '/users/:id(.:format)') |
| 101 | + path_double = instance_double('ActionDispatch::Journey::Path::Pattern', spec: spec_double) |
| 102 | + instance_double('ActionDispatch::Journey::Route', path: path_double) |
| 103 | + end |
| 104 | + |
| 105 | + before do |
| 106 | + allow(request).to receive(:env).and_return({'action_dispatch.route' => route_double}) |
| 107 | + end |
| 108 | + |
| 109 | + it { expect(described_class.route_pattern(request)).to eq('/users/:id') } |
90 | 110 | end |
91 | 111 |
|
92 | | - it { expect(described_class.route_pattern(request)).to eq('/api/v1/users/:id/posts/:post_id') } |
| 112 | + context 'when route has no format suffix' do |
| 113 | + let(:route_double) do |
| 114 | + spec_double = instance_double('ActionDispatch::Journey::Nodes::Cat', to_s: '/users/:id') |
| 115 | + path_double = instance_double('ActionDispatch::Journey::Path::Pattern', spec: spec_double) |
| 116 | + instance_double('ActionDispatch::Journey::Route', path: path_double) |
| 117 | + end |
| 118 | + |
| 119 | + before { allow(request).to receive(:env).and_return({'action_dispatch.route' => route_double}) } |
| 120 | + |
| 121 | + it { expect(described_class.route_pattern(request)).to eq('/users/:id') } |
| 122 | + end |
93 | 123 | end |
94 | 124 |
|
95 | | - context 'when route_uri_pattern is not set and request path_parameters is empty' do |
| 125 | + context 'when neither route or route_uri_pattern is set and request path_parameters are empty' do |
96 | 126 | before do |
97 | 127 | allow(request).to receive(:env).and_return({ |
98 | 128 | 'action_dispatch.routes' => route_set, |
|
114 | 144 | end |
115 | 145 | end |
116 | 146 |
|
117 | | - context 'when route_uri_pattern is not set and request path_parameters is present' do |
| 147 | + context 'when neither route route_uri_pattern is set and request path_parameters are present' do |
118 | 148 | let(:env) do |
119 | 149 | { |
120 | 150 | 'action_dispatch.routes' => route_set, |
|
123 | 153 | } |
124 | 154 | } |
125 | 155 | end |
| 156 | + let(:router) { double('ActionDispatch::Routing::RouteSet::Router') } |
| 157 | + let(:route_set) { double('ActionDispatch::Routing::RouteSet', router: router, request_class: action_dispatch_request_class) } |
| 158 | + let(:action_dispatch_request_class) { double('class ActionDispatch::Request', new: action_dispatch_request) } |
| 159 | + let(:action_dispatch_request) { double('ActionDispatch::Request', env: {}, script_name: '', path: '/users/1') } |
126 | 160 |
|
127 | | - context 'when request is HEAD' do |
128 | | - before do |
129 | | - allow(request).to receive(:env).and_return(env) |
130 | | - allow(action_dispatch_request).to receive(:env).and_return(env) |
131 | | - end |
| 161 | + before do |
| 162 | + allow(request).to receive(:env).and_return(env) |
| 163 | + allow(action_dispatch_request).to receive(:env).and_return(env) |
| 164 | + end |
132 | 165 |
|
133 | | - let(:router) { double('ActionDispatch::Routing::RouteSet::Router') } |
134 | | - let(:route_set) { double('ActionDispatch::Routing::RouteSet', router: router, request_class: action_dispatch_request_class) } |
| 166 | + context 'when request is HEAD' do |
135 | 167 | let(:request) { double('Rack::Request', env: {}, script_name: '', path: '/users/1', head?: true) } |
136 | | - let(:action_dispatch_request_class) { double('class ActionDispatch::Request', new: action_dispatch_request) } |
137 | | - let(:action_dispatch_request) { double('ActionDispatch::Request', env: {}, script_name: '', path: '/users/1') } |
138 | 168 |
|
139 | 169 | it 'uses action dispatch request for route recognition' do |
140 | 170 | expect(router).to receive(:recognize).with(action_dispatch_request).and_return('/users/:id(.:format)') |
|
143 | 173 | end |
144 | 174 |
|
145 | 175 | context 'when request is not HEAD' do |
146 | | - before { allow(request).to receive(:env).and_return(env) } |
147 | | - |
148 | | - let(:router) { double('ActionDispatch::Routing::RouteSet::Router') } |
149 | | - let(:route_set) { double('ActionDispatch::Routing::RouteSet', router: router) } |
150 | 176 | let(:request) { double('Rack::Request', env: {}, script_name: '', path: '/users/1', head?: false) } |
151 | 177 |
|
152 | 178 | it 'uses action dispatch request for route recognition' do |
153 | | - expect(router).to receive(:recognize).with(request).and_return('/users/:id(.:format)') |
| 179 | + expect(router).to receive(:recognize).with(action_dispatch_request).and_return('/users/:id(.:format)') |
154 | 180 | expect(described_class.route_pattern(request)).to eq('/users/:id') |
155 | 181 | end |
156 | 182 | end |
|
170 | 196 |
|
171 | 197 | it { expect(described_class.route_pattern(request)).to eq('/unmatched/route') } |
172 | 198 | end |
| 199 | + |
| 200 | + context 'when an error is raised during route recognition' do |
| 201 | + before do |
| 202 | + allow(Datadog::AppSec).to receive(:telemetry).and_return(telemetry) |
| 203 | + |
| 204 | + allow(request).to receive(:env).and_return({ |
| 205 | + 'action_dispatch.routes' => route_set, |
| 206 | + 'action_dispatch.request.path_parameters' => { |
| 207 | + 'controller' => 'users', |
| 208 | + 'action' => 'show', |
| 209 | + 'id' => '1' |
| 210 | + } |
| 211 | + }) |
| 212 | + |
| 213 | + expect(route_set).to receive(:request_class).and_raise(StandardError) |
| 214 | + end |
| 215 | + |
| 216 | + let(:route_set) { double('ActionDispatch::Routing::RouteSet') } |
| 217 | + let(:telemetry) { spy(Datadog::Core::Telemetry::Component) } |
| 218 | + |
| 219 | + it { expect(described_class.route_pattern(request)).to be_nil } |
| 220 | + |
| 221 | + it 'reports the error via telemetry' do |
| 222 | + expect(telemetry).to receive(:report) |
| 223 | + .with(an_instance_of(StandardError), description: 'AppSec: Could not extract route pattern') |
| 224 | + |
| 225 | + described_class.route_pattern(request) |
| 226 | + end |
| 227 | + end |
173 | 228 | end |
174 | 229 |
|
175 | 230 | context 'when Rack routing is present' do |
|
0 commit comments