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.
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