doorflow 1.0.1 → 1.1.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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +222 -316
  4. data/bin/post-generate +3 -1
  5. data/doorflow.gemspec +1 -1
  6. data/lib/doorflow-api/api/accounts_api.rb +1 -1
  7. data/lib/doorflow-api/api/admission_requests_api.rb +2 -2
  8. data/lib/doorflow-api/api/card_designs_api.rb +347 -0
  9. data/lib/doorflow-api/api/card_template_types_api.rb +350 -0
  10. data/lib/doorflow-api/api/card_templates_api.rb +811 -0
  11. data/lib/doorflow-api/api/channels_api.rb +11 -11
  12. data/lib/doorflow-api/api/credential_types_api.rb +1 -1
  13. data/lib/doorflow-api/api/credentials_api.rb +6 -6
  14. data/lib/doorflow-api/api/element_sessions_api.rb +90 -0
  15. data/lib/doorflow-api/api/events_api.rb +2 -2
  16. data/lib/doorflow-api/api/group_reservations_api.rb +68 -5
  17. data/lib/doorflow-api/api/groups_api.rb +1 -1
  18. data/lib/doorflow-api/api/notification_rules_api.rb +7 -7
  19. data/lib/doorflow-api/api/oauth_api.rb +1 -1
  20. data/lib/doorflow-api/api/people_api.rb +7 -7
  21. data/lib/doorflow-api/api/reservations_api.rb +5 -5
  22. data/lib/doorflow-api/api/roles_api.rb +1 -1
  23. data/lib/doorflow-api/api/sites_api.rb +1 -1
  24. data/lib/doorflow-api/api/sync_api.rb +4 -4
  25. data/lib/doorflow-api/api_client.rb +8 -4
  26. data/lib/doorflow-api/api_error.rb +9 -14
  27. data/lib/doorflow-api/api_model_base.rb +1 -1
  28. data/lib/doorflow-api/configuration.rb +1 -1
  29. data/lib/doorflow-api/models/account.rb +1 -1
  30. data/lib/doorflow-api/models/account_passport.rb +1 -1
  31. data/lib/doorflow-api/models/account_reseller.rb +1 -1
  32. data/lib/doorflow-api/models/account_sync.rb +1 -1
  33. data/lib/doorflow-api/models/account_user.rb +1 -1
  34. data/lib/doorflow-api/models/admission_request.rb +1 -1
  35. data/lib/doorflow-api/models/admission_request_door_controller.rb +1 -1
  36. data/lib/doorflow-api/models/admission_request_person.rb +1 -1
  37. data/lib/doorflow-api/models/admit_channel202_response.rb +1 -1
  38. data/lib/doorflow-api/models/admit_person202_response.rb +1 -1
  39. data/lib/doorflow-api/models/admit_person403_response.rb +1 -1
  40. data/lib/doorflow-api/models/admit_person_request.rb +1 -1
  41. data/lib/doorflow-api/models/auto_unlock_channel400_response.rb +1 -1
  42. data/lib/doorflow-api/models/auto_unlock_channel_request.rb +1 -1
  43. data/lib/doorflow-api/models/card_design.rb +243 -0
  44. data/lib/doorflow-api/models/card_design_image_back.rb +179 -0
  45. data/lib/doorflow-api/models/card_design_image_front.rb +179 -0
  46. data/lib/doorflow-api/models/card_design_input.rb +181 -0
  47. data/lib/doorflow-api/models/card_template.rb +475 -0
  48. data/lib/doorflow-api/models/card_template_input.rb +147 -0
  49. data/lib/doorflow-api/models/card_template_input_card_template.rb +347 -0
  50. data/lib/doorflow-api/models/card_template_type.rb +321 -0
  51. data/lib/doorflow-api/models/card_template_type_input.rb +147 -0
  52. data/lib/doorflow-api/models/card_template_type_input_card_template_type.rb +251 -0
  53. data/lib/doorflow-api/models/card_template_type_input_card_template_type_non_printable_zones_inner.rb +183 -0
  54. data/lib/doorflow-api/models/card_template_type_non_printable_zones_inner.rb +188 -0
  55. data/lib/doorflow-api/models/channel.rb +1 -1
  56. data/lib/doorflow-api/models/channel_auto_unlock.rb +1 -1
  57. data/lib/doorflow-api/models/channel_lockdown.rb +1 -1
  58. data/lib/doorflow-api/models/channel_lockdown_aux_lockdown.rb +1 -1
  59. data/lib/doorflow-api/models/channel_lockdown_card_lockdown.rb +1 -1
  60. data/lib/doorflow-api/models/channel_lockdown_rex_lockdown.rb +1 -1
  61. data/lib/doorflow-api/models/channel_mode.rb +1 -1
  62. data/lib/doorflow-api/models/channel_sync.rb +5 -2
  63. data/lib/doorflow-api/models/{create_credential422_response.rb → create_card_design422_response.rb} +5 -5
  64. data/lib/doorflow-api/models/{create_credential422_response_errors.rb → create_card_design422_response_errors.rb} +4 -4
  65. data/lib/doorflow-api/models/credential.rb +4 -4
  66. data/lib/doorflow-api/models/credential_input.rb +1 -1
  67. data/lib/doorflow-api/models/credential_input_person_credential.rb +1 -1
  68. data/lib/doorflow-api/models/credential_type.rb +1 -1
  69. data/lib/doorflow-api/models/credential_update_input.rb +1 -1
  70. data/lib/doorflow-api/models/credential_update_input_person_credential.rb +1 -1
  71. data/lib/doorflow-api/models/{delete_group_reservation200_response.rb → delete_card_design200_response.rb} +4 -4
  72. data/lib/doorflow-api/models/delete_card_template_type409_response.rb +147 -0
  73. data/lib/doorflow-api/models/element_session.rb +192 -0
  74. data/lib/doorflow-api/models/element_session_config.rb +265 -0
  75. data/lib/doorflow-api/models/element_session_error.rb +199 -0
  76. data/lib/doorflow-api/models/element_session_input.rb +271 -0
  77. data/lib/doorflow-api/models/element_session_member.rb +186 -0
  78. data/lib/doorflow-api/models/error.rb +1 -1
  79. data/lib/doorflow-api/models/event.rb +1 -1
  80. data/lib/doorflow-api/models/get_access_token200_response.rb +1 -1
  81. data/lib/doorflow-api/models/get_access_token400_response.rb +1 -1
  82. data/lib/doorflow-api/models/get_access_token401_response.rb +1 -1
  83. data/lib/doorflow-api/models/get_admission_request401_response.rb +1 -1
  84. data/lib/doorflow-api/models/get_admission_request403_response.rb +1 -1
  85. data/lib/doorflow-api/models/get_admission_request404_response.rb +1 -1
  86. data/lib/doorflow-api/models/get_admission_request500_response.rb +1 -1
  87. data/lib/doorflow-api/models/get_token_info200_response.rb +1 -1
  88. data/lib/doorflow-api/models/get_token_info200_response_application.rb +1 -1
  89. data/lib/doorflow-api/models/get_token_info401_response.rb +1 -1
  90. data/lib/doorflow-api/models/group.rb +1 -1
  91. data/lib/doorflow-api/models/group_reservation.rb +1 -1
  92. data/lib/doorflow-api/models/group_reservation_input.rb +1 -1
  93. data/lib/doorflow-api/models/group_reservation_input_group_reservation.rb +1 -1
  94. data/lib/doorflow-api/models/initiate_sync429_response.rb +1 -1
  95. data/lib/doorflow-api/models/linked_file.rb +217 -0
  96. data/lib/doorflow-api/models/list_events400_response.rb +1 -1
  97. data/lib/doorflow-api/models/lockdown_channel_request.rb +1 -1
  98. data/lib/doorflow-api/models/notification_rule.rb +1 -1
  99. data/lib/doorflow-api/models/notification_rule_actions_inner.rb +1 -1
  100. data/lib/doorflow-api/models/notification_rule_conditions_inner.rb +1 -1
  101. data/lib/doorflow-api/models/notification_rule_events_inner.rb +1 -1
  102. data/lib/doorflow-api/models/notification_rule_input.rb +1 -1
  103. data/lib/doorflow-api/models/person.rb +30 -8
  104. data/lib/doorflow-api/models/person_input.rb +1 -1
  105. data/lib/doorflow-api/models/reservation.rb +1 -1
  106. data/lib/doorflow-api/models/reservation_input.rb +1 -1
  107. data/lib/doorflow-api/models/revoke_token403_response.rb +1 -1
  108. data/lib/doorflow-api/models/role.rb +1 -1
  109. data/lib/doorflow-api/models/site.rb +1 -1
  110. data/lib/doorflow-api/models/site_site_ips_inner.rb +1 -1
  111. data/lib/doorflow-api/models/unlock_channel_request.rb +1 -1
  112. data/lib/doorflow-api/version.rb +2 -2
  113. data/lib/doorflow-api.rb +28 -9
  114. data/lib/doorflow.rb +7 -2
  115. metadata +43 -164
  116. data/spec/api/admission_requests_api_spec.rb +0 -47
  117. data/spec/api/channels_api_spec.rb +0 -174
  118. data/spec/api/credential_types_api_spec.rb +0 -49
  119. data/spec/api/group_reservations_api_spec.rb +0 -75
  120. data/spec/api/oauth_api_spec.rb +0 -97
  121. data/spec/doorflow/client_spec.rb +0 -109
  122. data/spec/doorflow/webhooks/event_spec.rb +0 -117
  123. data/spec/doorflow/webhooks/handler_spec.rb +0 -154
  124. data/spec/doorflow/webhooks/signature_verifier_spec.rb +0 -210
  125. data/spec/doorflow/webhooks/support.rb +0 -18
  126. data/spec/doorflow_spec.rb +0 -22
  127. data/spec/fixtures/vcr_cassettes/channel/list.yml +0 -70
  128. data/spec/fixtures/vcr_cassettes/channel/retrieve.yml +0 -131
  129. data/spec/fixtures/vcr_cassettes/person/auto_pagination.yml +0 -87
  130. data/spec/fixtures/vcr_cassettes/person/create.yml +0 -64
  131. data/spec/fixtures/vcr_cassettes/person/delete.yml +0 -125
  132. data/spec/fixtures/vcr_cassettes/person/list.yml +0 -90
  133. data/spec/fixtures/vcr_cassettes/person/not_found.yml +0 -62
  134. data/spec/fixtures/vcr_cassettes/person/retrieve.yml +0 -136
  135. data/spec/fixtures/vcr_cassettes/person/update.yml +0 -260
  136. data/spec/fixtures/vcr_cassettes/person/validation_error.yml +0 -62
  137. data/spec/integration/channel_spec.rb +0 -88
  138. data/spec/integration/person_spec.rb +0 -99
  139. data/spec/models/account_passport_spec.rb +0 -42
  140. data/spec/models/account_reseller_spec.rb +0 -60
  141. data/spec/models/account_sync_spec.rb +0 -52
  142. data/spec/models/account_user_spec.rb +0 -54
  143. data/spec/models/admission_request_door_controller_spec.rb +0 -42
  144. data/spec/models/admission_request_person_spec.rb +0 -42
  145. data/spec/models/admission_request_spec.rb +0 -82
  146. data/spec/models/admit_channel202_response_spec.rb +0 -46
  147. data/spec/models/admit_person202_response_spec.rb +0 -46
  148. data/spec/models/admit_person403_response_spec.rb +0 -42
  149. data/spec/models/admit_person_request_spec.rb +0 -36
  150. data/spec/models/auto_unlock_channel400_response_spec.rb +0 -42
  151. data/spec/models/auto_unlock_channel_request_spec.rb +0 -36
  152. data/spec/models/channel_auto_unlock_spec.rb +0 -42
  153. data/spec/models/channel_lockdown_aux_lockdown_spec.rb +0 -36
  154. data/spec/models/channel_lockdown_card_lockdown_spec.rb +0 -36
  155. data/spec/models/channel_lockdown_rex_lockdown_spec.rb +0 -36
  156. data/spec/models/channel_lockdown_spec.rb +0 -54
  157. data/spec/models/channel_mode_spec.rb +0 -52
  158. data/spec/models/channel_sync_spec.rb +0 -46
  159. data/spec/models/create_credential422_response_errors_spec.rb +0 -48
  160. data/spec/models/create_credential422_response_spec.rb +0 -42
  161. data/spec/models/credential_input_person_credential_spec.rb +0 -48
  162. data/spec/models/credential_input_spec.rb +0 -36
  163. data/spec/models/credential_spec.rb +0 -86
  164. data/spec/models/credential_update_input_person_credential_spec.rb +0 -36
  165. data/spec/models/credential_update_input_spec.rb +0 -36
  166. data/spec/models/delete_group_reservation200_response_spec.rb +0 -36
  167. data/spec/models/error_spec.rb +0 -42
  168. data/spec/models/get_access_token200_response_spec.rb +0 -66
  169. data/spec/models/get_access_token400_response_spec.rb +0 -46
  170. data/spec/models/get_access_token401_response_spec.rb +0 -42
  171. data/spec/models/get_admission_request401_response_spec.rb +0 -42
  172. data/spec/models/get_admission_request403_response_spec.rb +0 -42
  173. data/spec/models/get_admission_request404_response_spec.rb +0 -42
  174. data/spec/models/get_admission_request500_response_spec.rb +0 -42
  175. data/spec/models/get_token_info200_response_application_spec.rb +0 -36
  176. data/spec/models/get_token_info200_response_spec.rb +0 -66
  177. data/spec/models/get_token_info401_response_spec.rb +0 -42
  178. data/spec/models/group_reservation_input_group_reservation_spec.rb +0 -54
  179. data/spec/models/group_reservation_input_spec.rb +0 -36
  180. data/spec/models/group_reservation_spec.rb +0 -82
  181. data/spec/models/initiate_sync429_response_spec.rb +0 -52
  182. data/spec/models/list_events400_response_spec.rb +0 -42
  183. data/spec/models/lockdown_channel_request_spec.rb +0 -48
  184. data/spec/models/notification_rule_actions_inner_spec.rb +0 -48
  185. data/spec/models/notification_rule_conditions_inner_spec.rb +0 -48
  186. data/spec/models/notification_rule_events_inner_spec.rb +0 -42
  187. data/spec/models/notification_rule_input_spec.rb +0 -60
  188. data/spec/models/person_input_spec.rb +0 -180
  189. data/spec/models/reservation_input_spec.rb +0 -60
  190. data/spec/models/revoke_token403_response_spec.rb +0 -42
  191. data/spec/models/site_site_ips_inner_spec.rb +0 -36
  192. data/spec/models/unlock_channel_request_spec.rb +0 -36
  193. data/spec/resources/credential_bugs_spec.rb +0 -115
  194. data/spec/spec_helper.rb +0 -160
