files.com 1.1.767 → 1.1.769

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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +47 -7
  4. data/_VERSION +1 -1
  5. data/docs/event_record.md +2 -2
  6. data/docs/event_subscription.md +8 -8
  7. data/docs/file.md +37 -0
  8. data/docs/public_key.md +1 -1
  9. data/lib/files.com/api.rb +9 -4
  10. data/lib/files.com/api_client.rb +181 -55
  11. data/lib/files.com/list.rb +98 -66
  12. data/lib/files.com/models/action_log.rb +2 -2
  13. data/lib/files.com/models/action_notification_export.rb +3 -3
  14. data/lib/files.com/models/action_notification_export_result.rb +2 -2
  15. data/lib/files.com/models/ai_assistant_personality.rb +10 -8
  16. data/lib/files.com/models/ai_task.rb +10 -8
  17. data/lib/files.com/models/api_key.rb +12 -10
  18. data/lib/files.com/models/api_request_log.rb +2 -2
  19. data/lib/files.com/models/app.rb +2 -2
  20. data/lib/files.com/models/as2_incoming_message.rb +2 -2
  21. data/lib/files.com/models/as2_outgoing_message.rb +2 -2
  22. data/lib/files.com/models/as2_partner.rb +10 -8
  23. data/lib/files.com/models/as2_station.rb +10 -8
  24. data/lib/files.com/models/automation.rb +12 -10
  25. data/lib/files.com/models/automation_log.rb +2 -2
  26. data/lib/files.com/models/automation_run.rb +6 -6
  27. data/lib/files.com/models/bandwidth_snapshot.rb +2 -2
  28. data/lib/files.com/models/behavior.rb +12 -10
  29. data/lib/files.com/models/bundle.rb +10 -8
  30. data/lib/files.com/models/bundle_action.rb +2 -2
  31. data/lib/files.com/models/bundle_download.rb +2 -2
  32. data/lib/files.com/models/bundle_notification.rb +10 -8
  33. data/lib/files.com/models/bundle_recipient.rb +4 -4
  34. data/lib/files.com/models/bundle_registration.rb +2 -2
  35. data/lib/files.com/models/chat_session.rb +3 -3
  36. data/lib/files.com/models/child_site_management_policy.rb +10 -8
  37. data/lib/files.com/models/clickwrap.rb +10 -8
  38. data/lib/files.com/models/custom_domain.rb +11 -9
  39. data/lib/files.com/models/desktop_configuration_profile.rb +10 -8
  40. data/lib/files.com/models/dns_record.rb +2 -2
  41. data/lib/files.com/models/email_incoming_message.rb +2 -2
  42. data/lib/files.com/models/email_log.rb +2 -2
  43. data/lib/files.com/models/event_channel.rb +10 -8
  44. data/lib/files.com/models/event_delivery_attempt.rb +3 -3
  45. data/lib/files.com/models/event_record.rb +4 -4
  46. data/lib/files.com/models/event_subscription.rb +14 -12
  47. data/lib/files.com/models/event_target.rb +10 -8
  48. data/lib/files.com/models/exavault_api_request_log.rb +2 -2
  49. data/lib/files.com/models/expectation.rb +11 -9
  50. data/lib/files.com/models/expectation_evaluation.rb +3 -3
  51. data/lib/files.com/models/expectation_incident.rb +6 -6
  52. data/lib/files.com/models/external_event.rb +5 -5
  53. data/lib/files.com/models/file.rb +23 -15
  54. data/lib/files.com/models/file_comment.rb +9 -7
  55. data/lib/files.com/models/file_comment_reaction.rb +2 -2
  56. data/lib/files.com/models/file_migration.rb +1 -1
  57. data/lib/files.com/models/file_migration_log.rb +2 -2
  58. data/lib/files.com/models/folder.rb +3 -3
  59. data/lib/files.com/models/form_field_set.rb +10 -8
  60. data/lib/files.com/models/ftp_action_log.rb +2 -2
  61. data/lib/files.com/models/gpg_key.rb +10 -8
  62. data/lib/files.com/models/group.rb +10 -8
  63. data/lib/files.com/models/group_user.rb +9 -7
  64. data/lib/files.com/models/history.rb +10 -10
  65. data/lib/files.com/models/history_export.rb +3 -3
  66. data/lib/files.com/models/history_export_result.rb +2 -2
  67. data/lib/files.com/models/holiday_calendar.rb +10 -8
  68. data/lib/files.com/models/holiday_region.rb +2 -2
  69. data/lib/files.com/models/inbound_s3_log.rb +2 -2
  70. data/lib/files.com/models/inbox_recipient.rb +4 -4
  71. data/lib/files.com/models/inbox_registration.rb +2 -2
  72. data/lib/files.com/models/inbox_upload.rb +2 -2
  73. data/lib/files.com/models/integration_centric_profile.rb +10 -8
  74. data/lib/files.com/models/invoice.rb +3 -3
  75. data/lib/files.com/models/ip_address.rb +8 -8
  76. data/lib/files.com/models/key_lifecycle_rule.rb +10 -8
  77. data/lib/files.com/models/lock.rb +3 -3
  78. data/lib/files.com/models/metadata_category.rb +12 -10
  79. data/lib/files.com/models/notification.rb +10 -8
  80. data/lib/files.com/models/outbound_connection_log.rb +2 -2
  81. data/lib/files.com/models/partner.rb +10 -8
  82. data/lib/files.com/models/partner_channel.rb +10 -8
  83. data/lib/files.com/models/partner_channel_template.rb +10 -8
  84. data/lib/files.com/models/partner_site_request.rb +4 -4
  85. data/lib/files.com/models/payment.rb +3 -3
  86. data/lib/files.com/models/pending_work_event.rb +3 -3
  87. data/lib/files.com/models/permission.rb +4 -4
  88. data/lib/files.com/models/public_hosting_request_log.rb +2 -2
  89. data/lib/files.com/models/public_key.rb +11 -9
  90. data/lib/files.com/models/remote_bandwidth_snapshot.rb +2 -2
  91. data/lib/files.com/models/remote_mount_backend.rb +10 -8
  92. data/lib/files.com/models/remote_server.rb +13 -11
  93. data/lib/files.com/models/remote_server_credential.rb +10 -8
  94. data/lib/files.com/models/request.rb +6 -6
  95. data/lib/files.com/models/restore.rb +4 -4
  96. data/lib/files.com/models/schedule.rb +10 -8
  97. data/lib/files.com/models/scheduled_export.rb +10 -8
  98. data/lib/files.com/models/scim_log.rb +3 -3
  99. data/lib/files.com/models/secret.rb +10 -8
  100. data/lib/files.com/models/session.rb +2 -2
  101. data/lib/files.com/models/settings_change.rb +2 -2
  102. data/lib/files.com/models/sftp_action_log.rb +2 -2
  103. data/lib/files.com/models/sftp_host_key.rb +10 -8
  104. data/lib/files.com/models/share_group.rb +10 -8
  105. data/lib/files.com/models/siem_http_destination.rb +10 -8
  106. data/lib/files.com/models/siem_http_destination_event.rb +3 -3
  107. data/lib/files.com/models/site.rb +3 -3
  108. data/lib/files.com/models/site_subdomain_redirect.rb +3 -3
  109. data/lib/files.com/models/snapshot.rb +10 -8
  110. data/lib/files.com/models/sso_event.rb +3 -3
  111. data/lib/files.com/models/sso_strategy.rb +3 -3
  112. data/lib/files.com/models/style.rb +6 -5
  113. data/lib/files.com/models/sync.rb +10 -8
  114. data/lib/files.com/models/sync_log.rb +2 -2
  115. data/lib/files.com/models/sync_run.rb +3 -3
  116. data/lib/files.com/models/usage_daily_snapshot.rb +2 -2
  117. data/lib/files.com/models/usage_snapshot.rb +2 -2
  118. data/lib/files.com/models/user.rb +10 -8
  119. data/lib/files.com/models/user_additional_email_recipient.rb +10 -8
  120. data/lib/files.com/models/user_cipher_use.rb +2 -2
  121. data/lib/files.com/models/user_lifecycle_rule.rb +10 -8
  122. data/lib/files.com/models/user_request.rb +5 -5
  123. data/lib/files.com/models/user_security_event.rb +3 -3
  124. data/lib/files.com/models/user_sftp_client_use.rb +2 -2
  125. data/lib/files.com/models/web_dav_action_log.rb +2 -2
  126. data/lib/files.com/models/webhook_test.rb +2 -2
  127. data/lib/files.com/models/workspace.rb +10 -8
  128. data/lib/files.com/response.rb +24 -0
  129. data/lib/files.com/sizable_io.rb +65 -4
  130. data/lib/files.com/version.rb +1 -1
  131. data/lib/files.com.rb +23 -1
  132. data/spec/language_spec.rb +20 -10
  133. data/spec/lib/api_client_spec.rb +173 -18
  134. data/spec/list_spec.rb +203 -187
  135. data/spec/models/file_transfer_spec.rb +346 -0
  136. data/spec/models/persistence_spec.rb +167 -0
  137. data/spec/spec_helper.rb +122 -6
  138. data/spec/support/stubbed_api.rb +14 -0
  139. data/spec/support/transfer_server.rb +60 -0
  140. metadata +5 -1
