1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Shared helpers for the integration test suite.
//!
//! All integration tests require a valid `APIFY_TOKEN` for the test account. The API
//! base URL is taken from `APIFY_API_URL` (which includes the `/v2` suffix) and falls
//! back to `https://api.apify.com/v2`.
//!
//! Tests are designed to run concurrently — including against the same test account from
//! several language clients at once — so every test creates uniquely-named resources and
//! cleans them up afterwards.
use ApifyClient;
/// The API URL the tests target, mirroring the integration-test contract.
const DEFAULT_API_URL: &str = "https://api.apify.com/v2";
/// Builds an [`ApifyClient`] configured from the environment.
///
/// Returns `None` (so the caller can skip) if `APIFY_TOKEN` is not set.
/// Resolves the client `base_url` from an optional `APIFY_API_URL`.
///
/// `APIFY_API_URL` includes the `/v2` suffix (per the integration-test contract) and falls
/// back to `https://api.apify.com/v2`. Since the client appends `/v2` itself, the suffix is
/// stripped here.
/// Returns a client or prints a skip notice and returns early from the test.
///
/// Usage: `let client = require_client!();`
/// A panic-safe cleanup guard.
///
/// Holds a deferred cleanup action that runs when the guard is dropped — including when a
/// test panics partway through (a failed `assert!`/`expect`), which would otherwise leak the
/// created resource on the shared test account. The action is run to completion on the test's
/// own (multi-thread) Tokio runtime, so it works inside `#[tokio::test]` bodies.
///
/// Usage:
/// ```ignore
/// let store = client.key_value_stores().get_or_create(Some(&name)).await.unwrap();
/// let client2 = client.clone();
/// let id = store.id.clone();
/// let _guard = Cleanup::new(move || async move {
/// let _ = client2.key_value_store(&id).delete().await;
/// });
/// // ... test body; even if it panics, the store is deleted.
/// ```
/// Upper bound on how many items an iteration test pulls while searching for a specific
/// just-created resource. Iteration tests sort newest-first, so the target is normally in the
/// first page; the cap only guards against an unbounded scan on a busy shared account.
pub const ITER_SEARCH_CAP: usize = 1000;
/// Drives a lazy [`ListIterator`](apify_client::ListIterator) looking for an item matching
/// `pred`, pulling at most [`ITER_SEARCH_CAP`] items. Returns `true` as soon as a match is
/// found. Used by the per-collection iteration tests to confirm a just-created resource is
/// reachable through the iterator (exercising the transparent page-fetching path).
pub async
/// Number of times [`iter_contains_eventually`] rebuilds the iterator and re-scans while waiting
/// for a just-created resource to become visible in its collection LIST endpoint.
pub const ITER_RETRY_ATTEMPTS: usize = 16;
/// Delay between the attempts made by [`iter_contains_eventually`].
pub const ITER_RETRY_BACKOFF: Duration = from_millis;
/// Like [`iter_contains`], but tolerant of eventual consistency in collection LIST endpoints.
///
/// A resource created through a write endpoint is not always immediately reflected in its
/// collection's LIST response — the write and the list index converge asynchronously on the
/// server. A create-then-iterate test that scans the collection exactly once therefore races that
/// convergence and flakes when the just-created entity has not yet propagated.
///
/// This helper rebuilds a fresh iterator via `make_iter` and re-scans it with [`iter_contains`] up
/// to [`ITER_RETRY_ATTEMPTS`] times, sleeping [`ITER_RETRY_BACKOFF`] between attempts, returning
/// `true` as soon as `pred` matches. When the entity is already visible it matches on the first
/// attempt and returns immediately with no sleeping — so it is a no-op in the common
/// already-consistent case and only pays the backoff on the rare lagging run.
///
/// Budget: `(ITER_RETRY_ATTEMPTS - 1) * ITER_RETRY_BACKOFF` = ~15s of retrying before giving up.
/// This is deliberately larger than a "couple of seconds": the only Apify propagation lag actually
/// measured in this suite is the dataset-items count settling at ~10s, and the collection LIST
/// index convergence time is not independently measured, so a ~2s budget could let the flake recur
/// at a lower (harder-to-diagnose) frequency. ~15s gives real headroom above the ~10s observation
/// while still failing fast enough on a genuinely-missing entity (a true bug). The cost lands only
/// on lagging or genuinely-failing runs; a consistent account never sleeps.
pub async
/// Generates a unique, collision-resistant resource name for test isolation.
///
/// The name embeds the test-specific `prefix`, a random UUID fragment, and is kept short
/// enough for Apify's naming limits. Using a random component lets the same test run in
/// parallel (across processes and languages) without clobbering shared state.