main
rs 103 lines 3.15 KB
Raw
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
7 use serde::Serialize;
8 use smbcloud_ascapi_aso::build::Build;
9 use smbcloud_ascapi_aso::prelude::*;
10 use smbcloud_ascapi_core::Client;
11
12 /// The outcome of [`attach_build`].
13 #[derive(Debug, Clone, Serialize)]
14 #[serde(rename_all = "camelCase")]
15 pub 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.
25 pub 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.
53 fn 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)]
67 mod 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 }