punk 0.1.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/workflows/ship.yml +3 -1
  4. data/.github/workflows/test.yml +26 -1
  5. data/.rdoc_options +23 -0
  6. data/.rgignore +1 -0
  7. data/.rspec +2 -0
  8. data/Gemfile +5 -6
  9. data/Gemfile.lock +17 -30
  10. data/README.md +7 -1
  11. data/Rakefile +0 -1
  12. data/VERSION +1 -1
  13. data/app/migrations/001_lets_punk.rb +3 -0
  14. data/app/routes/hello.rb +4 -0
  15. data/env/.gitignore +3 -0
  16. data/env/spec/test.sh +3 -0
  17. data/lib/punk/actions/.keep +0 -0
  18. data/lib/punk/actions/groups/list.rb +24 -0
  19. data/lib/punk/actions/sessions/clear.rb +21 -0
  20. data/lib/punk/actions/sessions/create.rb +64 -0
  21. data/lib/punk/actions/sessions/list.rb +18 -0
  22. data/lib/punk/actions/sessions/verify.rb +24 -0
  23. data/lib/punk/actions/tenants/list.rb +18 -0
  24. data/lib/punk/actions/users/list_group.rb +18 -0
  25. data/lib/punk/actions/users/list_tenant.rb +18 -0
  26. data/lib/punk/actions/users/show.rb +18 -0
  27. data/lib/punk/commands/list.rb +12 -6
  28. data/lib/punk/config/defaults.json +3 -0
  29. data/lib/punk/config/schema.json +3 -0
  30. data/lib/punk/core/app.rb +4 -6
  31. data/lib/punk/core/commander.rb +12 -7
  32. data/lib/punk/core/exec.rb +2 -0
  33. data/lib/punk/core/load.rb +0 -1
  34. data/lib/punk/framework/command.rb +5 -1
  35. data/lib/punk/framework/plugins/validation.rb +0 -14
  36. data/lib/punk/helpers/loggable.rb +1 -1
  37. data/lib/punk/migrations/001_punk.rb +103 -0
  38. data/lib/punk/models/.keep +0 -0
  39. data/lib/punk/models/group.rb +20 -0
  40. data/lib/punk/models/group_user_metadata.rb +17 -0
  41. data/lib/punk/models/identity.rb +29 -0
  42. data/lib/punk/models/session.rb +89 -0
  43. data/lib/punk/models/tenant.rb +19 -0
  44. data/lib/punk/models/tenant_user_metadata.rb +17 -0
  45. data/lib/punk/models/user.rb +31 -0
  46. data/lib/punk/routes/groups.rb +31 -0
  47. data/lib/punk/routes/plivo.rb +4 -0
  48. data/lib/punk/routes/sessions.rb +108 -0
  49. data/lib/punk/routes/swagger.rb +9 -0
  50. data/lib/punk/routes/tenants.rb +29 -0
  51. data/lib/punk/routes/users.rb +36 -0
  52. data/lib/punk/services/.keep +0 -0
  53. data/lib/punk/services/challenge_claim.rb +46 -0
  54. data/lib/punk/services/create_identities.rb +25 -0
  55. data/lib/punk/services/generate_swagger.rb +25 -0
  56. data/lib/punk/services/prove_claim.rb +29 -0
  57. data/lib/punk/services/secret.rb +9 -0
  58. data/lib/punk/templates/groups/list.jbuilder +7 -0
  59. data/lib/punk/templates/plivo.slim +16 -0
  60. data/lib/punk/templates/sessions/list.jbuilder +6 -0
  61. data/lib/punk/templates/sessions/pending.jbuilder +4 -0
  62. data/lib/punk/templates/tenants/list.jbuilder +7 -0
  63. data/lib/punk/templates/tenants/list.slim +8 -0
  64. data/lib/punk/templates/users/list.jbuilder +7 -0
  65. data/lib/punk/templates/users/list.rcsv +4 -0
  66. data/lib/punk/templates/users/show.jbuilder +5 -0
  67. data/lib/punk/views/groups/list.rb +22 -0
  68. data/lib/punk/views/plivo_store.rb +15 -0
  69. data/lib/punk/views/sessions/list.rb +22 -0
  70. data/lib/punk/views/sessions/pending.rb +28 -0
  71. data/lib/punk/views/tenants/list.rb +22 -0
  72. data/lib/punk/views/users/list.rb +22 -0
  73. data/lib/punk/views/users/show.rb +22 -0
  74. data/lib/punk/workers/.keep +0 -0
  75. data/lib/punk/workers/expire_sessions.rb +9 -0
  76. data/lib/punk/workers/geocode_session_worker.rb +48 -0
  77. data/lib/punk/workers/identify_session_worker.rb +45 -0
  78. data/lib/punk/workers/secret.rb +18 -0
  79. data/lib/punk/workers/send_email_worker.rb +51 -0
  80. data/lib/punk/workers/send_sms_worker.rb +40 -0
  81. data/punk.gemspec +141 -15
  82. data/schema.psql +345 -0
  83. data/spec/actions/groups/punk/list_groups_action_spec.rb +36 -0
  84. data/spec/actions/sessions/punk/clear_session_action_spec.rb +29 -0
  85. data/spec/actions/sessions/punk/create_session_action_spec.rb +33 -0
  86. data/spec/actions/sessions/punk/list_sessions_action_spec.rb +26 -0
  87. data/spec/actions/sessions/punk/verify_session_action_spec.rb +59 -0
  88. data/spec/actions/tenants/punk/list_tenants_action_spec.rb +25 -0
  89. data/spec/actions/users/punk/list_group_users_action_spec.rb +26 -0
  90. data/spec/actions/users/punk/list_tenant_users_action_spec.rb +26 -0
  91. data/spec/factories/group.rb +12 -0
  92. data/spec/factories/group_user_metadata.rb +10 -0
  93. data/spec/factories/identity.rb +19 -0
  94. data/spec/factories/session.rb +12 -0
  95. data/spec/factories/tenant.rb +10 -0
  96. data/spec/factories/tenant_user_metadata.rb +10 -0
  97. data/spec/factories/user.rb +12 -0
  98. data/spec/lib/commands/auth_spec.rb +11 -0
  99. data/spec/lib/commands/generate_spec.rb +7 -0
  100. data/spec/lib/commands/http_spec.rb +23 -0
  101. data/spec/lib/commands/list_spec.rb +7 -0
  102. data/spec/lib/commands/swagger_spec.rb +7 -0
  103. data/spec/lib/engine/punk_env_spec.rb +13 -0
  104. data/spec/lib/engine/punk_exec_spec.rb +9 -0
  105. data/spec/lib/engine/punk_init_spec.rb +9 -0
  106. data/spec/lib/engine/punk_store_spec.rb +10 -0
  107. data/spec/lib/punk.env +7 -0
  108. data/spec/models/punk/group_spec.rb +50 -0
  109. data/spec/models/punk/group_user_metadata_spec.rb +61 -0
  110. data/spec/models/punk/identity_spec.rb +61 -0
  111. data/spec/models/punk/session_spec.rb +156 -0
  112. data/spec/models/punk/tenant_spec.rb +51 -0
  113. data/spec/models/punk/tenant_user_metadata_spec.rb +61 -0
  114. data/spec/models/punk/user_spec.rb +115 -0
  115. data/spec/routes/groups/get_groups_spec.rb +33 -0
  116. data/spec/routes/plivo/get_plivo_spec.rb +11 -0
  117. data/spec/routes/sessions/delete_session_spec.rb +11 -0
  118. data/spec/routes/sessions/get_sessions_spec.rb +30 -0
  119. data/spec/routes/sessions/patch_session_spec.rb +11 -0
  120. data/spec/routes/sessions/post_session_spec.rb +11 -0
  121. data/spec/routes/swagger/get_swagger_spec.rb +12 -0
  122. data/spec/routes/tenants/get_tenants_spec.rb +31 -0
  123. data/spec/routes/users/get_users_spec.rb +60 -0
  124. data/spec/services/punk/challenge_claim_service_spec.rb +7 -0
  125. data/spec/services/punk/create_identities_service_spec.rb +14 -0
  126. data/spec/services/punk/generate_swagger_service_spec.rb +7 -0
  127. data/spec/services/punk/prove_claim_service_spec.rb +7 -0
  128. data/spec/services/punk/secret_service_spec.rb +7 -0
  129. data/spec/spec_helper.rb +122 -0
  130. data/spec/vcr_cassettes/PUNK_GeocodeSessionWorker/updates_the_session_data.yml +57 -0
  131. data/spec/vcr_cassettes/PUNK_IdentifySessionWorker/updates_the_session_data.yml +112 -0
  132. data/spec/views/punk/plivo_store_spec.rb +7 -0
  133. data/spec/views/sessions/punk/list_sessions_view_spec.rb +7 -0
  134. data/spec/views/sessions/punk/pending_session_view_spec.rb +7 -0
  135. data/spec/views/tenants/punk/list_tenants_view_spec.rb +7 -0
  136. data/spec/views/users/punk/list_groups_view_spec.rb +7 -0
  137. data/spec/views/users/punk/list_users_view_spec.rb +7 -0
  138. data/spec/workers/punk/expire_sessions_worker_spec.rb +31 -0
  139. data/spec/workers/punk/geocode_session_worker_spec.rb +14 -0
  140. data/spec/workers/punk/identify_session_worker_spec.rb +15 -0
  141. data/spec/workers/punk/secret_worker_spec.rb +20 -0
  142. data/spec/workers/punk/send_email_worker_spec.rb +46 -0
  143. data/spec/workers/punk/send_sms_worker_spec.rb +33 -0
  144. metadata +150 -12
  145. 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