punk 0.1.4 → 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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/workflows/test.yml +26 -1
  4. data/.rdoc_options +23 -0
  5. data/.rgignore +1 -0
  6. data/.rspec +2 -0
  7. data/Gemfile +5 -6
  8. data/Gemfile.lock +16 -29
  9. data/README.md +1 -1
  10. data/VERSION +1 -1
  11. data/app/migrations/001_lets_punk.rb +3 -0
  12. data/app/routes/hello.rb +4 -0
  13. data/env/.gitignore +3 -0
  14. data/env/spec/test.sh +3 -0
  15. data/lib/punk/actions/.keep +0 -0
  16. data/lib/punk/actions/groups/list.rb +24 -0
  17. data/lib/punk/actions/sessions/clear.rb +21 -0
  18. data/lib/punk/actions/sessions/create.rb +64 -0
  19. data/lib/punk/actions/sessions/list.rb +18 -0
  20. data/lib/punk/actions/sessions/verify.rb +24 -0
  21. data/lib/punk/actions/tenants/list.rb +18 -0
  22. data/lib/punk/actions/users/list_group.rb +18 -0
  23. data/lib/punk/actions/users/list_tenant.rb +18 -0
  24. data/lib/punk/actions/users/show.rb +18 -0
  25. data/lib/punk/commands/list.rb +12 -6
  26. data/lib/punk/config/defaults.json +3 -0
  27. data/lib/punk/config/schema.json +3 -0
  28. data/lib/punk/core/app.rb +4 -6
  29. data/lib/punk/core/commander.rb +7 -4
  30. data/lib/punk/core/exec.rb +2 -0
  31. data/lib/punk/core/load.rb +0 -1
  32. data/lib/punk/framework/command.rb +5 -1
  33. data/lib/punk/framework/plugins/validation.rb +0 -14
  34. data/lib/punk/helpers/loggable.rb +1 -1
  35. data/lib/punk/migrations/001_punk.rb +103 -0
  36. data/lib/punk/models/.keep +0 -0
  37. data/lib/punk/models/group.rb +20 -0
  38. data/lib/punk/models/group_user_metadata.rb +17 -0
  39. data/lib/punk/models/identity.rb +29 -0
  40. data/lib/punk/models/session.rb +89 -0
  41. data/lib/punk/models/tenant.rb +19 -0
  42. data/lib/punk/models/tenant_user_metadata.rb +17 -0
  43. data/lib/punk/models/user.rb +31 -0
  44. data/lib/punk/routes/groups.rb +31 -0
  45. data/lib/punk/routes/plivo.rb +4 -0
  46. data/lib/punk/routes/sessions.rb +108 -0
  47. data/lib/punk/routes/swagger.rb +9 -0
  48. data/lib/punk/routes/tenants.rb +29 -0
  49. data/lib/punk/routes/users.rb +36 -0
  50. data/lib/punk/services/.keep +0 -0
  51. data/lib/punk/services/challenge_claim.rb +46 -0
  52. data/lib/punk/services/create_identities.rb +25 -0
  53. data/lib/punk/services/generate_swagger.rb +25 -0
  54. data/lib/punk/services/prove_claim.rb +29 -0
  55. data/lib/punk/services/secret.rb +9 -0
  56. data/lib/punk/templates/groups/list.jbuilder +7 -0
  57. data/lib/punk/templates/plivo.slim +16 -0
  58. data/lib/punk/templates/sessions/list.jbuilder +6 -0
  59. data/lib/punk/templates/sessions/pending.jbuilder +4 -0
  60. data/lib/punk/templates/tenants/list.jbuilder +7 -0
  61. data/lib/punk/templates/tenants/list.slim +8 -0
  62. data/lib/punk/templates/users/list.jbuilder +7 -0
  63. data/lib/punk/templates/users/list.rcsv +4 -0
  64. data/lib/punk/templates/users/show.jbuilder +5 -0
  65. data/lib/punk/views/groups/list.rb +22 -0
  66. data/lib/punk/views/plivo_store.rb +15 -0
  67. data/lib/punk/views/sessions/list.rb +22 -0
  68. data/lib/punk/views/sessions/pending.rb +28 -0
  69. data/lib/punk/views/tenants/list.rb +22 -0
  70. data/lib/punk/views/users/list.rb +22 -0
  71. data/lib/punk/views/users/show.rb +22 -0
  72. data/lib/punk/workers/.keep +0 -0
  73. data/lib/punk/workers/expire_sessions.rb +9 -0
  74. data/lib/punk/workers/geocode_session_worker.rb +48 -0
  75. data/lib/punk/workers/identify_session_worker.rb +45 -0
  76. data/lib/punk/workers/secret.rb +18 -0
  77. data/lib/punk/workers/send_email_worker.rb +51 -0
  78. data/lib/punk/workers/send_sms_worker.rb +40 -0
  79. data/punk.gemspec +140 -14
  80. data/schema.psql +345 -0
  81. data/spec/actions/groups/punk/list_groups_action_spec.rb +36 -0
  82. data/spec/actions/sessions/punk/clear_session_action_spec.rb +29 -0
  83. data/spec/actions/sessions/punk/create_session_action_spec.rb +33 -0
  84. data/spec/actions/sessions/punk/list_sessions_action_spec.rb +26 -0
  85. data/spec/actions/sessions/punk/verify_session_action_spec.rb +59 -0
  86. data/spec/actions/tenants/punk/list_tenants_action_spec.rb +25 -0
  87. data/spec/actions/users/punk/list_group_users_action_spec.rb +26 -0
  88. data/spec/actions/users/punk/list_tenant_users_action_spec.rb +26 -0
  89. data/spec/factories/group.rb +12 -0
  90. data/spec/factories/group_user_metadata.rb +10 -0
  91. data/spec/factories/identity.rb +19 -0
  92. data/spec/factories/session.rb +12 -0
  93. data/spec/factories/tenant.rb +10 -0
  94. data/spec/factories/tenant_user_metadata.rb +10 -0
  95. data/spec/factories/user.rb +12 -0
  96. data/spec/lib/commands/auth_spec.rb +11 -0
  97. data/spec/lib/commands/generate_spec.rb +7 -0
  98. data/spec/lib/commands/http_spec.rb +23 -0
  99. data/spec/lib/commands/list_spec.rb +7 -0
  100. data/spec/lib/commands/swagger_spec.rb +7 -0
  101. data/spec/lib/engine/punk_env_spec.rb +13 -0
  102. data/spec/lib/engine/punk_exec_spec.rb +9 -0
  103. data/spec/lib/engine/punk_init_spec.rb +9 -0
  104. data/spec/lib/engine/punk_store_spec.rb +10 -0
  105. data/spec/lib/punk.env +7 -0
  106. data/spec/models/punk/group_spec.rb +50 -0
  107. data/spec/models/punk/group_user_metadata_spec.rb +61 -0
  108. data/spec/models/punk/identity_spec.rb +61 -0
  109. data/spec/models/punk/session_spec.rb +156 -0
  110. data/spec/models/punk/tenant_spec.rb +51 -0
  111. data/spec/models/punk/tenant_user_metadata_spec.rb +61 -0
  112. data/spec/models/punk/user_spec.rb +115 -0
  113. data/spec/routes/groups/get_groups_spec.rb +33 -0
  114. data/spec/routes/plivo/get_plivo_spec.rb +11 -0
  115. data/spec/routes/sessions/delete_session_spec.rb +11 -0
  116. data/spec/routes/sessions/get_sessions_spec.rb +30 -0
  117. data/spec/routes/sessions/patch_session_spec.rb +11 -0
  118. data/spec/routes/sessions/post_session_spec.rb +11 -0
  119. data/spec/routes/swagger/get_swagger_spec.rb +12 -0
  120. data/spec/routes/tenants/get_tenants_spec.rb +31 -0
  121. data/spec/routes/users/get_users_spec.rb +60 -0
  122. data/spec/services/punk/challenge_claim_service_spec.rb +7 -0
  123. data/spec/services/punk/create_identities_service_spec.rb +14 -0
  124. data/spec/services/punk/generate_swagger_service_spec.rb +7 -0
  125. data/spec/services/punk/prove_claim_service_spec.rb +7 -0
  126. data/spec/services/punk/secret_service_spec.rb +7 -0
  127. data/spec/spec_helper.rb +122 -0
  128. data/spec/vcr_cassettes/PUNK_GeocodeSessionWorker/updates_the_session_data.yml +57 -0
  129. data/spec/vcr_cassettes/PUNK_IdentifySessionWorker/updates_the_session_data.yml +112 -0
  130. data/spec/views/punk/plivo_store_spec.rb +7 -0
  131. data/spec/views/sessions/punk/list_sessions_view_spec.rb +7 -0
  132. data/spec/views/sessions/punk/pending_session_view_spec.rb +7 -0
  133. data/spec/views/tenants/punk/list_tenants_view_spec.rb +7 -0
  134. data/spec/views/users/punk/list_groups_view_spec.rb +7 -0
  135. data/spec/views/users/punk/list_users_view_spec.rb +7 -0
  136. data/spec/workers/punk/expire_sessions_worker_spec.rb +31 -0
  137. data/spec/workers/punk/geocode_session_worker_spec.rb +14 -0
  138. data/spec/workers/punk/identify_session_worker_spec.rb +15 -0
  139. data/spec/workers/punk/secret_worker_spec.rb +20 -0
  140. data/spec/workers/punk/send_email_worker_spec.rb +46 -0
  141. data/spec/workers/punk/send_sms_worker_spec.rb +33 -0
  142. metadata +148 -11
  143. data/lib/punk/views/all.rb +0 -4
