descope 1.0.7 → 1.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yaml +51 -12
  3. data/.github/workflows/publish-gem.yaml +6 -33
  4. data/.github/workflows/release-please.yaml +36 -0
  5. data/.gitignore +3 -2
  6. data/.release-please-manifest.json +1 -1
  7. data/.rubocop.yml +1 -1
  8. data/.rubocop_todo.yml +1 -1
  9. data/.ruby-version +1 -1
  10. data/CHANGELOG.md +31 -0
  11. data/Gemfile +7 -6
  12. data/Gemfile.lock +46 -45
  13. data/README.md +244 -1
  14. data/examples/ruby-on-rails-api/descope/package-lock.json +16 -10
  15. data/examples/ruby-on-rails-api/descope/yarn.lock +3 -3
  16. data/lib/descope/api/v1/auth/enchantedlink.rb +76 -9
  17. data/lib/descope/api/v1/management/access_key.rb +8 -7
  18. data/lib/descope/api/v1/management/analytics.rb +62 -0
  19. data/lib/descope/api/v1/management/audit.rb +2 -2
  20. data/lib/descope/api/v1/management/authz.rb +15 -15
  21. data/lib/descope/api/v1/management/common.rb +76 -0
  22. data/lib/descope/api/v1/management/descoper.rb +45 -0
  23. data/lib/descope/api/v1/management/engine.rb +44 -0
  24. data/lib/descope/api/v1/management/fga.rb +64 -0
  25. data/lib/descope/api/v1/management/flow.rb +5 -5
  26. data/lib/descope/api/v1/management/group.rb +42 -0
  27. data/lib/descope/api/v1/management/jwt_template.rb +49 -0
  28. data/lib/descope/api/v1/management/lists.rb +96 -0
  29. data/lib/descope/api/v1/management/management_key.rb +54 -0
  30. data/lib/descope/api/v1/management/outbound_app.rb +112 -0
  31. data/lib/descope/api/v1/management/password.rb +2 -2
  32. data/lib/descope/api/v1/management/permission.rb +4 -4
  33. data/lib/descope/api/v1/management/project.rb +5 -5
  34. data/lib/descope/api/v1/management/role.rb +5 -5
  35. data/lib/descope/api/v1/management/scim.rb +14 -14
  36. data/lib/descope/api/v1/management/scope_claim_mapping.rb +34 -0
  37. data/lib/descope/api/v1/management/sso_application.rb +7 -7
  38. data/lib/descope/api/v1/management/sso_settings.rb +5 -5
  39. data/lib/descope/api/v1/management/tenant.rb +8 -6
  40. data/lib/descope/api/v1/management/third_party_application.rb +190 -0
  41. data/lib/descope/api/v1/management/user.rb +36 -36
  42. data/lib/descope/api/v1/management.rb +22 -0
  43. data/lib/descope/http_client.rb +30 -0
  44. data/lib/descope/mixins/common.rb +1 -0
  45. data/lib/descope/mixins/http.rb +25 -18
  46. data/lib/descope/mixins/initializer.rb +23 -11
  47. data/lib/descope/mixins/requests.rb +24 -0
  48. data/lib/descope/mixins.rb +2 -1
  49. data/lib/descope/version.rb +1 -1
  50. data/spec/integration/lib.descope/api/v1/auth/enchantedlink_spec.rb +19 -21
  51. data/spec/integration/lib.descope/api/v1/auth/magiclink_spec.rb +5 -1
  52. data/spec/integration/lib.descope/api/v1/auth/otp_spec.rb +5 -1
  53. data/spec/integration/lib.descope/api/v1/auth/session_spec.rb +24 -5
  54. data/spec/integration/lib.descope/api/v1/auth/totp_spec.rb +5 -1
  55. data/spec/integration/lib.descope/api/v1/management/access_key_spec.rb +10 -2
  56. data/spec/integration/lib.descope/api/v1/management/authz_spec.rb +26 -5
  57. data/spec/integration/lib.descope/api/v1/management/permissions_spec.rb +20 -2
  58. data/spec/integration/lib.descope/api/v1/management/project_spec.rb +16 -2
  59. data/spec/integration/lib.descope/api/v1/management/roles_spec.rb +114 -36
  60. data/spec/integration/lib.descope/api/v1/management/user_spec.rb +21 -4
  61. data/spec/lib.descope/api/v1/auth/enchantedlink_spec.rb +163 -1
  62. data/spec/lib.descope/api/v1/management/access_key_spec.rb +7 -7
  63. data/spec/lib.descope/api/v1/management/analytics_spec.rb +82 -0
  64. data/spec/lib.descope/api/v1/management/audit_spec.rb +2 -2
  65. data/spec/lib.descope/api/v1/management/authz_spec.rb +16 -16
  66. data/spec/lib.descope/api/v1/management/descoper_spec.rb +101 -0
  67. data/spec/lib.descope/api/v1/management/engine_spec.rb +115 -0
  68. data/spec/lib.descope/api/v1/management/fga_spec.rb +169 -0
  69. data/spec/lib.descope/api/v1/management/flow_spec.rb +5 -5
  70. data/spec/lib.descope/api/v1/management/group_spec.rb +72 -0
  71. data/spec/lib.descope/api/v1/management/jwt_template_spec.rb +103 -0
  72. data/spec/lib.descope/api/v1/management/lists_spec.rb +285 -0
  73. data/spec/lib.descope/api/v1/management/management_key_spec.rb +109 -0
  74. data/spec/lib.descope/api/v1/management/outbound_app_spec.rb +236 -0
  75. data/spec/lib.descope/api/v1/management/password_spec.rb +2 -2
  76. data/spec/lib.descope/api/v1/management/permission_spec.rb +4 -4
  77. data/spec/lib.descope/api/v1/management/project_spec.rb +4 -4
  78. data/spec/lib.descope/api/v1/management/role_spec.rb +5 -5
  79. data/spec/lib.descope/api/v1/management/scim_spec.rb +14 -14
  80. data/spec/lib.descope/api/v1/management/scope_claim_mapping_spec.rb +59 -0
  81. data/spec/lib.descope/api/v1/management/sso_application_spec.rb +6 -6
  82. data/spec/lib.descope/api/v1/management/sso_settings_spec.rb +5 -5
  83. data/spec/lib.descope/api/v1/management/tenant_spec.rb +6 -6
  84. data/spec/lib.descope/api/v1/management/third_party_application_spec.rb +171 -0
  85. data/spec/lib.descope/api/v1/management/user_spec.rb +36 -36
  86. data/spec/lib.descope/auth_management_key_spec.rb +151 -0
  87. data/spec/lib.descope/mixins/http_spec.rb +17 -6
  88. data/spec/support/client_config.rb +1 -0
  89. data/spec/support/dummy_class.rb +12 -9
  90. data/spec/support/utils.rb +15 -0
  91. metadata +29 -3
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'descope/http_client'
4
5
 
