ditty 0.9.0 → 0.9.1
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.
- checksums.yaml +4 -4
- data/ditty.gemspec +1 -1
- data/lib/ditty/cli.rb +1 -1
- data/lib/ditty/version.rb +1 -1
- data/spec/ditty/api_spec.rb +51 -0
- data/spec/ditty/controllers/roles_spec.rb +67 -0
- data/spec/ditty/controllers/user_login_traits_spec.rb +72 -0
- data/spec/ditty/controllers/users_spec.rb +72 -0
- data/spec/ditty/emails/base_spec.rb +76 -0
- data/spec/ditty/emails/forgot_password_spec.rb +19 -0
- data/spec/ditty/helpers/component_spec.rb +85 -0
- data/spec/ditty/models/user_spec.rb +36 -0
- data/spec/ditty/services/email_spec.rb +36 -0
- data/spec/ditty/services/logger_spec.rb +67 -0
- data/spec/ditty/services/settings_spec.rb +63 -0
- data/spec/ditty_spec.rb +9 -0
- data/spec/factories.rb +46 -0
- data/spec/fixtures/logger.yml +17 -0
- data/spec/fixtures/section.yml +3 -0
- data/spec/fixtures/settings.yml +8 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/api_shared_examples.rb +250 -0
- data/spec/support/crud_shared_examples.rb +145 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7260a6694f316ec8f4e3388932e7a749030f7fb6fc88657012f15a863069639
|
4
|
+
data.tar.gz: 3f0e6db669807bc153111ceaa182cbbc5323f90f0acf535e3bdb6bd199b531cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd5b41ce540f365575aeb1c7c33ffc487f87585894a0d1267428dc5bba8a60ea9b041fa9f0c8e8bcda62b265f94c989b3938c82e76a1770269a0abc8a26ed481
|
7
|
+
data.tar.gz: 385f6cae60979bc716d3268a07856255fc19beaa9652087c1d9f748042074c927d4fcb259188667ef6724083feb76004871c5e69e76d5c8eae4414dd63781997
|
data/ditty.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.homepage = 'https://github.com/eagerelk/ditty'
|
16
16
|
spec.license = 'MIT'
|
17
17
|
|
18
|
-
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.files = `git ls-files -z`.split("\x0") #.reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
19
|
spec.bindir = 'exe'
|
20
20
|
spec.executables = ['ditty']
|
21
21
|
spec.require_paths = ['lib']
|
data/lib/ditty/cli.rb
CHANGED
data/lib/ditty/version.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
Dir.glob('./lib/ditty/controllers/*.rb').each { |f| require f }
|
5
|
+
require 'support/api_shared_examples'
|
6
|
+
|
7
|
+
describe ::Ditty::RolesController, type: :controller do
|
8
|
+
def app
|
9
|
+
described_class
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:user) { create(:super_admin_user) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
env 'rack.session', 'user_id' => user.id
|
16
|
+
end
|
17
|
+
|
18
|
+
it_behaves_like 'an API interface', :role, {}
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ::Ditty::UsersController, type: :controller do
|
22
|
+
def app
|
23
|
+
described_class
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:user) { create(:super_admin_user) }
|
27
|
+
|
28
|
+
before { env 'rack.session', 'user_id' => user.id }
|
29
|
+
|
30
|
+
params = {
|
31
|
+
identity: {
|
32
|
+
username: 'test-user@abc.abc',
|
33
|
+
password: 'som3Password!',
|
34
|
+
password_confirmation: 'som3Password!'
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
it_behaves_like 'an API interface', :user, params
|
39
|
+
end
|
40
|
+
|
41
|
+
describe ::Ditty::UserLoginTraitsController, type: :controller do
|
42
|
+
def app
|
43
|
+
described_class
|
44
|
+
end
|
45
|
+
|
46
|
+
let(:user) { create(:super_admin_user) }
|
47
|
+
|
48
|
+
before { env 'rack.session', 'user_id' => user.id }
|
49
|
+
|
50
|
+
it_behaves_like 'an API interface', :user_login_trait, {}
|
51
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/controllers/roles_controller'
|
5
|
+
require 'support/crud_shared_examples'
|
6
|
+
|
7
|
+
describe ::Ditty::RolesController do
|
8
|
+
def app
|
9
|
+
described_class
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'as super_admin_user' do
|
13
|
+
let(:user) { create(:super_admin_user) }
|
14
|
+
let(:model) { create(app.model_class.name.to_sym) }
|
15
|
+
let(:create_data) do
|
16
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
17
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
18
|
+
end
|
19
|
+
let(:update_data) do
|
20
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
21
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
22
|
+
end
|
23
|
+
let(:invalid_create_data) do
|
24
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
25
|
+
{ group => { name: '' } }
|
26
|
+
end
|
27
|
+
let(:invalid_update_data) do
|
28
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
29
|
+
{ group => { name: '' } }
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
# Log in
|
34
|
+
env 'rack.session', 'user_id' => user.id
|
35
|
+
end
|
36
|
+
|
37
|
+
it_behaves_like 'a CRUD Controller', '/roles'
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'as user' do
|
41
|
+
let(:user) { create(:user) }
|
42
|
+
let(:model) { create(app.model_class.name.to_sym) }
|
43
|
+
let(:create_data) do
|
44
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
45
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
46
|
+
end
|
47
|
+
let(:update_data) do
|
48
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
49
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
50
|
+
end
|
51
|
+
let(:invalid_create_data) do
|
52
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
53
|
+
{ group => { name: '' } }
|
54
|
+
end
|
55
|
+
let(:invalid_update_data) do
|
56
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
57
|
+
{ group => { name: '' } }
|
58
|
+
end
|
59
|
+
|
60
|
+
before do
|
61
|
+
# Log in
|
62
|
+
env 'rack.session', 'user_id' => user.id
|
63
|
+
end
|
64
|
+
|
65
|
+
it_behaves_like 'a CRUD Controller', '/roles'
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/controllers/user_login_traits_controller'
|
5
|
+
require 'support/crud_shared_examples'
|
6
|
+
|
7
|
+
describe ::Ditty::UserLoginTraitsController do
|
8
|
+
def app
|
9
|
+
described_class
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'as super_admin_user' do
|
13
|
+
let(:user) { create(:super_admin_user) }
|
14
|
+
let(:model) { create(app.model_class.name.to_sym) }
|
15
|
+
let(:create_data) do
|
16
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
17
|
+
identity = build(:identity).to_hash
|
18
|
+
identity['password_confirmation'] = identity['password'] = 'som3Password!'
|
19
|
+
{
|
20
|
+
group => build(described_class.model_class.name.to_sym).to_hash,
|
21
|
+
'identity' => identity
|
22
|
+
}
|
23
|
+
end
|
24
|
+
let(:update_data) do
|
25
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
26
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
27
|
+
end
|
28
|
+
let(:invalid_create_data) do
|
29
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
30
|
+
{ group => { user_id: nil } }
|
31
|
+
end
|
32
|
+
let(:invalid_update_data) do
|
33
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
34
|
+
{ group => { user_id: nil } }
|
35
|
+
end
|
36
|
+
|
37
|
+
before do
|
38
|
+
# Log in
|
39
|
+
env 'rack.session', 'user_id' => user.id
|
40
|
+
end
|
41
|
+
|
42
|
+
it_behaves_like 'a CRUD Controller', '/user-login-traits'
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'as user' do
|
46
|
+
let(:user) { create(:user) }
|
47
|
+
let(:model) { create(app.model_class.name.to_sym, user: user) }
|
48
|
+
let(:create_data) do
|
49
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
50
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
51
|
+
end
|
52
|
+
let(:update_data) do
|
53
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
54
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
55
|
+
end
|
56
|
+
let(:invalid_create_data) do
|
57
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
58
|
+
{ group => { user_id: nil } }
|
59
|
+
end
|
60
|
+
let(:invalid_update_data) do
|
61
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
62
|
+
{ group => { user_id: nil } }
|
63
|
+
end
|
64
|
+
|
65
|
+
before do
|
66
|
+
# Log in
|
67
|
+
env 'rack.session', 'user_id' => user.id
|
68
|
+
end
|
69
|
+
|
70
|
+
it_behaves_like 'a CRUD Controller', '/user-login-traits'
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/controllers/users_controller'
|
5
|
+
require 'support/crud_shared_examples'
|
6
|
+
|
7
|
+
describe ::Ditty::UsersController do
|
8
|
+
def app
|
9
|
+
described_class
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'as super_admin_user' do
|
13
|
+
let(:user) { create(:super_admin_user) }
|
14
|
+
let(:model) { create(app.model_class.name.to_sym) }
|
15
|
+
let(:create_data) do
|
16
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
17
|
+
identity = build(:identity).to_hash
|
18
|
+
identity['password_confirmation'] = identity['password'] = 'som3Password!'
|
19
|
+
{
|
20
|
+
group => build(described_class.model_class.name.to_sym).to_hash,
|
21
|
+
'identity' => identity
|
22
|
+
}
|
23
|
+
end
|
24
|
+
let(:update_data) do
|
25
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
26
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
27
|
+
end
|
28
|
+
let(:invalid_create_data) do
|
29
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
30
|
+
{ group => { email: 'invalidemail' } }
|
31
|
+
end
|
32
|
+
let(:invalid_update_data) do
|
33
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
34
|
+
{ group => { email: 'invalidemail' } }
|
35
|
+
end
|
36
|
+
|
37
|
+
before do
|
38
|
+
# Log in
|
39
|
+
env 'rack.session', 'user_id' => user.id
|
40
|
+
end
|
41
|
+
|
42
|
+
it_behaves_like 'a CRUD Controller', '/users'
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'as user' do
|
46
|
+
let(:user) { create(:user) }
|
47
|
+
let(:model) { create(app.model_class.name.to_sym) }
|
48
|
+
let(:create_data) do
|
49
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
50
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
51
|
+
end
|
52
|
+
let(:update_data) do
|
53
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
54
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
55
|
+
end
|
56
|
+
let(:invalid_create_data) do
|
57
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
58
|
+
{ group => { email: 'invalidemail' } }
|
59
|
+
end
|
60
|
+
let(:invalid_update_data) do
|
61
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
62
|
+
{ group => { email: 'invalidemail' } }
|
63
|
+
end
|
64
|
+
|
65
|
+
before do
|
66
|
+
# Log in
|
67
|
+
env 'rack.session', 'user_id' => user.id
|
68
|
+
end
|
69
|
+
|
70
|
+
it_behaves_like 'a CRUD Controller', '/users'
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/emails/base'
|
5
|
+
require 'mail'
|
6
|
+
|
7
|
+
describe ::Ditty::Emails::Base do
|
8
|
+
let(:mail) do
|
9
|
+
mail = Mail.new
|
10
|
+
allow(mail).to receive(:deliver!)
|
11
|
+
mail
|
12
|
+
end
|
13
|
+
|
14
|
+
context '.new' do
|
15
|
+
it 'defaults to base options' do
|
16
|
+
expect(subject.options).to include subject: '(No Subject)', from: 'no-reply@ditty.io', view: :base
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'allows the use of layouts' do
|
20
|
+
skip 'Test is not accurate. The class no longer uses body.'
|
21
|
+
base = described_class.new(layout: 'action', mail: mail)
|
22
|
+
expect(mail).to receive(:body).with(/^<!DOCTYPE html>/m)
|
23
|
+
base.deliver!('test@email.com')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context '.deliver!' do
|
28
|
+
it 'delivers the email to the specified email address' do
|
29
|
+
expect(mail).to receive(:to).with('test@email.com')
|
30
|
+
expect(mail).to receive(:deliver!)
|
31
|
+
described_class.deliver!('test@email.com', mail: mail)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'passes down local variables' do
|
35
|
+
skip 'Test is not accurate. The class no longer uses body.'
|
36
|
+
expect(mail).to receive(:body).with("test content\n")
|
37
|
+
described_class.deliver!('test@email.com', locals: { content: 'test content' }, mail: mail)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'sets the email\'s subject and from address' do
|
41
|
+
expect(mail).to receive(:subject).with('test subject')
|
42
|
+
expect(mail).to receive(:from).with('from@test.com')
|
43
|
+
described_class.deliver!('test@email.com', subject: 'test subject', from: 'from@test.com', mail: mail)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context '#deliver!' do
|
48
|
+
it 'delivers the email to the specified email address' do
|
49
|
+
expect(mail).to receive(:to).with('test2@email.com')
|
50
|
+
base = described_class.new(mail: mail)
|
51
|
+
base.deliver!('test2@email.com')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'passes the local variables to the template' do
|
55
|
+
skip 'Test is not accurate. The class no longer uses body.'
|
56
|
+
expect(mail).to receive(:body).with("test content\n")
|
57
|
+
base = described_class.new(mail: mail)
|
58
|
+
base.deliver!('test@email.com', content: 'test content')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'sets the email\'s subject and from address' do
|
62
|
+
expect(mail).to receive(:subject).with('test subject')
|
63
|
+
expect(mail).to receive(:from).with('from@test.com')
|
64
|
+
base = described_class.new(subject: 'test subject', from: 'from@test.com', mail: mail)
|
65
|
+
base.deliver!('test@email.com')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'method_missing' do
|
70
|
+
it 'passes unknown message to the underlying mail object' do
|
71
|
+
expect(mail).to receive(:cc).with('cc@test.com')
|
72
|
+
base = described_class.new(mail: mail)
|
73
|
+
base.cc 'cc@test.com'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/emails/forgot_password'
|
5
|
+
require 'mail'
|
6
|
+
|
7
|
+
describe ::Ditty::Emails::ForgotPassword do
|
8
|
+
let(:mail) do
|
9
|
+
mail = Mail.new
|
10
|
+
allow(mail).to receive(:deliver!)
|
11
|
+
mail
|
12
|
+
end
|
13
|
+
|
14
|
+
context '.new' do
|
15
|
+
it 'defaults to base options' do
|
16
|
+
expect(subject.options).to include subject: 'Request to reset password', from: 'no-reply@ditty.io', view: :forgot_password
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/controllers/component_controller'
|
5
|
+
require 'ditty/helpers/component'
|
6
|
+
require 'ditty/models/user'
|
7
|
+
|
8
|
+
class DummyComponentController < ::Ditty::ComponentController
|
9
|
+
set model_class: Ditty::User
|
10
|
+
|
11
|
+
FILTERS = [{ name: :email }].freeze
|
12
|
+
SEARCHABLE = %i[name email].freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ::Ditty::Helpers::Component do
|
16
|
+
def app
|
17
|
+
DummyComponentController
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:user) { create(:super_admin_user) }
|
21
|
+
let(:model) { create(app.model_class.name.to_sym) }
|
22
|
+
let(:create_data) do
|
23
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
24
|
+
identity = build(:identity).to_hash
|
25
|
+
identity['password_confirmation'] = identity['password'] = 'som3Password!'
|
26
|
+
{
|
27
|
+
group => build(described_class.model_class.name.to_sym).to_hash,
|
28
|
+
'identity' => identity
|
29
|
+
}
|
30
|
+
end
|
31
|
+
let(:update_data) do
|
32
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
33
|
+
{ group => build(described_class.model_class.name.to_sym).to_hash }
|
34
|
+
end
|
35
|
+
let(:invalid_create_data) do
|
36
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
37
|
+
{ group => { email: 'invalidemail' } }
|
38
|
+
end
|
39
|
+
let(:invalid_update_data) do
|
40
|
+
group = described_class.model_class.to_s.demodulize.underscore
|
41
|
+
{ group => { email: 'invalidemail' } }
|
42
|
+
end
|
43
|
+
|
44
|
+
before do
|
45
|
+
env 'rack.session', 'user_id' => user.id
|
46
|
+
create(:user, email: 'bruce@wayne.com')
|
47
|
+
create(:user, email: 'tony@stark.com')
|
48
|
+
end
|
49
|
+
|
50
|
+
describe 'filters' do
|
51
|
+
it 'returns the matching items' do
|
52
|
+
header 'Accept', 'application/json'
|
53
|
+
get '/', email: 'bruce@wayne.com'
|
54
|
+
|
55
|
+
response = JSON.parse last_response.body
|
56
|
+
expect(response['count']).to eq(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns no items' do
|
60
|
+
header 'Accept', 'application/json'
|
61
|
+
get '/', email: 'not found'
|
62
|
+
|
63
|
+
response = JSON.parse last_response.body
|
64
|
+
expect(response['count']).to eq(0)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'search' do
|
69
|
+
it 'returns the matching items' do
|
70
|
+
header 'Accept', 'application/json'
|
71
|
+
get '/', q: 'wayne'
|
72
|
+
|
73
|
+
response = JSON.parse last_response.body
|
74
|
+
expect(response['count']).to eq(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns no items' do
|
78
|
+
header 'Accept', 'application/json'
|
79
|
+
get '/', q: 'not found'
|
80
|
+
|
81
|
+
response = JSON.parse last_response.body
|
82
|
+
expect(response['count']).to eq(0)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/models/user'
|
5
|
+
|
6
|
+
describe ::Ditty::User, type: :model do
|
7
|
+
let(:super_admin_role) { create(:role, name: 'super_admin') }
|
8
|
+
let(:admin_role) { create(:role, name: 'admin', parent_id: super_admin_role.id) }
|
9
|
+
let!(:user_role) { create(:role, name: 'user', parent_id: admin_role.id) }
|
10
|
+
let(:super_admin) { create(:user) }
|
11
|
+
let(:user) { create(:user) }
|
12
|
+
|
13
|
+
before { super_admin.add_role(super_admin_role) }
|
14
|
+
|
15
|
+
describe '#role?(check)' do
|
16
|
+
context 'when a user has a role without a parent' do
|
17
|
+
it 'returns true only for specific role' do
|
18
|
+
expect(user.role?('user')).to be_truthy
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns false for other roles' do
|
22
|
+
%w[admin super_admin].each do |role|
|
23
|
+
expect(user.role?(role)).to be_falsy
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when a user has a role with descendants' do
|
29
|
+
it 'returns true for all descendants' do
|
30
|
+
%w[user admin super_admin].each do |role|
|
31
|
+
expect(super_admin.role?(role)).to be_truthy
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/services/email'
|
5
|
+
|
6
|
+
describe ::Ditty::Services::Email do
|
7
|
+
after do
|
8
|
+
described_class.config = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'config!' do
|
12
|
+
it 'configures the Mail gem' do
|
13
|
+
expect(Mail).to receive(:defaults)
|
14
|
+
described_class.config!
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'uses the default settings' do
|
18
|
+
expect(described_class).to receive(:default).and_call_original
|
19
|
+
described_class.config!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'deliver!' do
|
24
|
+
it 'autoloads a ditty email from a symbol' do
|
25
|
+
mail = Mail.new
|
26
|
+
expect(mail).to receive(:deliver!)
|
27
|
+
described_class.deliver(:base, 'test@mail.com', locals: { content: 'content' }, mail: mail)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sends a mail object' do
|
31
|
+
mail = Mail.new
|
32
|
+
expect(mail).to receive(:deliver!)
|
33
|
+
described_class.deliver(mail)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'spec_helper'
|
5
|
+
require 'ditty/services/logger'
|
6
|
+
|
7
|
+
class TestLogger
|
8
|
+
WARN = 2
|
9
|
+
attr_accessor :level
|
10
|
+
def initialize(options = {})
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ::Ditty::Services::Logger, type: :service do
|
16
|
+
let(:subject) { described_class.clone }
|
17
|
+
let(:config_file) { File.read('./spec/fixtures/logger.yml') }
|
18
|
+
|
19
|
+
context 'initialize' do
|
20
|
+
it '.instance always refers to the same instance' do
|
21
|
+
expect(subject.instance).to eq subject.instance
|
22
|
+
end
|
23
|
+
|
24
|
+
it "creates default logger if config file does't exist" do
|
25
|
+
expect(subject.instance.loggers[0]).to be_instance_of Logger
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'reads config from file and creates an array of loggers' do
|
29
|
+
::Ditty::Services::Settings.values = nil
|
30
|
+
allow(File).to receive(:'file?').and_return(false)
|
31
|
+
allow(File).to receive(:'file?').with('./config/logger.yml').and_return(true)
|
32
|
+
allow(File).to receive(:read).and_return(config_file)
|
33
|
+
|
34
|
+
expect(subject.instance.loggers.size).to eq 4
|
35
|
+
expect(subject.instance.loggers[0]).to be_instance_of Logger
|
36
|
+
expect(subject.instance.loggers[1]).to be_instance_of TestLogger
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'sets the correct logging level' do
|
40
|
+
::Ditty::Services::Settings.values = nil
|
41
|
+
allow(File).to receive(:'file?').and_return(false)
|
42
|
+
allow(File).to receive(:'file?').with('./config/logger.yml').and_return(true)
|
43
|
+
allow(File).to receive(:read).and_return(config_file)
|
44
|
+
expect(subject.instance.loggers[0].level).to eq Logger::DEBUG
|
45
|
+
expect(subject.instance.loggers[2].level).to eq Logger::INFO
|
46
|
+
expect(subject.instance.loggers[3].level).to eq Logger::WARN
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'send messages' do
|
51
|
+
it 'receives message and passes it to the loggers' do
|
52
|
+
::Ditty::Services::Settings.values = nil
|
53
|
+
allow(File).to receive(:'file?').and_return(false)
|
54
|
+
allow(File).to receive(:'file?').with('./config/logger.yml').and_return(true)
|
55
|
+
allow(File).to receive(:read).and_return(config_file)
|
56
|
+
allow(Logger).to receive(:warn).with('Some message')
|
57
|
+
allow(TestLogger).to receive(:warn).with('Some message')
|
58
|
+
|
59
|
+
expect(subject.instance.loggers[0]).to receive(:warn).with('Some message')
|
60
|
+
expect(subject.instance.loggers[1]).to receive(:warn).with('Some message')
|
61
|
+
expect($stdout).to receive(:write).with(/Some message$/)
|
62
|
+
expect($stderr).to receive(:write).with(/Some message$/)
|
63
|
+
|
64
|
+
subject.instance.warn 'Some message'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ditty/services/settings'
|
5
|
+
|
6
|
+
describe ::Ditty::Services::Settings do
|
7
|
+
def setup_files
|
8
|
+
settings = File.read('./spec/fixtures/settings.yml')
|
9
|
+
section = File.read('./spec/fixtures/section.yml')
|
10
|
+
|
11
|
+
allow(File).to receive(:file?).and_return(false)
|
12
|
+
allow(File).to receive(:file?).with('./config/settings.yml').and_return(true)
|
13
|
+
allow(File).to receive(:file?).with('./config/section.yml').and_return(true)
|
14
|
+
|
15
|
+
allow(File).to receive(:read).with('./config/settings.yml').and_return(settings)
|
16
|
+
allow(File).to receive(:read).with('./config/section.yml').and_return(section)
|
17
|
+
end
|
18
|
+
|
19
|
+
context '#[]' do
|
20
|
+
before do
|
21
|
+
setup_files
|
22
|
+
described_class.values = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns the specified values from the global settings' do
|
26
|
+
expect(described_class[:option_a]).to eq 1
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'allows access to sectional settings' do
|
30
|
+
expect(described_class[:no_file_section]).to include(section_1: 2, section_2: 'set')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'allows using dots to travers' do
|
34
|
+
expect(described_class['nested.option']).to eq 'value'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context '#values' do
|
39
|
+
context 'uses the global file' do
|
40
|
+
before do
|
41
|
+
setup_files
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'to return global settings' do
|
45
|
+
expect(described_class.values).to include(option_a: 1, option_b: 'value')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'to return sectional settings' do
|
49
|
+
expect(described_class.values(:no_file_section)).to include(section_1: 2, section_2: 'set')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'uses the sectional file' do
|
54
|
+
before do
|
55
|
+
setup_files
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'prefers the sectional settings file' do
|
59
|
+
expect(described_class.values(:section)).to include(section_1: 3, section_2: 'section')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/ditty_spec.rb
ADDED
data/spec/factories.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faker'
|
4
|
+
require 'ditty/models/user'
|
5
|
+
require 'ditty/models/identity'
|
6
|
+
require 'ditty/models/role'
|
7
|
+
require 'ditty/models/user_login_trait'
|
8
|
+
|
9
|
+
FactoryBot.define do
|
10
|
+
to_create(&:save)
|
11
|
+
|
12
|
+
sequence(:email) { |n| "person-#{n}@example.com" }
|
13
|
+
sequence(:name) { |n| "Name-#{n}" }
|
14
|
+
|
15
|
+
factory :user, class: Ditty::User, aliases: [:'Ditty::User'] do
|
16
|
+
email
|
17
|
+
|
18
|
+
after(:create) do |user, _evaluator|
|
19
|
+
create(:identity, user: user)
|
20
|
+
end
|
21
|
+
|
22
|
+
factory :super_admin_user do
|
23
|
+
after(:create) do |user, _evaluator|
|
24
|
+
user.add_role(Ditty::Role.find_or_create(name: 'super_admin'))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
factory :identity, class: Ditty::Identity, aliases: [:'Ditty::Identity'] do
|
30
|
+
username { generate :email }
|
31
|
+
crypted_password { 'som3Password!' }
|
32
|
+
end
|
33
|
+
|
34
|
+
factory :role, class: Ditty::Role, aliases: [:'Ditty::Role'] do
|
35
|
+
name { "Role #{generate(:name)}" }
|
36
|
+
parent_id { nil }
|
37
|
+
end
|
38
|
+
|
39
|
+
factory :user_login_trait, class: Ditty::UserLoginTrait, aliases: [:'Ditty::UserLoginTrait'] do
|
40
|
+
association :user, strategy: :create, factory: :user
|
41
|
+
ip_address { Faker::Internet.ip_v4_address }
|
42
|
+
platform { Faker::Device.platform }
|
43
|
+
device { Faker::Device.model_name }
|
44
|
+
browser { 'Firefox' }
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
loggers:
|
2
|
+
- name: file
|
3
|
+
class: Logger
|
4
|
+
level: 'DEBUG'
|
5
|
+
- name: ES
|
6
|
+
class: TestLogger
|
7
|
+
options:
|
8
|
+
url: 'http://logging.ditty.io:9200'
|
9
|
+
log: false
|
10
|
+
- name: stdout
|
11
|
+
class: Logger
|
12
|
+
options: '$stdout'
|
13
|
+
level: INFO
|
14
|
+
- name: stderr
|
15
|
+
class: Logger
|
16
|
+
options: '$stderr'
|
17
|
+
level: WARN
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ENV['APP_ENV'] ||= 'test'
|
4
|
+
ENV['RACK_ENV'] ||= 'test'
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start
|
7
|
+
|
8
|
+
ENV['DATABASE_URL'] ||= 'sqlite::memory:'
|
9
|
+
|
10
|
+
require 'ditty'
|
11
|
+
require 'ditty/db'
|
12
|
+
require 'rspec'
|
13
|
+
require 'rack/test'
|
14
|
+
require 'factory_bot'
|
15
|
+
require 'database_cleaner'
|
16
|
+
require 'timecop'
|
17
|
+
|
18
|
+
if ENV['DATABASE_URL'] == 'sqlite::memory:'
|
19
|
+
folder = File.expand_path(File.dirname(__FILE__) + '/../migrate')
|
20
|
+
Sequel.extension :migration
|
21
|
+
Sequel::Migrator.apply(DB, folder)
|
22
|
+
|
23
|
+
# Seed the DB
|
24
|
+
require 'ditty/seed'
|
25
|
+
end
|
26
|
+
|
27
|
+
Ditty.component :ditty
|
28
|
+
RSpec.configure do |config|
|
29
|
+
config.include Rack::Test::Methods
|
30
|
+
config.include FactoryBot::Syntax::Methods
|
31
|
+
|
32
|
+
config.alias_example_to :fit, focus: true
|
33
|
+
config.filter_run focus: true
|
34
|
+
config.run_all_when_everything_filtered = true
|
35
|
+
|
36
|
+
config.before(:suite) do
|
37
|
+
DatabaseCleaner.strategy = :transaction
|
38
|
+
FactoryBot.find_definitions
|
39
|
+
Timecop.freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
config.around do |example|
|
43
|
+
DatabaseCleaner.cleaning do
|
44
|
+
example.run
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'active_support/core_ext/hash/except'
|
5
|
+
|
6
|
+
shared_examples 'an API interface' do |subject, params|
|
7
|
+
before { create(subject) }
|
8
|
+
|
9
|
+
context 'GET /' do
|
10
|
+
it 'returns HTML when requested' do
|
11
|
+
header 'Accept', 'text/html'
|
12
|
+
get '/'
|
13
|
+
|
14
|
+
expect(last_response).to be_ok
|
15
|
+
expect(last_response.headers['Content-Type']).to include('text/html;charset=utf-8')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns JSON when requested' do
|
19
|
+
header 'Accept', 'application/json'
|
20
|
+
get '/'
|
21
|
+
|
22
|
+
expect(last_response).to be_ok
|
23
|
+
expect(last_response.headers).to include('Content-Type' => 'application/json')
|
24
|
+
expect { JSON.parse(last_response.body) }.not_to raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns a list object' do
|
28
|
+
header 'Accept', 'application/json'
|
29
|
+
get '/'
|
30
|
+
|
31
|
+
response = JSON.parse last_response.body
|
32
|
+
expect(response).to include('page', 'count', 'total', 'items')
|
33
|
+
expect(response['page']).to be_an Integer
|
34
|
+
expect(response['count']).to be_an Integer
|
35
|
+
expect(response['total']).to be_an Integer
|
36
|
+
expect(response['items']).to be_an Array
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'GET /id' do
|
41
|
+
let(:entity) { create(subject) }
|
42
|
+
|
43
|
+
it 'returns HTML when requested' do
|
44
|
+
header 'Accept', 'text/html'
|
45
|
+
get "/#{entity.id}"
|
46
|
+
|
47
|
+
expect(last_response).to be_ok
|
48
|
+
expect(last_response.headers).to include('Content-Type' => 'text/html;charset=utf-8')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns JSON when requested' do
|
52
|
+
header 'Accept', 'application/json'
|
53
|
+
get "/#{entity.id}"
|
54
|
+
|
55
|
+
expect(last_response).to be_ok
|
56
|
+
expect(last_response.headers).to include('Content-Type' => 'application/json')
|
57
|
+
expect { JSON.parse(last_response.body) }.not_to raise_error
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns the fetched object' do
|
61
|
+
header 'Accept', 'application/json'
|
62
|
+
get "/#{entity.id}"
|
63
|
+
|
64
|
+
response = JSON.parse last_response.body
|
65
|
+
expect(response).to be_a Hash
|
66
|
+
entity_to_json = JSON.parse entity.values.to_json
|
67
|
+
expect(response).to include(entity_to_json)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'POST /' do
|
72
|
+
it 'returns HTML when requested' do
|
73
|
+
header 'Accept', 'text/html'
|
74
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
75
|
+
params[subject] = build(subject).to_hash
|
76
|
+
post '/', params
|
77
|
+
|
78
|
+
expect(last_response.headers).to include('Content-Type' => 'text/html;charset=utf-8')
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'returns a 302 Redirect response for a HTML Request' do
|
82
|
+
header 'Accept', 'text/html'
|
83
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
84
|
+
params[subject] = build(subject).to_hash
|
85
|
+
post '/', params
|
86
|
+
|
87
|
+
expect(last_response.status).to eq 302
|
88
|
+
expect(last_response.headers).to include('Location')
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'returns JSON when requested' do
|
92
|
+
header 'Accept', 'application/json'
|
93
|
+
header 'Content-Type', 'application/json'
|
94
|
+
params[subject] = build(subject).to_hash
|
95
|
+
post '/', params.to_json
|
96
|
+
|
97
|
+
expect(last_response.headers).to include('Content-Type' => 'application/json')
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'returns a 201 Created response for a JSON Request' do
|
101
|
+
header 'Accept', 'application/json'
|
102
|
+
header 'Content-Type', 'application/json'
|
103
|
+
params[subject] = build(subject).to_hash
|
104
|
+
post '/', params.to_json
|
105
|
+
|
106
|
+
expect(last_response.status).to eq 201
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns a Location Header for a JSON Request' do
|
110
|
+
header 'Accept', 'application/json'
|
111
|
+
header 'Content-Type', 'application/json'
|
112
|
+
params[subject] = build(subject).to_hash
|
113
|
+
post '/', params.to_json
|
114
|
+
|
115
|
+
expect(last_response.headers).to include 'Location'
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'returns an empty body for a JSON Request' do
|
119
|
+
header 'Accept', 'application/json'
|
120
|
+
header 'Content-Type', 'application/json'
|
121
|
+
params[subject] = build(subject).to_hash
|
122
|
+
post '/', params.to_json
|
123
|
+
|
124
|
+
expect(last_response.body).to eq ''
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'PUT /:id' do
|
129
|
+
let(:entity) { create(subject) }
|
130
|
+
|
131
|
+
it 'returns HTML when requested' do
|
132
|
+
header 'Accept', 'text/html'
|
133
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
134
|
+
|
135
|
+
values = entity.to_hash.except(:id)
|
136
|
+
params[subject] = values
|
137
|
+
put "/#{entity.id}", params
|
138
|
+
|
139
|
+
expect(last_response.headers).to include('Content-Type' => 'text/html;charset=utf-8')
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'returns a 302 Redirect response for a HTML Request' do
|
143
|
+
header 'Accept', 'text/html'
|
144
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
145
|
+
|
146
|
+
values = entity.to_hash.except(:id)
|
147
|
+
params[subject] = values
|
148
|
+
put "/#{entity.id}", params
|
149
|
+
|
150
|
+
expect(last_response.status).to eq 302
|
151
|
+
expect(last_response.headers).to include('Location')
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'returns JSON when requested' do
|
155
|
+
header 'Accept', 'application/json'
|
156
|
+
header 'Content-Type', 'application/json'
|
157
|
+
|
158
|
+
values = entity.to_hash.except(:id)
|
159
|
+
params[subject] = values
|
160
|
+
put "/#{entity.id}", params.to_json
|
161
|
+
|
162
|
+
expect(last_response.headers).to include('Content-Type' => 'application/json')
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'returns a 200 OK response for a JSON Request' do
|
166
|
+
header 'Accept', 'application/json'
|
167
|
+
header 'Content-Type', 'application/json'
|
168
|
+
|
169
|
+
values = entity.to_hash.except(:id)
|
170
|
+
params[subject] = values
|
171
|
+
put "/#{entity.id}", params.to_json
|
172
|
+
|
173
|
+
expect(last_response.status).to eq 200
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'returns a Location Header for a JSON Request' do
|
177
|
+
header 'Accept', 'application/json'
|
178
|
+
header 'Content-Type', 'application/json'
|
179
|
+
|
180
|
+
values = entity.to_hash.except(:id)
|
181
|
+
params[subject] = values
|
182
|
+
put "/#{entity.id}", params.to_json
|
183
|
+
|
184
|
+
expect(last_response.headers).to include 'Location'
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'returns the updated entity in the body for a JSON Request' do
|
188
|
+
header 'Accept', 'application/json'
|
189
|
+
header 'Content-Type', 'application/json'
|
190
|
+
|
191
|
+
values = entity.to_hash.except(:id)
|
192
|
+
params[subject] = values
|
193
|
+
put "/#{entity.id}", params.to_json
|
194
|
+
|
195
|
+
response = JSON.parse last_response.body
|
196
|
+
entity_to_hash = JSON.parse entity.values.to_json
|
197
|
+
expect(response).to eq entity_to_hash
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'DELETE /:id' do
|
202
|
+
let(:entity) { create(subject) }
|
203
|
+
|
204
|
+
it 'returns HTML when requested' do
|
205
|
+
header 'Accept', 'text/html'
|
206
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
207
|
+
|
208
|
+
delete "/#{entity.id}"
|
209
|
+
|
210
|
+
expect(last_response.headers).to include('Content-Type' => 'text/html;charset=utf-8')
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'returns a 302 Redirect response for a HTML Request' do
|
214
|
+
header 'Accept', 'text/html'
|
215
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
216
|
+
|
217
|
+
delete "/#{entity.id}"
|
218
|
+
|
219
|
+
expect(last_response.status).to eq 302
|
220
|
+
expect(last_response.headers).to include('Location')
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'returns JSON when requested' do
|
224
|
+
header 'Accept', 'application/json'
|
225
|
+
header 'Content-Type', 'application/json'
|
226
|
+
|
227
|
+
delete "/#{entity.id}"
|
228
|
+
|
229
|
+
expect(last_response.headers).to include('X-Content-Type-Options' => 'nosniff')
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'returns a 204 No Content response for a JSON Request' do
|
233
|
+
header 'Accept', 'application/json'
|
234
|
+
header 'Content-Type', 'application/json'
|
235
|
+
|
236
|
+
delete "/#{entity.id}"
|
237
|
+
|
238
|
+
expect(last_response.status).to eq 204
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'returns an empty body for a JSON Request' do
|
242
|
+
header 'Accept', 'application/json'
|
243
|
+
header 'Content-Type', 'application/json'
|
244
|
+
|
245
|
+
delete "/#{entity.id}"
|
246
|
+
|
247
|
+
expect(last_response.body).to eq ''
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
shared_examples 'a CRUD Controller' do |route|
|
4
|
+
context 'GET' do
|
5
|
+
it '/doesnotexist' do
|
6
|
+
get '/doesnotexist'
|
7
|
+
expect(last_response).not_to be_ok
|
8
|
+
expect(last_response.status).to eq 404
|
9
|
+
end
|
10
|
+
|
11
|
+
it route.to_s do
|
12
|
+
model # Ensure that there's at least one item in the list
|
13
|
+
get '/'
|
14
|
+
|
15
|
+
if Pundit.policy(user, app.model_class).list?
|
16
|
+
expect(last_response).to be_ok, "Expected OK response, got #{last_response.status}"
|
17
|
+
else
|
18
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "#{route}?count=1&page=1" do
|
23
|
+
model # Ensure that there's at least one item in the list
|
24
|
+
get '/?count=1&page=1'
|
25
|
+
|
26
|
+
if Pundit.policy(user, app.model_class).list?
|
27
|
+
expect(last_response).to be_ok, "Expected OK response, got #{last_response.status}"
|
28
|
+
else
|
29
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "#{route}/new" do
|
34
|
+
get '/new'
|
35
|
+
|
36
|
+
if Pundit.policy(user, app.model_class).create?
|
37
|
+
expect(last_response).to be_ok, "Expected OK response, got #{last_response.status}"
|
38
|
+
else
|
39
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "#{route}/id" do
|
44
|
+
get "/#{model.id}"
|
45
|
+
|
46
|
+
if Pundit.policy(user, model).read?
|
47
|
+
expect(last_response).to be_ok, "Expected OK response, got #{last_response.status}"
|
48
|
+
else
|
49
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "#{route}/id/edit" do
|
54
|
+
get "/#{model.id}/edit"
|
55
|
+
|
56
|
+
if Pundit.policy(user, model).update?
|
57
|
+
expect(last_response).to be_ok, "Expected OK response, got #{last_response.status}"
|
58
|
+
else
|
59
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'POST' do
|
65
|
+
it '/doesnotexist' do
|
66
|
+
header 'Accept', 'text/html'
|
67
|
+
post '/doesnotexist'
|
68
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
69
|
+
expect(last_response.status).to eq 404
|
70
|
+
end
|
71
|
+
|
72
|
+
it route.to_s do
|
73
|
+
header 'Accept', 'text/html'
|
74
|
+
post '/', create_data
|
75
|
+
|
76
|
+
if Pundit.policy(user, app.model_class).create?
|
77
|
+
expect(last_response.status).to eq 302
|
78
|
+
else
|
79
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "#{route} with invalid parameters" do
|
84
|
+
header 'Accept', 'text/html'
|
85
|
+
header 'Content-Type', 'application/x-www-form-urlencoded'
|
86
|
+
post '/', invalid_create_data
|
87
|
+
|
88
|
+
if Pundit.policy(user, app.model_class).create?
|
89
|
+
expect(last_response.status).to eq 400
|
90
|
+
else
|
91
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'PUT' do
|
97
|
+
it '/doesnotexist' do
|
98
|
+
header 'Accept', 'text/html'
|
99
|
+
put '/doesnotexist'
|
100
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
101
|
+
expect(last_response.status).to eq 404
|
102
|
+
end
|
103
|
+
|
104
|
+
it "#{route}/:id" do
|
105
|
+
header 'Accept', 'text/html'
|
106
|
+
put "/#{model.id}", update_data
|
107
|
+
|
108
|
+
if Pundit.policy(user, model).update?
|
109
|
+
expect(last_response.status).to eq 302
|
110
|
+
else
|
111
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "#{route} with invalid parameters" do
|
116
|
+
header 'Accept', 'text/html'
|
117
|
+
put "/#{model.id}", invalid_update_data
|
118
|
+
|
119
|
+
if Pundit.policy(user, model).update?
|
120
|
+
expect(last_response.status).to eq 400
|
121
|
+
else
|
122
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'DELETE' do
|
128
|
+
it '/doesnotexist' do
|
129
|
+
delete '/doesnotexist'
|
130
|
+
expect(last_response).not_to be_ok, "Expected a NOT OK response, got #{last_response.status}"
|
131
|
+
expect(last_response.status).to eq 404
|
132
|
+
end
|
133
|
+
|
134
|
+
it "#{route}/id" do
|
135
|
+
header 'Accept', 'text/html'
|
136
|
+
delete "/#{model.id}"
|
137
|
+
|
138
|
+
if Pundit.policy(user, model).delete?
|
139
|
+
expect(last_response.status).to eq 302
|
140
|
+
else
|
141
|
+
expect(last_response).not_to be_ok
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ditty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jurgens du Toit
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-03-
|
11
|
+
date: 2020-03-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -621,6 +621,25 @@ files:
|
|
621
621
|
- public/images/safari-pinned-tab.svg
|
622
622
|
- public/js/scripts.js
|
623
623
|
- public/manifest.json
|
624
|
+
- spec/ditty/api_spec.rb
|
625
|
+
- spec/ditty/controllers/roles_spec.rb
|
626
|
+
- spec/ditty/controllers/user_login_traits_spec.rb
|
627
|
+
- spec/ditty/controllers/users_spec.rb
|
628
|
+
- spec/ditty/emails/base_spec.rb
|
629
|
+
- spec/ditty/emails/forgot_password_spec.rb
|
630
|
+
- spec/ditty/helpers/component_spec.rb
|
631
|
+
- spec/ditty/models/user_spec.rb
|
632
|
+
- spec/ditty/services/email_spec.rb
|
633
|
+
- spec/ditty/services/logger_spec.rb
|
634
|
+
- spec/ditty/services/settings_spec.rb
|
635
|
+
- spec/ditty_spec.rb
|
636
|
+
- spec/factories.rb
|
637
|
+
- spec/fixtures/logger.yml
|
638
|
+
- spec/fixtures/section.yml
|
639
|
+
- spec/fixtures/settings.yml
|
640
|
+
- spec/spec_helper.rb
|
641
|
+
- spec/support/api_shared_examples.rb
|
642
|
+
- spec/support/crud_shared_examples.rb
|
624
643
|
- views/400.haml
|
625
644
|
- views/403.haml
|
626
645
|
- views/404.haml
|