@@ -1,97 +0,0 @@
1
- =begin
2
- #DoorFlow API
3
-
4
- #Access control and door management API for DoorFlow
5
-
6
- The version of the OpenAPI document: 3.0
7
-
8
- Generated by: https://openapi-generator.tech
9
- Generator version: 7.18.0-SNAPSHOT
10
-
11
- =end
12
-
13
- require 'spec_helper'
14
- require 'json'
15
-
16
- # Unit tests for DoorFlow::OauthApi
17
- # Automatically generated by openapi-generator (https://openapi-generator.tech)
18
- # Please update as you see appropriate
19
- describe 'OauthApi' do
20
- before do
21
- # run before each test
22
- @api_instance = DoorFlow::OauthApi.new
23
- end
24
-
25
- after do
26
- # run after each test
27
- end
28
-
29
- describe 'test an instance of OauthApi' do
30
- it 'should create an instance of OauthApi' do
31
- expect(@api_instance).to be_instance_of(DoorFlow::OauthApi)
32
- end
33
- end
34
-
35
- # unit tests for authorize_o_auth
36
- # Authorization endpoint
37
- # Initiates the OAuth 2.0 authorization code flow. Direct users to this endpoint to request authorization. **Authorization Code Flow Steps:** 1. Direct the user's browser to this endpoint with required parameters 2. User logs in (if not already authenticated) and authorizes your application 3. User is redirected to your `redirect_uri` with an authorization `code` parameter 4. Exchange the code for an access token at `/oauth/token` **PKCE (Proof Key for Code Exchange):** For public clients (mobile/SPA applications), PKCE is recommended for security: 1. Generate a random `code_verifier` (43-128 characters) 2. Create `code_challenge` = BASE64URL(SHA256(code_verifier)) 3. Include `code_challenge` and `code_challenge_method=S256` in this request 4. Include `code_verifier` when exchanging the code at `/oauth/token` **Important Notes:** - This is a browser-based flow - users will see a login/authorization screen - The `redirect_uri` must be pre-registered with your OAuth application - The authorization code expires after 10 minutes - Some applications may be configured to skip the authorization screen (auto-approve)
38
- # @param response_type OAuth response type (must be \"code\" for authorization code flow)
39
- # @param client_id Your OAuth application's client ID
40
- # @param redirect_uri URI to redirect to after authorization (must match registered redirect URI)
41
- # @param scope Space-separated list of requested scopes (see available scopes in security schemes section)
42
- # @param [Hash] opts the optional parameters
43
- # @option opts [String] :state Opaque value to maintain state between request and callback (recommended for CSRF protection)
44
- # @option opts [String] :code_challenge PKCE code challenge (BASE64URL(SHA256(code_verifier)))
45
- # @option opts [String] :code_challenge_method PKCE code challenge method (must be S256)
46
- # @return [nil]
47
- describe 'authorize_o_auth test' do
48
- it 'should work' do
49
- # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
50
- end
51
- end
52
-
53
- # unit tests for get_access_token
54
- # Obtain access token
55
- # Exchange authorization code for an access token, or refresh an existing token. **Authorization Code Flow:** 1. Direct users to `/oauth/authorize` with your client_id, redirect_uri, and requested scopes 2. User authorizes your application 3. You receive an authorization code at your redirect_uri 4. Exchange the code for an access token using this endpoint with `grant_type=authorization_code` **Refreshing Tokens:** - Use `grant_type=refresh_token` with a valid refresh_token to obtain a new access token - Access tokens expire after 1 hour - Refresh tokens are long-lived and should be stored securely
56
- # @param grant_type OAuth grant type (use \\\"authorization_code\\\" to exchange an authorization code, or \\\"refresh_token\\\" to refresh an access token)
57
- # @param [Hash] opts the optional parameters
58
- # @option opts [String] :client_id OAuth client ID (required for all grant types)
59
- # @option opts [String] :client_secret OAuth client secret (required for confidential clients)
60
- # @option opts [String] :code Authorization code (required when grant_type=authorization_code)
61
- # @option opts [String] :redirect_uri Redirect URI (required when grant_type=authorization_code, must match the one used in authorization request)
62
- # @option opts [String] :code_verifier PKCE code verifier (required if PKCE was used in authorization request)
63
- # @option opts [String] :refresh_token Refresh token (required when grant_type=refresh_token)
64
- # @option opts [String] :scope Space-separated list of requested scopes (optional, defaults to application's configured scopes)
65
- # @return [GetAccessToken200Response]
66
- describe 'get_access_token test' do
67
- it 'should work' do
68
- # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
69
- end
70
- end
71
-
72
- # unit tests for get_token_info
73
- # Get token information
74
- # Returns information about the current access token being used for authentication. This endpoint provides metadata about the access token including the resource owner, account, scopes, and expiration. **Authentication:** Requires a valid OAuth2 access token in the Authorization header.
75
- # @param [Hash] opts the optional parameters
76
- # @return [GetTokenInfo200Response]
77
- describe 'get_token_info test' do
78
- it 'should work' do
79
- # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
80
- end
81
- end
82
-
83
- # unit tests for revoke_token
84
- # Revoke access token or refresh token
85
- # Revokes an access token or refresh token, immediately invalidating it and blocking associated tokens from making further requests. **When to revoke:** - User disconnects their DoorFlow account from your integration - User logs out - Security breach - revoke compromised tokens - Application uninstall **Important:** - **We recommend revoking the refresh_token** - this will invalidate all associated access tokens - Returns HTTP 200 even if the token was already invalid (prevents token scanning attacks) - Client authentication is required via HTTP Basic Auth header **Authentication:** You must authenticate using HTTP Basic Authentication with your client credentials: 1. Combine your client_id and client_secret as: `{client_id}:{client_secret}` 2. Encode using URL-safe Base64 3. Include in Authorization header: `Authorization: Basic {encoded_credentials}`
86
- # @param authorization HTTP Basic Authentication using client_id:client_secret encoded with URL-safe Base64
87
- # @param token The access token or refresh token to revoke (we recommend using refresh_token)
88
- # @param [Hash] opts the optional parameters
89
- # @option opts [String] :token_type_hint Optional hint about the type of token being revoked. We recommend using 'refresh_token' for all revocations as it invalidates all associated tokens. Invalid token type hint values are ignored and do not affect the revocation response.
90
- # @return [Object]
91
- describe 'revoke_token test' do
92
- it 'should work' do
93
- # assertion here. ref: https://rspec.info/features/3-12/rspec-expectations/built-in-matchers/
94
- end
95
- end
96
-
97
- end
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe DoorFlow::Client do
6
- describe 'resource instance methods use instance client' do
7
- it 'Person#update uses the instance client, not class-level client' do
8
- # Create a mock people_api
9
- people_api = double('people_api')
10
- allow(people_api).to receive(:update_person).and_return(
11
- double('response', to_hash: { 'id' => 123, 'first_name' => 'Updated' })
12
- )
13
-
14
- # Create a client with the mock
15
- auth = double('auth', access_token: 'instance_token', authenticated?: true)
16
- client = DoorFlow::Client.new(auth: auth)
17
- allow(client).to receive(:people_api).and_return(people_api)
18
-
19
- # Create a person as if retrieved via client.people.retrieve
20
- person = DoorFlow::Resources::Person.new({ 'id' => 123, 'first_name' => 'Test' })
21
- person.instance_variable_set(:@_client, client)
22
-
23
- # Call update - this should use the instance client's people_api
24
- person.update(first_name: 'Updated')
25
-
26
- # Verify the instance client's people_api was called, not class-level
27
- expect(people_api).to have_received(:update_person).with(123, { first_name: 'Updated' })
28
- end
29
- end
30
-
31
-
32
- describe '#access_token' do
33
- context 'with an auth object' do
34
- it 'delegates to auth.access_token' do
35
- auth = double('auth', access_token: 'token_from_auth')
36
- client = DoorFlow::Client.new(auth: auth)
37
-
38
- expect(client.access_token).to eq('token_from_auth')
39
- end
40
- end
41
-
42
- context 'with a static access token' do
43
- it 'returns the static token' do
44
- client = DoorFlow::Client.new(access_token: 'static_token')
45
-
46
- expect(client.access_token).to eq('static_token')
47
- end
48
- end
49
- end
50
-
51
- describe '#authenticated?' do
52
- context 'with an auth object that responds to authenticated?' do
53
- it 'delegates to auth.authenticated?' do
54
- auth = double('auth', authenticated?: true)
55
- client = DoorFlow::Client.new(auth: auth)
56
-
57
- expect(client.authenticated?).to be true
58
- end
59
- end
60
-
61
- context 'with a static access token' do
62
- it 'returns true when token is present' do
63
- client = DoorFlow::Client.new(access_token: 'token')
64
- # The SDK uses .present? which requires ActiveSupport
65
- # In non-Rails context, we just check if it doesn't raise
66
- expect { client.authenticated? }.not_to raise_error
67
- end
68
-
69
- it 'returns false when token is nil' do
70
- client = DoorFlow::Client.new(access_token: nil)
71
-
72
- expect(client.authenticated?).to be false
73
- end
74
- end
75
- end
76
-
77
- describe 'dynamic token refresh' do
78
- it 'fetches fresh token on each API call, not cached at client creation' do
79
- call_count = 0
80
- tokens = ['first_token', 'refreshed_token']
81
-
82
- # Auth object that returns different tokens on each call (simulating refresh)
83
- auth = double('auth')
84
- allow(auth).to receive(:access_token) do
85
- token = tokens[call_count] || tokens.last
86
- call_count += 1
87
- token
88
- end
89
- allow(auth).to receive(:authenticated?).and_return(true)
90
-
91
- client = DoorFlow::Client.new(auth: auth)
92
-
93
- # Access the api_client to trigger config setup
94
- api_client = client.send(:api_client)
95
- config = api_client.config
96
-
97
- # First call should get first token
98
- first_token = config.access_token_with_refresh
99
- expect(first_token).to eq('first_token')
100
-
101
- # Second call should get refreshed token (not cached first token)
102
- second_token = config.access_token_with_refresh
103
- expect(second_token).to eq('refreshed_token')
104
-
105
- # Verify auth.access_token was called multiple times
106
- expect(call_count).to be >= 2
107
- end
108
- end
109
- end
@@ -1,117 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe DoorFlow::Webhooks::Event do
6
- let(:event_data) do
7
- {
8
- action: 'CREATE',
9
- resource: 'https://api.doorflow.com/api/v1/accounts/1/events/42',
10
- resource_type: 'Event',
11
- resource_id: 42,
12
- account_id: 1,
13
- ack_token: 'abc123'
14
- }
15
- end
16
-
17
- subject(:event) { described_class.new(event_data) }
18
-
19
- describe '#initialize' do
20
- it 'sets all attributes from the data hash' do
21
- expect(event.action).to eq('CREATE')
22
- expect(event.resource_url).to eq('https://api.doorflow.com/api/v1/accounts/1/events/42')
23
- expect(event.resource_type).to eq('Event')
24
- expect(event.resource_id).to eq('42')
25
- expect(event.account_id).to eq(1)
26
- expect(event.ack_token).to eq('abc123')
27
- end
28
-
29
- it 'converts resource_id to string' do
30
- expect(event.resource_id).to be_a(String)
31
- end
32
-
33
- it 'handles nil resource_id' do
34
- data = event_data.merge(resource_id: nil)
35
- event = described_class.new(data)
36
- expect(event.resource_id).to be_nil
37
- end
38
- end
39
-
40
- describe '#pattern' do
41
- it 'returns ResourceType.ACTION format' do
42
- expect(event.pattern).to eq('Event.CREATE')
43
- end
44
-
45
- it 'handles different resource types' do
46
- data = event_data.merge(resource_type: 'PersonCredential', action: 'UPDATE')
47
- event = described_class.new(data)
48
- expect(event.pattern).to eq('PersonCredential.UPDATE')
49
- end
50
- end
51
-
52
- describe '#create?' do
53
- it 'returns true for CREATE action' do
54
- expect(event.create?).to be true
55
- end
56
-
57
- it 'returns false for other actions' do
58
- data = event_data.merge(action: 'DELETE')
59
- expect(described_class.new(data).create?).to be false
60
- end
61
- end
62
-
63
- describe '#update?' do
64
- it 'returns true for UPDATE action' do
65
- data = event_data.merge(action: 'UPDATE')
66
- expect(described_class.new(data).update?).to be true
67
- end
68
-
69
- it 'returns false for other actions' do
70
- expect(event.update?).to be false
71
- end
72
- end
73
-
74
- describe '#delete?' do
75
- it 'returns true for DELETE action' do
76
- data = event_data.merge(action: 'DELETE')
77
- expect(described_class.new(data).delete?).to be true
78
- end
79
-
80
- it 'returns false for other actions' do
81
- expect(event.delete?).to be false
82
- end
83
- end
84
-
85
- describe '#to_h' do
86
- it 'returns a hash with all attributes' do
87
- expected = {
88
- action: 'CREATE',
89
- resource: 'https://api.doorflow.com/api/v1/accounts/1/events/42',
90
- resource_type: 'Event',
91
- resource_id: '42',
92
- account_id: 1,
93
- ack_token: 'abc123'
94
- }
95
- expect(event.to_h).to eq(expected)
96
- end
97
-
98
- it 'round-trips through Event.new' do
99
- reconstructed = described_class.new(event.to_h)
100
- expect(reconstructed.to_h).to eq(event.to_h)
101
- end
102
- end
103
-
104
- describe '#to_s' do
105
- it 'includes pattern and resource_id' do
106
- expect(event.to_s).to eq('Event.CREATE (42)')
107
- end
108
- end
109
-
110
- describe '#inspect' do
111
- it 'includes class name, pattern, and resource_id' do
112
- expect(event.inspect).to include('DoorFlow::Webhooks::Event')
113
- expect(event.inspect).to include('Event.CREATE')
114
- expect(event.inspect).to include('42')
115
- end
116
- end
117
- end
@@ -1,154 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require_relative 'support'
5
-
6
- RSpec.describe DoorFlow::Webhooks::Handler do
7
- include WebhookSpecSupport
8
-
9
- let(:secret) { WebhookSpecSupport::WEBHOOK_SECRET }
10
- let(:timestamp) { Time.now.to_i.to_s }
11
- let(:event_hash) { WebhookSpecSupport::SAMPLE_EVENT_HASH }
12
- let(:payload) { JSON.generate([event_hash]) }
13
-
14
- let(:signature) { compute_signature(timestamp, payload, secret) }
15
-
16
- subject(:handler) { described_class.new(secret: secret) }
17
-
18
- describe '#on' do
19
- it 'returns self for method chaining' do
20
- result = handler.on('Event.CREATE') { |e| }
21
- expect(result).to be(handler)
22
- end
23
-
24
- it 'supports chained registrations' do
25
- result = handler
26
- .on('Event.CREATE') { |e| }
27
- .on('Person.UPDATE') { |e| }
28
- .on('*') { |e| }
29
-
30
- expect(result).to be(handler)
31
- end
32
-
33
- it 'raises ArgumentError without a block' do
34
- expect { handler.on('Event.CREATE') }.to raise_error(ArgumentError, /Block required/)
35
- end
36
- end
37
-
38
- describe '#handle' do
39
- it 'dispatches events to matching handlers' do
40
- received = []
41
- handler.on('Event.CREATE') { |event| received << event }
42
-
43
- handler.handle(payload: payload, signature: signature, timestamp: timestamp)
44
-
45
- expect(received.length).to eq(1)
46
- expect(received.first.pattern).to eq('Event.CREATE')
47
- end
48
-
49
- it 'does not dispatch to non-matching handlers' do
50
- received = []
51
- handler.on('Person.UPDATE') { |event| received << event }
52
-
53
- handler.handle(payload: payload, signature: signature, timestamp: timestamp)
54
-
55
- expect(received).to be_empty
56
- end
57
-
58
- it 'dispatches to wildcard handler' do
59
- received = []
60
- handler.on('*') { |event| received << event }
61
-
62
- handler.handle(payload: payload, signature: signature, timestamp: timestamp)
63
-
64
- expect(received.length).to eq(1)
65
- end
66
-
67
- it 'dispatches to resource wildcard handler' do
68
- received = []
69
- handler.on('Event.*') { |event| received << event }
70
-
71
- handler.handle(payload: payload, signature: signature, timestamp: timestamp)
72
-
73
- expect(received.length).to eq(1)
74
- end
75
-
76
- it 'does not match wrong resource wildcard' do
77
- received = []
78
- handler.on('Person.*') { |event| received << event }
79
-
80
- handler.handle(payload: payload, signature: signature, timestamp: timestamp)
81
-
82
- expect(received).to be_empty
83
- end
84
-
85
- it 'dispatches to multiple matching handlers' do
86
- exact = []
87
- wildcard = []
88
- handler.on('Event.CREATE') { |event| exact << event }
89
- handler.on('*') { |event| wildcard << event }
90
-
91
- handler.handle(payload: payload, signature: signature, timestamp: timestamp)
92
-
93
- expect(exact.length).to eq(1)
94
- expect(wildcard.length).to eq(1)
95
- end
96
-
97
- it 'returns the parsed events' do
98
- events = handler.handle(payload: payload, signature: signature, timestamp: timestamp)
99
-
100
- expect(events).to be_an(Array)
101
- expect(events.length).to eq(1)
102
- expect(events.first).to be_a(DoorFlow::Webhooks::Event)
103
- end
104
- end
105
-
106
- describe '#handle_request' do
107
- context 'with a Rails-style request' do
108
- it 'extracts Signature and Timestamp headers' do
109
- headers = {
110
- 'Signature' => signature,
111
- 'Timestamp' => timestamp
112
- }
113
- request = double('rails_request',
114
- raw_post: payload,
115
- headers: headers
116
- )
117
-
118
- received = []
119
- handler.on('Event.CREATE') { |event| received << event }
120
- handler.handle_request(request)
121
-
122
- expect(received.length).to eq(1)
123
- expect(received.first.pattern).to eq('Event.CREATE')
124
- end
125
- end
126
-
127
- context 'with a Rack-style request' do
128
- it 'extracts headers from the env hash' do
129
- body = double('body')
130
- allow(body).to receive(:read).and_return(payload)
131
-
132
- request = double('rack_request',
133
- body: body,
134
- env: {
135
- 'HTTP_SIGNATURE' => signature,
136
- 'HTTP_TIMESTAMP' => timestamp
137
- }
138
- )
139
- # Rack request doesn't respond to raw_post or headers
140
- allow(request).to receive(:respond_to?).with(:raw_post).and_return(false)
141
- allow(request).to receive(:respond_to?).with(:body).and_return(true)
142
- allow(request).to receive(:respond_to?).with(:headers).and_return(false)
143
- allow(request).to receive(:respond_to?).with(:env).and_return(true)
144
-
145
- received = []
146
- handler.on('Event.CREATE') { |event| received << event }
147
- handler.handle_request(request)
148
-
149
- expect(received.length).to eq(1)
150
- expect(received.first.pattern).to eq('Event.CREATE')
151
- end
152
- end
153
- end
154
- end
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
- require_relative 'support'
5
-
6
- RSpec.describe DoorFlow::Webhooks::SignatureVerifier do
7
- include WebhookSpecSupport
8
-
9
- let(:secret) { WebhookSpecSupport::WEBHOOK_SECRET }
10
- let(:timestamp) { Time.now.to_i.to_s }
11
- let(:event_hash) { WebhookSpecSupport::SAMPLE_EVENT_HASH }
12
-
13
- describe '.verify' do
14
- context 'with a root-level array payload (documented format)' do
15
- let(:payload) { JSON.generate([event_hash]) }
16
- let(:signature) { compute_signature(timestamp, payload, secret) }
17
-
18
- it 'returns an array of Event objects' do
19
- events = described_class.verify(
20
- payload: payload,
21
- signature: signature,
22
- timestamp: timestamp,
23
- secret: secret
24
- )
25
-
26
- expect(events).to be_an(Array)
27
- expect(events.length).to eq(1)
28
- expect(events.first).to be_a(DoorFlow::Webhooks::Event)
29
- expect(events.first.resource_type).to eq('Event')
30
- expect(events.first.action).to eq('CREATE')
31
- end
32
- end
33
-
34
- context 'with multiple events in an array' do
35
- let(:second_event) do
36
- {
37
- action: 'UPDATE',
38
- resource: 'https://api.doorflow.com/api/v1/accounts/1/people/99',
39
- resource_type: 'Person',
40
- resource_id: 99,
41
- account_id: 1,
42
- ack_token: 'def456'
43
- }
44
- end
45
- let(:payload) { JSON.generate([event_hash, second_event]) }
46
- let(:signature) { compute_signature(timestamp, payload, secret) }
47
-
48
- it 'returns all events' do
49
- events = described_class.verify(
50
- payload: payload,
51
- signature: signature,
52
- timestamp: timestamp,
53
- secret: secret
54
- )
55
-
56
- expect(events.length).to eq(2)
57
- expect(events[0].pattern).to eq('Event.CREATE')
58
- expect(events[1].pattern).to eq('Person.UPDATE')
59
- end
60
- end
61
-
62
- context 'with a single event object payload' do
63
- let(:payload) { JSON.generate(event_hash) }
64
- let(:signature) { compute_signature(timestamp, payload, secret) }
65
-
66
- it 'wraps the single event in an array' do
67
- events = described_class.verify(
68
- payload: payload,
69
- signature: signature,
70
- timestamp: timestamp,
71
- secret: secret
72
- )
73
-
74
- expect(events.length).to eq(1)
75
- expect(events.first.pattern).to eq('Event.CREATE')
76
- end
77
- end
78
-
79
- context 'with an events-key payload (backwards compat)' do
80
- let(:payload) { JSON.generate({ events: [event_hash] }) }
81
- let(:signature) { compute_signature(timestamp, payload, secret) }
82
-
83
- it 'extracts events from the events key' do
84
- events = described_class.verify(
85
- payload: payload,
86
- signature: signature,
87
- timestamp: timestamp,
88
- secret: secret
89
- )
90
-
91
- expect(events.length).to eq(1)
92
- expect(events.first.pattern).to eq('Event.CREATE')
93
- end
94
- end
95
-
96
- context 'with an invalid signature' do
97
- let(:payload) { JSON.generate([event_hash]) }
98
-
99
- it 'raises SignatureError' do
100
- expect {
101
- described_class.verify(
102
- payload: payload,
103
- signature: 'invalid_signature_value',
104
- timestamp: timestamp,
105
- secret: secret
106
- )
107
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Signature mismatch/)
108
- end
109
- end
110
-
111
- context 'with an expired timestamp' do
112
- let(:old_timestamp) { (Time.now.to_i - 600).to_s }
113
- let(:payload) { JSON.generate([event_hash]) }
114
- let(:signature) { compute_signature(old_timestamp, payload, secret) }
115
-
116
- it 'raises SignatureError' do
117
- expect {
118
- described_class.verify(
119
- payload: payload,
120
- signature: signature,
121
- timestamp: old_timestamp,
122
- secret: secret
123
- )
124
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /too old/)
125
- end
126
- end
127
-
128
- context 'with tolerance disabled' do
129
- let(:old_timestamp) { (Time.now.to_i - 600).to_s }
130
- let(:payload) { JSON.generate([event_hash]) }
131
- let(:signature) { compute_signature(old_timestamp, payload, secret) }
132
-
133
- it 'does not check timestamp' do
134
- events = described_class.verify(
135
- payload: payload,
136
- signature: signature,
137
- timestamp: old_timestamp,
138
- secret: secret,
139
- tolerance: 0
140
- )
141
-
142
- expect(events.length).to eq(1)
143
- end
144
- end
145
-
146
- context 'with missing inputs' do
147
- let(:payload) { JSON.generate([event_hash]) }
148
- let(:signature) { compute_signature(timestamp, payload, secret) }
149
-
150
- it 'raises SignatureError for missing payload' do
151
- expect {
152
- described_class.verify(payload: nil, signature: signature, timestamp: timestamp, secret: secret)
153
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Missing payload/)
154
- end
155
-
156
- it 'raises SignatureError for empty payload' do
157
- expect {
158
- described_class.verify(payload: '', signature: signature, timestamp: timestamp, secret: secret)
159
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Missing payload/)
160
- end
161
-
162
- it 'raises SignatureError for missing signature' do
163
- expect {
164
- described_class.verify(payload: payload, signature: nil, timestamp: timestamp, secret: secret)
165
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Missing signature/)
166
- end
167
-
168
- it 'raises SignatureError for missing timestamp' do
169
- expect {
170
- described_class.verify(payload: payload, signature: signature, timestamp: nil, secret: secret)
171
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Missing timestamp/)
172
- end
173
-
174
- it 'raises SignatureError for missing secret' do
175
- expect {
176
- described_class.verify(payload: payload, signature: signature, timestamp: timestamp, secret: nil)
177
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Missing secret/)
178
- end
179
- end
180
-
181
- context 'with malformed JSON' do
182
- let(:payload) { 'not valid json{{{' }
183
- let(:signature) { compute_signature(timestamp, payload, secret) }
184
-
185
- it 'raises SignatureError' do
186
- expect {
187
- described_class.verify(
188
- payload: payload,
189
- signature: signature,
190
- timestamp: timestamp,
191
- secret: secret
192
- )
193
- }.to raise_error(DoorFlow::Webhooks::SignatureError, /Invalid JSON/)
194
- end
195
- end
196
- end
197
-
198
- describe '.compute_signature' do
199
- it 'returns a hex-encoded HMAC-SHA256 digest' do
200
- sig = described_class.compute_signature('12345', '{"test":true}', 'secret')
201
- expect(sig).to match(/\A[a-f0-9]{64}\z/)
202
- end
203
-
204
- it 'uses timestamp.payload format' do
205
- expected = OpenSSL::HMAC.hexdigest('SHA256', 'secret', '12345.{"test":true}')
206
- sig = described_class.compute_signature('12345', '{"test":true}', 'secret')
207
- expect(sig).to eq(expected)
208
- end
209
- end
210
- end