data/spec/list_spec.rb CHANGED
@@ -1,215 +1,231 @@
1
1
  require "spec_helper"
2
-
3
- ResourceWrapper = Struct.new(:object, :options)
2
+ require_relative "support/stubbed_api"
4
3
 
5
4
  RSpec.describe Files::List do
6
- let(:options) { { client: client } }
7
- let(:client) { instance_double(Files::ApiClient, cursor: nil) }
8
- let(:params) { {} }
5
+ include StubbedApi
6
+
7
+ let(:requests) { [] }
8
+
9
+ # Serves Workspace pages by request cursor. Each page is its workspace IDs and
10
+ # the cursor the API returns for the page after it, if any.
11
+ def serve_pages(pages)
12
+ stubbed_api_client do |stub|
13
+ stub.get("/api/rest/v1/workspaces") do |env|
14
+ requests << env.params
15
+ raise "the SDK requested more pages than the list has" if requests.size > pages.size + 2
16
+
17
+ ids, next_cursor = pages.fetch(env.params["cursor"])
18
+ json_response(200, ids.map { |id| { id: id, name: "Workspace #{id}" } }, next_cursor ? { "X-Files-Cursor" => next_cursor } : {})
19
+ end
20
+ yield stub if block_given?
21
+ end
22
+ end
23
+
24
+ def requested_cursors
25
+ requests.map { |params| params["cursor"] }
26
+ end
9
27
 
