sinatra-rest-api 0.1.3 → 0.1.4

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rubocop.yml +34 -0
  4. data/Gemfile +20 -0
  5. data/Gemfile.lock +92 -0
  6. data/LICENSE +5 -0
  7. data/README.md +135 -0
  8. data/examples/active_record/Gemfile +15 -0
  9. data/examples/active_record/Gemfile.lock +75 -0
  10. data/examples/active_record/Rakefile +8 -0
  11. data/examples/active_record/app.rb +117 -0
  12. data/examples/active_record/config.ru +8 -0
  13. data/examples/active_record/database.yml +7 -0
  14. data/examples/active_record/db/migrate/001_create_authors.rb +11 -0
  15. data/examples/active_record/db/migrate/002_create_categories.rb +10 -0
  16. data/examples/active_record/db/migrate/003_create_books.rb +16 -0
  17. data/examples/active_record/db/migrate/004_create_tags.rb +10 -0
  18. data/examples/active_record/db/migrate/005_create_books_tags.rb +11 -0
  19. data/examples/active_record/db/migrate/006_create_chapters.rb +12 -0
  20. data/examples/active_record/db/schema.rb +61 -0
  21. data/examples/active_record/db/seeds.rb +0 -0
  22. data/examples/activeresource/Gemfile +10 -0
  23. data/examples/activeresource/Gemfile.lock +68 -0
  24. data/examples/activeresource/app.rb +125 -0
  25. data/examples/activeresource/config.ru +9 -0
  26. data/examples/activeresource/layout.slim +78 -0
  27. data/examples/misc/set_verb_options.rb +12 -0
  28. data/examples/mongoid/Gemfile +11 -0
  29. data/examples/mongoid/Gemfile.lock +68 -0
  30. data/examples/mongoid/Rakefile +3 -0
  31. data/examples/mongoid/app.rb +144 -0
  32. data/examples/mongoid/config.ru +8 -0
  33. data/examples/mongoid/database.yml +13 -0
  34. data/examples/mongoid/lib/tasks/db.rake +79 -0
  35. data/examples/ng-admin/index.html +168 -0
  36. data/examples/ng-admin/package.json +14 -0
  37. data/examples/sequel/Gemfile +14 -0
  38. data/examples/sequel/Gemfile.lock +54 -0
  39. data/examples/sequel/Rakefile +3 -0
  40. data/examples/sequel/app.rb +138 -0
  41. data/examples/sequel/config.ru +9 -0
  42. data/examples/sequel/database.yml +7 -0
  43. data/examples/sequel/db/migrate/001_create_authors.rb +15 -0
  44. data/examples/sequel/db/migrate/002_create_categories.rb +14 -0
  45. data/examples/sequel/db/migrate/003_create_books.rb +20 -0
  46. data/examples/sequel/db/migrate/004_create_tags.rb +14 -0
  47. data/examples/sequel/db/migrate/005_create_books_tags.rb +15 -0
  48. data/examples/sequel/db/migrate/006_create_chapters.rb +16 -0
  49. data/examples/sequel/lib/tasks/db.rake +44 -0
  50. data/lib/sinatra-rest-api/actions.rb +2 -1
  51. data/sinatra-rest-api.gemspec +24 -0
  52. data/spec/action_create_spec.rb +164 -0
  53. data/spec/action_delete_spec.rb +32 -0
  54. data/spec/action_list_spec.rb +31 -0
  55. data/spec/action_read_spec.rb +28 -0
  56. data/spec/action_update_spec.rb +98 -0
  57. data/spec/app_active_record_spec.rb +53 -0
  58. data/spec/app_mongoid_spec.rb +46 -0
  59. data/spec/app_sequel_spec.rb +45 -0
  60. data/spec/spec_helper.rb +59 -0
  61. metadata +70 -3
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "test-ng-admin",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.html",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "Mat",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "ng-admin": "^1.0.0-beta3"
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'pry'
4
+
5
+ gem 'sinatra'
6
+ gem 'sinatra-contrib'
7
+
8
+ gem 'rake'
9
+ gem 'thin'
10
+
11
+ gem 'sequel'
12
+ gem 'sqlite3'
13
+
14
+ gem 'sinatra-rest-api' # , path: '../../../sinatra-rest-api'
@@ -0,0 +1,54 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ backports (3.8.0)
5
+ coderay (1.1.2)
6
+ daemons (1.2.4)
7
+ eventmachine (1.2.5)
8
+ method_source (0.9.0)
9
+ multi_json (1.12.2)
10
+ mustermann (1.0.1)
11
+ pry (0.11.1)
12
+ coderay (~> 1.1.0)
13
+ method_source (~> 0.9.0)
14
+ rack (2.0.3)
15
+ rack-protection (2.0.0)
16
+ rack
17
+ rake (12.1.0)
18
+ sequel (5.0.0)
19
+ sinatra (2.0.0)
20
+ mustermann (~> 1.0)
21
+ rack (~> 2.0)
22
+ rack-protection (= 2.0.0)
23
+ tilt (~> 2.0)
24
+ sinatra-contrib (2.0.0)
25
+ backports (>= 2.0)
26
+ multi_json
27
+ mustermann (~> 1.0)
28
+ rack-protection (= 2.0.0)
29
+ sinatra (= 2.0.0)
30
+ tilt (>= 1.3, < 3)
31
+ sinatra-rest-api (0.1.2)
32
+ sinatra (~> 2.0, > 1.4.0)
33
+ sqlite3 (1.3.13)
34
+ thin (1.7.2)
35
+ daemons (~> 1.0, >= 1.0.9)
36
+ eventmachine (~> 1.0, >= 1.0.4)
37
+ rack (>= 1, < 3)
38
+ tilt (2.0.8)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ pry
45
+ rake
46
+ sequel
47
+ sinatra
48
+ sinatra-contrib
49
+ sinatra-rest-api
50
+ sqlite3
51
+ thin
52
+
53
+ BUNDLED WITH
54
+ 1.15.4
@@ -0,0 +1,3 @@
1
+ # $VERBOSE = true
2
+
3
+ Dir.glob( 'lib/tasks/*.rake' ).each { |r| load r }
@@ -0,0 +1,138 @@
1
+ require 'pry'
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ require 'sinatra/base'
7
+
8
+ require 'sequel'
9
+ require 'sqlite3'
10
+
11
+ require 'sinatra-rest-api'
12
+
13
+ # SequelTest App
14
+ module SequelTest
15
+ env = ENV['RACK_ENV'] || 'development'
16
+ CONF = YAML.load( File.read( File.expand_path( '../database.yml', __FILE__ ) ) )
17
+ CONF[env]['database'] = File.expand_path( '../' + CONF[env]['database'], __FILE__ )
18
+ DB = Sequel.connect( CONF[env] )
19
+
20
+ unless ENV['SEQUEL_RECREATE_DB'].nil?
21
+ require 'rake'
22
+ FileUtils.rm CONF[env]['database'], force: true
23
+ FileUtils.touch CONF[env]['database']
24
+ load File.join( File.expand_path( '../lib', __FILE__ ), 'tasks', 'db.rake' )
25
+ Rake::Task['db:migrate'].invoke
26
+ end
27
+
28
+ Sequel::Model.plugin :association_pks
29
+ Sequel::Model.plugin :json_serializer
30
+ Sequel::Model.plugin :nested_attributes
31
+ Sequel::Model.plugin :timestamps
32
+ Sequel::Model.plugin :validation_helpers
33
+
34
+ # An author
35
+ class Author < Sequel::Model
36
+ plugin :timestamps, update_on_create: true
37
+
38
+ one_to_many :books
39
+
40
+ def validate
41
+ super
42
+ validates_presence [ :name, :email ]
43
+ validates_unique :email
44
+ end
45
+ end
46
+
47
+ # A book
48
+ class Book < Sequel::Model
49
+ plugin :timestamps, update_on_create: true
50
+
51
+ many_to_one :author
52
+ many_to_one :category
53
+ many_to_many :tags
54
+ one_to_many :chapters
55
+
56
+ nested_attributes :author
57
+ nested_attributes :category # , unmatched_pk: :create
58
+ nested_attributes :tags # , unmatched_pk: :create
59
+ nested_attributes :chapters
60
+
61
+ def validate
62
+ super
63
+ validates_presence :title
64
+ end
65
+ end
66
+
67
+ # A category
68
+ class Category < Sequel::Model
69
+ plugin :timestamps, update_on_create: true
70
+
71
+ one_to_many :books
72
+ end
73
+
74
+ # A tag
75
+ class Tag < Sequel::Model
76
+ plugin :timestamps, update_on_create: true
77
+
78
+ many_to_many :books
79
+ end
80
+
81
+ # A chapter
82
+ class Chapter < Sequel::Model
83
+ plugin :timestamps, update_on_create: true
84
+
85
+ many_to_one :book
86
+
87
+ def validate
88
+ super
89
+ validates_presence :title
90
+ end
91
+ end
92
+
93
+ ##############################################################################
94
+
95
+ # App entry point
96
+ class App < Sinatra::Application
97
+ register Sinatra::RestApi
98
+
99
+ # model Author, actions: { list: { verb: :post }, read: { verb: :post } }
100
+ # model Author, actions: { list: true, read: true }
101
+
102
+ set :restapi_request_type, :json
103
+
104
+ resource Author, singular: 'writer' # , plural: 'writers'
105
+ resource Book
106
+ resource Category do
107
+ actions [ :list, :read ]
108
+ end
109
+ resource Chapter
110
+ resource Tag # , actions: [ :list, :read ]
111
+
112
+ before do
113
+ content_type :json
114
+ headers( 'Access-Control-Allow-Origin' => '*',
115
+ 'Access-Control-Allow-Methods' => Sinatra::RestApi::Router::VERBS.join(',').upcase,
116
+ 'Access-Control-Allow-Headers' => 'Origin, Content-Type, Accept, Authorization, Token',
117
+ 'Access-Control-Expose-Headers' => 'X-Total-Count' )
118
+ end
119
+
120
+ options '*' do
121
+ response.headers['Access-Control-Allow-Origin'] = '*'
122
+ response.headers['Access-Control-Allow-Methods'] = 'HEAD,GET,PUT,DELETE,OPTIONS'
123
+ response.headers['Access-Control-Allow-Headers'] = 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept'
124
+ halt 200
125
+ end
126
+
127
+ get '/' do
128
+ Sinatra::RestApi::Router.list_routes.to_json
129
+ end
130
+
131
+ not_found do
132
+ return if JSON.parse( body[0] ).include?( 'error' ) rescue nil # Error already defined
133
+ [ 404, { message: 'This is not the page you are looking for...' }.to_json ]
134
+ end
135
+
136
+ run! if app_file == $PROGRAM_NAME # = $0 ## starts the server if executed directly by ruby
137
+ end
138
+ end
@@ -0,0 +1,9 @@
1
+ # config.ru (run with rackup)
2
+
3
+ # Launch with:
4
+ # rackup -o 0.0.0.0 -p 4567
5
+
6
+ require './app'
7
+
8
+ # run App
9
+ run SequelTest::App
@@ -0,0 +1,7 @@
1
+ development:
2
+ adapter: sqlite
3
+ database: 'db/db.sqlite3'
4
+
5
+ test:
6
+ adapter: sqlite
7
+ database: 'db/test.sqlite3'
@@ -0,0 +1,15 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :authors ) do
4
+ primary_key :id
5
+ String :name, null: false
6
+ String :email, null: false
7
+ Timestamp :created_at
8
+ Timestamp :updated_at
9
+ end
10
+ end
11
+
12
+ down do
13
+ drop_table( :authors )
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :categories ) do
4
+ primary_key :id
5
+ String :name, null: false
6
+ Timestamp :created_at
7
+ Timestamp :updated_at
8
+ end
9
+ end
10
+
11
+ down do
12
+ drop_table( :categories )
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :books ) do
4
+ primary_key :id
5
+ String :title, null: false
6
+ String :description, text: true
7
+ Integer :pages
8
+ Float :price
9
+ DateTime :dt
10
+ Integer :author_id
11
+ Integer :category_id
12
+ Timestamp :created_at
13
+ Timestamp :updated_at
14
+ end
15
+ end
16
+
17
+ down do
18
+ drop_table( :books )
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :tags ) do
4
+ primary_key :id
5
+ String :name, null: false
6
+ Timestamp :created_at
7
+ Timestamp :updated_at
8
+ end
9
+ end
10
+
11
+ down do
12
+ drop_table( :tags )
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :books_tags ) do
4
+ primary_key :id
5
+ Integer :book_id, null: false
6
+ Integer :tag_id, null: false
7
+ Timestamp :created_at
8
+ Timestamp :updated_at
9
+ end
10
+ end
11
+
12
+ down do
13
+ drop_table( :books_tags )
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ up do
3
+ create_table( :chapters ) do
4
+ primary_key :id
5
+ String :title, null: false
6
+ Integer :page
7
+ Integer :book_id, null: false
8
+ Timestamp :created_at
9
+ Timestamp :updated_at
10
+ end
11
+ end
12
+
13
+ down do
14
+ drop_table( :chapters )
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ # require File.expand_path( '../../../app.rb', __FILE__ )
2
+ require 'fileutils'
3
+ require 'sequel'
4
+ require 'yaml'
5
+
6
+ Sequel.extension :migration
7
+
8
+ PATH_MIGRATIONS = File.expand_path( '../../../db/migrate', __FILE__ ).freeze unless defined? PATH_MIGRATIONS
9
+
10
+ def get_db( connect = true )
11
+ env = ENV['RACK_ENV'] || 'development'
12
+ conf = YAML.load( File.read( File.expand_path( '../../../database.yml', __FILE__ ) ) )
13
+ conf[env]['database'] = File.expand_path( '../../../' + conf[env]['database'], __FILE__ )
14
+ connect ? Sequel.connect( conf[env] ) : conf
15
+ end
16
+
17
+ namespace :db do
18
+ desc 'Create database'
19
+ task :create do |_t, _args|
20
+ puts 'Create DB' # unless ENV['RACK_ENV'] == 'test'
21
+ env = ENV['RACK_ENV'] || 'development'
22
+ conf = get_db( false )
23
+ FileUtils.touch conf[env]['database']
24
+ end
25
+
26
+ # Ex. rake db:migrate[5]
27
+ desc 'Run migrations'
28
+ task :migrate, [ :version ] do |_t, args|
29
+ # db = Sequel.connect(ENV.fetch('DATABASE_URL'))
30
+ if args[:version]
31
+ puts "Migrating to version #{args[:version]}" # unless ENV['RACK_ENV'] == 'test'
32
+ Sequel::Migrator.run( get_db, PATH_MIGRATIONS, target: args[:version].to_i)
33
+ else
34
+ puts 'Migrating to latest' # unless ENV['RACK_ENV'] == 'test'
35
+ Sequel::Migrator.run( get_db, PATH_MIGRATIONS )
36
+ end
37
+ end
38
+
39
+ desc 'Reset database'
40
+ task :reset do |_t, _args|
41
+ puts 'Reset DB' # unless ENV['RACK_ENV'] == 'test'
42
+ Sequel::Migrator.run( get_db, PATH_MIGRATIONS, target: 0 )
43
+ end
44
+ end
@@ -46,10 +46,11 @@ module Sinatra
46
46
  end
