interprex_test/
releases.rs1use async_trait::async_trait;
2use bytes::Bytes;
3use futures_util::{TryStreamExt, stream};
4use interprex::{
5 AssetId, AssetStream, AssetStreamError, AssetUpload, NewRelease, ProviderError, Release,
6 ReleaseAsset, ReleaseId, ReleasesProvider, Repository, Result,
7};
8
9use crate::state::{FakeProvider, missing};
10
11#[async_trait]
12impl ReleasesProvider for FakeProvider {
13 async fn release_by_tag(&self, repository: &Repository, tag: &str) -> Result<Release> {
14 self.state
15 .read()
16 .await
17 .releases
18 .get(&(repository.clone(), tag.to_owned()))
19 .cloned()
20 .ok_or_else(|| missing(format!("release {tag} in {repository}")))
21 }
22
23 async fn create_release(
24 &self,
25 repository: &Repository,
26 release: &NewRelease,
27 ) -> Result<Release> {
28 let mut state = self.state.write().await;
29 state.next_release_id += 1;
30 let created = Release {
31 id: ReleaseId::new(state.next_release_id).expect("increment starts at one"),
32 tag: release.tag.clone(),
33 name: release.name.clone(),
34 body: release.body.clone(),
35 draft: release.draft,
36 prerelease: release.prerelease,
37 assets: Vec::new(),
38 };
39 state
40 .releases
41 .insert((repository.clone(), created.tag.clone()), created.clone());
42 Ok(created)
43 }
44
45 async fn upload_asset(
46 &self,
47 repository: &Repository,
48 release_id: ReleaseId,
49 name: &str,
50 label: Option<&str>,
51 upload: AssetUpload,
52 ) -> Result<ReleaseAsset> {
53 let (content_length, chunks) = upload.into_parts();
54 let chunks: Vec<Bytes> =
55 chunks
56 .try_collect()
57 .await
58 .map_err(|error| ProviderError::External {
59 provider: "fake",
60 operation: "read release asset upload",
61 message: error.to_string(),
62 })?;
63 let actual_length = chunks.iter().try_fold(0_u64, |length, chunk| {
64 length.checked_add(chunk.len() as u64)
65 });
66 if actual_length != Some(content_length) {
67 return Err(ProviderError::InvalidInput {
68 provider: "fake",
69 fact: format!(
70 "asset upload declared {content_length} bytes but yielded {}",
71 actual_length.map_or_else(
72 || "an overflowing length".to_owned(),
73 |value| value.to_string()
74 )
75 ),
76 });
77 }
78 let mut state = self.state.write().await;
79 state.next_asset_id += 1;
80 let asset = ReleaseAsset {
81 id: AssetId::new(state.next_asset_id).expect("increment starts at one"),
82 name: name.to_owned(),
83 label: label.map(str::to_owned),
84 size: content_length,
85 download_url: format!("memory://{}/{}", repository, state.next_asset_id),
86 };
87 let release = state
88 .releases
89 .values_mut()
90 .find(|release| release.id == release_id)
91 .ok_or_else(|| missing(format!("release {release_id:?}")))?;
92 release.assets.push(asset.clone());
93 state.assets.insert((repository.clone(), asset.id), chunks);
94 Ok(asset)
95 }
96
97 async fn download_asset(
98 &self,
99 repository: &Repository,
100 asset_id: AssetId,
101 ) -> Result<AssetStream> {
102 let chunks = self
103 .state
104 .read()
105 .await
106 .assets
107 .get(&(repository.clone(), asset_id))
108 .cloned()
109 .ok_or_else(|| missing(format!("asset {asset_id:?} in {repository}")))?;
110 Ok(Box::pin(stream::iter(
111 chunks.into_iter().map(Ok::<Bytes, AssetStreamError>),
112 )))
113 }
114}