10
28
  describe "#auto_paging_each" do
11
- context "when per_page is set" do
12
- let(:params) { { per_page: per_page } }
13
- let(:per_page) { 3 }
14
-
15
- context "when response includes a cursor" do
16
- let(:client) { instance_double(Files::ApiClient) }
17
- let(:stubbed_cursors) {
18
- [
19
- '3',
20
- '6',
21
- nil
22
- ]
23
- }
29
+ it "follows the cursor through short and empty pages with the caller's filters" do
30
+ client = serve_pages(nil => [ [ 1 ], "c2" ], "c2" => [ [], "c3" ], "c3" => [ [], "c4" ], "c4" => [ [ 4 ], nil ])
31
+ params = { per_page: 2, filter: { name: "Workspace" } }
24
32
 
25
- before do
26
- allow(client).to receive(:cursor).and_return(*stubbed_cursors)
27
- end
33
+ workspaces = Files::Workspace.list(params, api_key: "list-key", client: client).auto_paging_each.to_a
28
34
 
29
- it "does not call the API until out of responses" do
30
- server_results = ('a'..'h').to_a
31
- times_block_yielded = 0
32
- request_cursor = nil
33
- response_cursor = nil
34
-
35
- list = described_class.new(ResourceWrapper, params) {
36
- times_block_yielded += 1
37
- request_cursor = params[:cursor]
38
- range_start = params[:cursor] ? params[:cursor].to_i : 0
39
-
40
- response_data = server_results[range_start, per_page]
41
- response_cursor = (range_start + per_page).to_s
42
- [
43
- instance_double(Files::Response, data: response_data, http_status: 200, http_headers: { "x-files-cursor" => response_cursor }),
44
- options
45
- ]
46
- }
47
- results = list.auto_paging_each
48
- expect(results.class).to eq(Enumerator::Lazy)
49
- expect(times_block_yielded).to eq(0)
50
- expect(results.next.object).to eq('a')
51
- expect(times_block_yielded).to eq(1)
52
- expect(request_cursor).to eq(nil)
53
- expect(results.next.object).to eq('b')
54
- expect(results.next.object).to eq('c')
55
- expect(results.next.object).to eq('d')
56
- expect(times_block_yielded).to eq(2)
57
- expect(request_cursor).to eq("3")
58
- expect(results.next.object).to eq('e')
59
- expect(results.next.object).to eq('f')
60
- expect(results.next.object).to eq('g')
61
- expect(results.next.object).to eq('h')
62
- expect(times_block_yielded).to eq(3)
63
- expect(request_cursor).to eq("6")
64
- end
35
+ expect(workspaces.map(&:id)).to eq([ 1, 4 ])
36
+ expect(workspaces.map { |workspace| workspace.options[:api_key] }).to eq([ "list-key", "list-key" ])
37
+ expect(requested_cursors).to eq([ nil, "c2", "c3", "c4" ])
38
+ expect(requests).to all(include("per_page" => "2", "filter" => { "name" => "Workspace" }))
39
+ expect(params).to eq(per_page: 2, filter: { name: "Workspace" })
40
+ end
65
41
 
