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,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
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK::User do
4
+ it "is valid with valid attributes" do
5
+ expect { create(:user) }.not_to raise_error
6
+ end
7
+
8
+ it "is assigned a uuid on save" do
9
+ user = build(:user)
10
+ expect(user.id).to be_nil
11
+ user.save
12
+ expect(valid_uuid?(user.id)).to be(true)
13
+ end
14
+
15
+ it "can be saved with a custom uuid" do
16
+ uuid = generate(:uuid)
17
+ user = create(:user, id: uuid)
18
+ expect(user.id).to eq(uuid)
19
+ end
20
+
21
+ it "is invalid without a name" do
22
+ user = build(:user, name: nil)
23
+ expect(user.valid?).to be(false)
24
+ expect(user.errors[:name].first).to eq('is not present')
25
+ end
26
+
27
+ it "validates name presence in the database" do
28
+ user = build(:user, name: nil)
29
+ expect { user.save(validate: false) }.to raise_error(Sequel::NotNullConstraintViolation)
30
+ end
31
+
32
+ it "is valid without an icon" do
33
+ user = build(:user, icon: nil)
34
+ expect(user.valid?).to be(true)
35
+ end
36
+
37
+ it "is invalid if the icon is not a URL" do
38
+ user = build(:user, icon: Faker::Alphanumeric.alpha)
39
+ expect(user.valid?).to be(false)
40
+ expect(user.errors[:icon].first).to eq('is not a URL')
41
+ end
42
+
43
+ it "is valid without an email" do
44
+ user = build(:user, email: nil)
45
+ expect(user.valid?).to be(true)
46
+ end
47
+
48
+ it "is invalid if the email is not an email address" do
49
+ user = build(:user, email: Faker::Alphanumeric.alpha)
50
+ expect(user.valid?).to be(false)
51
+ expect(user.errors[:email].first).to eq('is not an email address')
52
+ end
53
+
54
+ it "has a unique email" do
55
+ email = create(:user).email
56
+ user = build(:user, email: email)
57
+ expect(user.valid?).to be(false)
58
+ expect(user.errors[:email].first).to eq('is already taken')
59
+ end
60
+
61
+ it "validates email uniqueness in the database" do
62
+ email = create(:user).email
63
+ user = build(:user, email: email)
64
+ expect { user.save(validate: false) }.to raise_error(Sequel::UniqueConstraintViolation)
65
+ end
66
+
67
+ it "is valid without a phone" do
68
+ user = build(:user, phone: nil)
69
+ expect(user.valid?).to be(true)
70
+ end
71
+
72
+ it "is invalid if the phone is not a phone number" do
73
+ user = build(:user, phone: Faker::Alphanumeric.alpha)
74
+ expect(user.valid?).to be(false)
75
+ expect(user.errors[:phone].first).to eq('is not a phone number')
76
+ end
77
+
78
+ it "has a unique phone" do
79
+ phone = create(:user).phone
80
+ user = build(:user, phone: phone)
81
+ expect(user.valid?).to be(false)
82
+ expect(user.errors[:phone].first).to eq('is already taken')
83
+ end
84
+
85
+ it "validates phone uniqueness in the database" do
86
+ phone = create(:user).phone
87
+ user = build(:user, phone: phone)
88
+ expect { user.save(validate: false) }.to raise_error(Sequel::UniqueConstraintViolation)
89
+ end
90
+
91
+ it "is invalid without either an email or a phone" do
92
+ user = build(:user, email: nil, phone: nil)
93
+ user.valid?
94
+ expect(user.errors[:email].first).to eq('is not present')
95
+ expect(user.errors[:phone].first).to eq('is not present')
96
+ end
97
+
98
+ it "can belong to multiple tenants" do
99
+ user = create(:user)
100
+ expect(user.tenants.count).to eq(0)
101
+ 3.times { create(:tenant).add_user(user) }
102
+ expect(user.tenants.count).to eq(3)
103
+ end
104
+
105
+ it "can belong to multiple groups" do
106
+ user = create(:user)
107
+ expect(user.groups.count).to eq(0)
108
+ 3.times { create(:group).add_user(user) }
109
+ expect(user.groups.count).to eq(3)
110
+ end
111
+
112
+ it "can have many sessions"
113
+
114
+ it "has an active sessions scope"
115
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe PUNK, "GET /groups" do
4
+ include_context "Punk"
5
+
6
+ context 'when the user is not authenticated' do
7
+ before do
8
+ get '/groups'
9
+ end
10
+
11
+ it { is_expected.not_to be_successful }
12
+ end
13
+
14
+ context 'when the user is authenticated' do
15
+ let(:tenant) { create(:tenant) }
16
+ let(:identity) { create(:identity, claim_type: 'phone') }
17
+ let(:group) { create(:group, tenant: tenant) }
18
+
19
+ before do
20
+ identity.user.add_tenant(tenant)
21
+ identity.user.add_group(group)
22
+ login(identity.claim)
23
+ get "/groups?tenant_id=#{tenant.id}"
24
+ end
25
+
26
+ after do
27
+ logout
28
+ end
29
+
30
+ it { is_expected.to be_successful }
31
+ its(:body) { is_expected.to match(group.name) }
32
+ end
33
+ end