5
6
  module Descope
6
7
  module Mixins
7
8
  # Helper class for initializing the Descope API
8
9
  module Initializer
9
10
  attr_accessor :public_keys, :mlock
11
+ attr_reader :base_uri, :headers
10
12
 
11
13
  def initialize(config)
12
14
  options = Hash[config.map { |(k, v)| [k.to_sym, v] }]
@@ -16,8 +18,10 @@ module Descope
16
18
  @headers['x-descope-project-id'] = @project_id
17
19
  @public_key = options[:public_key] || ENV['DESCOPE_PUBLIC_KEY']
18
20
  @mlock = Mutex.new
19
- log_level = options[:log_level] || ENV['DESCOPE_LOG_LEVEL'] || 'info'
20
- @logger ||= Descope::Mixins::Logging.logger_for(self.class.name, log_level, @project_id)
21
+ @logger = options[:logger] || begin
22
+ log_level = options[:log_level] || ENV['DESCOPE_LOG_LEVEL'] || 'info'
23
+ Descope::Mixins::Logging.logger_for(self.class.name, log_level, @project_id)
24
+ end
21
25
 
22
26
  @logger.debug("Initializing Descope API with project_id: #{@project_id} and base_uri: #{@base_uri}")
23
27
 
@@ -31,7 +35,10 @@ module Descope
31
35
  @skip_verify = options[:skip_verify]
32
36
  @secure = !@skip_verify
33
37
  @management_key = options[:management_key] || ENV['DESCOPE_MANAGEMENT_KEY']
34
- @logger.debug("Management Key ID: #{@management_key}")
38
+ # Sent with every authentication request so that methods whose public access has been
39
+ # disabled can still be used. Never sent on management requests, and can - and probably
40
+ # should - be a different management key than @management_key.
41
+ @auth_management_key = options[:auth_management_key] || ENV['DESCOPE_AUTH_MANAGEMENT_KEY']
35
42
  @timeout_seconds = options[:timeout_seconds] || Common::DEFAULT_TIMEOUT_SECONDS
36
43
  @jwt_validation_leeway = options[:jwt_validation_leeway] || Common::DEFAULT_JWT_VALIDATION_LEEWAY
37
44
 
@@ -58,16 +65,21 @@ module Descope
58
65
 
59
66
  end
60
67
 