66
- it "stops calling the API once there is an error" do
67
- server_results = ('a'..'h').to_a
68
- times_block_yielded = 0
69
- list = described_class.new(ResourceWrapper, params) {
70
- times_block_yielded += 1
71
- [
72
- instance_double(Files::Response, data: server_results.shift(per_page), http_status: 400),
73
- options
74
- ]
75
- }
76
- results = list.auto_paging_each
77
- expect(results.count).to eq(0)
78
- expect(times_block_yielded).to eq(1)
79
- end
80
- end
42
+ it "requests a later page only when iteration reaches it" do
43
+ client = serve_pages(nil => [ [ 1, 2 ], "c2" ], "c2" => [ [ 3 ], nil ])
44
+ list = Files::Workspace.list({}, api_key: "list-key", client: client)
81
45
 
82
- context "when response does not include a cursor" do
83
- let(:client) { instance_double(Files::ApiClient, cursor: nil) }
84
-
85
- it "stops requesting any more results after the first try" do
86
- server_results = ('a'..'h').to_a
87
- times_block_yielded = 0
88
- list = described_class.new(ResourceWrapper, params) {
89
- times_block_yielded += 1
90
- [
91
- instance_double(Files::Response, data: server_results.shift(per_page), http_status: 200),
92
- options
93
- ]
94
- }
95
- results = list.auto_paging_each
96
- expect(results.count).to eq(3)
97
- expect(times_block_yielded).to eq(1)
98
- end
99
- end
46
+ workspaces = list.auto_paging_each
47
+ expect(requests).to be_empty
48
+
49
+ expect(workspaces.first(2).map(&:id)).to eq([ 1, 2 ])
50
+ expect(workspaces.take(2).map(&:id).to_a).to eq([ 1, 2 ])
51
+ list.auto_paging_each { |workspace| break if workspace.id == 2 }
52
+ expect(requests.size).to eq(1)
53
+
54
+ expect(workspaces.map(&:id).to_a).to eq([ 1, 2, 3 ])
55
+ expect(requested_cursors).to eq([ nil, "c2" ])
100
56
  end
101
57
 
