refinerycms-api 1.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rbenv-gemsets +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +12 -0
  6. data/Gemfile +57 -0
  7. data/LICENSE +27 -0
  8. data/Rakefile +21 -0
  9. data/app/controllers/refinery/api/base_controller.rb +174 -0
  10. data/app/controllers/refinery/api/v1/blog/posts_controller.rb +78 -0
  11. data/app/controllers/refinery/api/v1/images_controller.rb +76 -0
  12. data/app/controllers/refinery/api/v1/inquiries/inquiries_controller.rb +69 -0
  13. data/app/controllers/refinery/api/v1/pages_controller.rb +77 -0
  14. data/app/controllers/refinery/api/v1/resources_controller.rb +66 -0
  15. data/app/decorators/models/refinery/authentication/devise/user_decorator.rb +5 -0
  16. data/app/helpers/refinery/api/api_helpers.rb +54 -0
  17. data/app/models/concerns/refinery/user_api_authentication.rb +19 -0
  18. data/app/models/refinery/ability.rb +59 -0
  19. data/app/views/refinery/api/errors/gateway_error.v1.rabl +2 -0
  20. data/app/views/refinery/api/errors/invalid_api_key.v1.rabl +2 -0
  21. data/app/views/refinery/api/errors/invalid_resource.v1.rabl +3 -0
  22. data/app/views/refinery/api/errors/must_specify_api_key.v1.rabl +2 -0
  23. data/app/views/refinery/api/errors/not_found.v1.rabl +2 -0
  24. data/app/views/refinery/api/errors/unauthorized.v1.rabl +2 -0
  25. data/app/views/refinery/api/v1/blog/posts/index.v1.rabl +5 -0
  26. data/app/views/refinery/api/v1/blog/posts/new.v1.rabl +3 -0
  27. data/app/views/refinery/api/v1/blog/posts/show.v1.rabl +5 -0
  28. data/app/views/refinery/api/v1/images/index.v1.rabl +5 -0
  29. data/app/views/refinery/api/v1/images/new.v1.rabl +3 -0
  30. data/app/views/refinery/api/v1/images/show.v1.rabl +6 -0
  31. data/app/views/refinery/api/v1/inquiries/inquiries/index.v1.rabl +5 -0
  32. data/app/views/refinery/api/v1/inquiries/inquiries/new.v1.rabl +3 -0
  33. data/app/views/refinery/api/v1/inquiries/inquiries/show.v1.rabl +5 -0
  34. data/app/views/refinery/api/v1/pages/index.v1.rabl +8 -0
  35. data/app/views/refinery/api/v1/pages/new.v1.rabl +3 -0
  36. data/app/views/refinery/api/v1/pages/pages.v1.rabl +5 -0
  37. data/app/views/refinery/api/v1/pages/show.v1.rabl +9 -0
  38. data/app/views/refinery/api/v1/resources/index.v1.rabl +5 -0
  39. data/app/views/refinery/api/v1/resources/new.v1.rabl +3 -0
  40. data/app/views/refinery/api/v1/resources/show.v1.rabl +6 -0
  41. data/bin/rails +5 -0
  42. data/bin/rake +21 -0
  43. data/bin/rspec +22 -0
  44. data/bin/spring +18 -0
  45. data/config/initializers/metal_load_paths.rb +1 -0
  46. data/config/locales/en.yml +27 -0
  47. data/config/routes.rb +24 -0
  48. data/db/migrate/20160501141738_add_api_key_to_refinery_authentication_devise_users.rb +8 -0
  49. data/lib/generators/refinery/api/api_generator.rb +16 -0
  50. data/lib/generators/refinery/api/templates/config/initializers/refinery/api.rb.erb +7 -0
  51. data/lib/refinery/api.rb +29 -0
  52. data/lib/refinery/api/configuration.rb +32 -0
  53. data/lib/refinery/api/controller_helpers/auth.rb +76 -0
  54. data/lib/refinery/api/controller_helpers/strong_parameters.rb +37 -0
  55. data/lib/refinery/api/controller_setup.rb +20 -0
  56. data/lib/refinery/api/engine.rb +52 -0
  57. data/lib/refinery/api/responders.rb +11 -0
  58. data/lib/refinery/api/responders/rabl_template.rb +30 -0
  59. data/lib/refinery/api/testing_support/caching.rb +10 -0
  60. data/lib/refinery/api/testing_support/helpers.rb +44 -0
  61. data/lib/refinery/api/testing_support/setup.rb +16 -0
  62. data/lib/refinery/permitted_attributes.rb +40 -0
  63. data/lib/refinery/responder.rb +45 -0
  64. data/lib/refinerycms-api.rb +3 -0
  65. data/readme.md +69 -0
  66. data/refinerycms_api.gemspec +22 -0
  67. data/script/rails +9 -0
  68. data/spec/controllers/refinery/api/base_controller_spec.rb +73 -0
  69. data/spec/controllers/refinery/api/v1/blog/posts_controller_spec.rb +140 -0
  70. data/spec/controllers/refinery/api/v1/images_controller_spec.rb +93 -0
  71. data/spec/controllers/refinery/api/v1/inquiries/inquiries_controller_spec.rb +126 -0
  72. data/spec/controllers/refinery/api/v1/pages_controller_spec.rb +150 -0
  73. data/spec/controllers/refinery/api/v1/resources_controller_spec.rb +94 -0
  74. data/spec/fixtures/refinery_is_awesome.txt +1 -0
  75. data/spec/fixtures/thinking-cat.jpg +0 -0
  76. data/spec/models/refinery/user_spec.rb +23 -0
  77. data/spec/requests/rabl_cache_spec.rb +17 -0
  78. data/spec/requests/ransackable_attributes_spec.rb +80 -0
  79. data/spec/requests/version_spec.rb +23 -0
  80. data/spec/shared_examples/protect_product_actions.rb +17 -0
  81. data/spec/spec_helper.rb +77 -0
  82. data/spec/support/controller_hacks.rb +33 -0
  83. data/spec/support/database_cleaner.rb +14 -0
  84. data/spec/support/have_attributes_matcher.rb +9 -0
  85. data/tasks/refinery_api.rake +14 -0
  86. data/tasks/rspec.rake +4 -0
  87. metadata +240 -0