61
- def authorization_header(pswd = nil)
62
- pswd = @default_pswd if pswd.nil? || pswd.empty?
63
- bearer = "#{@project_id}:#{pswd}"
64
- add_headers('Authorization' => "Bearer #{bearer}")
65
- end
66
-
67
68
  def initialize_api(options)
68
69
  initialize_v1(options)
69
- @default_pswd = options.fetch(:management_key, ENV['DESCOPE_MANAGEMENT_KEY'])
70
- authorization_header
70
+ @auth_http = http_client(@auth_management_key)
71
+ @mgmt_http = http_client(@management_key)
72
+ end
73
+
74
+ def http_client(key)
75
+ HttpClient.new(
76
+ base_uri: @base_uri,
77
+ project_id: @project_id,
78
+ key: key,
79
+ headers: @headers,
80
+ logger: @logger,
81
+ timeout: @timeout_seconds
82
+ )
71
83
  end
72
84
 
73
85
  def initialize_v1(_options)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'descope/mixins/http'
4
+
5
+ module Descope
6
+ module Mixins
7
+ # Routes an API call to the HTTP client that holds the right key: the bare verbs serve the
8
+ # authentication APIs and go out with the auth management key, the mgmt_ prefixed verbs serve
9
+ # the management APIs and go out with the management key.
10
+ module Requests
11
+ attr_reader :auth_http, :mgmt_http
12
+
13
+ Descope::Mixins::HTTP::HTTP_METHODS.each do |method|
14
+ define_method(method) do |*args|
15
+ @auth_http.public_send(method, *args)
16
+ end
17
+
18
+ define_method(:"mgmt_#{method}") do |*args|
19
+ @mgmt_http.public_send(method, *args)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -4,6 +4,7 @@ require 'logger'
4
4
  require 'jwt'
5
5
  require 'descope/mixins/headers'
6
6
  require 'descope/mixins/http'
7
+ require 'descope/mixins/requests'
7
8
  require 'descope/mixins/initializer'
8
9
  require 'descope/mixins/validation'
9
10
  require 'descope/mixins/logging'
@@ -15,7 +16,7 @@ module Descope
15
16
  module Mixins
16
17
  include Descope::Mixins::Common
17
18
  include Descope::Mixins::Headers
18
- include Descope::Mixins::HTTP
19
+ include Descope::Mixins::Requests
19
20
  include Descope::Mixins::Initializer
20
21
  include Descope::Mixins::Logging
21
22
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  # Current version of gem
4
4
  module Descope
5
- VERSION = '1.0.7'
5
+ VERSION = '1.2.0'
6
6
  SDK_VERSION = '1.0.0'
7
7
  end
@@ -3,31 +3,25 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  def poll_for_session(descope_client, pending_ref)
6
- max_tries = 15
7
- i = 0
8
- done = false
9
- while !done && i < max_tries
6
+ @client.logger.info('Waiting for session to be created...')
7
+
8
+ SpecUtils.wait_for_condition(max_wait: 60, interval: 3, description: 'enchanted link session') do
10
9
  begin
11
- i += 1
12
- @client.logger.info('waiting 4 seconds for session to be created...')
13
- sleep(4)
14
- print '.'
15
10
  @client.logger.info("Getting session for pending_ref: #{pending_ref}...")
16
11
  jwt_response = descope_client.enchanted_link_get_session(pending_ref)
17
- done = true
12
+
13
+ if jwt_response
14
+ @client.logger.info("jwt_response: #{jwt_response}")
15
+ refresh_token = jwt_response[Descope::Mixins::Common::REFRESH_SESSION_TOKEN_NAME]['jwt']
16
+ @client.logger.info("refresh_token: #{refresh_token}")
17
+ return refresh_token
18
+ end
19
+
20
+ false
18
21
  rescue Descope::AuthException, Descope::Unauthorized => e
19
- @client.logger.info("Failed pending session, err: #{e}")
20
- nil
22
+ @client.logger.info("Waiting for session, err: #{e}")
23
+ false
21
24
  end
22
-
23
- next unless jwt_response
24
-
25
- @client.logger.info("jwt_response: #{jwt_response}")
26
- refresh_token = jwt_response[Descope::Mixins::Common::REFRESH_SESSION_TOKEN_NAME]['jwt']
27
-
28
- @client.logger.info("refresh_token: #{refresh_token}")
29
- done = true
30
- return refresh_token
31
25
  end
32
26
  end
33
27
 
@@ -62,7 +56,11 @@ describe Descope::Api::V1::Auth::EnchantedLink do
62
56
  all_users['users'].each do |user|
63
57
  if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
64
58
  @client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
65
- @client.delete_user(user['loginIds'][0])
59
+ begin
60
+ @client.delete_user(user['loginIds'][0])
61
+ rescue Descope::NotFound => e
62
+ @client.logger.info("User already deleted: #{e.message}")
63
+ end
66
64
  end
