Skip to main content

coven_storage/
test_utils.rs

1//! An in-memory provider, fault injection and the shared provider conformance suite.
2/// The store shared by the deterministic storage and format fixtures.
3pub const STORE_ID: coven_foundation::id_source::StoreId =
4    coven_foundation::id_source::StoreId(uuid::Uuid::from_bytes([0x10; 16]));
5/// The device a test connection writes for, unless a test names another.
6pub const DEVICE: coven_foundation::id_source::DeviceId =
7    coven_foundation::id_source::DeviceId(4242);
8
9use crate::session::SessionState;
10use crate::*;
11use async_trait::async_trait;
12use coven_foundation::clock::ClockRef;
13use std::collections::BTreeMap;
14use std::sync::Arc;
15use std::time::Duration;
16use tokio::sync::Mutex;
17
18#[path = "memory_upload.rs"]
19mod memory_upload;
20use memory_upload::Pending;
21
22#[path = "memory_reads.rs"]
23mod memory_reads;
24pub use memory_reads::BodyFailure;
25#[path = "memory_listing.rs"]
26mod memory_listing;
27#[path = "memory_requests.rs"]
28mod memory_requests;
29pub use memory_requests::MemoryRequest;
30
31#[path = "memory_faults.rs"]
32mod memory_faults;
33#[path = "memory_objects.rs"]
34mod memory_objects;
35pub use memory_faults::{Faults, Garble};
36
37struct Object {
38    upload_id: Option<u64>,
39    bytes: Arc<[u8]>,
40    stored_at: coven_format::value::StorageTime,
41    revision: u64,
42}
43impl Object {
44    fn metadata(&self, path: &ObjectPath, time_resolution: Duration) -> StoredObject {
45        StoredObject {
46            path: path.clone(),
47            size: self.bytes.len() as u64,
48            stored_at: self.stored_at,
49            time_resolution,
50            object_id: path.as_str().into(),
51            revision: self.revision.to_string(),
52            replace_token: self.revision.to_string(),
53            checksum: None,
54        }
55    }
56}
57/// The account holding a fake store, as `account()` reports it to its owner.
58const OWNER_ACCOUNT: &str = "[email protected]";
59
60#[derive(Clone)]
61enum Account {
62    Owner,
63    Recipient(String),
64}
65enum AccountAccess {
66    Invited,
67    Joined,
68}
69struct MemoryState {
70    location_marker: Option<Vec<u8>>,
71    /// The store's folder's name, which `rename_folder` changes.
72    folder_name: String,
73    objects: memory_objects::MemoryObjects,
74    uploads: BTreeMap<u64, Pending>,
75    next: u64,
76    accounts: BTreeMap<String, AccountAccess>,
77    retained_access: BTreeMap<String, Vec<RetainedAccess>>,
78    faults: Faults,
79    /// The test's barrier and gate each create waits at, the gate each part
80    /// waits at, and the gate each read waits at (`MemoryStorage::hold_creations`
81    /// and the gates).
82    creation_barrier: Option<Arc<tokio::sync::Barrier>>,
83    creation_gate: Option<Arc<tokio::sync::Semaphore>>,
84    part_gate: Option<Arc<tokio::sync::Semaphore>>,
85    read_gate: Option<Arc<tokio::sync::Semaphore>>,
86    ranges: Vec<ByteRange>,
87    reads: Vec<(ObjectPath, u64, u64)>,
88    /// The bytes reads of each object moved (`MemoryStorage::moved_bytes`).
89    moved: BTreeMap<ObjectPath, u64>,
90    received_buffers: Vec<usize>,
91    sent_bytes: u64,
92    largest_part: usize,
93    held_listing: Option<HeldRequest>,
94    held_read: Option<memory_reads::HeldRead>,
95    held_body: Option<memory_reads::HeldBody>,
96    held_creation: Option<HeldRequest>,
97    held_request: Option<HeldNext>,
98    held_access: Option<memory_requests::HeldAccess>,
99    rejected_tokens: std::collections::BTreeSet<String>,
100}
101
102impl MemoryState {
103    fn next_id(&mut self) -> Result<u64, StorageError> {
104        let id = self.next;
105        self.next = id
106            .checked_add(1)
107            .ok_or(StorageFailure::Protocol.with_source("memory ids exhausted"))?;
108        Ok(id)
109    }
110
111    /// Answer a write that stored its object with a 429 while
112    /// `refuse_after_storing` lasts.
113    fn refused_after_storing(&mut self, provider: CloudProvider) -> Result<(), StorageError> {
114        if self.faults.refuse_after_storing == 0 {
115            return Ok(());
116        }
117        self.faults.refuse_after_storing -= 1;
118        Err(crate::providers::ProviderResponse::injected(
119            provider,
120            StorageFailure::RateLimited,
121            429,
122            None,
123        ))
124    }
125}
126
127/// Hears each request this provider's connections attempt, counted from the
128/// first, failed ones included (`MemoryStorage::subscribe_requests`).
129pub struct RequestWatch(tokio::sync::watch::Receiver<u64>);
130
131impl RequestWatch {
132    /// How many requests have been attempted, which later waits count on
133    /// from.
134    pub fn current(&mut self) -> u64 {
135        *self.0.borrow_and_update()
136    }
137
138    /// Resolves once another request is attempted; an error once the
139    /// provider is gone.
140    pub async fn changed(&mut self) -> Result<(), tokio::sync::watch::error::RecvError> {
141        self.0.changed().await
142    }
143
144    /// Resolves once `reached` holds of the count of requests attempted; an
145    /// error once the provider is gone first.
146    pub async fn wait_for(
147        &mut self,
148        reached: impl FnMut(&u64) -> bool,
149    ) -> Result<(), tokio::sync::watch::error::RecvError> {
150        self.0.wait_for(reached).await.map(drop)
151    }
152}
153
154/// The next request, of any kind or only `request`, held once it is
155/// recorded.
156struct HeldNext {
157    request: Option<MemoryRequest>,
158    started: tokio::sync::oneshot::Sender<()>,
159    resume: tokio::sync::oneshot::Receiver<()>,
160}
161
162struct HeldRequest {
163    prefix: ObjectPrefix,
164    listed: tokio::sync::oneshot::Sender<()>,
165    resume: tokio::sync::oneshot::Receiver<()>,
166}
167
168/// Clones share a provider's durable objects and sessions across simulated crashes.
169/// Publication is timestamped by the injected clock and retains the upload's
170/// identity after pending parts are discarded. [`Faults`] can lose part/completion
171/// replies or expire pending sessions without losing published objects.
172///
173/// [`Self::for_recipient`] shares the backend with a separate account and sign-in.
174/// Grants and acceptance govern that account's reads, writes and uploads; revoking
175/// it leaves the owner and other recipients untouched. Clones share their account's
176/// tokens, and replacement tokens govern subsequent calls on the same adapter.
177/// A token named by [`Self::reject_access_token`] is refused as a provider 401
178/// would be: the call refreshes once through its connection's custody and
179/// retries once, as the HTTP adapters do (§20.2).
180/// S3 console keys are outside this account-sharing model.
181/// Implements [`crate::providers::StorageConnector`] so application tests can
182/// exercise saved settings and credential custody through the `test-utils`
183/// builder's connector hook.
184pub type MemoryStorage = StorageConnection<MemoryProvider>;
185
186/// In-memory native calls used beneath the shared storage connection.
187#[derive(Clone)]
188pub struct MemoryProvider {
189    config: StorageConfig,
190    online: Arc<std::sync::atomic::AtomicBool>,
191    single_limit: u64,
192    part_size: usize,
193    /// Whether its sessions' parts are independent, as an S3 multipart
194    /// upload's are, rather than taken in order.
195    independent_parts: bool,
196    /// Whether a read moves every part it touches whole, as CloudKit's do.
197    whole_part_reads: bool,
198    listing_page: usize,
199    requests: Arc<tokio::sync::watch::Sender<u64>>,
200    request_trace: Arc<std::sync::Mutex<Vec<MemoryRequest>>>,
201    active_requests: Arc<std::sync::atomic::AtomicUsize>,
202    peak_requests: Arc<std::sync::atomic::AtomicUsize>,
203    account: Account,
204    clock: ClockRef,
205    time_resolution: Duration,
206    tokens: Arc<Mutex<Option<Arc<crate::HeldTokens>>>>,
207    s3_key: Arc<Mutex<Option<S3Credentials>>>,
208    state: Arc<Mutex<MemoryState>>,
209}
210impl Clone for MemoryStorage {
211    fn clone(&self) -> Self {
212        Self::with_gate(
213            self.provider.clone(),
214            self.device,
215            self.publications.clone(),
216            self.cooldown.clone(),
217            self.transfers.clone(),
218            self.provider.clock.clone(),
219        )
220    }
221}
222
223/// Custody for tokens installed directly on a fake connection: a rejection
224/// cannot be refreshed and reaches the caller as `Authentication`.
225pub struct RefusedRefresh;
226#[async_trait]
227impl crate::TokenCustody for RefusedRefresh {
228    async fn replace(&self, _: &OAuthTokens) -> Result<OAuthTokens, StorageError> {
229        Err(StorageFailure::Authentication.with_source("this sign-in has no custody to refresh it"))
230    }
231}
232
233/// Authority for raw provider fixtures, which seed objects without a sync owner.
234pub struct UnrestrictedPublications;
235impl PublicationGate for UnrestrictedPublications {
236    fn check(&self, _: &ObjectPath) -> Result<(), StorageError> {
237        Ok(())
238    }
239}
240
241/// Admit `bytes` as `session`'s next part as a connection does with no
242/// cooldown live and every destination open, for a fake [`Storage`] of a
243/// test: the part must fit the session, and a plan's first part marks it.
244pub fn admitted_part<'a>(
245    session: &mut UploadSession,
246    bytes: &'a [u8],
247) -> Result<PartPermit<'a>, StorageError> {
248    let part = session.next_part(bytes.len())?;
249    let publication =
250        PublicationRequest::new(Arc::new(UnrestrictedPublications), session.path().clone());
251    Ok(PartPermit::admit(session, bytes, part, publication))
252}
253
254impl MemoryStorage {
255    /// Compose a connection over `provider` as `from_provider` does, timing
256    /// its requests by the provider's own clock.
257    pub(crate) fn from_memory(provider: Arc<MemoryProvider>) -> Self {
258        let clock = provider.clock.clone();
259        Self::with_gate(
260            provider,
261            DEVICE,
262            Arc::new(UnrestrictedPublications),
263            Arc::new(crate::NoCooldown),
264            Arc::new(crate::TransferBudget::new(crate::TransferLimits::default())),
265            clock,
266        )
267    }
268    /// An S3 test location at the Unix epoch, with four-MiB single requests
269    /// and 64-KiB parts. Override only the choices relevant to the scenario.
270    pub fn builder() -> MemoryStorageBuilder {
271        MemoryStorageBuilder::new()
272    }
273    /// A separate non-owner account using the same provider location. Its sign-in
274    /// is independent; access requires a grant and, where needed, recipient joining.
275    /// S3 keys are administered outside the fake's account-sharing model.
276    pub fn for_recipient(owner: &Self, email: &str) -> Result<Self, StorageError> {
277        if owner.provider.config.provider() == CloudProvider::S3 || email.is_empty() {
278            return Err(StorageFailure::InvalidConfiguration
279                .with_source("recipient requires a sharing account"));
280        }
281        Ok(Self::from_memory(Arc::new(MemoryProvider {
282            config: owner.config(),
283            online: Arc::new(std::sync::atomic::AtomicBool::new(true)),
284            single_limit: owner.provider.single_limit,
285            part_size: owner.provider.part_size,
286            independent_parts: owner.provider.independent_parts,
287            whole_part_reads: owner.provider.whole_part_reads,
288            listing_page: owner.provider.listing_page,
289            requests: owner.provider.requests.clone(),
290            request_trace: owner.provider.request_trace.clone(),
291            active_requests: owner.provider.active_requests.clone(),
292            peak_requests: owner.provider.peak_requests.clone(),
293            account: Account::Recipient(email.to_ascii_lowercase()),
294            clock: owner.provider.clock.clone(),
295            time_resolution: owner.provider.time_resolution,
296            tokens: Arc::new(Mutex::new(None)),
297            s3_key: Arc::new(Mutex::new(None)),
298            state: owner.provider.state.clone(),
299        })))
300    }
301    /// Another device connected to the same durable backend, with independent
302    /// credentials, request notifications and network availability.
303    pub fn for_device(&self) -> Self {
304        let mut device = (*self.provider).clone();
305        device.online = Arc::new(std::sync::atomic::AtomicBool::new(true));
306        device.tokens = Arc::new(Mutex::new(None));
307        device.s3_key = Arc::new(Mutex::new(None));
308        device.requests = Arc::new(tokio::sync::watch::channel(0).0);
309        device.request_trace = Arc::new(std::sync::Mutex::new(Vec::new()));
310        device.active_requests = Arc::new(std::sync::atomic::AtomicUsize::new(0));
311        device.peak_requests = Arc::new(std::sync::atomic::AtomicUsize::new(0));
312        Self::from_memory(Arc::new(device))
313    }
314    /// Construct a candidate device connection whose credentials can be inspected
315    /// independently from the source's credentials and durable provider objects.
316    pub async fn connect_device(
317        &self,
318        config: StorageConfig,
319        auth: crate::ProviderAuth,
320    ) -> Result<Self, StorageError> {
321        crate::ConnectionCredentials { location: config.clone(), credentials: auth.credentials() }
322            .validate()?;
323        if !config.same_location(&self.provider.config) {
324            return Err(StorageFailure::InvalidConfiguration
325                .with_source("memory connector location mismatch"));
326        }
327        let mut connection = (*self.provider).clone();
328        connection.config = config;
329        // Candidate setup must not mutate the old adapter's sign-in. Network
330        // controls and request observations still belong to this test device.
331        connection.tokens = Arc::new(Mutex::new(None));
332        connection.s3_key = Arc::new(Mutex::new(None));
333        match auth {
334            crate::ProviderAuth::OAuth { tokens, custody } => {
335                *connection.tokens.lock().await =
336                    Some(Arc::new(crate::HeldTokens::new(tokens, custody)));
337            }
338            crate::ProviderAuth::S3(keys) => connection.set_s3_credentials(keys).await?,
339            crate::ProviderAuth::CloudKit => {}
340        }
341        Ok(Self::from_memory(Arc::new(connection)))
342    }
343
344    /// Set this connection's network availability without changing other devices.
345    pub fn set_online(&self, online: bool) {
346        self.provider.online.store(online, std::sync::atomic::Ordering::SeqCst);
347    }
348    /// Request notifications include failed attempts, so tests can wait without polling.
349    pub fn subscribe_requests(&self) -> RequestWatch {
350        RequestWatch(self.provider.requests.subscribe())
351    }
352    /// Greatest number of requests overlapping an injected delay or barrier.
353    pub fn peak_requests(&self) -> usize {
354        self.provider.peak_requests.load(std::sync::atomic::Ordering::SeqCst)
355    }
356    /// Reset the peak for subsequent request starts, retaining active requests.
357    pub fn reset_request_peak(&self) {
358        self.provider.peak_requests.store(0, std::sync::atomic::Ordering::SeqCst);
359    }
360    /// Every attempted provider request, including injected failures.
361    pub fn request_count(&self) -> u64 {
362        *self.provider.requests.borrow()
363    }
364    /// The key currently installed on this simulated provider connection.
365    pub async fn s3_credentials(&self) -> Option<S3Credentials> {
366        self.provider.s3_key.lock().await.clone()
367    }
368    /// The most recently installed S3 access-key id, without exposing its secret.
369    pub async fn s3_access_key_id(&self) -> Option<String> {
370        self.s3_credentials().await.map(|key| key.access_key_id)
371    }
372    /// Opened ranged responses, in order.
373    pub async fn ranges(&self) -> Vec<ByteRange> {
374        self.provider.state.lock().await.ranges.clone()
375    }
376    /// Opened responses as (path, offset, declared byte count), including whole objects.
377    pub async fn reads(&self) -> Vec<(ObjectPath, u64, u64)> {
378        self.provider.state.lock().await.reads.clone()
379    }
380    /// The bytes the reads of `path` moved as their bodies delivered them:
381    /// each byte, or, on a provider whose reads move whole parts, each part
382    /// whole once a body delivered a byte of it.
383    pub async fn moved_bytes(&self, path: &ObjectPath) -> u64 {
384        self.provider.state.lock().await.moved.get(path).copied().unwrap_or(0)
385    }
386    /// Part bytes with successful replies and their largest buffer, across sessions.
387    pub async fn transferred(&self) -> (u64, usize) {
388        let state = self.provider.state.lock().await;
389        (state.sent_bytes, state.largest_part)
390    }
391    /// Damage one stored byte without issuing a provider request.
392    pub async fn corrupt_byte(&self, path: &ObjectPath, offset: usize) -> Result<(), StorageError> {
393        let mut state = self.provider.state.lock().await;
394        let object = state.objects.get_mut(path).ok_or(StorageFailure::NotFound)?;
395        let byte =
396            Arc::make_mut(&mut object.bytes).get_mut(offset).ok_or(StorageFailure::InvalidRange)?;
397        *byte ^= 1;
398        Ok(())
399    }
400    /// Store an object without a request or this connection's size limits,
401    /// as anyone holding the storage's credentials could (§3).
402    pub async fn plant(&self, path: &ObjectPath, bytes: &[u8]) -> Result<(), StorageError> {
403        let stored_at = self.provider.storage_time()?;
404        let mut state = self.provider.state.lock().await;
405        let revision = state.next_id()?;
406        state.objects.insert(
407            path.clone(),
408            Object { revision, upload_id: None, bytes: bytes.into(), stored_at },
409        );
410        Ok(())
411    }
412    /// Resolves once the provider holds an object at a path `wanted`
413    /// accepts, with that path; no request is made. A request is in the
414    /// trace as it goes out, before its object is stored, so the trace
415    /// can't tell this.
416    pub async fn wait_for_object(&self, wanted: impl Fn(&ObjectPath) -> bool) -> ObjectPath {
417        let mut changes = self.provider.state.lock().await.objects.watch();
418        loop {
419            let state = self.provider.state.lock().await;
420            if let Some(path) = state.objects.keys().find(|path| wanted(path)) {
421                return path.clone();
422            }
423            drop(state);
424            changes.changed().await;
425        }
426    }
427    /// Refuse this access token from now on, on every connection, as a
428    /// provider 401 would (§20.2).
429    pub async fn reject_access_token(&self, token: &str) {
430        self.provider.state.lock().await.rejected_tokens.insert(token.into());
431    }
432    /// Set faults absolutely, so repeating the command has the same effect.
433    pub async fn set_faults(&self, faults: Faults) {
434        self.provider.state.lock().await.faults = faults;
435    }
436    /// Hold each create, including injected failures, until all writers
437    /// arrive at the test's `barrier`.
438    pub async fn hold_creations(&self, barrier: Arc<tokio::sync::Barrier>) {
439        self.provider.state.lock().await.creation_barrier = Some(barrier);
440    }
441    /// Hold each create until the test adds a permit to `gate`; each create
442    /// takes one, oldest first, so a test releases held creates one at a time.
443    pub async fn gate_creations(&self, gate: Arc<tokio::sync::Semaphore>) {
444        self.provider.state.lock().await.creation_gate = Some(gate);
445    }
446    /// Hold each part, its bytes taken, until the test adds a permit to
447    /// `gate`; each part takes one, oldest first.
448    pub async fn gate_parts(&self, gate: Arc<tokio::sync::Semaphore>) {
449        self.provider.state.lock().await.part_gate = Some(gate);
450    }
451    /// Hold each read until the test adds a permit to `gate`; each read takes
452    /// one, oldest first.
453    pub async fn gate_reads(&self, gate: Arc<tokio::sync::Semaphore>) {
454        self.provider.state.lock().await.read_gate = Some(gate);
455    }
456    /// Whether the store is shared with `account`: granted and not since
457    /// revoked.
458    pub async fn shared_with(&self, account: &str) -> bool {
459        self.provider.state.lock().await.accounts.contains_key(&account.to_ascii_lowercase())
460    }
461    /// Whether `account` has accepted the store's share, as a Dropbox mount
462    /// does, and not since left it or lost it.
463    pub async fn joined(&self, account: &str) -> bool {
464        matches!(
465            self.provider.state.lock().await.accounts.get(&account.to_ascii_lowercase()),
466            Some(AccountAccess::Joined)
467        )
468    }
469    /// Model native grants that remain until the owner changes them in the provider.
470    pub async fn set_retained_access(&self, account: &str, shares: Vec<RetainedAccess>) {
471        let mut state = self.provider.state.lock().await;
472        let account = account.to_ascii_lowercase();
473        if shares.is_empty() {
474            state.retained_access.remove(&account);
475        } else {
476            state.retained_access.insert(account, shares);
477        }
478    }
479}
480
481impl MemoryProvider {
482    /// Refuse the upload of the file at `path` as it starts, while
483    /// `Faults::refuse_file_uploads` counts refusals left, or panic in it
484    /// while `Faults::panic_file_uploads` counts panics left; any other path
485    /// goes through.
486    async fn refuse_file_upload(&self, path: &ObjectPath) -> Result<(), StorageError> {
487        if path.object_id().is_none() {
488            return Ok(());
489        }
490        let mut state = self.state.lock().await;
491        if state.faults.panic_file_uploads > 0 {
492            state.faults.panic_file_uploads -= 1;
493            drop(state);
494            panic!("the upload of {path:?} panicked");
495        }
496        if state.faults.refuse_file_uploads == 0 {
497            return Ok(());
498        }
499        state.faults.refuse_file_uploads -= 1;
500        let faults = &state.faults;
501        Err(self.injected((faults.failure, faults.response, faults.retry_after)))
502    }
503    fn session_id(&self, session: &UploadSession) -> Result<u64, StorageError> {
504        match session.state {
505            SessionState::Memory { id, .. } | SessionState::MemoryParts { id, .. } => Ok(id),
506            _ => Err(StorageFailure::SessionMismatch.into()),
507        }
508    }
509}
510
511#[async_trait]
512impl ProviderOps for MemoryProvider {
513    async fn create_location_marker(
514        &self,
515        store: coven_foundation::id_source::StoreId,
516        _device: coven_foundation::id_source::DeviceId,
517    ) -> Result<(), StorageError> {
518        self.before(MemoryRequest::Other("create_location_marker")).await?;
519        let mut state = self.state.lock().await;
520        if state.location_marker.is_some() {
521            return Err(StorageFailure::AlreadyExists.into());
522        }
523        state.location_marker = Some(store.to_string().into_bytes());
524        if std::mem::replace(&mut state.faults.lose_completion_reply, false) {
525            return Err(StorageFailure::Network.into());
526        }
527        Ok(())
528    }
529    async fn read_location_marker(&self) -> Result<Vec<u8>, StorageError> {
530        self.before(MemoryRequest::Other("read_location_marker")).await?;
531        self.state.lock().await.location_marker.clone().ok_or(StorageFailure::NotFound.into())
532    }
533    /// A Dropbox shared folder's or a OneDrive folder's address is under
534    /// `storage.example.test`, after its id.
535    async fn folder_label(&self) -> Result<crate::FolderLabel, StorageError> {
536        self.before(MemoryRequest::Other("folder_label")).await?;
537        let id = match &self.config {
538            StorageConfig::Dropbox {
539                location: crate::DropboxLocation::SharedFolder { namespace_id },
540                ..
541            } => namespace_id,
542            StorageConfig::OneDrive { folder_id, .. } => folder_id,
543            _ => return Err(StorageFailure::ProviderUnavailable.into()),
544        };
545        let mut web_url = url::Url::parse("https://storage.example.test/folders/")
546            .expect("a fixed address parses");
547        web_url.path_segments_mut().expect("an https address holds paths").pop_if_empty().push(id);
548        Ok(crate::FolderLabel { name: self.state.lock().await.folder_name.clone(), web_url })
549    }
550    async fn account(&self) -> Result<String, StorageError> {
551        self.before_request(MemoryRequest::Other("account")).await?;
552        Ok(match &self.account {
553            Account::Owner => OWNER_ACCOUNT.into(),
554            Account::Recipient(account) => account.clone(),
555        })
556    }
557    async fn set_s3_credentials(&self, credentials: S3Credentials) -> Result<(), StorageError> {
558        credentials.validate()?;
559        if self.config.provider() != CloudProvider::S3 {
560            return Err(StorageFailure::InvalidConfiguration.with_source("invalid S3 key"));
561        }
562        *self.s3_key.lock().await = Some(credentials);
563        Ok(())
564    }
565    fn config(&self) -> StorageConfig {
566        self.config.clone()
567    }
568    async fn set_oauth_tokens(&self, tokens: OAuthTokens) -> Result<(), StorageError> {
569        if !self.config.provider().signs_in_with_oauth() {
570            return Err(
571                StorageFailure::InvalidConfiguration.with_source("provider does not use OAuth")
572            );
573        }
574        let mut held = self.tokens.lock().await;
575        match held.as_ref() {
576            Some(held) => held.install(tokens).await,
577            None => {
578                *held = Some(Arc::new(crate::HeldTokens::new(tokens, Arc::new(RefusedRefresh))))
579            }
580        }
581        Ok(())
582    }
583    fn single_request_limit(&self) -> u64 {
584        self.single_limit
585    }
586    fn read_part_size(&self) -> Option<std::num::NonZeroU64> {
587        std::num::NonZeroU64::new(self.part_size as u64).filter(|_| self.whole_part_reads)
588    }
589    #[expect(
590        clippy::map_err_ignore,
591        reason = "a send fails only when the receiver is gone, which this names"
592    )]
593    async fn create(
594        &self,
595        publication: &PublicationRequest,
596        path: &ObjectPath,
597        bytes: &[u8],
598        sent: &ByteProgress,
599        _device: coven_foundation::id_source::DeviceId,
600    ) -> Result<Option<StoredObject>, StorageError> {
601        publication.check()?;
602        // The whole body is taken as the call starts; a held create waits
603        // for its reply with every byte sent.
604        sent.moved(bytes.len() as u64);
605        self.before(MemoryRequest::Create(path.clone())).await?;
606        self.refuse_file_upload(path).await?;
607        let mut state = self.state.lock().await;
608        if let Some((failure, status)) = state.faults.create_failures.get(path) {
609            return Err(crate::providers::ProviderResponse::injected(
610                self.config.provider(),
611                *failure,
612                *status,
613                None,
614            ));
615        }
616        let held = if state.held_creation.as_ref().is_some_and(|held| held.prefix.contains(path)) {
617            state.held_creation.take()
618        } else {
619            None
620        };
621        drop(state);
622        if let Some(held) = held {
623            held.listed
624                .send(())
625                .map_err(|_| StorageFailure::Protocol.with_source("creation observer dropped"))?;
626            held.resume
627                .await
628                .map_err(|_| StorageFailure::Protocol.with_source("creation release dropped"))?;
629        }
630        let mut state = self.state.lock().await;
631        if state.objects.contains_key(path) && !state.faults.overwrite_create {
632            if state.faults.fail_duplicate_cleanup {
633                return Err(StorageError::Cleanup {
634                    operation: Box::new(StorageError::Failure(StorageFailure::AlreadyExists)),
635                    cleanup: Box::new(StorageError::Failure(StorageFailure::Network)),
636                });
637            }
638            return Err(StorageFailure::AlreadyExists.into());
639        }
640        let revision = state.next_id()?;
641        let object = Object {
642            revision,
643            upload_id: None,
644            bytes: bytes.into(),
645            stored_at: self.storage_time()?,
646        };
647        let created = object.metadata(path, self.time_resolution);
648        state.objects.insert(path.clone(), object);
649        if std::mem::replace(&mut state.faults.lose_completion_reply, false) {
650            return Err(StorageFailure::Network.into());
651        }
652        state.refused_after_storing(self.config.provider())?;
653        Ok(Some(created))
654    }
655
656    async fn replace(
657        &self,
658        publication: &PublicationRequest,
659        previous: &StoredObject,
660        bytes: &[u8],
661        _device: coven_foundation::id_source::DeviceId,
662    ) -> Result<(), StorageError> {
663        let path = &previous.path;
664        publication.check()?;
665        self.before(MemoryRequest::Replace(path.clone())).await?;
666        let mut state = self.state.lock().await;
667        if state
668            .objects
669            .get(path)
670            .is_none_or(|object| object.revision.to_string() != previous.replace_token)
671        {
672            return Err(StorageFailure::Conflict.into());
673        }
674        if state.faults.refuse_replacements > 0 {
675            state.faults.refuse_replacements -= 1;
676            return Err(StorageFailure::Conflict.into());
677        }
678        let revision = state.next_id()?;
679        state.objects.insert(
680            path.clone(),
681            Object {
682                revision,
683                upload_id: None,
684                bytes: bytes.into(),
685                stored_at: self.storage_time()?,
686            },
687        );
688        if std::mem::replace(&mut state.faults.lose_completion_reply, false) {
689            return Err(StorageFailure::Network.into());
690        }
691        state.refused_after_storing(self.config.provider())
692    }
693    async fn status(&self, path: &ObjectPath) -> Result<Option<StoredObject>, StorageError> {
694        self.before(MemoryRequest::Status(path.clone())).await?;
695        let state = self.state.lock().await;
696        if let Some(failure) = state.faults.status_failures.get(path) {
697            return Err((*failure).into());
698        }
699        Ok(state.objects.get(path).map(|object| object.metadata(path, self.time_resolution)))
700    }
701    async fn stream_object(
702        &self,
703        path: &ObjectPath,
704        range: Option<ByteRange>,
705        // The response body yields bytes as they arrive; the connection
706        // hears its reads.
707        _received: &ByteProgress,
708    ) -> Result<ObjectStream<'_>, StorageError> {
709        self.stream_recorded(path, range).await
710    }
711
712    #[expect(
713        clippy::map_err_ignore,
714        reason = "a send fails only when the receiver is gone, which this names"
715    )]
716    async fn list(&self, listing: &mut ObjectListing) -> Result<(), StorageError> {
717        let prefix = listing.prefix();
718        self.before(MemoryRequest::List(prefix.clone())).await?;
719        let now = self.storage_time()?;
720        let state = self.state.lock().await;
721        if let Some(failure) = state.faults.list_failures.get(prefix) {
722            return Err((*failure).into());
723        }
724        let objects: Vec<_> = state
725            .objects
726            .iter()
727            .filter(|(path, object)| {
728                !state.faults.hide_listings
729                    && prefix.contains(path)
730                    && state.faults.listing_lag.is_none_or(|lag| {
731                        now.duration_since(object.stored_at).is_some_and(|age| age > lag)
732                    })
733            })
734            .map(|(path, object)| object.metadata(path, self.time_resolution))
735            .collect::<Vec<_>>();
736        drop(state);
737        self.further_pages(MemoryRequest::List(prefix.clone()), objects.len()).await?;
738        let mut state = self.state.lock().await;
739        let hold = if state.held_listing.as_ref().is_some_and(|held| &held.prefix == prefix) {
740            state.held_listing.take()
741        } else {
742            None
743        };
744        drop(state);
745        if let Some(held) = hold {
746            held.listed
747                .send(())
748                .map_err(|_| StorageFailure::Protocol.with_source("listing observer dropped"))?;
749            held.resume
750                .await
751                .map_err(|_| StorageFailure::Protocol.with_source("listing release dropped"))?;
752        }
753        for object in objects {
754            listing.insert(object)?;
755        }
756        Ok(())
757    }
758    async fn delete(&self, path: &ObjectPath) -> Result<(), StorageError> {
759        self.before(MemoryRequest::Delete(path.clone())).await?;
760        let mut state = self.state.lock().await;
761        if let Some(failure) = state.faults.delete_failures.get(path) {
762            return Err((*failure).into());
763        }
764        state.objects.remove(path);
765        Ok(())
766    }
767    async fn grant_access(&self, account: &str) -> Result<AccessGrant, StorageError> {
768        self.hold_access(account, true).await;
769        self.before_request(MemoryRequest::Other("grant_access")).await?;
770        if self.config.provider() == CloudProvider::S3 {
771            return Ok(AccessGrant::CreateAccessKey);
772        }
773        if !matches!(self.account, Account::Owner) {
774            return Err(StorageFailure::NotStoreOwner.into());
775        }
776        if account.is_empty() {
777            return Err(StorageFailure::InvalidConfiguration.with_source("empty sharing account"));
778        }
779        if account.eq_ignore_ascii_case(OWNER_ACCOUNT) {
780            return Err(StorageFailure::InviteeIsStoreOwner.into());
781        }
782        if let Some(failure) = self.state.lock().await.faults.grant_failure {
783            return Err(failure.into());
784        }
785        let invitation = match self.config.provider() {
786            CloudProvider::CloudKit => StorageInvitation::new(
787                self.config(),
788                crate::invitation::InvitationAcceptance::CloudKitShare {
789                    url: coven_crypto::SecretText::new("https://icloud.com/share/memory".into()),
790                },
791            )?,
792            CloudProvider::OneDrive => StorageInvitation::new(
793                self.config(),
794                crate::invitation::InvitationAcceptance::OneDriveShare {
795                    token: coven_crypto::SecretText::new(account.to_ascii_lowercase()),
796                },
797            )?,
798            _ => StorageInvitation::for_account(self.config())?,
799        };
800        self.state
801            .lock()
802            .await
803            .accounts
804            .entry(account.to_ascii_lowercase())
805            .or_insert(AccountAccess::Invited);
806        Ok(AccessGrant::Granted { invitation })
807    }
808    async fn join(&self, invitation: &StorageInvitation) -> Result<(), StorageError> {
809        self.before_request(MemoryRequest::Other("join")).await?;
810        use crate::invitation::InvitationAcceptance as Acceptance;
811        if matches!(invitation.acceptance, Acceptance::Granted) {
812            return Ok(());
813        }
814        if let Acceptance::CloudKitShare { url } = &invitation.acceptance {
815            if url.as_str() != "https://icloud.com/share/memory" {
816                return Err(StorageFailure::InvitationMismatch.into());
817            }
818        }
819        if let Account::Recipient(email) = &self.account {
820            match &invitation.acceptance {
821                Acceptance::OneDriveShare { token } if token.as_str() != email => {
822                    return Err(StorageFailure::InvitationMismatch.into())
823                }
824                _ => {}
825            }
826            let mut state = self.state.lock().await;
827            let access = state.accounts.get_mut(email).ok_or(StorageFailure::PermissionDenied)?;
828            *access = AccountAccess::Joined;
829        }
830        Ok(())
831    }
832    /// As Dropbox's unmount, the only provider undo: a mounted recipient
833    /// goes back to invited; one not joined, or no longer shared, is done.
834    /// Other providers' acceptance stays, as theirs does.
835    async fn leave(&self, invitation: &StorageInvitation) -> Result<(), StorageError> {
836        self.before_request(MemoryRequest::Other("leave")).await?;
837        use crate::invitation::InvitationAcceptance as Acceptance;
838        if let (Acceptance::DropboxMount, Account::Recipient(email)) =
839            (&invitation.acceptance, &self.account)
840        {
841            if let Some(access) = self.state.lock().await.accounts.get_mut(email) {
842                *access = AccountAccess::Invited;
843            }
844        }
845        Ok(())
846    }
847    async fn revoke_access(&self, account: &str) -> Result<Vec<RetainedAccess>, StorageError> {
848        self.hold_access(account, false).await;
849        self.before_request(MemoryRequest::Other("revoke_access")).await?;
850        if self.config.provider() == CloudProvider::S3 {
851            return Err(
852                StorageFailure::InvalidConfiguration.with_source("S3 has no account sharing")
853            );
854        }
855        if !matches!(self.account, Account::Owner) {
856            return Err(StorageFailure::NotStoreOwner.into());
857        }
858        let mut state = self.state.lock().await;
859        let account = account.to_ascii_lowercase();
860        if let Some(shares) = state.retained_access.get(&account) {
861            return Ok(shares.clone());
862        }
863        state.accounts.remove(&account);
864        Ok(Vec::new())
865    }
866    async fn begin_upload(
867        &self,
868        publication: &PublicationRequest,
869        target: &UploadTarget,
870        total: u64,
871        device: coven_foundation::id_source::DeviceId,
872    ) -> Result<UploadSession, StorageError> {
873        self.memory_begin_upload(publication, target, total, device).await
874    }
875    async fn device_uploads(
876        &self,
877        store: coven_foundation::id_source::StoreId,
878        device: coven_foundation::id_source::DeviceId,
879    ) -> Result<Vec<LeftUpload>, StorageError> {
880        self.memory_device_uploads(store, device).await
881    }
882    async fn settled_claim_uploads(
883        &self,
884        store: coven_foundation::id_source::StoreId,
885        through: u64,
886    ) -> Result<Vec<LeftUpload>, StorageError> {
887        self.memory_settled_claim_uploads(store, through).await
888    }
889    fn dates_floor_sessions(&self) -> bool {
890        true
891    }
892    async fn superseded_floor_uploads(
893        &self,
894        floor: &StoredObject,
895    ) -> Result<Vec<LeftUpload>, StorageError> {
896        self.memory_superseded_floor_uploads(floor).await
897    }
898    async fn end_left_upload(&self, upload: &LeftUpload) -> Result<(), StorageError> {
899        self.memory_end_left_upload(upload).await
900    }
901    async fn resume_upload(
902        &self,
903        publication: &PublicationRequest,
904        session: &mut UploadSession,
905    ) -> Result<(), StorageError> {
906        self.memory_resume_upload(publication, session).await
907    }
908    async fn upload_part(
909        &self,
910        publication: &PublicationRequest,
911        session: &mut UploadSession,
912        bytes: &[u8],
913        end: u64,
914        sent: &ByteProgress,
915    ) -> Result<(), StorageError> {
916        self.memory_upload_part(publication, session, bytes, end, sent).await
917    }
918    async fn send_independent_part(
919        &self,
920        publication: &PublicationRequest,
921        session: &UploadSession,
922        offset: u64,
923        bytes: &[u8],
924        sent: &ByteProgress,
925    ) -> Result<crate::ConfirmedPart, StorageError> {
926        self.memory_send_part(publication, session, offset, bytes, sent).await
927    }
928    async fn finish_upload(
929        &self,
930        publication: &PublicationRequest,
931        session: &mut UploadSession,
932    ) -> Result<(), StorageError> {
933        self.memory_finish_upload(publication, session).await
934    }
935    async fn abort_upload(
936        &self,
937        publication: &PublicationRequest,
938        session: &UploadSession,
939    ) -> Result<(), StorageError> {
940        self.memory_abort_upload(publication, session).await
941    }
942}
943
944#[path = "conformance.rs"]
945mod conformance;
946pub use conformance::Conformance;
947
948#[cfg(test)]
949#[path = "test_utils_tests.rs"]
950mod tests;
951
952#[path = "memory_connector.rs"]
953mod connector;
954pub use connector::{conforming_detection, DETECTED_S3_SETTINGS};
955
956#[path = "memory_storage_builder.rs"]
957mod builder;
958pub use builder::MemoryStorageBuilder;