47
47
 
48
48
  def self.list( route_args, params, mapping )
49
- # TODO: option to enable X-Total-Count ?
49
+ # TODO: option to enable/disable X-Total-Count ?
50
50
  params[:_where] = params[:_where].nil? ? {} : JSON.parse( params[:_where] )
51
51
  route_args[:response].headers['X-Total-Count'] = mapping[:count].call( params ).to_s
52
52
  result = mapping[:list].call( params, route_args[:fields] )
53
+ result = result.limit( route_args[:options][:limit].to_i ) if route_args[:options].include?( :limit )
53
54
  [ 200, result.to_json( include: route_args[:options].include?( :include ) ? route_args[:options][:include] : mapping[:relations].call( nil ) ) ]
54
55
  end
55
56
 
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path( '../lib', __FILE__ )
3
+ $LOAD_PATH.unshift( lib ) unless $LOAD_PATH.include?( lib )
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'sinatra-rest-api'
7
+ spec.version = '0.1.4'
8
+ spec.authors = [ 'Mattia Roccoberton' ]
9
+ spec.email = 'mat@blocknot.es'
10
+ spec.homepage = 'https://github.com/blocknotes/sinatra-rest-api'
11
+ spec.summary = 'Sinatra REST API generator'
12
+ spec.description = 'Sinatra REST API generator: CRUD actions, nested resources, supports ActiveRecord, Sequel and Mongoid'
13
+ spec.platform = Gem::Platform::RUBY
14
+ spec.license = 'ISC'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '>= 2.0.0'
22
+
23
+ spec.add_runtime_dependency 'sinatra', '> 1.4'
24
+ end
@@ -0,0 +1,164 @@
1
+ $VERBOSE = false
2
+
3
+ # require 'pry'
4
+
5
+ # Tests actions
6
+ module ActionTests
7
+ RSpec.shared_examples 'Action: create' do |models|
8
+ ##############################################################################
9
+
10
+ context 'Creating authors...' do
11
+ # path = '/authors'
12
+ path = '/writers' # renamed resource
13
+ random_authors = []
14
+ (1..( rand( 15 ) + 5 )).each do |_i|
15
+ random_authors.push( name: Faker::Book.author, email: Faker::Internet.email )
16
+ end
17
+ random_authors.uniq! { |a| a[:email] }
18
+
19
+ # action: create
20
+ it "should create #{random_authors.length} authors" do
21
+ random_authors.each do |author|
22
+ post path, writer: author
23
+ expect( last_response.status ).to eq( 201 ) # response = created ?
24
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true # id is returned ?
25
+ expect( models[:author].where( author ).count ).to eq( 1 ) # record exists ?
26
+ end
27
+ expect( models[:author].count ).to eq( random_authors.length ) # check number of authors created
28
+ end
29
+
30
+ it 'should not create authors with invalid data' do
31
+ authors = [
32
+ {},
33
+ { name: '', email: ' ' },
34
+ { email: 'aaa@bbb.ccc' },
35
+ { name: '', email: 'aaa@bbb.ccc' },
36
+ { name: ' ', email: 'aaa@bbb.ccc' },
37
+ { name: 'Dick' },
38
+ { name: 'Dick', email: '' },
39
+ { name: 'Dick', email: ' ' }
40
+ ]
41
+ authors.each do |author|
42
+ post path, writer: author
43
+ expect( last_response.status ).to eq( 400 )
44
+ end
45
+ end
46
+
47
+ # action: create
48
+ it 'no authors with same email' do
49
+ post path, writer: { name: 'Jane Doe', email: random_authors.sample[:email] }
50
+ expect( last_response.status ).to eq( 400 )
51
+ end
52
+ end
53
+
54
+ ##############################################################################
55
+
56
+ context 'Creating tags...' do
57
+ random_tags = []
58
+ (1..( rand( 6 ) + 3 ) ).each { |_i| random_tags.push( name: Faker::Hipster.word ) }
59
+ random_tags.uniq! { |t| t[:name] }
60
+
61
+ # action: create
62
+ it "should create #{random_tags.length} tags" do
63
+ random_tags.each do |tag|
64
+ post '/tags', tag: tag
65
+ expect( last_response.status ).to eq( 201 )
66
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true
67
+ expect( models[:tag].where( tag ).count ).to eq( 1 )
68
+ end
69
+ expect( models[:tag].count ).to eq( random_tags.length )
70
+ end
71
+ end
72
+
73
+ ##############################################################################
74
+
75
+ context 'Creating books...' do
76
+ random_books = []
77
+ (1..( rand( 4 ) + 2 )).each do |_i|
78
+ random_books.push( title: Faker::Book.title )
79
+ end
80
+ random_books.uniq! { |b| b[:title] }
81
+ (1..( rand( 4 ) + 2 )).each do |_i|
82
+ random_books.push( title: Faker::Hipster.sentence, pages: rand( 1000 ), price: rand * 20 )
83
+ end
84
+ new_cats = rand( 4 ) + 2
85
+ (1..new_cats).each do |_i|
86
+ random_books.push( title: Faker::Superhero.name, description: Faker::StarWars.quote, pages: rand( 1000 ), price: rand * 20, category_attributes: { name: Faker::Book.genre } )
87
+ end
88
+ new_tags = 2
89
+ (1..new_tags).each do |_i|
90
+ random_books.push( title: Faker::Superhero.name, description: Faker::StarWars.quote, pages: rand( 1000 ), price: rand * 20, tags_attributes: [ { name: Faker::Hipster.word } ] )
91
+ end
92
+
93
+ # action: create
94
+ it "should create #{random_books.length} books, #{new_cats} categories and #{new_tags} tags" do
95
+ tags_ids = models[:tag].all.map( &:id )
96
+ (1..( rand( 4 ) + 2 )).each do |_i|
97
+ random_books.push( title: Faker::University.name, pages: rand( 1000 ), price: rand * 20, tag_ids: tags_ids.sample( rand( 3 ) ) )
98
+ end
99
+ random_books.each do |book|
100
+ book[:author_id] = models[:author].all.sample.id if rand > 0.5
101
+ post '/books', book: book
102
+ expect( last_response.status ).to eq( 201 ) # check if the status is equal to created
103
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true # check if the id is returned
104
+ category_attributes = book.delete :category_attributes
105
+ tags_attributes = book.delete :tags_attributes
106
+ tag_ids = book.delete :tag_ids
107
+ expect( models[:book].where( book ).count ).to eq( 1 ) # check if book is created
108
+ # check the relations
109
+ row = models[:book].where( book ).first
110
+ expect( row.tags.map( &:id ).sort ).to eq( tag_ids.sort ) unless tag_ids.nil?
111
+ expect( row.tags.map( &:name ) ).to eq( tags_attributes.map { |a| a[:name] } ) unless tags_attributes.nil?
112
+ expect( row.category.name ).to eq( category_attributes[:name] ) unless category_attributes.nil?
113
+ end
114
+ expect( models[:book].count ).to eq( random_books.length )
115
+ expect( models[:category].count ).to eq( new_cats )
116
+ expect( models[:tag].count ).to eq( tags_ids.length + new_tags )
117
+ end
118
+ end
119
+
120
+ ##############################################################################
121
+
122
+ context 'Creating chapters...' do
123
+ random_chapters = []
124
+ (1..( rand( 10 ) + 2 )).each do |_i|
125
+ random_chapters.push( title: Faker::Superhero.name )
126
+ end
127
+ random_chapters.uniq! { |c| c[:title] }
128
+
129
+ # action: create
130
+ it "should create #{random_chapters.length} chapters" do
131
+ ids = models[:book].all.map( &:id )
132
+ random_chapters.each do |chapter|
133
+ chapter[:book_id] = ids.sample
134
+ post '/chapters', chapter: chapter
135
+ expect( last_response.status ).to eq( 201 )
136
+ expect( JSON.parse( last_response.body ).include?( 'id' ) ).to be true
137
+ expect( models[:chapter].where( chapter ).count ).to eq( 1 )
138
+ end
139
+ expect( models[:chapter].count ).to eq( random_chapters.length )
140
+ end
141
+
142
+ it 'should not create a chapter without data' do
143
+ chapters = [
144
+ {},
145
+ { book_id: models[:book].last.id.to_s }
146
+ ]
147
+ chapters.each do |chapter|
148
+ post '/chapters', chapter: chapter
149
+ expect( last_response.status ).to eq( 400 )
150
+ end
151
+ end
152
+ end
153
+
154
+ ##############################################################################
155
+
156
+ context 'Creating categories...' do
157
+ # action: create
158
+ it 'should not create categories' do
159
+ post '/categories', category: { name: 'A cat' }
160
+ expect( last_response.status ).to eq( 404 )
161
+ end
162
+ end
163
+ end
164
+ end