@@ -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
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK::Identity do
4
+ it 'is valid with valid attributes' do
5
+ expect { create(:identity) }.not_to raise_error
6
+ end
7
+
8
+ it 'must claim an email or a phone' do
9
+ identity = build(:identity, claim_type: 'zork', claim: 'xyzzy')
10
+ expect(identity.valid?).to be(false)
11
+ expect(identity.errors[:claim_type].first).to eq('is not in range or set: [:email, :phone]')
12
+ end
13
+
14
+ it 'is assigned a uuid on save' do
15
+ identity = build(:identity)
16
+ expect(identity.id).to be_nil
17
+ identity.save
18
+ expect(valid_uuid?(identity.id)).to be(true)
19
+ end
20
+
21
+ it 'can be saved with a custom uuid' do
22
+ uuid = generate(:uuid)
23
+ identity = create(:identity, id: uuid)
24
+ expect(identity.id).to eq(uuid)
25
+ end
26
+
27
+ it 'may belong to a user' do
28
+ identity_with_user = create(:identity)
29
+ expect(identity_with_user.user).to exist
30
+ identity_without_user = create(:identity, user: nil)
31
+ expect(identity_without_user.user).to be_nil
32
+ end
33
+
34
+ it 'has an email? accessor' do
35
+ identity_with_email = build(:identity, claim_type: 'email')
36
+ expect(identity_with_email.email?).to be(true)
37
+ identity_without_email = build(:identity, claim_type: 'phone')
38
+ expect(identity_without_email.email?).to be(false)
39
+ end
40
+
41
+ it 'has a phone? accessor' do
42
+ identity_with_phone = create(:identity, claim_type: 'phone')
43
+ expect(identity_with_phone.phone?).to be(true)
44
+ identity_without_phone = create(:identity, claim_type: 'email')
45
+ expect(identity_without_phone.phone?).to be(false)
46
+ end
47
+
48
+ it 'has a unique claim' do
49
+ identity = create(:identity)
50
+ duplicate_identity = build(:identity, claim_type: identity.claim_type, claim: identity.claim)
51
+ expect(duplicate_identity.valid?).to be(false)
52
+ expect(duplicate_identity.errors[:claim].first).to eq('is already taken')
53
+ end
54
+
55
+ it 'can have multiple sessions' do
56
+ identity = create(:identity)
57
+ expect(identity.sessions.count).to eq(0)
58
+ create_list(:session, 3, identity: identity)
59
+ expect(identity.sessions.count).to eq(3)
60
+ end
61
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK::Session do
4
+ it "is valid with valid attributes" do
5
+ expect { create(:session) }.not_to raise_error
6
+ end
7
+
8
+ it "is assigned a uuid on save" do
9
+ session = build(:session)
10
+ expect(session.id).to be_nil
11
+ session.save
12
+ expect(valid_uuid?(session.id)).to be(true)
13
+ end
14
+
15
+ it "can be saved with a custom uuid" do
16
+ uuid = generate(:uuid)
17
+ session = create(:session, id: uuid)
18
+ expect(session.id).to eq(uuid)
19
+ end
20
+
21
+ it "must belong to an identity" do
22
+ session = build(:session, identity: nil)
23
+ expect(session.valid?).to be(false)
24
+ expect(session.errors[:identity].first).to eq('is not present')
25
+ end
26
+
27
+ it "may have a user" do
28
+ session = build(:session)
29
+ expect(session.user).to exist
30
+ session = build(:session, identity: create(:identity, user: nil))
31
+ expect(session.user).to be_nil
32
+ end
33
+
34
+ it "can contain client data" do
35
+ identity = create(:session, data: { foo: 'bar' })
36
+ expect(identity.data[:foo]).to eq('bar')
37
+ end
38
+
39
+ it "permits only three validation attempts" do
40
+ session = create(:session, attempt_count: 3)
41
+ expect(session.valid?).to be(true)
42
+ expect { session.increment_attempts }.to raise_error(Sequel::ValidationFailed, "attempt_count is not in range or set: [0, 1, 2, 3]")
43
+ end
44
+
45
+ it "starts in the created state" do
46
+ session = create(:session)
47
+ expect(session.created?).to be(true)
48
+ end
49
+
50
+ it "can be challenged" do
51
+ session = create(:session)
52
+ expect(session.may_challenge?).to be(true)
53
+ session.challenge!
54
+ expect(session.pending?).to be(true)
55
+ end
56
+
57
+ it "can be verified" do
58
+ session = create(:session)
59
+ session.challenge!
60
+ expect(session.may_verify?).to be(true)
61
+ session.verify!
62
+ expect(session.active?).to be(true)
63
+ end
64
+
65
+ it "will not timeout when first created" do
66
+ session = create(:session)
67
+ session.timeout?
68
+ expect(session.created?).to be(true)
69
+ end
70
+
71
+ it "will timeout after 5 minutes if not active" do
72
+ session = create(:session)
73
+ Timecop.travel(6.minutes.from_now)
74
+ expect(described_class.expiring.count).to eq(1)
75
+ session.timeout?
76
+ expect(session.expired?).to be(true)
77
+ end
78
+
79
+ it "will not time out after 5 minutes if active" do
80
+ session = create(:session, state: :active)
81
+ Timecop.travel(1.week.from_now)
82
+ expect(described_class.expiring.count).to eq(0)
83
+ session.timeout?
84
+ expect(session.active?).to be(true)
85
+ end
86
+
87
+ it "will timeout after 1 month if active but unused" do
88
+ session = create(:session, state: :active)
89
+ Timecop.travel((1.month + 1.day).from_now)
90
+ expect(described_class.expiring.count).to eq(1)
91
+ session.timeout?
92
+ expect(session.expired?).to be(true)
93
+ end
94
+
95
+ it "will not timeout after 1 month if active and used" do
96
+ session = create(:session, state: :active)
97
+ Timecop.travel(6.months.from_now)
98
+ session.touch
99
+ session.timeout?
100
+ expect(session.active?).to be(true)
101
+ end
102
+
103
+ it "will timeout after 1 year if active and used" do
104
+ session = create(:session, state: :active)
105
+ Timecop.travel((1.year + 1.day).from_now)
106
+ session.touch
107
+ session.timeout?
108
+ expect(session.expired?).to be(true)
109
+ end
110
+
111
+ it "can be cleared" do
112
+ session = create(:session, state: :active)
113
+ expect(session.may_clear?).to be(true)
114
+ session.clear!
115
+ expect(session.deleted?).to be(true)
116
+ end
117
+
118
+ context "when many sessions exist" do
119
+ before do
120
+ create(:session, state: :created)
121
+ create(:session, state: :pending)
122
+ create(:session, state: :active)
123
+ create(:session, state: :expired)
124
+ create(:session, state: :deleted)
125
+ end
126
+
127
+ it "can be scoped to created sessions" do
128
+ expect(described_class.count).to eq(5)
129
+ expect(described_class.created.count).to eq(1)
130
+ end
131
+
132
+ it "can be scoped to pending sessions" do
133
+ expect(described_class.count).to eq(5)
134
+ expect(described_class.pending.count).to eq(1)
135
+ end
136
+
137
+ it "can be scoped to active sessions" do
138
+ expect(described_class.count).to eq(5)
139
+ expect(described_class.active.count).to eq(1)
140
+ end
141
+
142
+ it "can be scoped to expired sessions" do
143
+ expect(described_class.count).to eq(5)
144
+ expect(described_class.expired.count).to eq(1)
145
+ end
146
+
147
+ it "can be scoped to deleted sessions" do
148
+ expect(described_class.count).to eq(5)
149
+ expect(described_class.deleted.count).to eq(1)
150
+ end
151
+
152
+ it "can be accessed at random" do
153
+ expect { described_class.sample }.not_to raise_error
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK::Tenant do
4
+ it "is valid with valid attributes" do
5
+ expect { create(:tenant) }.not_to raise_error
6
+ end
7
+
8
+ it "is assigned a uuid on save" do
9
+ tenant = build(:tenant)
10
+ expect(tenant.id).to be_nil
11
+ tenant.save
12
+ expect(valid_uuid?(tenant.id)).to be(true)
13
+ end
14
+
15
+ it "can be saved with a custom uuid" do
16
+ uuid = generate(:uuid)
17
+ tenant = create(:tenant, id: uuid)
18
+ expect(tenant.id).to eq(uuid)
19
+ end
20
+
21
+ it "is invalid without a name" do
22
+ tenant = build(:tenant, name: nil)
23
+ expect(tenant.valid?).to be(false)
24
+ expect(tenant.errors[:name].first).to eq('is not present')
25
+ end
26
+
27
+ it "is valid without an icon" do
28
+ tenant = build(:tenant, icon: nil)
29
+ expect(tenant.valid?).to be(true)
30
+ end
31
+
32
+ it "is invalid if the icon is not a URL" do
33
+ tenant = build(:tenant, icon: Faker::Alphanumeric.alpha)
34
+ expect(tenant.valid?).to be(false)
35
+ expect(tenant.errors[:icon].first).to eq('is not a URL')
36
+ end
37
+
38
+ it "can have multiple users" do
39
+ tenant = create(:tenant)
40
+ expect(tenant.users.count).to eq(0)
41
+ 3.times { create(:user).add_tenant(tenant) }
42
+ expect(tenant.users.count).to eq(3)
43
+ end
44
+
45
+ it "can have multiple groups" do
46
+ tenant = create(:tenant)
47
+ expect(tenant.groups.count).to eq(0)
48
+ create_list(:group, 3, tenant: tenant)
49
+ expect(tenant.groups.count).to eq(3)
50
+ end
51
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK::TenantUserMetadata do
4
+ it "is valid with valid attributes" do
5
+ expect { create(:tenant_user_metadata) }.not_to raise_error
6
+ end
7
+
8
+ it "is invalid without a tenant" do
9
+ tenant_user_metadata = build(:tenant_user_metadata, tenant: nil)
10
+ expect(tenant_user_metadata.valid?).to be(false)
11
+ expect(tenant_user_metadata.errors[:tenant].first).to eq('is not present')
12
+ end
13
+
14
+ it "is invalid without a user" do
15
+ tenant_user_metadata = build(:tenant_user_metadata, user: nil)
16
+ expect(tenant_user_metadata.valid?).to be(false)
17
+ expect(tenant_user_metadata.errors[:user].first).to eq('is not present')
18
+ end
19
+
20
+ it "displays as the two IDs concatenated" do
21
+ tenant_user_metadata = create(:tenant_user_metadata)
22
+ expect(tenant_user_metadata.to_s).to include(tenant_user_metadata.tenant.id)
23
+ expect(tenant_user_metadata.to_s).to include(tenant_user_metadata.user.id)
24
+ end
25
+
26
+ context "when a user and a tenant exist" do
27
+ let(:user) { create(:user) }
28
+ let(:tenant) { create(:tenant) }
29
+
30
+ context "when a user is added to a tenant" do
31
+ let(:tenant_user_metadata) do
32
+ described_class[tenant: tenant, user: user]
33
+ end
34
+
35
+ before do
36
+ tenant.add_user(user)
37
+ end
38
+
39
+ it "is created automatically" do
40
+ expect(tenant_user_metadata).not_to be_nil
41
+ expect(tenant.users).to include(user)
42
+ end
43
+
44
+ it "destroying it will remove the user from the tenant" do
45
+ expect(tenant.users).to include(user)
46
+ tenant_user_metadata.destroy
47
+ tenant.reload
48
+ expect(tenant.users).not_to include(user)
49
+ end
50
+ end
51
+
52
+ context "when it is created" do
53
+ it "adds a user to a tenant" do
54
+ expect(tenant.users).not_to include(user)
55
+ create(:tenant_user_metadata, tenant: tenant, user: user)
56
+ tenant.reload
57
+ expect(tenant.users).to include(user)
58
+ end
59
+ end
60
+ end
61
+ end