punk 0.0.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/workflows/ship.yml +28 -0
  4. data/.github/workflows/test.yml +45 -0
  5. data/.rdoc_options +23 -0
  6. data/.rgignore +1 -0
  7. data/.rspec +2 -0
  8. data/.rubocop.yml +243 -0
  9. data/Gemfile +6 -6
  10. data/Gemfile.lock +18 -30
  11. data/README.md +8 -0
  12. data/Rakefile +7 -9
  13. data/VERSION +1 -1
  14. data/app/migrations/001_lets_punk.rb +3 -0
  15. data/app/routes/hello.rb +4 -0
  16. data/bin/punk +0 -1
  17. data/env/.gitignore +3 -0
  18. data/env/spec/test.sh +3 -0
  19. data/env/test.sh +5 -0
  20. data/lib/punk/actions/.keep +0 -0
  21. data/lib/punk/actions/groups/list.rb +24 -0
  22. data/lib/punk/actions/sessions/clear.rb +21 -0
  23. data/lib/punk/actions/sessions/create.rb +64 -0
  24. data/lib/punk/actions/sessions/list.rb +18 -0
  25. data/lib/punk/actions/sessions/verify.rb +24 -0
  26. data/lib/punk/actions/tenants/list.rb +18 -0
  27. data/lib/punk/actions/users/list_group.rb +18 -0
  28. data/lib/punk/actions/users/list_tenant.rb +18 -0
  29. data/lib/punk/actions/users/show.rb +18 -0
  30. data/lib/punk/commands/http.rb +3 -3
  31. data/lib/punk/commands/list.rb +12 -6
  32. data/lib/punk/config/defaults.json +3 -0
  33. data/lib/punk/config/schema.json +3 -0
  34. data/lib/punk/core/app.rb +6 -8
  35. data/lib/punk/core/commander.rb +9 -6
  36. data/lib/punk/core/exec.rb +2 -0
  37. data/lib/punk/core/load.rb +0 -1
  38. data/lib/punk/framework/command.rb +5 -1
  39. data/lib/punk/framework/plugins/validation.rb +0 -14
  40. data/lib/punk/framework/runnable.rb +1 -1
  41. data/lib/punk/helpers/loggable.rb +1 -1
  42. data/lib/punk/migrations/001_punk.rb +103 -0
  43. data/lib/punk/models/.keep +0 -0
  44. data/lib/punk/models/group.rb +20 -0
  45. data/lib/punk/models/group_user_metadata.rb +17 -0
  46. data/lib/punk/models/identity.rb +29 -0
  47. data/lib/punk/models/session.rb +89 -0
  48. data/lib/punk/models/tenant.rb +19 -0
  49. data/lib/punk/models/tenant_user_metadata.rb +17 -0
  50. data/lib/punk/models/user.rb +31 -0
  51. data/lib/punk/routes/groups.rb +31 -0
  52. data/lib/punk/routes/plivo.rb +4 -0
  53. data/lib/punk/routes/sessions.rb +108 -0
  54. data/lib/punk/routes/swagger.rb +9 -0
  55. data/lib/punk/routes/tenants.rb +29 -0
  56. data/lib/punk/routes/users.rb +36 -0
  57. data/lib/punk/services/.keep +0 -0
  58. data/lib/punk/services/challenge_claim.rb +46 -0
  59. data/lib/punk/services/create_identities.rb +25 -0
  60. data/lib/punk/services/generate_swagger.rb +25 -0
  61. data/lib/punk/services/prove_claim.rb +29 -0
  62. data/lib/punk/services/secret.rb +9 -0
  63. data/lib/punk/templates/groups/list.jbuilder +7 -0
  64. data/lib/punk/templates/plivo.slim +16 -0
  65. data/lib/punk/templates/sessions/list.jbuilder +6 -0
  66. data/lib/punk/templates/sessions/pending.jbuilder +4 -0
  67. data/lib/punk/templates/tenants/list.jbuilder +7 -0
  68. data/lib/punk/templates/tenants/list.slim +8 -0
  69. data/lib/punk/templates/users/list.jbuilder +7 -0
  70. data/lib/punk/templates/users/list.rcsv +4 -0
  71. data/lib/punk/templates/users/show.jbuilder +5 -0
  72. data/lib/punk/views/groups/list.rb +22 -0
  73. data/lib/punk/views/plivo_store.rb +15 -0
  74. data/lib/punk/views/sessions/list.rb +22 -0
  75. data/lib/punk/views/sessions/pending.rb +28 -0
  76. data/lib/punk/views/tenants/list.rb +22 -0
  77. data/lib/punk/views/users/list.rb +22 -0
  78. data/lib/punk/views/users/show.rb +22 -0
  79. data/lib/punk/workers/.keep +0 -0
  80. data/lib/punk/workers/expire_sessions.rb +9 -0
  81. data/lib/punk/workers/geocode_session_worker.rb +48 -0
  82. data/lib/punk/workers/identify_session_worker.rb +45 -0
  83. data/lib/punk/workers/secret.rb +18 -0
  84. data/lib/punk/workers/send_email_worker.rb +51 -0
  85. data/lib/punk/workers/send_sms_worker.rb +40 -0
  86. data/punk.gemspec +149 -16
  87. data/schema.psql +345 -0
  88. data/spec/actions/groups/punk/list_groups_action_spec.rb +36 -0
  89. data/spec/actions/sessions/punk/clear_session_action_spec.rb +29 -0
  90. data/spec/actions/sessions/punk/create_session_action_spec.rb +33 -0
  91. data/spec/actions/sessions/punk/list_sessions_action_spec.rb +26 -0
  92. data/spec/actions/sessions/punk/verify_session_action_spec.rb +59 -0
  93. data/spec/actions/tenants/punk/list_tenants_action_spec.rb +25 -0
  94. data/spec/actions/users/punk/list_group_users_action_spec.rb +26 -0
  95. data/spec/actions/users/punk/list_tenant_users_action_spec.rb +26 -0
  96. data/spec/factories/group.rb +12 -0
  97. data/spec/factories/group_user_metadata.rb +10 -0
  98. data/spec/factories/identity.rb +19 -0
  99. data/spec/factories/session.rb +12 -0
  100. data/spec/factories/tenant.rb +10 -0
  101. data/spec/factories/tenant_user_metadata.rb +10 -0
  102. data/spec/factories/user.rb +12 -0
  103. data/spec/lib/commands/auth_spec.rb +11 -0
  104. data/spec/lib/commands/generate_spec.rb +7 -0
  105. data/spec/lib/commands/http_spec.rb +23 -0
  106. data/spec/lib/commands/list_spec.rb +7 -0
  107. data/spec/lib/commands/swagger_spec.rb +7 -0
  108. data/spec/lib/engine/punk_env_spec.rb +13 -0
  109. data/spec/lib/engine/punk_exec_spec.rb +9 -0
  110. data/spec/lib/engine/punk_init_spec.rb +9 -0
  111. data/spec/lib/engine/punk_store_spec.rb +10 -0
  112. data/spec/lib/punk.env +7 -0
  113. data/spec/models/punk/group_spec.rb +50 -0
  114. data/spec/models/punk/group_user_metadata_spec.rb +61 -0
  115. data/spec/models/punk/identity_spec.rb +61 -0
  116. data/spec/models/punk/session_spec.rb +156 -0
  117. data/spec/models/punk/tenant_spec.rb +51 -0
  118. data/spec/models/punk/tenant_user_metadata_spec.rb +61 -0
  119. data/spec/models/punk/user_spec.rb +115 -0
  120. data/spec/routes/groups/get_groups_spec.rb +33 -0
  121. data/spec/routes/plivo/get_plivo_spec.rb +11 -0
  122. data/spec/routes/sessions/delete_session_spec.rb +11 -0
  123. data/spec/routes/sessions/get_sessions_spec.rb +30 -0
  124. data/spec/routes/sessions/patch_session_spec.rb +11 -0
  125. data/spec/routes/sessions/post_session_spec.rb +11 -0
  126. data/spec/routes/swagger/get_swagger_spec.rb +12 -0
  127. data/spec/routes/tenants/get_tenants_spec.rb +31 -0
  128. data/spec/routes/users/get_users_spec.rb +60 -0
  129. data/spec/services/punk/challenge_claim_service_spec.rb +7 -0
  130. data/spec/services/punk/create_identities_service_spec.rb +14 -0
  131. data/spec/services/punk/generate_swagger_service_spec.rb +7 -0
  132. data/spec/services/punk/prove_claim_service_spec.rb +7 -0
  133. data/spec/services/punk/secret_service_spec.rb +7 -0
  134. data/spec/spec_helper.rb +122 -0
  135. data/spec/vcr_cassettes/PUNK_GeocodeSessionWorker/updates_the_session_data.yml +57 -0
  136. data/spec/vcr_cassettes/PUNK_IdentifySessionWorker/updates_the_session_data.yml +112 -0
  137. data/spec/views/punk/plivo_store_spec.rb +7 -0
  138. data/spec/views/sessions/punk/list_sessions_view_spec.rb +7 -0
  139. data/spec/views/sessions/punk/pending_session_view_spec.rb +7 -0
  140. data/spec/views/tenants/punk/list_tenants_view_spec.rb +7 -0
  141. data/spec/views/users/punk/list_groups_view_spec.rb +7 -0
  142. data/spec/views/users/punk/list_users_view_spec.rb +7 -0
  143. data/spec/workers/punk/expire_sessions_worker_spec.rb +31 -0
  144. data/spec/workers/punk/geocode_session_worker_spec.rb +14 -0
  145. data/spec/workers/punk/identify_session_worker_spec.rb +15 -0
  146. data/spec/workers/punk/secret_worker_spec.rb +20 -0
  147. data/spec/workers/punk/send_email_worker_spec.rb +46 -0
  148. data/spec/workers/punk/send_sms_worker_spec.rb +33 -0
  149. metadata +169 -13
  150. 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :group, class: 'PUNK::Group' do
