populate-me 0.0.33 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/LICENSE +1 -1
- data/README.md +399 -4
- data/Rakefile +14 -0
- data/example/config.ru +67 -0
- data/lib/populate_me.rb +2 -0
- data/lib/populate_me/admin.rb +143 -0
- data/lib/populate_me/admin/__assets__/css/main.css +174 -0
- data/lib/populate_me/admin/__assets__/js/columnav.js +82 -0
- data/lib/populate_me/admin/__assets__/js/main.js +251 -0
- data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
- data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
- data/lib/populate_me/admin/views/page.erb +163 -0
- data/lib/populate_me/api.rb +124 -0
- data/lib/populate_me/attachment.rb +182 -0
- data/lib/populate_me/document.rb +178 -0
- data/lib/populate_me/document_mixins/admin_adapter.rb +131 -0
- data/lib/populate_me/document_mixins/callbacks.rb +104 -0
- data/lib/populate_me/document_mixins/outcasting.rb +49 -0
- data/lib/populate_me/document_mixins/persistence.rb +92 -0
- data/lib/populate_me/document_mixins/schema.rb +99 -0
- data/lib/populate_me/document_mixins/typecasting.rb +60 -0
- data/lib/populate_me/document_mixins/validation.rb +44 -0
- data/lib/populate_me/file_system_attachment.rb +40 -0
- data/lib/populate_me/grid_fs_attachment.rb +127 -0
- data/lib/populate_me/mongo.rb +98 -3
- data/lib/populate_me/variation.rb +34 -0
- data/lib/populate_me/version.rb +4 -0
- data/populate-me.gemspec +20 -12
- data/test/helper.rb +37 -0
- data/test/test_admin.rb +161 -0
- data/test/test_api.rb +246 -0
- data/test/test_attachment.rb +155 -0
- data/test/test_document.rb +120 -0
- data/test/test_document_admin_adapter.rb +43 -0
- data/test/test_document_callbacks.rb +107 -0
- data/test/test_document_persistence.rb +56 -0
- data/test/test_document_typecasting.rb +121 -0
- data/test/test_mongo.rb +217 -0
- data/test/test_variation.rb +91 -0
- data/test/test_version.rb +11 -0
- metadata +115 -66
- data/lib/populate_me/control.rb +0 -196
- data/lib/populate_me/control/_public/css/main.css +0 -207
- data/lib/populate_me/control/_public/css/plugin.asmselect.css +0 -63
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
- data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +0 -430
- data/lib/populate_me/control/_public/img/grip.png +0 -0
- data/lib/populate_me/control/_public/img/icons-cms-solarized.png +0 -0
- data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
- data/lib/populate_me/control/_public/img/placeholder.png +0 -0
- data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
- data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
- data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
- data/lib/populate_me/control/_public/js/addon.timepicker.js +0 -20
- data/lib/populate_me/control/_public/js/jquery-ui.js +0 -114
- data/lib/populate_me/control/_public/js/jquery.js +0 -167
- data/lib/populate_me/control/_public/js/jquery.mustache.js +0 -559
- data/lib/populate_me/control/_public/js/main.js +0 -144
- data/lib/populate_me/control/_public/js/plugin.asmselect.js +0 -407
- data/lib/populate_me/control/_public/js/plugin.form.js +0 -11
- data/lib/populate_me/control/_public/js/plugin.quicksearch.js +0 -1
- data/lib/populate_me/control/_public/js/plugin.underwood.js +0 -4
- data/lib/populate_me/control/views/populate_me_layout.erb +0 -75
- data/lib/populate_me/ext.rb +0 -16
- data/lib/populate_me/mongo/backend_api_plug.rb +0 -78
- data/lib/populate_me/mongo/crushyform.rb +0 -243
- data/lib/populate_me/mongo/mutation.rb +0 -324
- data/lib/populate_me/mongo/plug.rb +0 -134
- data/lib/populate_me/mongo/stash.rb +0 -171
- data/test/spec_ext.rb +0 -29
- data/test/spec_mongo_mutation.rb +0 -279
data/lib/populate_me/mongo.rb
CHANGED
@@ -1,3 +1,98 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
|
1
|
+
require 'populate_me/document'
|
2
|
+
require 'mongo'
|
3
|
+
|
4
|
+
module PopulateMe
|
5
|
+
|
6
|
+
class MissingMongoDBError < StandardError; end
|
7
|
+
|
8
|
+
class Mongo < Document
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def inherited sub
|
13
|
+
super
|
14
|
+
sub.set :collection_name, WebUtils.dasherize_class_name(sub.name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def collection
|
18
|
+
raise MissingMongoDBError, "Document class #{self.name} does not have a Mongo database." if settings.db.nil?
|
19
|
+
settings.db[settings.collection_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_id_field
|
23
|
+
field :_id, {type: :id}
|
24
|
+
end
|
25
|
+
|
26
|
+
def sort_by f, direction=1
|
27
|
+
direction = 1 if direction==:asc
|
28
|
+
direction = -1 if direction==:desc
|
29
|
+
if f.is_a?(Hash)
|
30
|
+
@current_sort = f
|
31
|
+
elsif f.is_a?(Array)
|
32
|
+
@current_sort = f.inject({}) do |h,pair|
|
33
|
+
h.store(pair[0], pair[1])
|
34
|
+
h
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise(ArgumentError) unless [1,-1].include? direction
|
38
|
+
raise(ArgumentError) unless self.new.respond_to? f
|
39
|
+
@current_sort = {f => direction}
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def id_string_key
|
45
|
+
(self.fields.keys[0]||'_id').to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_indexes f, ids=[]
|
49
|
+
requests = ids.each_with_index.inject([]) do |list, (id, i)|
|
50
|
+
list << {update_one:
|
51
|
+
{
|
52
|
+
filter: {self.id_string_key=>id},
|
53
|
+
update: {'$set'=>{f=>i}}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
collection.bulk_write requests
|
58
|
+
end
|
59
|
+
|
60
|
+
def admin_get theid
|
61
|
+
theid = BSON::ObjectId.from_string(theid) if BSON::ObjectId.legal?(theid)
|
62
|
+
self.cast{ collection.find({id_string_key => theid}).first }
|
63
|
+
end
|
64
|
+
alias_method :[], :admin_get
|
65
|
+
|
66
|
+
def admin_find o={}
|
67
|
+
query = o.delete(:query) || {}
|
68
|
+
o[:sort] ||= @current_sort
|
69
|
+
self.cast{ collection.find(query, o) }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_accessor :_id
|
75
|
+
|
76
|
+
def id; @_id; end
|
77
|
+
def id= value; @_id = value; end
|
78
|
+
|
79
|
+
def perform_create
|
80
|
+
result = self.class.collection.insert_one(self.to_h)
|
81
|
+
if result.ok? and self.id.nil?
|
82
|
+
self.id = result.inserted_id
|
83
|
+
end
|
84
|
+
self.id
|
85
|
+
end
|
86
|
+
|
87
|
+
def perform_update
|
88
|
+
self.class.collection.update_one({'_id'=> self.id}, self.to_h)
|
89
|
+
self.id
|
90
|
+
end
|
91
|
+
|
92
|
+
def perform_delete
|
93
|
+
self.class.collection.delete_one({'_id'=> self.id})
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PopulateMe
|
2
|
+
|
3
|
+
class Variation < Struct.new :name, :ext, :job
|
4
|
+
|
5
|
+
# Simple class to deal with variations of an attachment
|
6
|
+
# Mainly variation of images using ImageMagick
|
7
|
+
# but it could be anything else like creating the pdf version
|
8
|
+
# of a text file
|
9
|
+
|
10
|
+
def initialize name, ext, job_as_proc=nil, &job_as_block
|
11
|
+
super name, ext, job_as_proc||job_as_block
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def new_image_magick_job name, ext, convert_string, options={}
|
17
|
+
o = {
|
18
|
+
strip: true, progressive: true,
|
19
|
+
}.merge(options)
|
20
|
+
defaults = ""
|
21
|
+
defaults << "-strip " if o[:strip]
|
22
|
+
defaults << "-interlace Plane " if o[:progressive] and [:jpg,:jpeg].include?(ext.to_sym)
|
23
|
+
job = lambda{ |src,dst|
|
24
|
+
Kernel.system "convert \"#{src}\" #{defaults}#{convert_string} \"#{dst}\""
|
25
|
+
}
|
26
|
+
self.new name, ext, job
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
data/populate-me.gemspec
CHANGED
@@ -1,24 +1,32 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'lib/populate_me/version')
|
2
|
+
|
1
3
|
Gem::Specification.new do |s|
|
4
|
+
|
5
|
+
s.authors = ["Mickael Riga"]
|
6
|
+
s.email = ["mig@mypeplum.com"]
|
7
|
+
s.homepage = "https://github.com/mig-hub/populate-me"
|
8
|
+
s.licenses = ['MIT']
|
9
|
+
|
2
10
|
s.name = 'populate-me'
|
3
|
-
s.version =
|
11
|
+
s.version = PopulateMe::VERSION
|
12
|
+
s.summary = "PopulateMe is an admin system for web applications."
|
13
|
+
s.description = "PopulateMe is an admin system for managing structured content of web applications. It is built on top of the Sinatra framework, but can be used along any framework using Rack."
|
14
|
+
|
4
15
|
s.platform = Gem::Platform::RUBY
|
5
|
-
s.summary = "ALPHA !!! Populate Me is relatively complete but simple CMS"
|
6
|
-
s.description = "ALPHA !!! Populate Me is relatively complete but simple CMS. It includes a Rack middleware for putting in your Rack stack, and a bespoke MongoDB ODM. But Populate Me is not really finished yet."
|
7
16
|
s.files = `git ls-files`.split("\n").sort
|
8
|
-
s.
|
9
|
-
s.
|
10
|
-
s.email = "mig@mypeplum.com"
|
11
|
-
s.homepage = "https://github.com/mig-hub/populate-me"
|
12
|
-
s.licenses = ['MIT']
|
17
|
+
s.test_files = s.files.grep(/^test\//)
|
18
|
+
s.require_paths = ['lib']
|
13
19
|
|
14
|
-
s.add_dependency '
|
20
|
+
s.add_dependency 'web-utils', '~> 0'
|
21
|
+
s.add_dependency 'sinatra', '~> 1.0'
|
15
22
|
s.add_dependency 'json', '~> 2.1'
|
16
|
-
s.add_dependency 'mongo', '~> 2.0'
|
17
23
|
|
18
24
|
s.add_development_dependency 'bundler', '~> 1.13'
|
19
|
-
s.add_development_dependency '
|
25
|
+
s.add_development_dependency 'minitest', '~> 5.8'
|
20
26
|
s.add_development_dependency 'rack-test', '~> 0.6'
|
27
|
+
s.add_development_dependency 'rack-cerberus', '~> 1.0'
|
28
|
+
s.add_development_dependency 'mongo', '~> 2.0'
|
21
29
|
s.add_development_dependency 'racksh', '~> 1.0'
|
22
|
-
|
30
|
+
s.add_development_dependency 'rake', '~> 10.1'
|
23
31
|
end
|
24
32
|
|
data/test/helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'rack/test'
|
5
|
+
|
6
|
+
module Minitest::Assertions
|
7
|
+
def assert_json response
|
8
|
+
assert response.content_type=="application/json", "Expected #{response.inspect} to be a JSON response"
|
9
|
+
end
|
10
|
+
def assert_for_view json, view_name, title=nil
|
11
|
+
assert json['template']==view_name, "Expected #{json.inspect} to have 'template' set to '#{view_name}'"
|
12
|
+
unless title.nil?
|
13
|
+
assert json['page_title']==title, "Expected #{json.inspect} to have 'page_title' set to '#{title}'"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert_receive obj, meth, retval=nil, args=[]
|
18
|
+
mocked_meth = Minitest::Mock.new
|
19
|
+
mocked_meth.expect(:call, retval, args)
|
20
|
+
obj.stub meth, mocked_meth do
|
21
|
+
yield
|
22
|
+
end
|
23
|
+
assert mocked_meth.verify, "Expected #{obj.inspect} to receive :#{meth}"
|
24
|
+
end
|
25
|
+
def refute_receive obj, meth
|
26
|
+
proof = nil
|
27
|
+
obj.stub meth, proc{proof = :received} do
|
28
|
+
yield
|
29
|
+
assert(proof!=:received, "Expected #{obj.inspect} not to receive :#{meth}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Minitest::Spec
|
35
|
+
include Rack::Test::Methods
|
36
|
+
end
|
37
|
+
|
data/test/test_admin.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'populate_me/admin'
|
3
|
+
|
4
|
+
class Admin < PopulateMe::Admin
|
5
|
+
enable :sessions
|
6
|
+
set :menu, [
|
7
|
+
['Home Details', '/admin/form/home-details/0'],
|
8
|
+
['Project Page', [
|
9
|
+
['Project Page Intro', '/admin/form/project-page-intro/0'],
|
10
|
+
['Projects', '/admin/list/project'],
|
11
|
+
['Checks', [
|
12
|
+
['Check 1', '/check/1'],
|
13
|
+
['Check 2', '/check/2']
|
14
|
+
]]
|
15
|
+
]]
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
class AdminWithCerberusPass < Admin
|
20
|
+
def self.cerberus_pass
|
21
|
+
'123'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AdminCerberusNotAvailable < AdminWithCerberusPass
|
26
|
+
def self.cerberus_available?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class AdminCerberusDisabled < AdminWithCerberusPass
|
32
|
+
disable :cerberus
|
33
|
+
end
|
34
|
+
|
35
|
+
describe PopulateMe::Admin do
|
36
|
+
|
37
|
+
parallelize_me!
|
38
|
+
|
39
|
+
let(:app) { ::Admin.new }
|
40
|
+
|
41
|
+
let(:settings) { app.settings }
|
42
|
+
|
43
|
+
let(:json) { JSON.parse(last_response.body) }
|
44
|
+
|
45
|
+
describe 'Settings' do
|
46
|
+
it 'Sets paths based on the subclass file path' do
|
47
|
+
assert_equal __FILE__, settings.app_file
|
48
|
+
end
|
49
|
+
it 'Has a default value for the page title tag' do
|
50
|
+
assert_equal 'Populate Me', settings.meta_title
|
51
|
+
end
|
52
|
+
it 'Has a default index_path' do
|
53
|
+
assert_equal '/menu', settings.index_path
|
54
|
+
end
|
55
|
+
it 'Has cerberus enabled by default' do
|
56
|
+
assert settings.cerberus?
|
57
|
+
end
|
58
|
+
describe 'when ENV CERBERUS_PASS is not set' do
|
59
|
+
it 'Does not have cerberus_active' do
|
60
|
+
refute settings.cerberus_active
|
61
|
+
end
|
62
|
+
end
|
63
|
+
describe 'when ENV CERBERUS_PASS is set' do
|
64
|
+
let(:app) { AdminWithCerberusPass.new }
|
65
|
+
it 'Has cerberus_active' do
|
66
|
+
assert settings.cerberus_active
|
67
|
+
end
|
68
|
+
end
|
69
|
+
describe 'when ENV CERBERUS_PASS is set but gem not loaded' do
|
70
|
+
let(:app) { AdminCerberusNotAvailable.new }
|
71
|
+
it 'Does not have cerberus_active' do
|
72
|
+
refute settings.cerberus_active
|
73
|
+
end
|
74
|
+
end
|
75
|
+
describe 'when ENV CERBERUS_PASS is set but cerberus is disabled' do
|
76
|
+
let(:app) { AdminCerberusDisabled.new }
|
77
|
+
it 'Does not have cerberus_active' do
|
78
|
+
refute settings.cerberus_active
|
79
|
+
end
|
80
|
+
end
|
81
|
+
describe 'when Cerberus is active' do
|
82
|
+
let(:app) { AdminWithCerberusPass.new }
|
83
|
+
it 'Sets logout_path to /logout' do
|
84
|
+
assert_equal '/logout', settings.logout_path
|
85
|
+
end
|
86
|
+
end
|
87
|
+
describe 'when Cerberus is not active' do
|
88
|
+
it 'Sets logout_path to false' do
|
89
|
+
refute settings.logout_path
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'Middlewares' do
|
95
|
+
|
96
|
+
it 'Has API middleware mounted on /api' do
|
97
|
+
get '/api'
|
98
|
+
assert_predicate last_response, :ok?
|
99
|
+
assert_json last_response
|
100
|
+
assert json['success']
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'Has assets available on /__assets__' do
|
104
|
+
get('/__assets__/css/main.css')
|
105
|
+
assert_predicate last_response, :ok?
|
106
|
+
assert_equal 'text/css', last_response.content_type
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'when cerberus is active' do
|
110
|
+
let(:app) { AdminWithCerberusPass.new }
|
111
|
+
it 'Uses Cerberus for authentication' do
|
112
|
+
get '/'
|
113
|
+
assert_equal 401, last_response.status
|
114
|
+
end
|
115
|
+
end
|
116
|
+
describe 'when cerberus is inactive' do
|
117
|
+
it 'Does not use Cerberus' do
|
118
|
+
get '/'
|
119
|
+
assert_predicate last_response, :ok?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'Handlers' do
|
126
|
+
|
127
|
+
describe '/menu' do
|
128
|
+
|
129
|
+
describe 'when url is root' do
|
130
|
+
it 'Returns the correct info' do
|
131
|
+
get '/menu'
|
132
|
+
assert_predicate last_response, :ok?
|
133
|
+
assert_json last_response
|
134
|
+
assert_for_view json, 'template_menu', 'Menu'
|
135
|
+
assert_equal 2, json['items'].size
|
136
|
+
expected_h = {
|
137
|
+
'title'=> 'Home Details', 'href'=> '/admin/form/home-details/0'
|
138
|
+
}
|
139
|
+
assert_equal expected_h, json['items'][0]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
describe 'when url is nested' do
|
143
|
+
it 'Returns the correct info' do
|
144
|
+
get '/menu/project-page/checks'
|
145
|
+
assert_predicate last_response, :ok?
|
146
|
+
assert_json last_response
|
147
|
+
assert_for_view json, 'template_menu', 'Checks'
|
148
|
+
assert_equal 2, json['items'].size
|
149
|
+
expected_h = {
|
150
|
+
'title'=> 'Check 1', 'href'=> '/check/1'
|
151
|
+
}
|
152
|
+
assert_equal expected_h, json['items'][0]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
data/test/test_api.rb
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
require 'populate_me/document'
|
4
|
+
class Band < PopulateMe::Document
|
5
|
+
attr_accessor :name, :awsome, :position
|
6
|
+
def members; @members ||= []; end
|
7
|
+
def validate
|
8
|
+
error_on(:name,"WTF") if self.name=='ZZ Top'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
class Band::Member < PopulateMe::Document
|
12
|
+
attr_accessor :name
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'populate_me/api'
|
16
|
+
|
17
|
+
describe 'PopulateMe::API' do
|
18
|
+
|
19
|
+
# This middleware has the CRUD interface for
|
20
|
+
# managing documents through a JSON-based API
|
21
|
+
#
|
22
|
+
# The API needs the Document class to implement these methods:
|
23
|
+
# - Class.admin_get
|
24
|
+
# - Needs to be able to accept the ID as a string
|
25
|
+
# - The class is responsible for conversion
|
26
|
+
# - So a mix of different classes of IDs is not possible
|
27
|
+
# - instance.to_h
|
28
|
+
# - instance.save
|
29
|
+
# - instance.delete
|
30
|
+
|
31
|
+
parallelize_me!
|
32
|
+
|
33
|
+
let(:app) {
|
34
|
+
PopulateMe::API.new
|
35
|
+
}
|
36
|
+
|
37
|
+
let(:json) {
|
38
|
+
JSON.parse(last_response.body)
|
39
|
+
}
|
40
|
+
|
41
|
+
def assert_not_found
|
42
|
+
assert_json last_response
|
43
|
+
assert_equal 404, last_response.status
|
44
|
+
assert_equal 'pass', last_response.headers['X-Cascade']
|
45
|
+
refute json['success']
|
46
|
+
assert_equal 'Not Found', json['message']
|
47
|
+
end
|
48
|
+
|
49
|
+
def assert_successful_creation
|
50
|
+
assert_json last_response
|
51
|
+
assert_equal 201, last_response.status
|
52
|
+
assert json['success']
|
53
|
+
assert_equal 'Created Successfully', json['message']
|
54
|
+
end
|
55
|
+
|
56
|
+
def assert_successful_sorting
|
57
|
+
assert_json last_response
|
58
|
+
assert_predicate last_response, :ok?
|
59
|
+
assert json['success']
|
60
|
+
assert_equal 'Sorted Successfully', json['message']
|
61
|
+
end
|
62
|
+
|
63
|
+
def assert_invalid_instance
|
64
|
+
assert_json last_response
|
65
|
+
assert_equal 400, last_response.status
|
66
|
+
refute json['success']
|
67
|
+
assert_equal 'Invalid Document', json['message']
|
68
|
+
end
|
69
|
+
|
70
|
+
def assert_successful_instance
|
71
|
+
assert_json last_response
|
72
|
+
assert_predicate last_response, :ok?
|
73
|
+
assert json['success']
|
74
|
+
end
|
75
|
+
|
76
|
+
def assert_successful_update
|
77
|
+
assert_json last_response
|
78
|
+
assert_predicate last_response, :ok?
|
79
|
+
assert json['success']
|
80
|
+
assert_equal 'Updated Successfully', json['message']
|
81
|
+
end
|
82
|
+
|
83
|
+
def assert_successful_deletion
|
84
|
+
assert_json last_response
|
85
|
+
assert_predicate last_response, :ok?
|
86
|
+
assert json['success']
|
87
|
+
assert_equal 'Deleted Successfully', json['message']
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'GET /version' do
|
91
|
+
it 'Returns the PopulateMe version' do
|
92
|
+
get('/version')
|
93
|
+
assert_json last_response
|
94
|
+
assert_predicate last_response, :ok?
|
95
|
+
assert json['success']
|
96
|
+
assert_equal PopulateMe::VERSION, json['version']
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe 'POST /:model' do
|
101
|
+
|
102
|
+
it 'Creates successfully' do
|
103
|
+
post('/band', {data: {id: 'neurosis', name: 'Neurosis'}})
|
104
|
+
assert_successful_creation
|
105
|
+
assert_equal 'Neurosis', json['data']['name']
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'Typecasts before creating' do
|
109
|
+
post('/band', {data: {name: 'Arcade Fire', awsome: 'true'}})
|
110
|
+
assert_successful_creation
|
111
|
+
assert json['data']['awsome']
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'Can create a doc even if no data is sent' do
|
115
|
+
post '/band'
|
116
|
+
assert_successful_creation
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'Fails if the doc is invalid' do
|
120
|
+
post('/band', {data: {id: 'invalid_doc_post', name: 'ZZ Top'}})
|
121
|
+
assert_invalid_instance
|
122
|
+
assert_equal({'name'=>['WTF']}, json['data'])
|
123
|
+
assert_nil Band.admin_get('invalid_doc_post')
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'Redirects if destination is given' do
|
127
|
+
post '/band', {'_destination'=>'http://example.org/anywhere'}
|
128
|
+
assert_equal 302, last_response.status
|
129
|
+
assert_equal 'http://example.org/anywhere', last_response.header['Location']
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'PUT /:model' do
|
135
|
+
|
136
|
+
it 'Can set indexes for sorting' do
|
137
|
+
post('/band', {data: {id: 'sortable1', name: 'Sortable 1'}})
|
138
|
+
post('/band', {data: {id: 'sortable2', name: 'Sortable 2'}})
|
139
|
+
post('/band', {data: {id: 'sortable3', name: 'Sortable 3'}})
|
140
|
+
put '/band', {
|
141
|
+
'action'=>'sort',
|
142
|
+
'field'=>'position',
|
143
|
+
'ids'=> ['sortable2','sortable3','sortable1']
|
144
|
+
}
|
145
|
+
assert_successful_sorting
|
146
|
+
assert_equal 0, Band.admin_get('sortable2').position
|
147
|
+
assert_equal 1, Band.admin_get('sortable3').position
|
148
|
+
assert_equal 2, Band.admin_get('sortable1').position
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'Redirects after sorting if destination is given' do
|
152
|
+
post('/band', {data: {id: 'redirectsortable1', name: 'Redirect Sortable 1'}})
|
153
|
+
post('/band', {data: {id: 'redirectsortable2', name: 'Redirect Sortable 2'}})
|
154
|
+
post('/band', {data: {id: 'redirectsortable3', name: 'Redirect Sortable 3'}})
|
155
|
+
put '/band', {
|
156
|
+
'action'=>'sort',
|
157
|
+
'field'=>'position',
|
158
|
+
'ids'=> ['redirectsortable2','redirectsortable3','redirectsortable1'],
|
159
|
+
'_destination'=>'http://example.org/anywhere'
|
160
|
+
}
|
161
|
+
assert_equal 302, last_response.status
|
162
|
+
assert_equal 'http://example.org/anywhere', last_response.header['Location']
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
describe 'GET /:model/:id' do
|
168
|
+
it 'Sends a not-found when the model is not a class' do
|
169
|
+
get('/wizz/42')
|
170
|
+
assert_not_found
|
171
|
+
end
|
172
|
+
it 'Sends not-found when the model is a class but not a model' do
|
173
|
+
get('/string/42')
|
174
|
+
assert_not_found
|
175
|
+
end
|
176
|
+
it 'Sends not-found when the id is not provided' do
|
177
|
+
get('/band/')
|
178
|
+
assert_not_found
|
179
|
+
end
|
180
|
+
it 'Sends not-found when the instance does not exist' do
|
181
|
+
get('/band/666')
|
182
|
+
assert_not_found
|
183
|
+
end
|
184
|
+
it 'Sends the instance if it exists' do
|
185
|
+
post('/band', {data: {id: 'sendable', name: 'Morphine'}})
|
186
|
+
get('/band/sendable')
|
187
|
+
assert_successful_instance
|
188
|
+
assert_equal Band.admin_get('sendable').to_h, json['data']
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe 'PUT /:model/:id' do
|
193
|
+
it 'Sends not-found if the instance does not exist' do
|
194
|
+
put('/band/666')
|
195
|
+
assert_not_found
|
196
|
+
end
|
197
|
+
it 'Fails if the document is invalid' do
|
198
|
+
post('/band', {data: {id: 'invalid_doc_put', name: 'Valid here'}})
|
199
|
+
put('/band/invalid_doc_put', {data: {name: 'ZZ Top'}})
|
200
|
+
assert_invalid_instance
|
201
|
+
assert_equal({'name'=>['WTF']}, json['data'])
|
202
|
+
refute_equal 'ZZ Top', Band.admin_get('invalid_doc_put').name
|
203
|
+
end
|
204
|
+
it 'Updates documents' do
|
205
|
+
post('/band', {data: {id: 'updatable', name: 'Updatable'}})
|
206
|
+
put('/band/updatable', {data: {awsome: 'yes'}})
|
207
|
+
assert_successful_update
|
208
|
+
obj = Band.admin_get('updatable')
|
209
|
+
assert_equal 'yes', obj.awsome
|
210
|
+
assert_equal 'Updatable', obj.name
|
211
|
+
end
|
212
|
+
# it 'Updates nested documents' do
|
213
|
+
# obj = Band.admin_get('3')
|
214
|
+
# put('/band/3', {data: {members: [
|
215
|
+
# {id: obj.members[0].id, _class: 'Band::Member', name: 'Joey Ramone'},
|
216
|
+
# {id: obj.members[1].id, _class: 'Band::Member'},
|
217
|
+
# ]}})
|
218
|
+
# assert_successful_update
|
219
|
+
# obj = Band.admin_get('3')
|
220
|
+
# assert_equal 'yes', obj.awsome
|
221
|
+
# assert_equal 'The Ramones', obj.name
|
222
|
+
# assert_equal 2, obj.members.size
|
223
|
+
# assert_equal 'Joey Ramone', obj.members[0].name
|
224
|
+
# assert_equal 'Deedee Ramone', obj.members[1].name
|
225
|
+
# end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe 'DELETE /:model/:id' do
|
229
|
+
it 'Sends not-found if the instance does not exist' do
|
230
|
+
delete('/band/666')
|
231
|
+
assert_not_found
|
232
|
+
end
|
233
|
+
it 'Returns a deletion response when the instance exists' do
|
234
|
+
post('/band', {data: {id: 'deletable', name: '1D'}})
|
235
|
+
delete('/band/deletable')
|
236
|
+
assert_successful_deletion
|
237
|
+
assert_instance_of Hash, json['data']
|
238
|
+
end
|
239
|
+
it 'Redirects if destination is given' do
|
240
|
+
delete('/band/2', {'_destination'=>'http://example.org/anywhere'})
|
241
|
+
assert_equal 302, last_response.status
|
242
|
+
assert_equal 'http://example.org/anywhere', last_response.header['Location']
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|