67
65
  end
68
66
  end
@@ -13,7 +13,11 @@ describe Descope::Api::V1::Auth::MagicLink do
13
13
  all_users['users'].each do |user|
14
14
  if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
15
15
  @client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
16
- @client.delete_user(user['loginIds'][0])
16
+ begin
17
+ @client.delete_user(user['loginIds'][0])
18
+ rescue Descope::NotFound => e
19
+ @client.logger.info("User already deleted: #{e.message}")
20
+ end
17
21
  end
18
22
  end
19
23
  end
@@ -21,7 +21,11 @@ describe Descope::Api::V1::Auth::OTP do
21
21
  all_users['users'].each do |user|
22
22
  if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
23
23
  @client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
24
- @client.delete_user(user['loginIds'][0])
24
+ begin
25
+ @client.delete_user(user['loginIds'][0])
26
+ rescue Descope::NotFound => e
27
+ @client.logger.info("User already deleted: #{e.message}")
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -5,6 +5,20 @@ require 'spec_helper'
5
5
  describe Descope::Api::V1::Session do
6
6
  before(:all) do
7
7
  @client = DescopeClient.new(Configuration.config)
8
+
9
+ # Cleanup any existing test users before starting
10
+ @client.logger.info('Cleaning up any existing test users before starting...')
11
+ all_users = @client.search_all_users
12
+ all_users['users'].each do |user|
13
+ if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
14
+ @client.logger.info("Deleting existing ruby spec test user #{user['loginIds'][0]}")
15
+ begin
16
+ @client.delete_user(user['loginIds'][0])
17
+ rescue Descope::NotFound => e
18
+ @client.logger.info("User already deleted: #{e.message}")
19
+ end
20
+ end
21
+ end
8
22
  end
9
23
 
10
24
  after(:all) do
@@ -13,7 +27,11 @@ describe Descope::Api::V1::Session do
13
27
  all_users['users'].each do |user|
14
28
  if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
15
29
  @client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
16
- @client.delete_user(user['loginIds'][0])
30
+ begin
31
+ @client.delete_user(user['loginIds'][0])
32
+ rescue Descope::NotFound => e
33
+ @client.logger.info("User already deleted: #{e.message}")
34
+ end
17
35
  end
18
36
  end
19
37
  end
@@ -21,9 +39,10 @@ describe Descope::Api::V1::Session do
21
39
  context 'test session methods' do
22
40
  it 'should refresh session with refresh token' do
23
41
  @password = SpecUtils.generate_password
24
- user = build(:user)
42
+ # Add timestamp to ensure unique login_id across multiple test runs
43
+ user = build(:user, login_id: "session_test_#{Time.now.to_i}_#{SecureRandom.hex(4)}")
25
44
 
26
- @client.logger.info('1. Sign up with password')
45
+ @client.logger.info("1. Sign up with password for user: #{user[:login_id]}")
27
46
  res = @client.password_sign_up(login_id: user[:login_id], password: @password, user:)
28
47
  @client.logger.info("sign up with password res: #{res}")
29
48
  original_refresh_token = res[REFRESH_SESSION_TOKEN_NAME]['jwt']
@@ -32,8 +51,8 @@ describe Descope::Api::V1::Session do
32
51
  login_res = @client.password_sign_in(login_id: user[:login_id], password: @password)
33
52
  @client.logger.info("sign_in res: #{login_res}")
34
53
 
35
- @client.logger.info('3. sleep 1 second before calling refresh_session')
36
- sleep(1)
54
+ @client.logger.info('3. Wait briefly to ensure token timestamps differ')
55
+ sleep(2) # Wait 2 seconds to ensure new token will have different 'iat' timestamp
37
56
 
38
57
  @client.logger.info('4. Refresh session')
39
58
  refresh_session_res = @client.refresh_session(refresh_token: login_res[REFRESH_SESSION_TOKEN_NAME]['jwt'])
@@ -14,7 +14,11 @@ describe Descope::Api::V1::Auth::TOTP do
14
14
  all_users['users'].each do |user|
15
15
  if user['middleName'] == "#{SpecUtils.build_prefix}Ruby-SDK-User"
16
16
  @client.logger.info("Deleting ruby spec test user #{user['loginIds'][0]}")
17
- @client.delete_user(user['loginIds'][0])
17
+ begin
18
+ @client.delete_user(user['loginIds'][0])
19
+ rescue Descope::NotFound => e
20
+ @client.logger.info("User already deleted: #{e.message}")
21
+ end
18
22
  end
19
23
  end
20
24
  end
