Skip to main content

smbcloud_ascapi_frontend/
attach_build.rs

1//! Attaching a build to an App Store Version, and confirming it took.
2//!
3//! The PATCH response is the version resource, whose `build` relationship
4//! holds only links, so it cannot say which build is attached. Reading the
5//! build back is what catches a wrong attachment before App Review does.
6
7use serde::Serialize;
8use smbcloud_ascapi_aso::build::Build;
9use smbcloud_ascapi_aso::prelude::*;
10use smbcloud_ascapi_core::Client;
11
12/// The outcome of [`attach_build`].
13#[derive(Debug, Clone, Serialize)]
14#[serde(rename_all = "camelCase")]
15pub struct AttachedBuild {
16    pub version_id: String,
17    /// The build read back from App Store Connect: its id, `version`,
18    /// `processingState` and `usesNonExemptEncryption`. `None` only when
19    /// verification was skipped.
20    pub build: Option<Build>,
21}
22
23/// Point `version_id` at `build_id`, then (when `verify`) read the attached
24/// build back and fail unless it is the one requested.
25pub async fn attach_build(
26    client: &Client,
27    version_id: &str,
28    build_id: &str,
29    verify: bool,
30) -> Result<AttachedBuild, String> {
31    client
32        .set_app_store_version_build(version_id, build_id)
33        .await
34        .map_err(|error| error.to_string())?;
35    if !verify {
36        return Ok(AttachedBuild {
37            version_id: version_id.to_string(),
38            build: None,
39        });
40    }
41    let attached = client
42        .get_app_store_version_build(version_id)
43        .await
44        .map_err(|error| error.to_string())?;
45    check_attached(build_id, attached.as_ref())?;
46    Ok(AttachedBuild {
47        version_id: version_id.to_string(),
48        build: attached,
49    })
50}
51
52/// The read-back check, split out so it can be tested without a network.
53fn check_attached(expected: &str, attached: Option<&Build>) -> Result<(), String> {
54    match attached {
55        None => Err(format!(
56            "attached build {expected}, but the version reports no build"
57        )),
58        Some(build) if build.id != expected => Err(format!(
59            "asked to attach build {expected}, but the version reports build {}",
60            build.id
61        )),
62        Some(_) => Ok(()),
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use smbcloud_ascapi_aso::build::BuildAttributes;
70
71    fn build(id: &str) -> Build {
72        Build {
73            id: id.to_string(),
74            resource_type: "builds".to_string(),
75            attributes: BuildAttributes {
76                version: Some("1".to_string()),
77                uploaded_date: None,
78                expired: None,
79                processing_state: None,
80                min_os_version: None,
81                uses_non_exempt_encryption: None,
82            },
83            relationships: None,
84        }
85    }
86
87    #[test]
88    fn a_matching_build_passes() {
89        assert!(check_attached("b1", Some(&build("b1"))).is_ok());
90    }
91
92    #[test]
93    fn no_build_is_an_error_naming_the_expected_id() {
94        let error = check_attached("b1", None).unwrap_err();
95        assert!(error.contains("b1"));
96    }
97
98    #[test]
99    fn a_different_build_names_both_ids() {
100        let error = check_attached("b1", Some(&build("b2"))).unwrap_err();
101        assert!(error.contains("b1") && error.contains("b2"));
102    }
103}