refinerycms-api 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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