@@ -30,8 +30,16 @@ describe Descope::Api::V1::Management::AccessKey do
30
30
  @tenant_id = @client.create_tenant(name: 'some-new-tenant')['id']
31
31
  @client.logger.info('creating access key')
32
32
  @access_key = @client.create_access_key(name: @key_name, key_tenants: [{ tenant_id: @tenant_id }])
33
- @client.logger.info("waiting for access key #{@access_key['key']['id']} to be active 60 seconds")
34
- sleep 60
33
+ @client.logger.info("waiting for access key #{@access_key['key']['id']} to be active")
34
+ SpecUtils.wait_for_condition(max_wait: 65, interval: 3, description: 'access key to be active') do
35
+ begin
36
+ key = @client.load_access_key(@access_key['key']['id'])
37
+ key['key']['status'] == 'active'
38
+ rescue StandardError => e
39
+ @client.logger.info("Waiting for key activation: #{e.message}")
40
+ false
41
+ end
42
+ end
35
43
  end
36
44
 
37
45
  it 'should create the access key and load it' do
@@ -12,7 +12,12 @@ describe Descope::Api::V1::Management::Authz do
12
12
 
13
13
  context 'authz ops' do
14
14
  before(:all) do
15
- @client.authz_delete_schema
15
+ # Instead of deleting the entire schema, we'll work with it
16
+ # This allows parallel test execution
17
+ # Only delete schema if we're running in isolation
18
+ if ENV['AUTHZ_FULL_TEST'] == 'true'
19
+ @client.authz_delete_schema
20
+ end
16
21
  end
17
22
 
18
23
  it 'should create a new schema' do
@@ -160,13 +165,17 @@ describe Descope::Api::V1::Management::Authz do
160
165
  end
161
166
 
162
167
  it 'should create a relation between a resource and user' do
168
+ # Use unique resource name to avoid conflicts between parallel tests
169
+ unique_resource = "#{SpecUtils.build_prefix}some-doc"
170
+ unique_user = "#{SpecUtils.build_prefix}user1"
171
+
163
172
  @client.authz_create_relations(
164
173
  [
165
174
  {
166
- "resource": 'some-doc',
175
+ "resource": unique_resource,
167
176
  "relationDefinition": 'owner',
168
177
  "namespace": 'note',
169
- "target": 'user1'
178
+ "target": unique_user
170
179
  }
171
180
  ]
172
181
  )
@@ -176,14 +185,26 @@ describe Descope::Api::V1::Management::Authz do
176
185
  relations = @client.authz_has_relations?(
177
186
  [
178
187
  {
179
- "resource": 'some-doc',
188
+ "resource": unique_resource,
180
189
  "relationDefinition": 'viewer',
181
190
  "namespace": 'note',
182
- "target": 'user1'
191
+ "target": unique_user
183
192
  }
184
193
  ]
185
194
  )
186
195
  expect(relations['relationQueries'][0]['hasRelation']).to be_truthy
196
+
197
+ # Clean up the test relations
198
+ @client.authz_delete_relations(
199
+ [
200
+ {
201
+ "resource": unique_resource,
202
+ "relationDefinition": 'owner',
203
+ "namespace": 'note',
204
+ "target": unique_user
205
+ }
206
+ ]
207
+ )
187
208
  end
188
209
  end
189
210
  end
@@ -7,16 +7,34 @@ describe Descope::Api::V1::Management::Permission do
7
7
  raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
8
8
 
9
9
  @client = DescopeClient.new(Configuration.config)
10
+ @test_description = "#{SpecUtils.build_prefix} Ruby SDK"
11
+
12
+ # Cleanup any leftover permissions from previous failed runs
10
13
  @client.load_all_permissions['permissions'].each do |perm|
11
- if perm['description'] == "#{SpecUtils.build_prefix} Ruby SDK"
14
+ if perm['description'] == @test_description
12
15
  puts "Deleting permission: #{perm['name']}"
13
16
  @client.delete_permission(perm['name'])
14
17
  end
15
18
  end
16
19
  end
20
+
21
+ after(:all) do
22
+ # Safety cleanup to ensure no test permissions are left behind
23
+ @client.logger.info('Cleaning up test permissions...')
24
+ begin
25
+ @client.load_all_permissions['permissions'].each do |perm|
26
+ if perm['description'] == @test_description
27
+ @client.logger.info("Deleting leftover permission: #{perm['name']}")
28
+ @client.delete_permission(perm['name'])
29
+ end
30
+ end
31
+ rescue StandardError => e
32
+ @client.logger.info("Error during permission cleanup: #{e.message}")
33
+ end
34
+ end
17
35
 
18
36
  it 'should create update and delete a permission' do
