descope 1.0.6 → 1.0.7

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +2 -2
  3. data/.github/workflows/publish-gem.yaml +10 -3
  4. data/.gitignore +2 -0
  5. data/.ruby-version +1 -1
  6. data/Gemfile +7 -7
  7. data/Gemfile.lock +68 -55
  8. data/README.md +159 -51
  9. data/examples/ruby-on-rails-api/descope/Gemfile +8 -8
  10. data/examples/ruby-on-rails-api/descope/Gemfile.lock +1 -1
  11. data/examples/ruby-on-rails-api/descope/package-lock.json +187 -131
  12. data/examples/ruby-on-rails-api/descope/package.json +1 -1
  13. data/examples/ruby-on-rails-api/descope/yarn.lock +182 -84
  14. data/lib/descope/api/v1/auth/enchantedlink.rb +3 -1
  15. data/lib/descope/api/v1/auth/magiclink.rb +3 -1
  16. data/lib/descope/api/v1/auth/otp.rb +3 -1
  17. data/lib/descope/api/v1/auth/password.rb +6 -2
  18. data/lib/descope/api/v1/auth/totp.rb +3 -1
  19. data/lib/descope/api/v1/auth.rb +47 -12
  20. data/lib/descope/api/v1/management/common.rb +20 -5
  21. data/lib/descope/api/v1/management/sso_application.rb +236 -0
  22. data/lib/descope/api/v1/management/sso_settings.rb +2 -24
  23. data/lib/descope/api/v1/management/user.rb +151 -13
  24. data/lib/descope/api/v1/management.rb +2 -0
  25. data/lib/descope/api/v1/session.rb +37 -4
  26. data/lib/descope/mixins/common.rb +1 -0
  27. data/lib/descope/mixins/http.rb +60 -9
  28. data/lib/descope/mixins/initializer.rb +2 -1
  29. data/lib/descope/mixins/logging.rb +12 -4
  30. data/lib/descope/version.rb +1 -1
  31. data/spec/descope/api/v1/auth_spec.rb +29 -0
  32. data/spec/descope/api/v1/auth_token_extraction_spec.rb +126 -0
  33. data/spec/descope/api/v1/session_refresh_spec.rb +98 -0
  34. data/spec/factories/user.rb +1 -1
  35. data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +1 -1
  36. data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +1 -1
  37. data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +1 -1
  38. data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +49 -0
  39. data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +1 -1
  40. data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +3 -0
  41. data/spec/integration/lib.descope/api/v1/management/audit_spec.rb +5 -3
  42. data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +2 -0
  43. data/spec/integration/lib.descope/api/v1/management/flow_spec.rb +3 -1
  44. data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +4 -2
  45. data/spec/integration/lib.descope/api/v1/management/project_spec.rb +2 -0
  46. data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +2 -0
  47. data/spec/integration/lib.descope/api/v1/management/user_spec.rb +55 -6
  48. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +11 -2
  49. data/spec/lib.descope/api/v1/auth/password_spec.rb +10 -1
  50. data/spec/lib.descope/api/v1/auth_spec.rb +167 -5
  51. data/spec/lib.descope/api/v1/cookie_domain_fix_integration_spec.rb +245 -0
  52. data/spec/lib.descope/api/v1/management/sso_application_spec.rb +217 -0
  53. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +2 -2
  54. data/spec/lib.descope/api/v1/management/user_spec.rb +134 -46
  55. data/spec/lib.descope/api/v1/session_spec.rb +119 -6
  56. data/spec/lib.descope/mixins/http_spec.rb +218 -0
  57. data/spec/support/client_config.rb +0 -1
  58. data/spec/support/utils.rb +6 -0
  59. metadata +13 -8
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Descope::Api::V1::Management::SSOApplication do
6
+ before(:all) do
7
+ dummy_instance = DummyClass.new
8
+ dummy_instance.extend(Descope::Api::V1::Management::SSOApplication)
9
+ @instance = dummy_instance
10
+ end
11
+
12
+ context('.create_sso_oidc_application') do
13
+ it 'should respond to .create_saml_application' do
14
+ expect(@instance).to respond_to :create_saml_application
15
+ end
16
+
17
+ it 'is expected to create SAML application' do
18
+ expect(@instance).to receive(:post).with(
19
+ SSO_APPLICATION_OIDC_CREATE_PATH, {
20
+ id: 'tenant1',
21
+ name: 'test',
22
+ description: 'awesome tenant',
23
+ enabled: true,
24
+ logo: 'https://logo.com',
25
+ loginPageUrl: 'https://dummy.com/login'
26
+ }
27
+ )
28
+ expect do
29
+ @instance.create_sso_oidc_app(
30
+ id: 'tenant1',
31
+ name: 'test',
32
+ description: 'awesome tenant',
33
+ enabled: true,
34
+ logo: 'https://logo.com',
35
+ login_page_url: 'https://dummy.com/login'
36
+ )
37
+ end.not_to raise_error
38
+ end
39
+ end
40
+
41
+ context('.create_saml_application') do
42
+ it 'should respond to .create_saml_application' do
43
+ expect(@instance).to respond_to :create_saml_application
44
+ end
45
+
46
+ it 'is expected to create SAML application' do
47
+ expect(@instance).to receive(:post).with(
48
+ SSO_APPLICATION_SAML_CREATE_PATH, {
49
+ name: 'test',
50
+ description: 'awesome tenant',
51
+ id: 'tenant1',
52
+ loginPageUrl: 'https://dummy.com/login',
53
+ logo: 'https://logo.com',
54
+ enabled: true,
55
+ useMetadataInfo: true,
56
+ metadataUrl: 'https://dummy.com/metadata',
57
+ entityId: 'ent1234',
58
+ acsUrl: 'https://dummy.com/acs',
59
+ certificate: 'something',
60
+ attributeMapping: [
61
+ {
62
+ 'abc': '123'
63
+ }
64
+ ],
65
+ groupsMapping: [
66
+ {
67
+ 'abc': '123'
68
+ }
69
+ ],
70
+ acsAllowedCallbacks: true,
71
+ subjectNameIdType: 'test',
72
+ subjectNameIdFormat: 'test',
73
+ defaultRelayState: 'test',
74
+ forceAuthentication: true,
75
+ logoutRedirectUrl: 'https://dummy.com/logout'
76
+ }
77
+ )
78
+ expect do
79
+ @instance.create_saml_application(
80
+ name: 'test',
81
+ login_page_url: 'https://dummy.com/login',
82
+ id: 'tenant1',
83
+ description: 'awesome tenant',
84
+ logo: 'https://logo.com',
85
+ enabled: true,
86
+ use_metadata_info: true,
87
+ metadata_url: 'https://dummy.com/metadata',
88
+ entity_id: 'ent1234',
89
+ acs_url: 'https://dummy.com/acs',
90
+ certificate: 'something',
91
+ attribute_mapping: [
92
+ {
93
+ 'abc': '123'
94
+ }
95
+ ],
96
+ groups_mapping: [
97
+ {
98
+ 'abc': '123'
99
+ }
100
+ ],
101
+ acs_allowed_callbacks: true,
102
+ subject_name_id_type: 'test',
103
+ subject_name_id_format: 'test',
104
+ default_relay_state: 'test',
105
+ force_authentication: true,
106
+ logout_redirect_url: 'https://dummy.com/logout'
107
+ )
108
+ end.not_to raise_error
109
+ end
110
+
111
+ it 'is expected to raise error if metadata_url is empty' do
112
+ expect do
113
+ @instance.create_saml_application(
114
+ name: 'test',
115
+ login_page_url: 'https://dummy.com/login',
116
+ id: 'tenant1',
117
+ description: 'awesome tenant',
118
+ logo: 'https://logo.com',
119
+ enabled: true,
120
+ use_metadata_info: true,
121
+ entity_id: 'ent1234',
122
+ acs_url: 'https://dummy.com/acs',
123
+ certificate: 'something',
124
+ attribute_mapping: [
125
+ {
126
+ 'abc': '123'
127
+ }
128
+ ],
129
+ groups_mapping: [
130
+ {
131
+ 'abc': '123'
132
+ }
133
+ ],
134
+ acs_allowed_callbacks: true,
135
+ subject_name_id_type: 'test',
136
+ subject_name_id_format: 'test',
137
+ default_relay_state: 'test',
138
+ force_authentication: true,
139
+ logout_redirect_url: 'https://dummy.com/logout'
140
+ )
141
+ end.to raise_error(Descope::ArgumentException, 'metadata_url argument must be set')
142
+ end
143
+ end
144
+
145
+ it 'is expected to raise error if entity_id acs_url and certificate arguments are missing' do
146
+ expect do
147
+ @instance.create_saml_application(
148
+ name: 'test',
149
+ login_page_url: 'https://dummy.com/login',
150
+ id: 'tenant1',
151
+ description: 'awesome tenant',
152
+ logo: 'https://logo.com',
153
+ enabled: true,
154
+ attribute_mapping: [
155
+ {
156
+ 'abc': '123'
157
+ }
158
+ ],
159
+ groups_mapping: [
160
+ {
161
+ 'abc': '123'
162
+ }
163
+ ],
164
+ acs_allowed_callbacks: true,
165
+ subject_name_id_type: 'test',
166
+ subject_name_id_format: 'test',
167
+ default_relay_state: 'test',
168
+ force_authentication: true,
169
+ logout_redirect_url: 'https://dummy.com/logout'
170
+ )
171
+ end.to raise_error(Descope::ArgumentException, 'entity_id, acs_url, certificate arguments must be set')
172
+ end
173
+
174
+ it 'is expected to update sso oidc application' do
175
+ expect(@instance).to receive(:post).with(
176
+ SSO_APPLICATION_OIDC_UPDATE_PATH, {
177
+ id: 'tenant1',
178
+ name: 'test',
179
+ description: 'awesome tenant',
180
+ enabled: true,
181
+ logo: 'https://logo.com',
182
+ loginPageUrl: 'https://dummy.com/login'
183
+ }
184
+ )
185
+ expect do
186
+ @instance.update_sso_oidc_app(
187
+ id: 'tenant1',
188
+ name: 'test',
189
+ description: 'awesome tenant',
190
+ enabled: true,
191
+ logo: 'https://logo.com',
192
+ login_page_url: 'https://dummy.com/login'
193
+ )
194
+ end.not_to raise_error
195
+ end
196
+
197
+ it 'is expected to delete sso app' do
198
+ expect(@instance).to receive(:delete).with(
199
+ SSO_APPLICATION_DELETE_PATH, { id: 'tenant1' }
200
+ )
201
+ expect { @instance.delete_sso_app('tenant1') }.not_to raise_error
202
+ end
203
+
204
+ it 'is expected to load sso app' do
205
+ expect(@instance).to receive(:get).with(
206
+ SSO_APPLICATION_LOAD_PATH, { id: 'tenant1' }
207
+ )
208
+ expect { @instance.load_sso_app('tenant1') }.not_to raise_error
209
+ end
210
+
211
+ it 'is expected to load all sso apps' do
212
+ expect(@instance).to receive(:get).with(
213
+ SSO_APPLICATION_LOAD_ALL_PATH, {}
214
+ )
215
+ expect { @instance.load_all_sso_apps }.not_to raise_error
216
+ end
217
+ end
@@ -87,7 +87,7 @@ describe Descope::Api::V1::Management::SSOSettings do
87
87
 
