diff --git a/test/test_request.rb b/test/test_request.rb index 7ef944d..bef58ac 100644 --- a/test/test_request.rb +++ b/test/test_request.rb @@ -35,6 +35,20 @@ class RequestTest < Minitest::Test assert_equal 'yes', request.headers['X-Custom'] end + def test_user_agent_and_remote_addr + env = env_for('GET', '/', headers: { 'User-Agent' => 'TestBrowser/1.0' }) + env['REMOTE_ADDR'] = '203.0.113.7' + request = Prism::Request.new(env) + assert_equal 'TestBrowser/1.0', request.user_agent + assert_equal '203.0.113.7', request.remote_addr + end + + def test_user_agent_and_remote_addr_absent + request = Prism::Request.new(env_for('GET', '/')) + assert_nil request.user_agent + assert_nil request.remote_addr + end + def test_params_precedence env = env_for('POST', '/x?id=query', headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }, @@ -43,4 +57,60 @@ class RequestTest < Minitest::Test request.path_params = { 'id' => 'path' } assert_equal 'path', request.params['id'] end + + def test_method_override_via_body_param + env = env_for('POST', '/posts/1', headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }, + body: 'title=hi&_method=patch') + request = Prism::Request.new(env) + assert_equal 'PATCH', request.request_method + end + + def test_method_override_via_header + env = env_for('POST', '/posts/1', headers: { 'X-Http-Method-Override' => 'delete' }) + request = Prism::Request.new(env) + assert_equal 'DELETE', request.request_method + end + + def test_method_override_ignores_unknown_verb + env = env_for('POST', '/posts', headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }, + body: '_method=trace') + request = Prism::Request.new(env) + assert_equal 'POST', request.request_method + end + + def test_method_override_does_not_starve_body_for_a_second_reader + env = env_for('POST', '/posts', headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }, + body: 'title=hello&_method=put') + first = Prism::Request.new(env) + assert_equal 'PUT', first.request_method + second = Prism::Request.new(env) + assert_equal 'hello', second.params['title'] + end + + def test_multipart_body_mixes_fields_and_files + boundary = '----PrismReqBoundary42' + body = "--#{boundary}\r\n" \ + "Content-Disposition: form-data; name=\"title\"\r\n\r\nmy post\r\n" \ + "--#{boundary}\r\n" \ + "Content-Disposition: form-data; name=\"avatar\"; filename=\"pic.png\"\r\n" \ + "Content-Type: image/png\r\n\r\nbinarydata\r\n" \ + "--#{boundary}--\r\n" + env = env_for('POST', '/uploads', + headers: { 'Content-Type' => "multipart/form-data; boundary=#{boundary}" }, + body: body) + request = Prism::Request.new(env) + assert_equal 'my post', request.params['title'] + + file = request.params['avatar'] + assert_instance_of Prism::UploadedFile, file + assert_equal 'pic.png', file.original_filename + assert_equal 'image/png', file.content_type + assert_equal 'binarydata', file.read + end + + def test_multipart_without_boundary_yields_empty_params + env = env_for('POST', '/uploads', headers: { 'Content-Type' => 'multipart/form-data' }, body: 'ignored') + request = Prism::Request.new(env) + assert_empty request.params + end end diff --git a/test/test_resources_advanced.rb b/test/test_resources_advanced.rb new file mode 100644 index 0000000..7e071cf --- /dev/null +++ b/test/test_resources_advanced.rb @@ -0,0 +1,132 @@ +require 'minitest/autorun' +require 'prism' +require 'support' + +class ArticlesController < Prism::Controller + def index = html('articles-index') + def show = html("article-#{params['id']}") + + def publish + html("published-#{params['id']}") + end + + def archive + html('archived-collection') + end +end + +class RemarksController < Prism::Controller + def index = html("remarks-for-article-#{params['article_id']}") + def show = html("remark-#{params['id']}-on-article-#{params['article_id']}") +end + +class AccountController < Prism::Controller + def show + respond_to do |format| + format.html { html('the-account') } + format.json { json(kind: 'account') } + end + end + + def edit = html('edit-the-account') +end + +class PreferencesController < Prism::Controller + def index = html('preferences-for-account') +end + +class NestedResourcesApp < Prism::Application + resources :articles do + member do + patch :publish + end + collection do + get :archive + end + resources :remarks + end + + resource :account do + resources :preferences, only: [:index] + end +end + +class ResourcesAdvancedTest < Minitest::Test + include TestSupport + + def call(method, path, **opts) + NestedResourcesApp.call(env_for(method, path, **opts)) + end + + def test_member_route_is_scoped_under_the_resource_id + status, _, body = call('PATCH', '/articles/9/publish') + assert_equal 200, status + assert_equal 'published-9', body.join + end + + def test_member_route_gets_a_rails_style_default_name + assert_equal '/articles/9/publish', NestedResourcesApp.router.path_to(:publish_article, id: 9) + end + + def test_collection_route_is_scoped_under_the_resource_without_an_id + status, _, body = call('GET', '/articles/archive') + assert_equal 200, status + assert_equal 'archived-collection', body.join + end + + def test_collection_route_gets_a_rails_style_default_name + assert_equal '/articles/archive', NestedResourcesApp.router.path_to(:archive_articles) + end + + def test_nested_resource_index_is_scoped_under_the_parent_id + status, _, body = call('GET', '/articles/3/remarks') + assert_equal 200, status + assert_equal 'remarks-for-article-3', body.join + end + + def test_nested_resource_show_receives_both_ids + status, _, body = call('GET', '/articles/3/remarks/5') + assert_equal 200, status + assert_equal 'remark-5-on-article-3', body.join + end + + def test_singular_resource_has_no_id_segment + status, _, body = call('GET', '/account') + assert_equal 200, status + assert_equal 'the-account', body.join + end + + def test_singular_resource_edit_path + status, _, body = call('GET', '/account/edit') + assert_equal 200, status + assert_equal 'edit-the-account', body.join + end + + def test_resources_nested_under_a_singular_resource + status, _, body = call('GET', '/account/preferences') + assert_equal 200, status + assert_equal 'preferences-for-account', body.join + end + + # `resource` used to build its routes without `format: true`, unlike + # `resources` - a singular resource such as this one could only ever be + # reached as JSON through Accept-header negotiation, never through the + # explicit ".json" extension every plural resource already supports. + def test_singular_resource_supports_the_json_extension + status, headers, body = call('GET', '/account.json') + assert_equal 200, status + assert_equal 'application/json', headers['Content-Type'] + assert_equal({ 'kind' => 'account' }, JSON.parse(body.join)) + end + + def test_member_and_collection_require_an_id_param_and_reject_singular_resources + error = assert_raises(ArgumentError) do + Class.new(Prism::Application) do + resource :account do + member { get :foo } + end + end + end + assert_match(/singular resource/, error.message) + end +end