@@ -28,6 +28,79 class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base | |||||
28 | Setting.rest_api_enabled = '0' |
|
28 | Setting.rest_api_enabled = '0' | |
29 | end |
|
29 | end | |
30 |
|
30 | |||
|
31 | def test_api_should_deny_without_credentials | |||
|
32 | get '/users/current.xml', {} | |||
|
33 | assert_response 401 | |||
|
34 | assert_equal User.anonymous, User.current | |||
|
35 | assert response.headers.has_key?('WWW-Authenticate') | |||
|
36 | end | |||
|
37 | ||||
|
38 | def test_api_should_accept_http_basic_auth_using_username_and_password | |||
|
39 | user = User.generate! do |user| | |||
|
40 | user.password = 'my_password' | |||
|
41 | end | |||
|
42 | get '/users/current.xml', {}, credentials(user.login, 'my_password') | |||
|
43 | assert_response 200 | |||
|
44 | assert_equal user, User.current | |||
|
45 | end | |||
|
46 | ||||
|
47 | def test_api_should_deny_http_basic_auth_using_username_and_wrong_password | |||
|
48 | user = User.generate! do |user| | |||
|
49 | user.password = 'my_password' | |||
|
50 | end | |||
|
51 | get '/users/current.xml', {}, credentials(user.login, 'wrong_password') | |||
|
52 | assert_response 401 | |||
|
53 | assert_equal User.anonymous, User.current | |||
|
54 | end | |||
|
55 | ||||
|
56 | def test_api_should_accept_http_basic_auth_using_api_key | |||
|
57 | user = User.generate! | |||
|
58 | token = Token.create!(:user => user, :action => 'api') | |||
|
59 | get '/users/current.xml', {}, credentials(token.value, 'X') | |||
|
60 | assert_response 200 | |||
|
61 | assert_equal user, User.current | |||
|
62 | end | |||
|
63 | ||||
|
64 | def test_api_should_deny_http_basic_auth_using_wrong_api_key | |||
|
65 | user = User.generate! | |||
|
66 | token = Token.create!(:user => user, :action => 'feeds') # not the API key | |||
|
67 | get '/users/current.xml', {}, credentials(token.value, 'X') | |||
|
68 | assert_response 401 | |||
|
69 | assert_equal User.anonymous, User.current | |||
|
70 | end | |||
|
71 | ||||
|
72 | def test_api_should_accept_auth_using_api_key_as_parameter | |||
|
73 | user = User.generate! | |||
|
74 | token = Token.create!(:user => user, :action => 'api') | |||
|
75 | get "/users/current.xml?key=#{token.value}", {} | |||
|
76 | assert_response 200 | |||
|
77 | assert_equal user, User.current | |||
|
78 | end | |||
|
79 | ||||
|
80 | def test_api_should_deny_auth_using_wrong_api_key_as_parameter | |||
|
81 | user = User.generate! | |||
|
82 | token = Token.create!(:user => user, :action => 'feeds') # not the API key | |||
|
83 | get "/users/current.xml?key=#{token.value}", {} | |||
|
84 | assert_response 401 | |||
|
85 | assert_equal User.anonymous, User.current | |||
|
86 | end | |||
|
87 | ||||
|
88 | def test_api_should_accept_auth_using_api_key_as_request_header | |||
|
89 | user = User.generate! | |||
|
90 | token = Token.create!(:user => user, :action => 'api') | |||
|
91 | get "/users/current.xml", {}, {'X-Redmine-API-Key' => token.value.to_s} | |||
|
92 | assert_response 200 | |||
|
93 | assert_equal user, User.current | |||
|
94 | end | |||
|
95 | ||||
|
96 | def test_api_should_deny_auth_using_wrong_api_key_as_request_header | |||
|
97 | user = User.generate! | |||
|
98 | token = Token.create!(:user => user, :action => 'feeds') # not the API key | |||
|
99 | get "/users/current.xml", {}, {'X-Redmine-API-Key' => token.value.to_s} | |||
|
100 | assert_response 401 | |||
|
101 | assert_equal User.anonymous, User.current | |||
|
102 | end | |||
|
103 | ||||
31 | def test_api_should_trigger_basic_http_auth_with_basic_authorization_header |
|
104 | def test_api_should_trigger_basic_http_auth_with_basic_authorization_header | |
32 | ApplicationController.any_instance.expects(:authenticate_with_http_basic).once |
|
105 | ApplicationController.any_instance.expects(:authenticate_with_http_basic).once | |
33 | get '/users/current.xml', {}, credentials('jsmith') |
|
106 | get '/users/current.xml', {}, credentials('jsmith') |
@@ -48,44 +48,6 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base | |||||
48 | Setting.rest_api_enabled = '1' |
|
48 | Setting.rest_api_enabled = '1' | |
49 | end |
|
49 | end | |
50 |
|
50 | |||
51 | # Use a private project to make sure auth is really working and not just |
|
|||
52 | # only showing public issues. |
|
|||
53 | should_allow_api_authentication(:get, "/projects/private-child/issues.xml") |
|
|||
54 | should_allow_api_authentication(:get, "/projects/private-child/issues.json") |
|
|||
55 |
|
||||
56 | should_allow_api_authentication(:get, "/issues/6.xml") |
|
|||
57 | should_allow_api_authentication(:get, "/issues/6.json") |
|
|||
58 |
|
||||
59 | should_allow_api_authentication( |
|
|||
60 | :post, |
|
|||
61 | '/issues.xml', |
|
|||
62 | {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, |
|
|||
63 | {:success_code => :created} |
|
|||
64 | ) |
|
|||
65 | should_allow_api_authentication(:post, |
|
|||
66 | '/issues.json', |
|
|||
67 | {:issue => {:project_id => 1, :subject => 'API test', |
|
|||
68 | :tracker_id => 2, :status_id => 3}}, |
|
|||
69 | {:success_code => :created}) |
|
|||
70 |
|
||||
71 | should_allow_api_authentication(:put, |
|
|||
72 | '/issues/6.xml', |
|
|||
73 | {:issue => {:subject => 'API update', :notes => 'A new note'}}, |
|
|||
74 | {:success_code => :ok}) |
|
|||
75 | should_allow_api_authentication(:put, |
|
|||
76 | '/issues/6.json', |
|
|||
77 | {:issue => {:subject => 'API update', :notes => 'A new note'}}, |
|
|||
78 | {:success_code => :ok}) |
|
|||
79 |
|
||||
80 | should_allow_api_authentication(:delete, |
|
|||
81 | '/issues/6.xml', |
|
|||
82 | {}, |
|
|||
83 | {:success_code => :ok}) |
|
|||
84 | should_allow_api_authentication(:delete, |
|
|||
85 | '/issues/6.json', |
|
|||
86 | {}, |
|
|||
87 | {:success_code => :ok}) |
|
|||
88 |
|
||||
89 | test "GET /issues.xml should contain metadata" do |
|
51 | test "GET /issues.xml should contain metadata" do | |
90 | get '/issues.xml' |
|
52 | get '/issues.xml' | |
91 | assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]', |
|
53 | assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]', |
@@ -31,9 +31,6 class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base | |||||
31 | Setting.rest_api_enabled = '1' |
|
31 | Setting.rest_api_enabled = '1' | |
32 | end |
|
32 | end | |
33 |
|
33 | |||
34 | should_allow_api_authentication(:get, "/projects/onlinestore/news.xml") |
|
|||
35 | should_allow_api_authentication(:get, "/projects/onlinestore/news.json") |
|
|||
36 |
|
||||
37 | test "GET /news.xml should return news" do |
|
34 | test "GET /news.xml should return news" do | |
38 | get '/news.xml' |
|
35 | get '/news.xml' | |
39 |
|
36 |
@@ -27,23 +27,6 class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base | |||||
27 | set_tmp_attachments_directory |
|
27 | set_tmp_attachments_directory | |
28 | end |
|
28 | end | |
29 |
|
29 | |||
30 | # TODO: A private project is needed because should_allow_api_authentication |
|
|||
31 | # actually tests that authentication is *required*, not just allowed |
|
|||
32 | should_allow_api_authentication(:get, "/projects/2.xml") |
|
|||
33 | should_allow_api_authentication(:get, "/projects/2.json") |
|
|||
34 | should_allow_api_authentication(:post, |
|
|||
35 | '/projects.xml', |
|
|||
36 | {:project => {:name => 'API test', :identifier => 'api-test'}}, |
|
|||
37 | {:success_code => :created}) |
|
|||
38 | should_allow_api_authentication(:put, |
|
|||
39 | '/projects/2.xml', |
|
|||
40 | {:project => {:name => 'API update'}}, |
|
|||
41 | {:success_code => :ok}) |
|
|||
42 | should_allow_api_authentication(:delete, |
|
|||
43 | '/projects/2.xml', |
|
|||
44 | {}, |
|
|||
45 | {:success_code => :ok}) |
|
|||
46 |
|
||||
47 | test "GET /projects.xml should return projects" do |
|
30 | test "GET /projects.xml should return projects" do | |
48 | get '/projects.xml' |
|
31 | get '/projects.xml' | |
49 | assert_response :success |
|
32 | assert_response :success |
@@ -24,45 +24,6 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base | |||||
24 | Setting.rest_api_enabled = '1' |
|
24 | Setting.rest_api_enabled = '1' | |
25 | end |
|
25 | end | |
26 |
|
26 | |||
27 | should_allow_api_authentication(:get, "/users.xml") |
|
|||
28 | should_allow_api_authentication(:get, "/users.json") |
|
|||
29 | should_allow_api_authentication(:post, |
|
|||
30 | '/users.xml', |
|
|||
31 | {:user => { |
|
|||
32 | :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', |
|
|||
33 | :mail => 'foo@example.net', :password => 'secret123' |
|
|||
34 | }}, |
|
|||
35 | {:success_code => :created}) |
|
|||
36 | should_allow_api_authentication(:post, |
|
|||
37 | '/users.json', |
|
|||
38 | {:user => { |
|
|||
39 | :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', |
|
|||
40 | :mail => 'foo@example.net' |
|
|||
41 | }}, |
|
|||
42 | {:success_code => :created}) |
|
|||
43 | should_allow_api_authentication(:put, |
|
|||
44 | '/users/2.xml', |
|
|||
45 | {:user => { |
|
|||
46 | :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', |
|
|||
47 | :mail => 'jsmith@somenet.foo' |
|
|||
48 | }}, |
|
|||
49 | {:success_code => :ok}) |
|
|||
50 | should_allow_api_authentication(:put, |
|
|||
51 | '/users/2.json', |
|
|||
52 | {:user => { |
|
|||
53 | :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', |
|
|||
54 | :mail => 'jsmith@somenet.foo' |
|
|||
55 | }}, |
|
|||
56 | {:success_code => :ok}) |
|
|||
57 | should_allow_api_authentication(:delete, |
|
|||
58 | '/users/2.xml', |
|
|||
59 | {}, |
|
|||
60 | {:success_code => :ok}) |
|
|||
61 | should_allow_api_authentication(:delete, |
|
|||
62 | '/users/2.xml', |
|
|||
63 | {}, |
|
|||
64 | {:success_code => :ok}) |
|
|||
65 |
|
||||
66 | test "GET /users/:id.xml should return the user" do |
|
27 | test "GET /users/:id.xml should return the user" do | |
67 | get '/users/2.xml' |
|
28 | get '/users/2.xml' | |
68 |
|
29 |
@@ -263,255 +263,6 module Redmine | |||||
263 |
|
263 | |||
264 | # Base class for API tests |
|
264 | # Base class for API tests | |
265 | class Base < ActionDispatch::IntegrationTest |
|
265 | class Base < ActionDispatch::IntegrationTest | |
266 | # Test that a request allows the three types of API authentication |
|
|||
267 | # |
|
|||
268 | # * HTTP Basic with username and password |
|
|||
269 | # * HTTP Basic with an api key for the username |
|
|||
270 | # * Key based with the key=X parameter |
|
|||
271 | # |
|
|||
272 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) |
|
|||
273 | # @param [String] url the request url |
|
|||
274 | # @param [optional, Hash] parameters additional request parameters |
|
|||
275 | # @param [optional, Hash] options additional options |
|
|||
276 | # @option options [Symbol] :success_code Successful response code (:success) |
|
|||
277 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) |
|
|||
278 | def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) |
|
|||
279 | should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) |
|
|||
280 | should_allow_http_basic_auth_with_key(http_method, url, parameters, options) |
|
|||
281 | should_allow_key_based_auth(http_method, url, parameters, options) |
|
|||
282 | end |
|
|||
283 |
|
||||
284 | # Test that a request allows the username and password for HTTP BASIC |
|
|||
285 | # |
|
|||
286 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) |
|
|||
287 | # @param [String] url the request url |
|
|||
288 | # @param [optional, Hash] parameters additional request parameters |
|
|||
289 | # @param [optional, Hash] options additional options |
|
|||
290 | # @option options [Symbol] :success_code Successful response code (:success) |
|
|||
291 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) |
|
|||
292 | def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) |
|
|||
293 | success_code = options[:success_code] || :success |
|
|||
294 | failure_code = options[:failure_code] || :unauthorized |
|
|||
295 |
|
||||
296 | context "should allow http basic auth using a username and password for #{http_method} #{url}" do |
|
|||
297 | context "with a valid HTTP authentication" do |
|
|||
298 | setup do |
|
|||
299 | @user = User.generate! do |user| |
|
|||
300 | user.admin = true |
|
|||
301 | user.password = 'my_password' |
|
|||
302 | end |
|
|||
303 | send(http_method, url, parameters, credentials(@user.login, 'my_password')) |
|
|||
304 | end |
|
|||
305 |
|
||||
306 | should_respond_with success_code |
|
|||
307 | should_respond_with_content_type_based_on_url(url) |
|
|||
308 | should "login as the user" do |
|
|||
309 | assert_equal @user, User.current |
|
|||
310 | end |
|
|||
311 | end |
|
|||
312 |
|
||||
313 | context "with an invalid HTTP authentication" do |
|
|||
314 | setup do |
|
|||
315 | @user = User.generate! |
|
|||
316 | send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) |
|
|||
317 | end |
|
|||
318 |
|
||||
319 | should_respond_with failure_code |
|
|||
320 | should_respond_with_content_type_based_on_url(url) |
|
|||
321 | should "not login as the user" do |
|
|||
322 | assert_equal User.anonymous, User.current |
|
|||
323 | end |
|
|||
324 | end |
|
|||
325 |
|
||||
326 | context "without credentials" do |
|
|||
327 | setup do |
|
|||
328 | send(http_method, url, parameters) |
|
|||
329 | end |
|
|||
330 |
|
||||
331 | should_respond_with failure_code |
|
|||
332 | should_respond_with_content_type_based_on_url(url) |
|
|||
333 | should "include_www_authenticate_header" do |
|
|||
334 | assert @controller.response.headers.has_key?('WWW-Authenticate') |
|
|||
335 | end |
|
|||
336 | end |
|
|||
337 | end |
|
|||
338 | end |
|
|||
339 |
|
||||
340 | # Test that a request allows the API key with HTTP BASIC |
|
|||
341 | # |
|
|||
342 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) |
|
|||
343 | # @param [String] url the request url |
|
|||
344 | # @param [optional, Hash] parameters additional request parameters |
|
|||
345 | # @param [optional, Hash] options additional options |
|
|||
346 | # @option options [Symbol] :success_code Successful response code (:success) |
|
|||
347 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) |
|
|||
348 | def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) |
|
|||
349 | success_code = options[:success_code] || :success |
|
|||
350 | failure_code = options[:failure_code] || :unauthorized |
|
|||
351 |
|
||||
352 | context "should allow http basic auth with a key for #{http_method} #{url}" do |
|
|||
353 | context "with a valid HTTP authentication using the API token" do |
|
|||
354 | setup do |
|
|||
355 | @user = User.generate! do |user| |
|
|||
356 | user.admin = true |
|
|||
357 | end |
|
|||
358 | @token = Token.create!(:user => @user, :action => 'api') |
|
|||
359 | send(http_method, url, parameters, credentials(@token.value, 'X')) |
|
|||
360 | end |
|
|||
361 | should_respond_with success_code |
|
|||
362 | should_respond_with_content_type_based_on_url(url) |
|
|||
363 | should_be_a_valid_response_string_based_on_url(url) |
|
|||
364 | should "login as the user" do |
|
|||
365 | assert_equal @user, User.current |
|
|||
366 | end |
|
|||
367 | end |
|
|||
368 |
|
||||
369 | context "with an invalid HTTP authentication" do |
|
|||
370 | setup do |
|
|||
371 | @user = User.generate! |
|
|||
372 | @token = Token.create!(:user => @user, :action => 'feeds') |
|
|||
373 | send(http_method, url, parameters, credentials(@token.value, 'X')) |
|
|||
374 | end |
|
|||
375 | should_respond_with failure_code |
|
|||
376 | should_respond_with_content_type_based_on_url(url) |
|
|||
377 | should "not login as the user" do |
|
|||
378 | assert_equal User.anonymous, User.current |
|
|||
379 | end |
|
|||
380 | end |
|
|||
381 | end |
|
|||
382 | end |
|
|||
383 |
|
||||
384 | # Test that a request allows full key authentication |
|
|||
385 | # |
|
|||
386 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) |
|
|||
387 | # @param [String] url the request url, without the key=ZXY parameter |
|
|||
388 | # @param [optional, Hash] parameters additional request parameters |
|
|||
389 | # @param [optional, Hash] options additional options |
|
|||
390 | # @option options [Symbol] :success_code Successful response code (:success) |
|
|||
391 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) |
|
|||
392 | def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) |
|
|||
393 | success_code = options[:success_code] || :success |
|
|||
394 | failure_code = options[:failure_code] || :unauthorized |
|
|||
395 |
|
||||
396 | context "should allow key based auth using key=X for #{http_method} #{url}" do |
|
|||
397 | context "with a valid api token" do |
|
|||
398 | setup do |
|
|||
399 | @user = User.generate! do |user| |
|
|||
400 | user.admin = true |
|
|||
401 | end |
|
|||
402 | @token = Token.create!(:user => @user, :action => 'api') |
|
|||
403 | # Simple url parse to add on ?key= or &key= |
|
|||
404 | request_url = if url.match(/\?/) |
|
|||
405 | url + "&key=#{@token.value}" |
|
|||
406 | else |
|
|||
407 | url + "?key=#{@token.value}" |
|
|||
408 | end |
|
|||
409 | send(http_method, request_url, parameters) |
|
|||
410 | end |
|
|||
411 | should_respond_with success_code |
|
|||
412 | should_respond_with_content_type_based_on_url(url) |
|
|||
413 | should_be_a_valid_response_string_based_on_url(url) |
|
|||
414 | should "login as the user" do |
|
|||
415 | assert_equal @user, User.current |
|
|||
416 | end |
|
|||
417 | end |
|
|||
418 |
|
||||
419 | context "with an invalid api token" do |
|
|||
420 | setup do |
|
|||
421 | @user = User.generate! do |user| |
|
|||
422 | user.admin = true |
|
|||
423 | end |
|
|||
424 | @token = Token.create!(:user => @user, :action => 'feeds') |
|
|||
425 | # Simple url parse to add on ?key= or &key= |
|
|||
426 | request_url = if url.match(/\?/) |
|
|||
427 | url + "&key=#{@token.value}" |
|
|||
428 | else |
|
|||
429 | url + "?key=#{@token.value}" |
|
|||
430 | end |
|
|||
431 | send(http_method, request_url, parameters) |
|
|||
432 | end |
|
|||
433 | should_respond_with failure_code |
|
|||
434 | should_respond_with_content_type_based_on_url(url) |
|
|||
435 | should "not login as the user" do |
|
|||
436 | assert_equal User.anonymous, User.current |
|
|||
437 | end |
|
|||
438 | end |
|
|||
439 | end |
|
|||
440 |
|
||||
441 | context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do |
|
|||
442 | setup do |
|
|||
443 | @user = User.generate! do |user| |
|
|||
444 | user.admin = true |
|
|||
445 | end |
|
|||
446 | @token = Token.create!(:user => @user, :action => 'api') |
|
|||
447 | send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) |
|
|||
448 | end |
|
|||
449 | should_respond_with success_code |
|
|||
450 | should_respond_with_content_type_based_on_url(url) |
|
|||
451 | should_be_a_valid_response_string_based_on_url(url) |
|
|||
452 | should "login as the user" do |
|
|||
453 | assert_equal @user, User.current |
|
|||
454 | end |
|
|||
455 | end |
|
|||
456 | end |
|
|||
457 |
|
||||
458 | # Uses should_respond_with_content_type based on what's in the url: |
|
|||
459 | # |
|
|||
460 | # '/project/issues.xml' => should_respond_with_content_type :xml |
|
|||
461 | # '/project/issues.json' => should_respond_with_content_type :json |
|
|||
462 | # |
|
|||
463 | # @param [String] url Request |
|
|||
464 | def self.should_respond_with_content_type_based_on_url(url) |
|
|||
465 | case |
|
|||
466 | when url.match(/xml/i) |
|
|||
467 | should "respond with XML" do |
|
|||
468 | assert_equal 'application/xml', @response.content_type |
|
|||
469 | end |
|
|||
470 | when url.match(/json/i) |
|
|||
471 | should "respond with JSON" do |
|
|||
472 | assert_equal 'application/json', @response.content_type |
|
|||
473 | end |
|
|||
474 | else |
|
|||
475 | raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" |
|
|||
476 | end |
|
|||
477 | end |
|
|||
478 |
|
||||
479 | # Uses the url to assert which format the response should be in |
|
|||
480 | # |
|
|||
481 | # '/project/issues.xml' => should_be_a_valid_xml_string |
|
|||
482 | # '/project/issues.json' => should_be_a_valid_json_string |
|
|||
483 | # |
|
|||
484 | # @param [String] url Request |
|
|||
485 | def self.should_be_a_valid_response_string_based_on_url(url) |
|
|||
486 | case |
|
|||
487 | when url.match(/xml/i) |
|
|||
488 | should_be_a_valid_xml_string |
|
|||
489 | when url.match(/json/i) |
|
|||
490 | should_be_a_valid_json_string |
|
|||
491 | else |
|
|||
492 | raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" |
|
|||
493 | end |
|
|||
494 | end |
|
|||
495 |
|
||||
496 | # Checks that the response is a valid JSON string |
|
|||
497 | def self.should_be_a_valid_json_string |
|
|||
498 | should "be a valid JSON string (or empty)" do |
|
|||
499 | assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) |
|
|||
500 | end |
|
|||
501 | end |
|
|||
502 |
|
||||
503 | # Checks that the response is a valid XML string |
|
|||
504 | def self.should_be_a_valid_xml_string |
|
|||
505 | should "be a valid XML string" do |
|
|||
506 | assert REXML::Document.new(response.body) |
|
|||
507 | end |
|
|||
508 | end |
|
|||
509 |
|
||||
510 | def self.should_respond_with(status) |
|
|||
511 | should "respond with #{status}" do |
|
|||
512 | assert_response status |
|
|||
513 | end |
|
|||
514 | end |
|
|||
515 | end |
|
266 | end | |
516 |
|
267 | |||
517 | class Routing < Redmine::RoutingTest |
|
268 | class Routing < Redmine::RoutingTest |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now