102
- it "returns all results at once" do
103
- server_results = ('a'..'h').to_a
104
- times_block_yielded = 0
105
- list = described_class.new(ResourceWrapper, params) {
106
- times_block_yielded += 1
107
- [
108
- instance_double(Files::Response, data: server_results, http_status: 200),
109
- options
110
- ]
111
- }
112
- results = list.auto_paging_each
113
- expect(results.count).to eq(8)
114
- expect(times_block_yielded).to eq(1)
58
+ it "starts at the page it is called on and leaves next_page where it was" do
59
+ client = serve_pages(nil => [ [ 1, 2 ], "c2" ], "c2" => [ [ 3, 4 ], nil ])
60
+ list = Files::Workspace.list({}, api_key: "list-key", client: client)
61
+
62
+ expect(list.map(&:id)).to eq([ 1, 2 ])
63
+ expect(list.auto_paging_each.map(&:id).to_a).to eq([ 1, 2, 3, 4 ])
64
+ expect(list.next_page.map(&:id)).to eq([ 3, 4 ])
65
+ expect(requested_cursors).to eq([ nil, "c2", "c2" ])
66
+ end
67
+
68
+ it "raises instead of following a cursor the list already used" do
69
+ client = serve_pages(nil => [ [ 1 ], "a" ], "a" => [ [ 2 ], "a" ])
70
+ seen = []
71
+
72
+ expect {
73
+ Files::Workspace.list({ per_page: 1 }, api_key: "list-key", client: client).auto_paging_each { |workspace| seen << workspace.id }
74
+ }.to raise_error(Files::APIConnectionError, /repeated a list cursor/)
75
+ expect(seen).to eq([ 1, 2 ])
76
+ expect(requested_cursors).to eq([ nil, "a" ])
115
77
  end
116
78
  end
117
79
 
118
80
  describe "#each" do
119
- it "returns all results at once" do
120
- server_results = ('a'..'h').to_a
121
- times_block_yielded = 0
122
- results = described_class.new(ResourceWrapper, params) {
123
- times_block_yielded += 1
124
- [
125
- instance_double(Files::Response, data: server_results, http_status: 200),
126
- options
127
- ]
128
- }
129
- expect(results.count).to eq(8)
130
- expect(times_block_yielded).to eq(1)
81
+ it "returns the block's results, or an Enumerator without a block" do
82
+ client = serve_pages(nil => [ [ 1, 2 ], nil ])
83
+ list = Files::Workspace.list({}, api_key: "list-key", client: client)
84
+
85
+ expect(list.each(&:name)).to eq([ "Workspace 1", "Workspace 2" ])
86
+ expect(list.each).to be_an(Enumerator)
87
+ expect(list.each.map(&:id)).to eq([ 1, 2 ])
88
+ expect(requests.size).to eq(1)
131
89
  end
90
+ end
132
91
 
133
- context "when per_page is set" do
134
- let(:params) { { per_page: per_page } }
135
- let(:per_page) { 3 }
136
-
137
- context "when response includes a cursor" do
138
- let(:client) { instance_double(Files::ApiClient, cursor: "XXX-XXX") }
139
-
140
- it "does not call the API until out of responses" do
141
- server_results = ('a'..'h').to_a
142
- times_block_yielded = 0
143
- list = described_class.new(ResourceWrapper, params) {
144
- times_block_yielded += 1
145
- [
146
- instance_double(Files::Response, data: server_results.shift(per_page), http_status: 200),
147
- options
148
- ]
149
- }
150
- results = list.enum_for(:each)
151
- expect(times_block_yielded).to eq(0)
152
- expect(results.next.object).to eq('a')
153
- expect(times_block_yielded).to eq(1)
154
- expect(results.next.object).to eq('b')
155
- expect(results.next.object).to eq('c')
156
- next_results = list.next_page.enum_for(:each)
157
- expect(times_block_yielded).to eq(2)
158
- expect(next_results.next.object).to eq('d')
159
- expect(next_results.next.object).to eq('e')
160
- expect(next_results.next.object).to eq('f')
161
- next_results = list.next_page.enum_for(:each)
162
- expect(times_block_yielded).to eq(3)
163
- expect(next_results.next.object).to eq('g')
164
- expect(next_results.next.object).to eq('h')
165
- next_results = list.next_page.enum_for(:each)
166
- expect(next_results.count).to eq(0)
167
- end
92
+ describe "manual paging" do
93
+ it "steps through every page, including empty ones, with has_next_page? and next_page" do
94
+ client = serve_pages(nil => [ [ 1, 2 ], "c2" ], "c2" => [ [], "c3" ], "c3" => [ [ 3 ], "c4" ], "c4" => [ [], nil ])
95
+ page = Files::Workspace.list({}, api_key: "list-key", client: client)
168
96
 