5
+ to_create(&:save)
6
+
7
+ tenant
8
+
9
+ name { Faker::Name.name }
10
+ icon { Faker::Internet.url }
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :group_user_metadata, class: 'PUNK::GroupUserMetadata' do
5
+ to_create(&:save)
6
+
7
+ group
8
+ user
9
+ end
10
+ 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :session, class: 'PUNK::Session' do
5
+ to_create(&:save)
6
+
7
+ identity
8
+
9
+ remote_addr { Faker::Internet.ip_v4_address }
10
+ user_agent { Faker::Internet.user_agent }
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :tenant, class: 'PUNK::Tenant' do
5
+ to_create(&:save)
6
+
7
+ name { Faker::Name.name }
8
+ icon { Faker::Internet.url }
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :tenant_user_metadata, class: 'PUNK::TenantUserMetadata' do
5
+ to_create(&:save)
6
+
7
+ tenant
8
+ user
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :user, class: 'PUNK::User' do
5
+ to_create(&:save)
6
+
7
+ name { Faker::Name.name }
8
+ icon { Faker::Internet.url }
9
+ email { Faker::Internet.email }
10
+ phone { generate(:phone) }
11
+ end
12
+ 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe 'generate command', type: :feature do
4
+ context 'when no tests have been written' do
5
+ it 'displays "not yet implemented"'
6
+ end
7
+ 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe 'list command', type: :feature do
4
+ context 'when no tests have been written' do
5
+ it 'displays "not yet implemented"'
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe 'swagger command', type: :feature do
4
+ context 'when no tests have been written' do
5
+ it 'displays "not yet implemented"'
6
+ end
7
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK do
4
+ describe '.exec' do
5
+ it 'starts the PUNK engine' do
6
+ expect(described_class.store.state).to eq(:started)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK do
4
+ describe '.init' do
5
+ it 'cannot be called twice' do
6
+ expect { described_class.init }.to raise_error(PUNK::InternalServerError, "Cannot call PUNK.init multiple times!")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK do
4
+ describe '.store' do
5
+ it 'acts like a dotted hash' do
6
+ described_class.store["foo"] = "bar"
7
+ expect(described_class.store.foo).to eq("bar")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ describe PUNK do
2
+ context 'env' do
3
+ it 'is populated' do
4
+ puts PUNK.get.log.to_h
5
+ end
6
+ end
7
+ end
@@ -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