19
- @client.create_permission(name: 'test_permission', description: "#{SpecUtils.build_prefix} Ruby SDK")
37
+ @client.create_permission(name: 'test_permission', description: @test_description)
20
38
  all_permissions = @client.load_all_permissions['permissions']
21
39
  expect(all_permissions.any? { |perm| perm['name'] == 'test_permission' }).to eq(true)
22
40
  @client.update_permission(name: 'test_permission', new_name: 'test_permission_2')
@@ -8,15 +8,29 @@ describe Descope::Api::V1::Management::Project do
8
8
 
9
9
  @client = DescopeClient.new(Configuration.config)
10
10
  @export_output = @client.export_project
11
+ @original_project_name = nil
11
12
  end
12
13
 
13
14
  context 'Test project methods' do
15
+ before(:all) do
16
+ # Get the current project name before we modify it
17
+ # Store it so we can restore it later
18
+ begin
19
+ @original_project_name = @client.export_project['project']['name']
20
+ rescue StandardError
21
+ @original_project_name = 'Ruby-SDK-Prod'
22
+ end
23
+ end
24
+
14
25
  after(:all) do
15
- @client.rename_project('Ruby-SDK-Prod')
26
+ # Restore the original project name
27
+ @client.rename_project(@original_project_name) if @original_project_name
16
28
  end
17
29
 
18
30
  it 'should rename a project' do
19
- @client.rename_project('TEST-Ruby-SDK-Prod')
31
+ # Use a unique name based on build prefix to avoid conflicts
32
+ unique_name = "#{SpecUtils.build_prefix}TEST-Ruby-SDK-Prod"
33
+ @client.rename_project(unique_name)
20
34
  end
21
35
 
22
36
  it 'should export a project' do
@@ -7,74 +7,144 @@ describe Descope::Api::V1::Management::Role do
7
7
  raise 'DESCOPE_MANAGEMENT_KEY is not set' if ENV['DESCOPE_MANAGEMENT_KEY'].nil?
8
8
 
9
9
  @client = DescopeClient.new(Configuration.config)
10
- @client.logger.info('Staring cleanup before tests...')
11
- @client.logger.info('Deleting all permissions for Ruby SDK...')
10
+ @test_prefix = SpecUtils.build_prefix
11
+ @client.logger.info("Starting cleanup before tests with prefix: #{@test_prefix}...")
12
+
13
+ # Define resource names with unique prefix for parallel execution
14
+ @permission_viewer = "#{@test_prefix}viewer"
15
+ @permission_editor = "#{@test_prefix}editor"
16
+ @permission_admin = "#{@test_prefix}admin"
17
+ @role_viewer = "#{@test_prefix}Ruby-SDK-test-viewer"
18
+ @role_editor = "#{@test_prefix}Ruby-SDK-test-editor"
19
+ @role_admin = "#{@test_prefix}Ruby-SDK-test-admin"
20
+ @tenant_name = "#{@test_prefix}Ruby-SDK-test"
21
+ @description = "#{@test_prefix}Ruby SDK"
22
+
23
+ # Cleanup any leftover resources from previous failed runs
24
+ @client.logger.info('Deleting all permissions for this test run...')
12
25
  @client.load_all_permissions['permissions'].each do |perm|
13
- if perm['description'] =~ /Ruby SDK/
26
+ if perm['description'] == @description
14
27
  @client.logger.info("Deleting permission: #{perm['name']}")
15
28
  @client.delete_permission(perm['name'])
16
29
  end
17
30
  end
18
31
 
19
- @client.logger.info('Deleting all roles for Ruby SDK...')
32
+ @client.logger.info('Deleting all roles for this test run...')
20
33
  @client.load_all_roles['roles'].each do |role|
21
- puts "got role: #{role}"
22
- if role['description'] == 'Ruby SDK'
34
+ if role['description'] == @description
23
35
  @client.logger.info("Deleting role: #{role['name']}")
24
36
  @client.delete_role(name: role['name'], tenant_id: role['tenantId'])
25
37
  end
26
38
  end
27
39
 
28
- @client.logger.info('Deleting all tenants for Ruby SDK...')
29
- @client.search_all_tenants(names: ['Ruby-SDK-test'])['tenants'].each do |tenant|
40
+ @client.logger.info('Deleting all tenants for this test run...')
41
+ @client.search_all_tenants(names: [@tenant_name])['tenants'].each do |tenant|
30
42
  @client.logger.info("Deleting tenant: #{tenant['name']}")
31
43
  @client.delete_tenant(tenant['id'])
32
44
  end
33
45
  @client.logger.info('Cleanup completed. Starting tests...')
34
46
  end