169
- it "return the same data when call more than once" do
170
- server_results = ('a'..'h').to_a
171
- list = described_class.new(ResourceWrapper, params) {
172
- [
173
- instance_double(Files::Response, data: server_results.shift(per_page), http_status: 200),
174
- options
175
- ]
176
- }
177
- expect(list.to_a).to eq(list.to_a)
97
+ pages = []
98
+ loop do
99
+ pages << page.each.map(&:id)
100
+ break unless page.has_next_page?
101
+
102
+ page = page.next_page
103
+ end
104
+
105
+ expect(pages).to eq([ [ 1, 2 ], [], [ 3 ], [] ])
106
+ expect(requested_cursors).to eq([ nil, "c2", "c3", "c4" ])
107
+
108
+ after_last = page.next_page
109
+ expect(after_last).to be_a(Files::List)
110
+ expect(after_last.to_a).to be_empty
111
+ expect(after_last.has_next_page?).to be(false)
112
+ expect(requests.size).to eq(4)
113
+ end
114
+
115
+ it "continues from each page's own cursor when other requests happen in between" do
116
+ client = serve_pages(nil => [ [ 1 ], "c2" ], "c2" => [ [ 2 ], "c3" ], "c3" => [ [ 3 ], "c4" ], "c4" => [ [ 4 ], nil ]) do |stub|
117
+ stub.get("/api/rest/v1/workspaces/9") { json_response(200, { id: 9, name: "Workspace 9" }) }
118
+ end
119
+ list = Files::Workspace.list({}, api_key: "list-key", client: client)
120
+
121
+ expect(list.map(&:id)).to eq([ 1 ])
122
+ second = list.next_page
123
+ Files::Workspace.find(9, {}, api_key: "list-key", client: client)
124
+ expect(second.map(&:id)).to eq([ 2 ])
125
+ expect(second.map(&:id)).to eq([ 2 ])
126
+
127
+ third = list.next_page
128
+ expect(third.map(&:id)).to eq([ 3 ])
129
+ expect(third.next_page.map(&:id)).to eq([ 4 ])
130
+ expect(list.has_next_page?).to be(false)
131
+ expect(requested_cursors).to eq([ nil, "c2", "c3", "c4" ])
132
+ end
133
+
134
+ it "requests the same page again after a failed request" do
135
+ failures = [ json_response(429, { error: "Slow down", type: "too-many-requests" }) ]
136
+ client = stubbed_api_client do |stub|
137
+ stub.get("/api/rest/v1/workspaces") do |env|
138
+ requests << env.params
139
+ next failures.shift if env.params["cursor"] == "c2" && failures.any?
140
+
141
+ env.params["cursor"] ? json_response(200, [ { id: 2 } ]) : json_response(200, [ { id: 1 } ], "X-Files-Cursor" => "c2")
178
142
  end
143
+ end
144
+ page = Files::Workspace.list({}, api_key: "list-key", client: client)
145
+
146
+ expect(page.has_next_page?).to be(true)
147
+ expect { page.next_page }.to raise_error(Files::TooManyRequestsError)
148
+ expect(page.next_page.map(&:id)).to eq([ 2 ])
149
+ expect(requested_cursors).to eq([ nil, "c2", "c2" ])
150
+ end
179
151
 
180
- it "stops calling the API once there is an error" do
181
- server_results = ('a'..'h').to_a
182
- times_block_yielded = 0
183
- results = described_class.new(ResourceWrapper, params) {
184
- times_block_yielded += 1
185
- [
186
- instance_double(Files::Response, data: server_results, http_status: 400),
187
- options
188
- ]
189
- }
190
- expect(results.count).to eq(0)
191
- expect(times_block_yielded).to eq(1)
152
+ {
153
+ "JSON null" => "null",
154
+ "a JSON object" => { data: [ { id: 2 } ], note: "sentinel" }.to_json,
155
+ "a JSON scalar" => "\"sentinel\"",
156
+ "a list holding something other than objects" => [ { id: 2 }, "sentinel" ].to_json,
157
+ }.each do |description, malformed|
158
+ it "raises APIConnectionError for a page that is #{description}, and can request it again" do
159
+ replies = [ malformed, [ { id: 2 } ].to_json ]
160
+ client = stubbed_api_client do |stub|
161
+ stub.get("/api/rest/v1/workspaces") do |env|
162
+ requests << env.params
163
+ next json_response(200, [ { id: 1 } ], "X-Files-Cursor" => "c2") unless env.params["cursor"]
164
+
165
+ [ 200, { "Content-Type" => "application/json" }, replies.shift ]
166
+ end
192
167
  end
