diff --git a/blog_demo.rb b/blog_demo.rb index cd38490..9a3f871 100644 --- a/blog_demo.rb +++ b/blog_demo.rb @@ -510,6 +510,179 @@ Net::HTTP.start('127.0.0.1', port) do |http| ) json_create = http.post('/posts.json', json_create_body, 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie) + raise "expected creating a post via .json to return 201, got #{json_create.code}" unless json_create.code == '201' + created_via_json = JSON.parse(json_create.body) + json_post_id = created_via_json['id'] + # posts#create/#index/#update .json bodies now carry `slug` alongside + # `id` (see PostsController) - a JSON client has no other way to build + # a valid /posts/:slug URL for a post it just created, now that show/ + # update/destroy are slug-keyed (see app.rb's `param: :slug`). + json_post_slug = created_via_json['slug'] + raise 'json create response should echo the saved title' unless created_via_json['title'] == 'JSON API post' + raise 'json create response should carry the generated slug' if json_post_slug.to_s.empty? + puts "POST /posts.json -> #{json_create.code}, created post #{json_post_id} as a JSON payload (no redirect)" + + json_create_cookie = cookie_from(json_create) + cookie = json_create_cookie unless json_create_cookie.empty? + + invalid_json_create_body = URI.encode_www_form( + 'post[title]' => '', 'post[body]' => '', 'authenticity_token' => token + ) + invalid_json_create = http.post('/posts.json', invalid_json_create_body, + 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie) + unless invalid_json_create.code == '422' + raise "expected a blank title via .json to fail with 422, got #{invalid_json_create.code}" + end + invalid_create_errors = JSON.parse(invalid_json_create.body)['errors'] + unless invalid_create_errors.is_a?(Array) && invalid_create_errors.any? { |e| e['source']['pointer'] == '/data/attributes/title' } + raise "expected a JSON:API error object pointing at /data/attributes/title, got #{invalid_json_create.body}" + end + raise 'every JSON:API error object should carry the response status as a string' unless invalid_create_errors.all? { |e| e['status'] == '422' } + puts "POST /posts.json with a blank title -> #{invalid_json_create.code}, " \ + "errors[0].source.pointer=#{invalid_create_errors.first['source']['pointer']}" + + json_update_body = URI.encode_www_form( + 'post[title]' => 'JSON API post (edited)', 'post[body]' => 'Updated via the .json format.', + 'post[lock_version]' => '0', '_method' => 'PATCH', 'authenticity_token' => token + ) + json_update = http.post("/posts/#{json_post_slug}.json", json_update_body, + 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie) + raise "expected updating a post via .json to return 200, got #{json_update.code}" unless json_update.code == '200' + updated_via_json = JSON.parse(json_update.body) + raise 'json update response should reflect the new title' unless updated_via_json['title'] == 'JSON API post (edited)' + puts "PATCH /posts/#{json_post_slug}.json -> #{json_update.code}, title is now '#{updated_via_json['title']}'" + + json_update_cookie = cookie_from(json_update) + cookie = json_update_cookie unless json_update_cookie.empty? + + stale_json_update_body = URI.encode_www_form( + 'post[title]' => 'stale attempt', 'post[body]' => 'this must be rejected', + 'post[lock_version]' => '0', '_method' => 'PATCH', 'authenticity_token' => token + ) + stale_json_update = http.post("/posts/#{json_post_slug}.json", stale_json_update_body, + 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie) + unless stale_json_update.code == '409' + raise "expected a stale lock_version via .json to be rejected with 409, got #{stale_json_update.code}" + end + stale_update_errors = JSON.parse(stale_json_update.body)['errors'] + unless stale_update_errors.is_a?(Array) && stale_update_errors.first['source']['pointer'] == '/data/attributes/lock_version' + raise "expected the 409 body to be a JSON:API error pointing at lock_version, got #{stale_json_update.body}" + end + puts "PATCH /posts/#{json_post_slug}.json with a stale lock_version -> #{stale_json_update.code} " \ + "(StaleObjectError still caught, now with a JSON:API error body instead of a re-rendered form)" + + # 'no-such-post-slug', not a bare number - posts#show is slug-keyed now + # (see app.rb's `param: :slug`), so this exercises the SAME "no row + # matches" 404 path a real mistyped/stale URL would hit. + missing_html = http.get('/posts/no-such-post-slug', 'Cookie' => cookie) + puts "GET /posts/no-such-post-slug (HTML) -> #{missing_html.code} #{missing_html['content-type']}" + raise "expected a plain-text/HTML 404 for a missing post, got #{missing_html.code}" unless missing_html.code == '404' + raise 'an HTML-format 404 must not be a JSON:API error body' if missing_html.body.strip.start_with?('{') + + missing_json = http.get('/posts/no-such-post-slug.json', 'Cookie' => cookie) + puts "GET /posts/no-such-post-slug.json -> #{missing_json.code} #{missing_json['content-type']}" + raise "expected a JSON:API 404 for a missing post via .json, got #{missing_json.code}" unless missing_json.code == '404' + missing_errors = JSON.parse(missing_json.body)['errors'] + unless missing_errors.is_a?(Array) && missing_errors.size == 1 && missing_errors.first['status'] == '404' + raise "expected a single JSON:API error object with status 404, got #{missing_json.body}" + end + raise 'a 404 JSON:API error has no single attribute to point at, so source must be absent' if missing_errors.first.key?('source') + puts "GET /posts/no-such-post-slug.json -> #{missing_json.code}, errors[0]={status: #{missing_errors.first['status'].inspect}, " \ + "detail: #{missing_errors.first['detail'].inspect}} (respond_not_found: same JSON:API shape as render_errors, no source.pointer)" + + puts '-' * 40 + puts 'Comment belongs_to :post, counter_cache: true - posts#index reads post.comments_count, ' \ + 'a plain column, instead of running a query per post to count its comments' + home_with_two_comments = http.get('/', 'Cookie' => cookie) + unless home_with_two_comments.body.include?('2 comment(s)') + raise "expected the index listing to show the counter-cached total of 2 comments, body was: " \ + "#{home_with_two_comments.body[/