punk 0.0.3 → 0.2.0
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/.editorconfig +9 -0
- data/.github/workflows/ship.yml +28 -0
- data/.github/workflows/test.yml +45 -0
- data/.rdoc_options +23 -0
- data/.rgignore +1 -0
- data/.rspec +2 -0
- data/.rubocop.yml +243 -0
- data/Gemfile +6 -6
- data/Gemfile.lock +18 -30
- data/README.md +8 -0
- data/Rakefile +7 -9
- data/VERSION +1 -1
- data/app/migrations/001_lets_punk.rb +3 -0
- data/app/routes/hello.rb +4 -0
- data/bin/punk +0 -1
- data/env/.gitignore +3 -0
- data/env/spec/test.sh +3 -0
- data/env/test.sh +5 -0
- data/lib/punk/actions/.keep +0 -0
- data/lib/punk/actions/groups/list.rb +24 -0
- data/lib/punk/actions/sessions/clear.rb +21 -0
- data/lib/punk/actions/sessions/create.rb +64 -0
- data/lib/punk/actions/sessions/list.rb +18 -0
- data/lib/punk/actions/sessions/verify.rb +24 -0
- data/lib/punk/actions/tenants/list.rb +18 -0
- data/lib/punk/actions/users/list_group.rb +18 -0
- data/lib/punk/actions/users/list_tenant.rb +18 -0
- data/lib/punk/actions/users/show.rb +18 -0
- data/lib/punk/commands/http.rb +3 -3
- data/lib/punk/commands/list.rb +12 -6
- data/lib/punk/config/defaults.json +3 -0
- data/lib/punk/config/schema.json +3 -0
- data/lib/punk/core/app.rb +6 -8
- data/lib/punk/core/commander.rb +9 -6
- data/lib/punk/core/exec.rb +2 -0
- data/lib/punk/core/load.rb +0 -1
- data/lib/punk/framework/command.rb +5 -1
- data/lib/punk/framework/plugins/validation.rb +0 -14
- data/lib/punk/framework/runnable.rb +1 -1
- data/lib/punk/helpers/loggable.rb +1 -1
- data/lib/punk/migrations/001_punk.rb +103 -0
- data/lib/punk/models/.keep +0 -0
- data/lib/punk/models/group.rb +20 -0
- data/lib/punk/models/group_user_metadata.rb +17 -0
- data/lib/punk/models/identity.rb +29 -0
- data/lib/punk/models/session.rb +89 -0
- data/lib/punk/models/tenant.rb +19 -0
- data/lib/punk/models/tenant_user_metadata.rb +17 -0
- data/lib/punk/models/user.rb +31 -0
- data/lib/punk/routes/groups.rb +31 -0
- data/lib/punk/routes/plivo.rb +4 -0
- data/lib/punk/routes/sessions.rb +108 -0
- data/lib/punk/routes/swagger.rb +9 -0
- data/lib/punk/routes/tenants.rb +29 -0
- data/lib/punk/routes/users.rb +36 -0
- data/lib/punk/services/.keep +0 -0
- data/lib/punk/services/challenge_claim.rb +46 -0
- data/lib/punk/services/create_identities.rb +25 -0
- data/lib/punk/services/generate_swagger.rb +25 -0
- data/lib/punk/services/prove_claim.rb +29 -0
- data/lib/punk/services/secret.rb +9 -0
- data/lib/punk/templates/groups/list.jbuilder +7 -0
- data/lib/punk/templates/plivo.slim +16 -0
- data/lib/punk/templates/sessions/list.jbuilder +6 -0
- data/lib/punk/templates/sessions/pending.jbuilder +4 -0
- data/lib/punk/templates/tenants/list.jbuilder +7 -0
- data/lib/punk/templates/tenants/list.slim +8 -0
- data/lib/punk/templates/users/list.jbuilder +7 -0
- data/lib/punk/templates/users/list.rcsv +4 -0
- data/lib/punk/templates/users/show.jbuilder +5 -0
- data/lib/punk/views/groups/list.rb +22 -0
- data/lib/punk/views/plivo_store.rb +15 -0
- data/lib/punk/views/sessions/list.rb +22 -0
- data/lib/punk/views/sessions/pending.rb +28 -0
- data/lib/punk/views/tenants/list.rb +22 -0
- data/lib/punk/views/users/list.rb +22 -0
- data/lib/punk/views/users/show.rb +22 -0
- data/lib/punk/workers/.keep +0 -0
- data/lib/punk/workers/expire_sessions.rb +9 -0
- data/lib/punk/workers/geocode_session_worker.rb +48 -0
- data/lib/punk/workers/identify_session_worker.rb +45 -0
- data/lib/punk/workers/secret.rb +18 -0
- data/lib/punk/workers/send_email_worker.rb +51 -0
- data/lib/punk/workers/send_sms_worker.rb +40 -0
- data/punk.gemspec +149 -16
- data/schema.psql +345 -0
- data/spec/actions/groups/punk/list_groups_action_spec.rb +36 -0
- data/spec/actions/sessions/punk/clear_session_action_spec.rb +29 -0
- data/spec/actions/sessions/punk/create_session_action_spec.rb +33 -0
- data/spec/actions/sessions/punk/list_sessions_action_spec.rb +26 -0
- data/spec/actions/sessions/punk/verify_session_action_spec.rb +59 -0
- data/spec/actions/tenants/punk/list_tenants_action_spec.rb +25 -0
- data/spec/actions/users/punk/list_group_users_action_spec.rb +26 -0
- data/spec/actions/users/punk/list_tenant_users_action_spec.rb +26 -0
- data/spec/factories/group.rb +12 -0
- data/spec/factories/group_user_metadata.rb +10 -0
- data/spec/factories/identity.rb +19 -0
- data/spec/factories/session.rb +12 -0
- data/spec/factories/tenant.rb +10 -0
- data/spec/factories/tenant_user_metadata.rb +10 -0
- data/spec/factories/user.rb +12 -0
- data/spec/lib/commands/auth_spec.rb +11 -0
- data/spec/lib/commands/generate_spec.rb +7 -0
- data/spec/lib/commands/http_spec.rb +23 -0
- data/spec/lib/commands/list_spec.rb +7 -0
- data/spec/lib/commands/swagger_spec.rb +7 -0
- data/spec/lib/engine/punk_env_spec.rb +13 -0
- data/spec/lib/engine/punk_exec_spec.rb +9 -0
- data/spec/lib/engine/punk_init_spec.rb +9 -0
- data/spec/lib/engine/punk_store_spec.rb +10 -0
- data/spec/lib/punk.env +7 -0
- data/spec/models/punk/group_spec.rb +50 -0
- data/spec/models/punk/group_user_metadata_spec.rb +61 -0
- data/spec/models/punk/identity_spec.rb +61 -0
- data/spec/models/punk/session_spec.rb +156 -0
- data/spec/models/punk/tenant_spec.rb +51 -0
- data/spec/models/punk/tenant_user_metadata_spec.rb +61 -0
- data/spec/models/punk/user_spec.rb +115 -0
- data/spec/routes/groups/get_groups_spec.rb +33 -0
- data/spec/routes/plivo/get_plivo_spec.rb +11 -0
- data/spec/routes/sessions/delete_session_spec.rb +11 -0
- data/spec/routes/sessions/get_sessions_spec.rb +30 -0
- data/spec/routes/sessions/patch_session_spec.rb +11 -0
- data/spec/routes/sessions/post_session_spec.rb +11 -0
- data/spec/routes/swagger/get_swagger_spec.rb +12 -0
- data/spec/routes/tenants/get_tenants_spec.rb +31 -0
- data/spec/routes/users/get_users_spec.rb +60 -0
- data/spec/services/punk/challenge_claim_service_spec.rb +7 -0
- data/spec/services/punk/create_identities_service_spec.rb +14 -0
- data/spec/services/punk/generate_swagger_service_spec.rb +7 -0
- data/spec/services/punk/prove_claim_service_spec.rb +7 -0
- data/spec/services/punk/secret_service_spec.rb +7 -0
- data/spec/spec_helper.rb +122 -0
- data/spec/vcr_cassettes/PUNK_GeocodeSessionWorker/updates_the_session_data.yml +57 -0
- data/spec/vcr_cassettes/PUNK_IdentifySessionWorker/updates_the_session_data.yml +112 -0
- data/spec/views/punk/plivo_store_spec.rb +7 -0
- data/spec/views/sessions/punk/list_sessions_view_spec.rb +7 -0
- data/spec/views/sessions/punk/pending_session_view_spec.rb +7 -0
- data/spec/views/tenants/punk/list_tenants_view_spec.rb +7 -0
- data/spec/views/users/punk/list_groups_view_spec.rb +7 -0
- data/spec/views/users/punk/list_users_view_spec.rb +7 -0
- data/spec/workers/punk/expire_sessions_worker_spec.rb +31 -0
- data/spec/workers/punk/geocode_session_worker_spec.rb +14 -0
- data/spec/workers/punk/identify_session_worker_spec.rb +15 -0
- data/spec/workers/punk/secret_worker_spec.rb +20 -0
- data/spec/workers/punk/send_email_worker_spec.rb +46 -0
- data/spec/workers/punk/send_sms_worker_spec.rb +33 -0
- metadata +169 -13
- data/lib/punk/views/all.rb +0 -4
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK::VerifySessionAction do
|
|
4
|
+
context 'with no session provided' do
|
|
5
|
+
it 'returns a validation error' do
|
|
6
|
+
view = described_class.run.result.render(:json)
|
|
7
|
+
expect(view).to match('session is not present')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context 'with no secret provided' do
|
|
12
|
+
it 'returns a validation error' do
|
|
13
|
+
session = create(:session)
|
|
14
|
+
view = described_class.run(session: session).result.render(:json)
|
|
15
|
+
expect(view).to match('secret is not present')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'with an inactive session provided' do
|
|
20
|
+
it 'returns a validation error' do
|
|
21
|
+
session = create(:session)
|
|
22
|
+
view = described_class.run(session: session, secret: 'xyzzy').result.render(:json)
|
|
23
|
+
expect(view).to match('session is not in pending state')
|
|
24
|
+
expect(view).to match('session may not verify')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context 'with a pending session provided' do
|
|
29
|
+
let(:session) { create(:session) }
|
|
30
|
+
|
|
31
|
+
before do
|
|
32
|
+
allow_any_instance_of(PUNK::SecretService).to receive(:process).and_return("xyzzy") # rubocop:disable RSpec/AnyInstance
|
|
33
|
+
PUNK::ChallengeClaimService.run(session: session)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'returns an error if the secret is not correct' do
|
|
37
|
+
expect { described_class.run(session: session, secret: 'qwerty') }.to raise_error(PUNK::BadRequest, "Secret is incorrect")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'expires the session after three failed attempts' do
|
|
41
|
+
described_class.run(session: session, secret: 'qwerty') rescue nil # rubocop:disable Style/RescueModifier
|
|
42
|
+
described_class.run(session: session, secret: 'qwerty') rescue nil # rubocop:disable Style/RescueModifier
|
|
43
|
+
expect { described_class.run(session: session, secret: 'qwerty') }.to raise_error(PUNK::BadRequest, "Too many attempts")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'expires the session if it is more than five minutes old' do
|
|
47
|
+
Timecop.travel(5.minutes.from_now)
|
|
48
|
+
view = described_class.run(session: session, secret: 'xyzzy').result.render(:json)
|
|
49
|
+
expect(view).to match('session may not verify')
|
|
50
|
+
expect(session.expired?).to be(true)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'verifies the session if the secret is correct' do
|
|
54
|
+
view = described_class.run(session: session, secret: 'xyzzy').result.render(:json)
|
|
55
|
+
expect(view).to match('We have succesfully verified your identity')
|
|
56
|
+
expect(session.active?).to be(true)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK::ListTenantsAction do
|
|
4
|
+
context 'with no user provided' do
|
|
5
|
+
it 'returns a validation error' do
|
|
6
|
+
view = described_class.run.result.render(:json)
|
|
7
|
+
expect(view).to match('user is not present')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context 'with a user provided' do
|
|
12
|
+
let(:user) { create(:user) }
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
create_list(:tenant, 3)
|
|
16
|
+
3.times { create(:tenant).add_user(user) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'returns tenants that the user belongs to' do
|
|
20
|
+
expect(PUNK::Tenant.count).to eq(6)
|
|
21
|
+
view = JSON.parse(described_class.run(user: user).result.render(:json))
|
|
22
|
+
expect(view.map { |h| h['id'] }.sort).to eq(user.tenants.map(&:id).sort)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK::ListGroupUsersAction do
|
|
4
|
+
context 'with no group provided' do
|
|
5
|
+
it 'returns a validation error' do
|
|
6
|
+
view = described_class.run.result.render(:json)
|
|
7
|
+
expect(view).to match('group is not present')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context 'with valid arguments' do
|
|
12
|
+
let(:group) { create(:group) }
|
|
13
|
+
let(:users) { create_list(:user, 3) }
|
|
14
|
+
|
|
15
|
+
before do
|
|
16
|
+
create_list(:user, 2)
|
|
17
|
+
users.each { |user| group.add_user(user) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'returns users that are members of the given group' do
|
|
21
|
+
expect(PUNK::User.count).to eq(5)
|
|
22
|
+
view = JSON.parse(described_class.run(group: group).result.render(:json))
|
|
23
|
+
expect(view.map { |h| h['id'] }.sort).to eq(group.users.map(&:id).sort)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK::ListTenantUsersAction do
|
|
4
|
+
context 'with no tenant provided' do
|
|
5
|
+
it 'returns a validation error' do
|
|
6
|
+
view = described_class.run.result.render(:json)
|
|
7
|
+
expect(view).to match('tenant is not present')
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context 'with valid arguments' do
|
|
12
|
+
let(:tenant) { create(:tenant) }
|
|
13
|
+
let(:users) { create_list(:user, 3) }
|
|
14
|
+
|
|
15
|
+
before do
|
|
16
|
+
create_list(:user, 2)
|
|
17
|
+
users.each { |user| tenant.add_user(user) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'returns users that are members of the given tenant' do
|
|
21
|
+
expect(PUNK::User.count).to eq(5)
|
|
22
|
+
view = JSON.parse(described_class.run(tenant: tenant).result.render(:json))
|
|
23
|
+
expect(view.map { |h| h['id'] }.sort).to eq(tenant.users.map(&:id).sort)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
FactoryBot.define do
|
|
4
|
+
factory :identity, class: 'PUNK::Identity' do
|
|
5
|
+
to_create(&:save)
|
|
6
|
+
|
|
7
|
+
user
|
|
8
|
+
|
|
9
|
+
claim_type { ['email', 'phone'].sample }
|
|
10
|
+
claim do
|
|
11
|
+
case claim_type
|
|
12
|
+
when 'email'
|
|
13
|
+
Faker::Internet.email
|
|
14
|
+
when 'phone'
|
|
15
|
+
generate(:phone)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# describe 'auth', type: :feature do
|
|
4
|
+
# describe 'login command', type: :feature do
|
|
5
|
+
# it 'displays "not yet implemented"'
|
|
6
|
+
# end
|
|
7
|
+
|
|
8
|
+
# describe 'logout command', type: :feature do
|
|
9
|
+
# it 'displays "not yet implemented"'
|
|
10
|
+
# end
|
|
11
|
+
# end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# describe 'http', type: :feature do
|
|
4
|
+
# describe 'GET command', type: :feature do
|
|
5
|
+
# it 'displays "not yet implemented"'
|
|
6
|
+
# end
|
|
7
|
+
|
|
8
|
+
# describe 'PATCH command', type: :feature do
|
|
9
|
+
# it 'displays "not yet implemented"'
|
|
10
|
+
# end
|
|
11
|
+
|
|
12
|
+
# describe 'POST command', type: :feature do
|
|
13
|
+
# it 'displays "not yet implemented"'
|
|
14
|
+
# end
|
|
15
|
+
|
|
16
|
+
# describe 'PUT command', type: :feature do
|
|
17
|
+
# it 'displays "not yet implemented"'
|
|
18
|
+
# end
|
|
19
|
+
|
|
20
|
+
# describe 'DELETE command', type: :feature do
|
|
21
|
+
# it 'displays "not yet implemented"'
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK do
|
|
4
|
+
describe '.env' do
|
|
5
|
+
it 'loads environment' do
|
|
6
|
+
expect(described_class.get.trace).to eq('spec_test')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'loads configuration' do
|
|
10
|
+
expect(described_class.env.to_sym).to eq(:test)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/spec/lib/punk.env
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK::Group do
|
|
4
|
+
it "is valid with valid attributes" do
|
|
5
|
+
expect { create(:group) }.not_to raise_error
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "is assigned a uuid on save" do
|
|
9
|
+
group = build(:group)
|
|
10
|
+
expect(group.id).to be_nil
|
|
11
|
+
group.save
|
|
12
|
+
expect(valid_uuid?(group.id)).to be(true)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "can be saved with a custom uuid" do
|
|
16
|
+
uuid = generate(:uuid)
|
|
17
|
+
group = create(:group, id: uuid)
|
|
18
|
+
expect(group.id).to eq(uuid)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "is invalid without a name" do
|
|
22
|
+
group = build(:group, name: nil)
|
|
23
|
+
expect(group.valid?).to be(false)
|
|
24
|
+
expect(group.errors[:name].first).to eq('is not present')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "is valid without an icon" do
|
|
28
|
+
group = build(:group, icon: nil)
|
|
29
|
+
expect(group.valid?).to be(true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "is invalid if the icon is not a URL" do
|
|
33
|
+
group = build(:group, icon: Faker::Alphanumeric.alpha)
|
|
34
|
+
expect(group.valid?).to be(false)
|
|
35
|
+
expect(group.errors[:icon].first).to eq('is not a URL')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "must belong to a tenant" do
|
|
39
|
+
group = build(:group, tenant: nil)
|
|
40
|
+
expect(group.valid?).to be(false)
|
|
41
|
+
expect(group.errors[:tenant].first).to eq('is not present')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "can have multiple members" do
|
|
45
|
+
group = create(:group)
|
|
46
|
+
expect(group.users.count).to eq(0)
|
|
47
|
+
3.times { create(:user).add_group(group) }
|
|
48
|
+
expect(group.users.count).to eq(3)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PUNK::GroupUserMetadata do
|
|
4
|
+
it "is valid with valid attributes" do
|
|
5
|
+
expect { create(:group_user_metadata) }.not_to raise_error
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "is invalid without a group" do
|
|
9
|
+
group_user_metadata = build(:group_user_metadata, group: nil)
|
|
10
|
+
expect(group_user_metadata.valid?).to be(false)
|
|
11
|
+
expect(group_user_metadata.errors[:group].first).to eq('is not present')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "is invalid without a user" do
|
|
15
|
+
group_user_metadata = build(:group_user_metadata, user: nil)
|
|
16
|
+
expect(group_user_metadata.valid?).to be(false)
|
|
17
|
+
expect(group_user_metadata.errors[:user].first).to eq('is not present')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "displays as the two IDs concatenated" do
|
|
21
|
+
group_user_metadata = create(:group_user_metadata)
|
|
22
|
+
expect(group_user_metadata.to_s).to include(group_user_metadata.group.id)
|
|
23
|
+
expect(group_user_metadata.to_s).to include(group_user_metadata.user.id)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "when a user and a group exist" do
|
|
27
|
+
let(:user) { create(:user) }
|
|
28
|
+
let(:group) { create(:group) }
|
|
29
|
+
|
|
30
|
+
context "when a user is added to a group" do
|
|
31
|
+
let(:group_user_metadata) do
|
|
32
|
+
described_class[group: group, user: user]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
before do
|
|
36
|
+
group.add_user(user)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "is created automatically" do
|
|
40
|
+
expect(group_user_metadata).not_to be_nil
|
|
41
|
+
expect(group.users).to include(user)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "destroying it will remove the user from the group" do
|
|
45
|
+
expect(group.users).to include(user)
|
|
46
|
+
group_user_metadata.destroy
|
|
47
|
+
group.reload
|
|
48
|
+
expect(group.users).not_to include(user)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "when it is created" do
|
|
53
|
+
it "adds a user to a group" do
|
|
54
|
+
expect(group.users).not_to include(user)
|
|
55
|
+
create(:group_user_metadata, group: group, user: user)
|
|
56
|
+
group.reload
|
|
57
|
+
expect(group.users).to include(user)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|