@@ -0,0 +1,11 @@
1
+ require 'refinery/api/responders/rabl_template'
2
+
3
+ module Refinery
4
+ module Api
5
+ module Responders
6
+ class AppResponder < ActionController::Responder
7
+ include RablTemplate
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module Refinery
2
+ module Api
3
+ module Responders
4
+ module RablTemplate
5
+ def to_format
6
+ if template
7
+ render template, :status => options[:status] || 200
8
+ else
9
+ super
10
+ end
11
+ rescue ActionView::MissingTemplate => e
12
+ api_behavior
13
+ end
14
+
15
+ def template
16
+ options[:default_template]
17
+ end
18
+
19
+ def api_behavior
20
+ if controller.params[:action] == "destroy"
21
+ # Render a blank template
22
+ super
23
+ else
24
+ # Do nothing and fallback to the default template
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ RSpec.configure do |config|
2
+ config.before(:each, :caching => true) do
3
+ ActionController::Base.perform_caching = true
4
+ end
5
+
6
+ config.after(:each, :caching => true) do
7
+ ActionController::Base.perform_caching = false
8
+ Rails.cache.clear
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ module Refinery
2
+ module Api
3
+ module TestingSupport
4
+ module Helpers
5
+ def json_response
6
+ case body = JSON.parse(response.body)
7
+ when Hash
8
+ body.with_indifferent_access
9
+ when Array
10
+ body
11
+ end
12
+ end
13
+
14
+ def assert_not_found!
15
+ expect(json_response).to eq({ "error" => "The resource you were looking for could not be found." })
16
+ expect(response.status).to eq 404
17
+ end
18
+
19
+ def assert_unauthorized!
20
+ expect(json_response).to eq({ "error" => "You are not authorized to perform that action." })
21
+ expect(response.status).to eq 401
22
+ end
23
+
24
+ def stub_authentication!
25
+ allow(Refinery::Api.user_class).to receive(:find_by).with(hash_including(:refinery_api_key)) { current_api_user }
26
+ end
27
+
28
+ # This method can be overriden (with a let block) inside a context
29
+ # For instance, if you wanted to have an admin user instead.
30
+ def current_api_user
31
+ @current_api_user ||= stub_model(Refinery::Api.user_class, email: "refinery@example.com")
32
+ end
33
+
34
+ def file_path(filename)
35
+ File.open(Refinery::Api::Engine.root + "spec/fixtures" + filename)
36
+ end
37
+
38
+ def upload_file(filename, mime_type)
39
+ fixture_file_upload(file_path(filename).path, mime_type)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ module Refinery
2
+ module Api
3
+ module TestingSupport
4
+ module Setup
5
+ def sign_in_as_admin!
6
+ let!(:current_api_user) do
7
+ user = stub_model(Refinery::Api.user_class)
8
+ allow(user).to receive_message_chain(:roles, :pluck).and_return(["superuser"])
9
+ allow(user).to receive(:has_role?).with("superuser").and_return(true)
10
+ user
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ module Refinery
2
+ module PermittedAttributes
3
+ ATTRIBUTES = [
4
+ :image_attributes,
5
+ :page_attributes,
6
+ :page_part_attributes,
7
+ :resource_attributes,
8
+ :blog_post_attributes,
9
+ :inquiries_inquiry_attributes
10
+ ]
11
+
12
+ mattr_reader *ATTRIBUTES
13
+
14
+ @@image_attributes = [
15
+ { image: [] }, :image_size, :image_title, :image_alt
16
+ ]
17
+
18
+ @@page_attributes = [
19
+ :browser_title, :draft, :link_url, :menu_title, :meta_description,
20
+ :parent_id, :skip_to_first_child, :show_in_menu, :title, :view_template,
21
+ :layout_template, :custom_slug, parts_attributes: [:id, :title, :slug, :body, :position]
22
+ ]
23
+
24
+ @@page_part_attributes = [
25
+ :title, :slug, :body, :locale
26
+ ]
27
+
28
+ @@resource_attributes = [
29
+ :resource_title, { file: [] }
30
+ ]
31
+
32
+ @@blog_post_attributes = [
33
+ :title, :body, :custom_teaser, :tag_list,
34
+ :draft, :published_at, :custom_url, :user_id, :username, :browser_title,
35
+ :meta_description, :source_url, :source_url_title, category_ids: []
36
+ ]
37
+
38
+ @@inquiries_inquiry_attributes = [:name, :phone, :message, :email]
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ module Refinery
2
+ class Responder < ::ActionController::Responder #:nodoc:
3
+
4
+ attr_accessor :on_success, :on_failure
5
+
6
+ def initialize(controller, resources, options={})
7
+ super
8
+
9
+ class_name = controller.class.name.to_sym
10
+ action_name = options.delete(:action_name)
11
+
12
+ if result = Refinery::BaseController.refinery_responders[class_name].try(:[], action_name).try(:[], self.format.to_sym)
13
+ self.on_success = handler(controller, result, :success)
14
+ self.on_failure = handler(controller, result, :failure)
15
+ end
16
+ end
17
+
18
+ def to_html
19
+ super and return if !(on_success || on_failure)
20
+ has_errors? ? controller.instance_exec(&on_failure) : controller.instance_exec(&on_success)
21
+ end
22
+
23
+ def to_format
24
+ super and return if !(on_success || on_failure)
25
+ has_errors? ? controller.instance_exec(&on_failure) : controller.instance_exec(&on_success)
26
+ end
27
+
28
+ private
29
+
30
+ def handler(controller, result, status)
31
+ return result if result.respond_to? :call
32
+
33
+ case result
34
+ when Hash
35
+ if result[status].is_a? Symbol
36
+ controller.method(result[status])
37
+ else
38
+ result[status]
39
+ end
40
+ when Symbol
41
+ controller.method(result)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ require 'refinery/api'
2
+ require 'refinery/api/responders'
3
+ require 'versioncake'
@@ -0,0 +1,69 @@
1
+ # Refinery CMS Api
2
+
3
+ [![Build Status](https://travis-ci.org/refinerycms-contrib/refinerycms-api.svg?branch=master)](https://travis-ci.org/refinerycms-contrib/refinerycms-api)
4
+
5
+ This extension allows you to use a Rest API with Refinery CMS 3.0 and later.
6
+
7
+ ## TODO
8
+ * [ ] Check Abilities
9
+ * [ ] Fix specs
10
+
11
+ ## Installation
12
+
13
+ Simply put this in the Gemfile of your Refinery application:
14
+
15
+ ```ruby
16
+ gem 'refinerycms-api', github: 'refinerycms-contrib/refinerycms-api', master: 'branch'
17
+ ```
18
+
19
+ Then run `bundle install` to install it.
20
+
21
+
22
+ ### Generate and run migrations
23
+
24
+ ```sh
25
+ $ rails g refinery:api # Generate initializer
26
+ $ rake db:migrate
27
+ ```
28
+
29
+ Then restart your server.
30
+
31
+ ## Usage
32
+
33
+ ### Create an API Token for your user
34
+
35
+ ```ruby
36
+ $ rake refinery_api:user:api_token:generate EMAIL=refinery@example.org
37
+ ```
38
+
39
+ ### Pages
40
+
41
+ ```ruby
42
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/pages
43
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/pages/1
44
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/pages/new
45
+
46
+ $ curl -X POST --header "X-Refinery-Token: YOUR_API_TOKEN" "http://localhost:3000/api/v1/pages" -d 'page[title]=test'
47
+ $ curl -X PUT --header "X-Refinery-Token: YOUR_API_TOKEN" "http://localhost:3000/api/v1/pages/1" -d 'page[title]=test2'
48
+ $ curl -X DELETE -H "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/pages/1
49
+ ```
50
+
51
+ ### Images
52
+
53
+ ```ruby
54
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/images
55
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/images/1
56
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/images/new
57
+ ```
58
+
59
+ ### Resources
60
+
61
+ ```ruby
62
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/resources
63
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/resources/1
64
+ $ curl --header "X-Refinery-Token: YOUR_API_TOKEN" http://localhost:3000/api/v1/resources/new
65
+ ```
66
+
67
+ ## Contributing
68
+
69
+ Please see the [contributing.md](contributing.md) file for instructions.
@@ -0,0 +1,22 @@
1
+ # Encoding: UTF-8
2
+ Gem::Specification.new do |s|
3
+ s.authors = ["Brice Sanchez", "Ryan Bigg"]
4
+ s.description = %q{Refinery CMS's API}
5
+ s.summary = %q{Refinery CMS's API}
6
+ s.homepage = 'http://www.refinerycms.com'
7
+ s.license = 'BSD-3'
8
+
9
+ s.files = `git ls-files`.split($\)
10
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
12
+ s.name = %q{refinerycms-api}
13
+ s.require_paths = ["lib"]
14
+ s.version = %q{1.0.0.beta}
15
+
16
+ s.add_dependency 'refinerycms-core', ['~> 3.0', '>= 3.0.0']
17
+ s.add_dependency 'cancancan', '~> 1.10.1'
18
+ s.add_dependency 'decorators', '~> 2.0'
19
+ s.add_dependency 'rabl', '~> 0.12.0'
20
+ s.add_dependency 'versioncake', '~> 2.3.1'
21
+ s.add_dependency 'responders'
22
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
5
+ ENGINE_PATH = File.expand_path('../../lib/refinery/api/engine', __FILE__)
6
+
7
+ require 'rails/all'
8
+ require 'rails/engine/commands'
9
+
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ class FakesController < Refinery::Api::BaseController
4
+ end
5
+
6
+ describe Refinery::Api::BaseController, type: :controller do
7
+ render_views
8
+ controller(Refinery::Api::BaseController) do
9
+ def index
10
+ render text: { "pages": [] }.to_json
11
+ end
12
+ end
13
+
14
+ before do
15
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
16
+ r.draw { get 'index', to: 'refinery/api/base#index' }
17
+ end
18
+ end
19
+
20
+ context "cannot make a request to the API" do
21
+ it "without an API key" do
22
+ api_get :index
23
+ expect(json_response).to eq({ "error" => "You must specify an API key." })
24
+ expect(response.status).to eq(401)
25
+ end
26
+
27
+ it "with an invalid API key" do
28
+ request.headers["X-Refinery-Token"] = "fake_key"
29
+ get :index, {}
30
+ expect(json_response).to eq({ "error" => "Invalid API key (fake_key) specified." })
31
+ expect(response.status).to eq(401)
32
+ end
33
+
34
+ it "using an invalid token param" do
35
+ get :index, token: "fake_key"
36
+ expect(json_response).to eq({ "error" => "Invalid API key (fake_key) specified." })
37
+ end
38
+ end
39
+
40
+ it 'handles parameter missing exceptions' do
41
+ expect(subject).to receive(:authenticate_user).and_return(true)
42
+ expect(subject).to receive(:load_user_roles).and_return(true)
43
+ expect(subject).to receive(:index).and_raise(ActionController::ParameterMissing.new('foo'))
44
+ get :index, token: 'exception-message'
45
+ expect(json_response).to eql('exception' => 'param is missing or the value is empty: foo')
46
+ end
47
+
48
+ it 'handles record invalid exceptions' do
49
+ expect(subject).to receive(:authenticate_user).and_return(true)
50
+ expect(subject).to receive(:load_user_roles).and_return(true)
51
+ resource = Refinery::Page.new
52
+ resource.valid? # get some errors
53
+ expect(subject).to receive(:index).and_raise(ActiveRecord::RecordInvalid.new(resource))
54
+ get :index, token: 'exception-message'
55
+ expect(json_response).to eql('exception' => "Validation failed: Title can't be blank")
56
+ end
57
+
58
+ it "maps semantic keys to nested_attributes keys" do
59
+ klass = double(nested_attributes_options: { line_items: {},
60
+ bill_address: {} })
61
+ attributes = { 'line_items': { id: 1 },
62
+ 'bill_address': { id: 2 },
63
+ 'name': 'test order' }
64
+
65
+ mapped = subject.map_nested_attributes_keys(klass, attributes)
66
+ expect(mapped.has_key?('line_items_attributes')).to be true
67
+ expect(mapped.has_key?('name')).to be true
68
+ end
69
+
70
+ it "lets a subclass override the product associations that are eager-loaded" do
71
+ expect(controller.respond_to?(:product_includes, true)).to be
72
+ end
73
+ end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+
3
+ module Refinery
4
+ describe Api::V1::Blog::PostsController, type: :controller do
5
+ render_views
6
+
7
+ let!(:post) { FactoryGirl.create(:blog_post, :title => "Ruby") }
8
+
9
+ let!(:attributes_new) { [ "title", "body", "custom_teaser", "tag_list", "draft", "published_at", "custom_url", "user_id", "username", "browser_title", "meta_description", "source_url", "source_url_title", {"category_ids"=>[]} ] }
10
+ let!(:attributes) { [ "title", "body", "custom_teaser", "tag_list", "draft", "published_at", "custom_url", "user_id", "username", "browser_title", "meta_description", "source_url", "source_url_title", "category_ids" ] }
11
+
12
+ before do
13
+ stub_authentication!
14
+ end
15
+
16
+ context "as a normal user" do
17
+ it "can get a list of blog posts" do
18
+ api_get :index
19
+
20
+ expect(response.status).to eq(200)
21
+ expect(json_response).to have_key("posts")
22
+ attributes.delete("category_ids")
23
+ expect(json_response["posts"].first.keys).to eq(attributes)
24
+ end
25
+
26
+ # it "paginates through taxons" do
27
+ # new_taxon = create(:taxon, :name => "Go", :taxonomy => taxonomy)
28
+ # taxonomy.root.children << new_taxon
29
+ # expect(taxonomy.root.children.count).to eql(2)
30
+ # api_get :index, :taxonomy_id => taxonomy.id, :page => 1, :per_page => 1
31
+ # expect(json_response["count"]).to eql(1)
32
+ # expect(json_response["total_count"]).to eql(2)
33
+ # expect(json_response["current_page"]).to eql(1)
34
+ # expect(json_response["per_page"]).to eql(1)
35
+ # expect(json_response["pages"]).to eql(2)
36
+ # end
37
+
38
+ # describe 'searching' do
39
+ # context 'with a name' do
40
+ # before do
41
+ # api_get :index, :q => { :name_cont => name }
42
+ # end
43
+
44
+ # context 'with one result' do
45
+ # let(:name) { "Ruby" }
46
+
47
+ # it "returns an array including the matching taxon" do
48
+ # expect(json_response['taxons'].count).to eq(1)
49
+ # expect(json_response['taxons'].first['name']).to eq "Ruby"
50
+ # end
51
+ # end
52
+
53
+ # context 'with no results' do
54
+ # let(:name) { "Imaginary" }
55
+
56
+ # it 'returns an empty array of taxons' do
57
+ # expect(json_response.keys).to include('taxons')
58
+ # expect(json_response['taxons'].count).to eq(0)
59
+ # end
60
+ # end
61
+ # end
62
+
63
+ # context 'with no filters' do
64
+ # it "gets all taxons" do
65
+ # api_get :index
66
+
67
+ # expect(json_response['taxons'].first['name']).to eq taxonomy.root.name
68
+ # children = json_response['taxons'].first['taxons']
69
+ # expect(children.count).to eq 1
70
+ # expect(children.first['name']).to eq taxon.name
71
+ # expect(children.first['taxons'].count).to eq 1
72
+ # end
73
+ # end
74
+ # end
75
+
76
+ it "gets a single blog post" do
77
+ api_get :show, id: post.id
78
+
79
+ expect(json_response['title']).to eq post.title
80
+ expect(json_response['posts']).to be_nil
81
+ end
82
+
83
+ it "can learn how to create a new blog post" do
84
+ api_get :new
85
+ expect(json_response["attributes"]).to eq(attributes_new)
86
+ required_attributes = json_response["required_attributes"]
87
+ expect(required_attributes).to include("title")
88
+ end
89
+
90
+ it "cannot create a new blog post if not an admin" do
91
+ api_post :create, blog_post: { title: "Location" }
92
+ assert_unauthorized!
93
+ end
94
+
95
+ it "cannot update a blog post" do
96
+ api_put :update, id: post.id, post: { title: "I hacked your website!" }
97
+ assert_unauthorized!
98
+ end
99
+
100
+ it "cannot delete a blog post" do
101
+ api_delete :destroy, id: post.id
102
+ assert_unauthorized!
103
+ end
104
+ end
105
+
106
+ context "as an admin" do
107
+ sign_in_as_admin!
108
+
109
+ it "can create" do
110
+ expect do
111
+ api_post :create, post: { title: "Colors", body: "Colors of life", published_at: Time.now, username: "John Doe" }
112
+ attributes.delete("category_ids")
113
+ expect(json_response.keys).to eq(attributes)
114
+ expect(response.status).to eq(201)
115
+ end.to change(Blog::Post, :count).by(1)
116
+ end
117
+
118
+ it "can update the title" do
119
+ api_put :update, id: post.id, post: { title: "I update your website!" }
120
+ expect(response.status).to eq(200)
121
+ expect(Blog::Post.last.title).to eql "I update your website!"
122
+ end
123
+
124
+ it "cannot create a new blog post with invalid attributes" do
125
+ api_post :create, post: {}
126
+ expect(response.status).to eq(422)
127
+ expect(json_response["error"]).to eq("Invalid resource. Please fix errors and try again.")
128
+ errors = json_response["errors"]
129
+
130
+ expect(Blog::Post.count).to eq 1
131
+ end
132
+
133
+ it "can destroy" do
134
+ api_delete :destroy, :id => post.id
135
+ expect(response.status).to eq(204)
136
+ end
137
+ end
138
+
139
+ end
140
+ end