168
+ page = Files::Workspace.list({}, api_key: "list-key", client: client)
169
+ expect(page.map(&:id)).to eq([ 1 ])
170
+
171
+ expect { page.next_page }.to raise_error(Files::APIConnectionError) { |error|
172
+ expect(error).to be_an_instance_of(Files::APIConnectionError)
173
+ expect(error.message).not_to include("sentinel")
174
+ expect(error.http_status).to eq(200)
175
+ expect(error.http_body).to eq(malformed)
176
+ }
177
+ expect(page.map(&:id)).to eq([ 1 ])
178
+ expect(page.has_next_page?).to be(true)
179
+ expect(page.next_page.map(&:id)).to eq([ 2 ])
180
+ expect(requested_cursors).to eq([ nil, "c2", "c2" ])
193
181
  end
182
+ end
194
183
 
195
- context "when response does not include a cursor" do
196
- let(:client) { instance_double(Files::ApiClient, cursor: nil) }
197
-
198
- it "stops requesting any more results after the first try" do
199
- server_results = ('a'..'h').to_a
200
- times_block_yielded = 0
201
- list = described_class.new(ResourceWrapper, params) {
202
- times_block_yielded += 1
203
- [
204
- instance_double(Files::Response, data: server_results, http_status: 200),
205
- options
206
- ]
207
- }
208
- results = list.enum_for(:each)
209
- expect(results.count).to eq(8)
210
- expect(times_block_yielded).to eq(1)
184
+ it "leaves the caller's options alone when the first page is rejected, then keeps the client that succeeds" do
185
+ rejecting = stubbed_api_client do |stub|
186
+ stub.get("/api/rest/v1/workspaces") do
187
+ requests << :rejecting
188
+ [ 200, { "Content-Type" => "application/json" }, "null" ]
189
+ end
190
+ end
191
+ accepting = stubbed_api_client do |stub|
192
+ stub.get("/api/rest/v1/workspaces") do |env|
193
+ requests << :accepting
194
+ env.params["cursor"] ? json_response(200, [ { id: 2 } ]) : json_response(200, [ { id: 1 } ], "X-Files-Cursor" => "c2")
195
+ end
196
+ end
197
+ other = stubbed_api_client do |stub|
198
+ stub.get("/api/rest/v1/workspaces") do
199
+ requests << :other
200
+ json_response(200, [])
211
201
  end
212
202
  end
203
+ options = { api_key: "list-key" }
204
+ list = Files::Workspace.list({}, options)
205
+
206
+ expect { rejecting.request { list.to_a } }.to raise_error(Files::APIConnectionError)
207
+ expect(options).to eq(api_key: "list-key")
208
+
209
+ first_ids = nil
210
+ accepting.request { first_ids = list.map(&:id) }
211
+ expect(first_ids).to eq([ 1 ])
212
+ expect(options).to include(api_key: "list-key", client: accepting)
213
+
214
+ second = nil
215
+ other.request { second = list.next_page }
216
+ expect(second.map(&:id)).to eq([ 2 ])
217
+ expect(requests).to eq(%i[rejecting accepting accepting])
218
+ end
219
+
220
+ it "raises instead of returning to a page it already requested" do
221
+ client = serve_pages(nil => [ [ 1 ], "a" ], "a" => [ [ 2 ], "b" ], "b" => [ [ 3 ], "a" ])
222
+ page = Files::Workspace.list({}, api_key: "list-key", client: client)
223
+
224
+ page = page.next_page.next_page
225
+ expect(page.map(&:id)).to eq([ 3 ])
226
+ expect(page.has_next_page?).to be(true)
227
+ expect { page.next_page }.to raise_error(Files::APIConnectionError, /repeated a list cursor/)
228
+ expect(requested_cursors).to eq([ nil, "a", "b" ])
213
229
  end
214
230
  end
215
231
  end