47
+
48
+ after(:all) do
49
+ # Cleanup after tests to ensure no resources are left behind
50
+ @client.logger.info('Cleaning up test resources...')
51
+
52
+ begin
53
+ @client.delete_permission(@permission_viewer) if defined?(@permission_viewer)
54
+ rescue StandardError => e
55
+ @client.logger.info("Permission #{@permission_viewer} already deleted or doesn't exist: #{e.message}")
56
+ end
57
+
58
+ begin
59
+ @client.delete_permission(@permission_editor) if defined?(@permission_editor)
60
+ rescue StandardError => e
61
+ @client.logger.info("Permission #{@permission_editor} already deleted or doesn't exist: #{e.message}")
62
+ end
63
+
64
+ begin
65
+ @client.delete_permission(@permission_admin) if defined?(@permission_admin)
66
+ rescue StandardError => e
67
+ @client.logger.info("Permission #{@permission_admin} already deleted or doesn't exist: #{e.message}")
68
+ end
69
+
70
+ # Delete any roles with our test description
71
+ begin
72
+ @client.load_all_roles['roles'].each do |role|
73
+ if role['description'] == @description
74
+ @client.delete_role(name: role['name'], tenant_id: role['tenantId'])
75
+ end
76
+ end
77
+ rescue StandardError => e
78
+ @client.logger.info("Error cleaning up roles: #{e.message}")
79
+ end
80
+
81
+ # Delete tenant
82
+ begin
83
+ @client.search_all_tenants(names: [@tenant_name])['tenants'].each do |tenant|
84
+ @client.delete_tenant(tenant['id'])
85
+ end
86
+ rescue StandardError => e
87
+ @client.logger.info("Error cleaning up tenant: #{e.message}")
88
+ end
89
+
90
+ @client.logger.info('Cleanup completed.')
91
+ end
35
92
 
36
93
  it 'should create update and delete a role' do
37
94
  @client.logger.info('Testing role creation, update, deletion and search...')
38
95
 
39
96
  # Create permissions
40
97
  @client.logger.info('creating viewer permission for role')
41
- @client.create_permission(name: 'viewer', description: 'Viewer Permission Ruby SDK')
98
+ @client.create_permission(name: @permission_viewer, description: @description)
42
99
 
43
100
  @client.logger.info('creating editor permission for role')
44
- @client.create_permission(name: 'editor', description: 'Editor Permission Ruby SDK')
101
+ @client.create_permission(name: @permission_editor, description: @description)
45
102
 
46
103
  @client.logger.info('creating admin permission for role')
47
- @client.create_permission(name: 'admin', description: 'Admin Permission Ruby SDK')
104
+ @client.create_permission(name: @permission_admin, description: @description)
48
105
 
49
106
  # Create tenants
50
- @client.logger.info('creating Ruby-SDK-test tenant')
51
- tenant_id = @client.create_tenant(name: 'Ruby-SDK-test')['id']
107
+ @client.logger.info("creating #{@tenant_name} tenant")
108
+ tenant_id = @client.create_tenant(name: @tenant_name)['id']
109
+ @client.logger.info("Created tenant with id: #{tenant_id}")
110
+
111
+ # Wait for tenant to be available (polling, up to 5 seconds)
112
+ timeout = 5.0
113
+ interval = 0.1
114
+ waited = 0.0
115
+ loop do
116
+ tenants = @client.search_all_tenants(names: [@tenant_name])['tenants']
117
+ break if tenants.any? { |t| t['id'] == tenant_id }
118
+ raise "Tenant #{@tenant_name} not available after #{timeout} seconds" if waited >= timeout
119
+ sleep(interval)
120
+ waited += interval
121
+ end
52
122
 
53
123
  # Create roles
54
- @client.logger.info('creating Ruby-SDK-test role')
55
- @client.create_role(name: 'Ruby-SDK-test-viewer', description: 'Ruby SDK', permission_names: ['viewer'])
56
- @client.logger.info('creating Ruby-SDK-test-admin role')
57
- @client.create_role(name: 'Ruby-SDK-test-admin', description: 'Ruby SDK', permission_names: ['admin'], tenant_id:)
124
+ @client.logger.info("creating #{@role_viewer} role")
125
+ @client.create_role(name: @role_viewer, description: @description, permission_names: [@permission_viewer])
126
+ @client.logger.info("creating #{@role_admin} role")
127
+ @client.create_role(name: @role_admin, description: @description, permission_names: [@permission_admin], tenant_id:)
58
128
 
59
129
  # check all roles matching the correct permission
60
130
  @client.logger.info('check all roles matching the correct permission (load roles)')
61
131
  roles = @client.load_all_roles['roles']
62
132
  roles.each do |role|