88
88
  it 'is expected to configure SSO settings' do
89
89
  expect(@instance).to receive(:post).with(
90
- SSO_SAML_PATH, {
90
+ SSO_SETTINGS_PATH, {
91
91
  tenantId: '123',
92
92
  settings: {
93
93
  name: 'test',
@@ -132,7 +132,7 @@ describe Descope::Api::V1::Management::SSOSettings do
132
132
 
133
133
  it 'is expected to configure SAML metadata' do
134
134
  expect(@instance).to receive(:post).with(
135
- SSO_SAML_METADATA_PATH, {
135
+ SSO_METADATA_PATH, {
136
136
  tenantId: '123',
137
137
  settings: {
138
138
  name: 'test',
@@ -9,55 +9,69 @@ describe Descope::Api::V1::Management::User do
9
9
  @instance = dummy_instance
10
10
  end
11
11
 
12
- context '.create_user' do
12
+ context '.create_user_and_test_user' do
13
13
  it 'is expected to respond to a user create method' do
14
14
  expect(@instance).to respond_to(:create_user)
15
+ expect(@instance).to respond_to(:create_test_user)
15
16
  end
16
17
 
18
+ user_tenants_args = [
19
+ {
20
+ tenant_id: 'tenant1'
21
+ },
22
+ {
23
+ tenant_id: 'tenant2',
24
+ role_names: %w[role1 role2]
25
+ }
26
+ ]
27
+
28
+ params = {
29
+ loginId: 'name@mail.com',
30
+ email: 'name@mail.com',
31
+ phone: '+1-212-669-2542',
32
+ name: 'name',
33
+ givenName: 'name',
34
+ familyName: 'Ruby SDK',
35
+ userTenants: associated_tenants_to_hash_array(user_tenants_args),
36
+ test: false,
37
+ picture: 'https://www.example.com/picture.png',
38
+ customAttributes: { 'attr1' => 'value1', 'attr2' => 'value2' },
39
+ additionalIdentifiers: %w[id-1 id-2],
40
+ password: 's3cr3t',
41
+ ssoAppIds: %w[app1 app2],
42
+ invite: false
43
+ }
44
+
45
+ args = {
46
+ login_id: 'name@mail.com',
47
+ email: 'name@mail.com',
48
+ phone: '+1-212-669-2542',
49
+ name: 'name',
50
+ given_name: 'name',
51
+ family_name: 'Ruby SDK',
52
+ user_tenants: user_tenants_args,
53
+ picture: 'https://www.example.com/picture.png',
54
+ custom_attributes: { 'attr1' => 'value1', 'attr2' => 'value2' },
55
+ additional_identifiers: %w[id-1 id-2],
56
+ password: 's3cr3t',
57
+ sso_app_ids: %w[app1 app2]
58
+ }
59
+
17
60
  it 'is expected to create a user with user data' do
18
- user_tenants_args = [
19
- {
20
- tenant_id: 'tenant1'
21
- },
22
- {
23
- tenant_id: 'tenant2',
24
- role_names: %w[role1 role2]
25
- }
26
- ]
27
- expect(@instance).to receive(:post).with(
28
- USER_CREATE_PATH, {
29
- loginId: 'name@mail.com',
30
- email: 'name@mail.com',
31
- phone: '+1-212-669-2542',
32
- name: 'name',
33
- givenName: 'name',
34
- familyName: 'Ruby SDK',
35
- userTenants: associated_tenants_to_hash_array(user_tenants_args),
36
- test: false,
37
- picture: 'https://www.example.com/picture.png',
38
- customAttributes: { 'attr1' => 'value1', 'attr2' => 'value2' },
39
- additionalIdentifiers: %w[id-1 id-2],
40
- password: 's3cr3t',
41
- ssoAppIds: %w[app1 app2],
42
- invite: false
43
- }
44
- )
61
+ expect(@instance).to receive(:post).with(USER_CREATE_PATH, params)
45
62
 
46
63
  expect do
47
- @instance.create_user(
48
- login_id: 'name@mail.com',
49
- email: 'name@mail.com',
50
- phone: '+1-212-669-2542',
51
- name: 'name',
52
- given_name: 'name',
53
- family_name: 'Ruby SDK',
54
- user_tenants: user_tenants_args,
55
- picture: 'https://www.example.com/picture.png',
56
- custom_attributes: { 'attr1' => 'value1', 'attr2' => 'value2' },
57
- additional_identifiers: %w[id-1 id-2],
58
- password: 's3cr3t',
59
- sso_app_ids: %w[app1 app2]
60
- )
64
+ @instance.create_user(**args)
65
+ end.not_to raise_error
66
+ end
67
+
68
+ it 'is expected to create a test user with user data' do
69
+ params[:test] = true
70
+ expect(@instance).to receive(:post).with(TEST_USER_CREATE_PATH, params)
71
+
72
+ expect do
73
+ args[:test] = true
74
+ @instance.create_test_user(**args)
61
75
  end.not_to raise_error
62
76
  end
63
77
  end
@@ -108,14 +122,16 @@ describe Descope::Api::V1::Management::User do
108
122
  loginId: 'name@mail.com',
109
123
  email: 'name@mail.com',
110
124
  test: false,
111
- invite: true
125
+ invite: true,
126
+ templateId: "tid",
112
127
  }
113
128
  )
114
129
 
115
130
  expect do
116
131
  @instance.invite_user(
117
132
  login_id: 'name@mail.com',
118
- email: 'name@mail.com'
133
+ email: 'name@mail.com',
134
+ template_id: "tid",
119
135
  )
120
136
  end.not_to raise_error
121
137
  end
@@ -223,6 +239,8 @@ describe Descope::Api::V1::Management::User do
223
239
  it 'is expected to respond to a search_all method' do
224
240
  expect(@instance).to respond_to(:search_all_users)
225
241
 
242
+ tenant_role_ids = { 'tenant1' => ['roleA', 'roleB'] }
243
+ tenant_role_names = { 'tenant1' => ['roleName1', 'roleName2'] }
226
244
  expect(@instance).to receive(:post).with(
227
245
  USERS_SEARCH_PATH, {
228
246
  loginId: 'someone@example.com',
@@ -234,7 +252,13 @@ describe Descope::Api::V1::Management::User do
234
252
  ssoOnly: false,
235
253
  text: 'some text',
236
254
  testUsersOnly: false,
237
- withTestUser: false
255
+ withTestUser: false,
256
+ tenantRoleIds: {
257
+ 'tenant1' => { values: ['roleA', 'roleB'] }
258
+ },
259
+ tenantRoleNames: {
260
+ 'tenant1' => { values: ['roleName1', 'roleName2'] }
261
+ }
238
262
  }
239
263
  )
240
264
 
@@ -248,7 +272,9 @@ describe Descope::Api::V1::Management::User do
248
272
  page: 1,
249
273
  sso_app_ids: [],
250
274
  test_users_only: false,
251
- with_test_user: false
275
+ with_test_user: false,
276
+ tenant_role_ids: tenant_role_ids,
277
+ tenant_role_names: tenant_role_names
252
278
  )
253
279
  end.not_to raise_error
254
280
  end
@@ -704,4 +730,66 @@ describe Descope::Api::V1::Management::User do
704
730
  end.not_to raise_error
705
731
  end
706
732
  end
733
+
734
+ context '.patch_user' do
735
+ it 'is expected to respond to a patch user method' do
736
+ expect(@instance).to respond_to(:patch_user)
737
+ end
738
+
739
+ it 'is expected to respond to a user patch method' do
740
+ expect(@instance).to receive(:patch).with(
741
+ USER_PATCH_PATH, {
742
+ loginId: 'name@mail.com',
743
+ email: 'name@mail.com',
744
+ givenName: 'mister',
745
+ name: 'something else',
746
+ test: false,
747
+ invite: false
748
+ }
749
+ )
750
+
751
+ expect do
752
+ @instance.patch_user(
753
+ login_id: 'name@mail.com',
754
+ email: 'name@mail.com',
755
+ given_name: 'mister',
756
+ name: 'something else'
757
+ )
758
+ end.not_to raise_error
759
+ end
760
+ end
761
+
762
+ context '.search_all_test_users' do
763
+ it 'is expected to respond to a search_all_test_users method' do
764
+ expect(@instance).to respond_to(:search_all_test_users)
765
+
766
+ tenant_role_ids = { 'tenant1' => ['roleA', 'roleB'] }
767
+ tenant_role_names = { 'tenant1' => ['roleName1', 'roleName2'] }
768
+ expect(@instance).to receive(:post).with(
769
+ TEST_USERS_SEARCH_PATH, {
770
+ tenantIds: %w[t1 t2],
771
+ roleNames: %w[r1 r2],
772
+ limit: 0,
773
+ page: 0,
774
+ testUsersOnly: true,
775
+ withTestUser: true,
776
+ tenantRoleIds: {
777
+ 'tenant1' => { values: ['roleA', 'roleB'] }
778
+ },
779
+ tenantRoleNames: {
780
+ 'tenant1' => { values: ['roleName1', 'roleName2'] }
781
+ }
782
+ }
783
+ )
784
+
785
+ expect do
786
+ @instance.search_all_test_users(
787
+ tenant_ids: %w[t1 t2],
788
+ role_names: %w[r1 r2],
789
+ tenant_role_ids: tenant_role_ids,
790
+ tenant_role_names: tenant_role_names
791
+ )
792
+ end.not_to raise_error
793
+ end
794
+ end
707
795
  end
@@ -31,12 +31,32 @@ describe Descope::Api::V1::Session do
31
31
  end
32
32
 
33
33
  it 'is expected to post refresh session' do
34
- jwt_response = { 'fake': 'response' }
35
- allow(@instance).to receive(:generate_jwt_response).and_return(jwt_response)
36
-
37
- expect(@instance).to receive(:post).with(REFRESH_TOKEN_PATH, {}, {}, 'refresh_token')
38
- allow(@instance).to receive(:validate_token).with('refresh_token', nil).and_return({})
39
- expect { @instance.refresh_session(refresh_token: 'refresh_token') }.not_to raise_error
34
+ jwt_response = {
35
+ 'sessionJwt' => 'fake_session_jwt',
36
+ 'refreshJwt' => 'fake_refresh_jwt',
37
+ 'cookies' => {
38
+ 'refresh_token' => 'fake_refresh_cookie'
39
+ }
40
+ }
41
+ refresh_token = 'refresh_token'
42
+ audience = nil
43
+
44
+ allow(@instance).to receive(:validate_refresh_token_not_nil).with(refresh_token).and_return(true)
45
+ allow(@instance).to receive(:validate_token).with(refresh_token, audience).and_return(true)
46
+ allow(@instance).to receive(:post).with(REFRESH_TOKEN_PATH, {}, {}, refresh_token).and_return(jwt_response)
47
+ refresh_cookie = jwt_response['cookies'][REFRESH_SESSION_COOKIE_NAME] || jwt_response['refreshJwt']
48
+
49
+ allow(@instance).to receive(:generate_jwt_response).with(
50
+ response_body: jwt_response,
51
+ refresh_cookie:,
52
+ audience:
53
+ ).and_return(jwt_response)
54
+
55
+ expect { @instance.refresh_session(refresh_token:, audience:) }.not_to raise_error
56
+
57
+ # Optionally verify the response if needed
58
+ result = @instance.refresh_session(refresh_token:, audience:)
59
+ expect(result).to eq(jwt_response)
40
60
  end
41
61
  end
42
62
 
@@ -114,4 +134,97 @@ describe Descope::Api::V1::Session do
114
134
  expect { @instance.validate_and_refresh_session(session_token: 'session_token', refresh_token: 'refresh_token') }.to_not raise_error
115
135
  end
116
136
  end
137
+
138
+ context 'cookie domain fix for refresh_session' do
139
+ let(:refresh_token) { 'test_refresh_token' }
140
+ let(:session_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.signature' }
141
+ let(:refresh_jwt) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.refresh_sig' }
142
+
143
+ context 'when using cookie-only tokens with custom domain' do
144
+ let(:cookie_only_response) do
145
+ {
146
+ 'userId' => 'test123',
147
+ 'cookieExpiration' => 1640704758,
148
+ 'cookieDomain' => 'dev.lulukuku.com',
149
+ 'cookies' => {
150
+ 'DS' => session_jwt,
151
+ 'DSR' => refresh_jwt
152
+ }
153
+ }
154
+ end
155
+
156
+ it 'extracts tokens from cookies when not in response body' do
157
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
158
+ allow(@instance).to receive(:validate_token).and_return({})
159
+ allow(@instance).to receive(:post).and_return(cookie_only_response)
160
+ allow(@instance).to receive(:generate_jwt_response).and_return(cookie_only_response)
161
+
162
+ expect { @instance.refresh_session(refresh_token: refresh_token) }.not_to raise_error
163
+
164
+ result = @instance.refresh_session(refresh_token: refresh_token)
165
+ expect(result).to eq(cookie_only_response)
166
+ end
167
+
168
+ it 'passes correct refresh_cookie to generate_jwt_response' do
169
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
170
+ allow(@instance).to receive(:validate_token).and_return({})
171
+ allow(@instance).to receive(:post).and_return(cookie_only_response)
172
+
173
+ # Verify that refresh_cookie is extracted correctly from cookies
174
+ expected_refresh_cookie = refresh_jwt
175
+ expect(@instance).to receive(:generate_jwt_response).with(
176
+ response_body: cookie_only_response,
177
+ refresh_cookie: expected_refresh_cookie,
178
+ audience: nil
179
+ ).and_return(cookie_only_response)
180
+
181
+ @instance.refresh_session(refresh_token: refresh_token)
182
+ end
183
+ end
184
+
185
+ context 'when using mixed configuration (some tokens in body, some in cookies)' do
186
+ let(:mixed_response) do
187
+ {
188
+ 'sessionJwt' => session_jwt, # Session token in response body
189
+ 'userId' => 'test123',
190
+ 'cookies' => {
191
+ 'DSR' => refresh_jwt # Refresh token in cookies only
192
+ }
193
+ }
194
+ end
195
+
196
+ it 'handles mixed token locations correctly' do
197
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
198
+ allow(@instance).to receive(:validate_token).and_return({})
199
+ allow(@instance).to receive(:post).and_return(mixed_response)
200
+ allow(@instance).to receive(:generate_jwt_response).and_return(mixed_response)
201
+
202
+ expect { @instance.refresh_session(refresh_token: refresh_token) }.not_to raise_error
203
+ end
204
+ end
205
+
206
+ context 'backward compatibility with traditional response body tokens' do
207
+ let(:traditional_response) do
208
+ {
209
+ 'sessionJwt' => session_jwt,
210
+ 'refreshJwt' => refresh_jwt,
211
+ 'userId' => 'test123'
212
+ }
213
+ end
214
+
215
+ it 'continues to work with response body tokens' do
216
+ allow(@instance).to receive(:validate_refresh_token_not_nil).and_return(true)
217
+ allow(@instance).to receive(:validate_token).and_return({})
218
+ allow(@instance).to receive(:post).and_return(traditional_response)
219
+
220
+ expect(@instance).to receive(:generate_jwt_response).with(
221
+ response_body: traditional_response,
222
+ refresh_cookie: refresh_jwt, # Should use refreshJwt from response body
223
+ audience: nil
224
+ ).and_return(traditional_response)
225
+
226
+ @instance.refresh_session(refresh_token: refresh_token)
227
+ end
228
+ end
229
+ end
117
230
  end