63
- expect(role['permissionNames']).to include('viewer') if role['name'] == 'Ruby-SDK-test-viewer'
64
- expect(role['permissionNames']).to include('admin') if role['name'] == 'Ruby-SDK-test-admin'
133
+ expect(role['permissionNames']).to include(@permission_viewer) if role['name'] == @role_viewer
134
+ expect(role['permissionNames']).to include(@permission_admin) if role['name'] == @role_admin
65
135
  end
66
136
 
67
137
  @client.logger.info('updating role')
68
138
  @client.update_role(
69
- name: 'Ruby-SDK-test-viewer',
70
- new_name: 'Ruby-SDK-test-editor',
71
- description: 'Ruby SDK',
72
- permission_names: ['editor']
139
+ name: @role_viewer,
140
+ new_name: @role_editor,
141
+ description: @description,
142
+ permission_names: [@permission_editor]
73
143
  )
74
144
 
75
145
  @client.logger.info('searching for roles by role names...')
76
- all_roles = @client.search_roles(role_names: %w[Ruby-SDK-test-admin Ruby-SDK-test-editor])['roles']
77
- expected_roles = %w[Ruby-SDK-test-editor Ruby-SDK-test-admin]
146
+ all_roles = @client.search_roles(role_names: [@role_admin, @role_editor])['roles']
147
+ expected_roles = [@role_editor, @role_admin]
78
148
  role_count = 0
79
149
  expected_roles.each do |expected_role|
80
150
  expect(all_roles.map { |role| role['name'] }).to include(expected_role)
@@ -83,8 +153,8 @@ describe Descope::Api::V1::Management::Role do
83
153
  expect(role_count).to eq(2)
84
154
 
85
155
  @client.logger.info('searching for roles with role name like...')
86
- all_roles = @client.search_roles(role_name_like: 'Ruby-SDK-test')['roles']
87
- expected_roles = %w[Ruby-SDK-test-editor Ruby-SDK-test-admin]
156
+ all_roles = @client.search_roles(role_name_like: "#{@test_prefix}Ruby-SDK-test")['roles']
157
+ expected_roles = [@role_editor, @role_admin]
88
158
  role_count = 0
89
159
  expected_roles.each do |expected_role|
90
160
  expect(all_roles.map { |role| role['name'] }).to include(expected_role)
@@ -94,25 +164,33 @@ describe Descope::Api::V1::Management::Role do
94
164
  expect(role_count).to eq(2)
95
165
 
96
166
  @client.logger.info('searching for roles with permission names...')
97
- all_roles = @client.search_roles(permission_names: %w[admin])['roles']
98
- expect(all_roles.map { |role| role['name'] }).to include('Ruby-SDK-test-admin')
167
+ all_roles = @client.search_roles(permission_names: [@permission_admin])['roles']
168
+ expect(all_roles.map { |role| role['name'] }).to include(@role_admin)
99
169
 
100
170
  @client.logger.info('searching for roles with tenant ids...')
101
- all_roles = @client.search_roles(role_name_like: 'Ruby-SDK-test', tenant_ids: [tenant_id])['roles']
102
- expect(all_roles.map { |role| role['name'] }).to include('Ruby-SDK-test-admin')
171
+ all_roles = @client.search_roles(role_name_like: "#{@test_prefix}Ruby-SDK-test", tenant_ids: [tenant_id])['roles']
172
+ expect(all_roles.map { |role| role['name'] }).to include(@role_admin)
103
173
 
104
174
  @client.logger.info('deleting permission')
105
175
 
106
- @client.delete_permission('editor')
107
- @client.delete_permission('admin')
176
+ @client.delete_permission(@permission_editor)
177
+ @client.delete_permission(@permission_admin)
108
178
 
109
179
  @client.logger.info('deleting editor role')
110
- @client.delete_role(name: 'Ruby-SDK-test-editor')
180
+ @client.delete_role(name: @role_editor)
111
181
 
112
182
  @client.logger.info('deleting admin role')
113
- @client.delete_role(name: 'Ruby-SDK-test-admin', tenant_id:)
183
+ begin
184
+ @client.delete_role(name: @role_admin, tenant_id:)
185
+ rescue Descope::Unauthorized, Descope::NotFound => e
186
+ @client.logger.info("Admin role already deleted or tenant invalid: #{e.message}")
187
+ end
114
188
 
115
189
  @client.logger.info('deleting tenant')
116
- @client.delete_tenant(tenant_id)
190
+ begin
191
+ @client.delete_tenant(tenant_id)
192
+ rescue Descope::NotFound => e
193
+ @client.logger.info("Tenant already deleted: #{e.message}")
194
+ end
117
195
  end
118
196
  end