Skip to content

Commit 13f0739

Browse files
authored
Add Key Vault Keys integration tests (Azure#2092)
Also refactors out some test utilities into a new azure_security_keyvault_test module with `publish = false`.
1 parent d9b74d0 commit 13f0739

File tree

10 files changed

+513
-5
lines changed

10 files changed

+513
-5
lines changed

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/keyvault/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "rust",
44
"TagPrefix": "rust/keyvault",
5-
"Tag": "rust/keyvault_84345f715b"
5+
"Tag": "rust/keyvault_26744e28fb"
66
}

sdk/keyvault/azure_security_keyvault_keys/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,16 @@ serde = { workspace = true }
2020
serde_json = { workspace = true }
2121
time = { workspace = true }
2222
typespec_client_core = { workspace = true, features = ["derive"] }
23+
24+
[dev-dependencies]
25+
azure_core_test = { workspace = true, features = ["tracing"] }
26+
azure_identity.path = "../../identity/azure_identity"
27+
azure_security_keyvault_test = { path = "../azure_security_keyvault_test" }
28+
rand.workspace = true
29+
tokio.workspace = true
30+
31+
[build-dependencies]
32+
rustc_version.workspace = true
33+
34+
[lints]
35+
workspace = true

sdk/keyvault/azure_security_keyvault_keys/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Code generated by Microsoft (R) Rust Code Generator. DO NOT EDIT.
55

66
mod generated;
7+
mod resource;
78

89
pub mod clients {
910
pub use crate::generated::clients::*;
@@ -25,3 +26,5 @@ pub use crate::generated::clients::{
2526
KeyClientUpdateKeyOptions, KeyClientUpdateKeyRotationPolicyOptions, KeyClientVerifyOptions,
2627
KeyClientWrapKeyOptions,
2728
};
29+
30+
pub use resource::*;
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#[cfg(doc)]
5+
use crate::{models::KeyBundle, KeyClient};
6+
use azure_core::{error::ErrorKind, Result, Url};
7+
8+
/// Information about the resource.
9+
///
10+
/// Call [`ResourceExt::resource_id()`] on supported models e.g., [`KeyBundle`] to get this information.
11+
#[derive(Clone, Debug, Eq, PartialEq)]
12+
pub struct ResourceId {
13+
/// The source URL of the resource.
14+
pub source_id: String,
15+
16+
/// The vault URL containing the resource.
17+
pub vault_url: String,
18+
19+
/// The name of the resource.
20+
pub name: String,
21+
22+
/// The optional version of the resource.
23+
pub version: Option<String>,
24+
}
25+
26+
/// Extension methods to get a [`ResourceId`] from models in this crate.
27+
pub trait ResourceExt {
28+
/// Gets the [`ResourceId`] from this model.
29+
///
30+
/// You can parse the name and version to pass to subsequent [`KeyClient`] method calls.
31+
///
32+
/// # Examples
33+
///
34+
/// ```
35+
/// use azure_security_keyvault_keys::{models::{JsonWebKey, KeyBundle}, ResourceExt as _};
36+
///
37+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38+
/// // KeyClient::get_key() will return a KeyBundle.
39+
/// let mut jwk = JsonWebKey::default();
40+
/// jwk.kid = Some("https://my-vault.vault.azure.net/keys/my-key/abcd1234?api-version=7.5".into());
41+
/// let mut key = KeyBundle::default();
42+
/// key.key = Some(jwk);
43+
///
44+
/// let id = key.resource_id()?;
45+
/// assert_eq!(id.vault_url, "https://my-vault.vault.azure.net");
46+
/// assert_eq!(id.name, "my-key");
47+
/// assert_eq!(id.version, Some("abcd1234".into()));
48+
/// # Ok(())
49+
/// # }
50+
/// ```
51+
fn resource_id(&self) -> Result<ResourceId>;
52+
}
53+
54+
impl<T> ResourceExt for T
55+
where
56+
T: private::AsId,
57+
{
58+
fn resource_id(&self) -> Result<ResourceId> {
59+
let Some(id) = self.as_id() else {
60+
return Err(azure_core::Error::message(
61+
ErrorKind::DataConversion,
62+
"missing resource id",
63+
));
64+
};
65+
66+
let url: Url = id.parse()?;
67+
deconstruct(url)
68+
}
69+
}
70+
71+
fn deconstruct(url: Url) -> Result<ResourceId> {
72+
let vault_url = format!("{}://{}", url.scheme(), url.authority(),);
73+
let mut segments = url
74+
.path_segments()
75+
.ok_or_else(|| azure_core::Error::message(ErrorKind::DataConversion, "invalid url"))?;
76+
segments
77+
.next()
78+
.and_then(none_if_empty)
79+
.ok_or_else(|| azure_core::Error::message(ErrorKind::DataConversion, "missing collection"))
80+
.and_then(|col| {
81+
if col != "keys" {
82+
return Err(azure_core::Error::message(
83+
ErrorKind::DataConversion,
84+
"not in keys collection",
85+
));
86+
}
87+
Ok(col)
88+
})?;
89+
let name = segments
90+
.next()
91+
.and_then(none_if_empty)
92+
.ok_or_else(|| azure_core::Error::message(ErrorKind::DataConversion, "missing name"))
93+
.map(String::from)?;
94+
let version = segments.next().and_then(none_if_empty).map(String::from);
95+
96+
Ok(ResourceId {
97+
source_id: url.as_str().into(),
98+
vault_url,
99+
name,
100+
version,
101+
})
102+
}
103+
104+
fn none_if_empty(s: &str) -> Option<&str> {
105+
if s.is_empty() {
106+
return None;
107+
}
108+
109+
Some(s)
110+
}
111+
112+
mod private {
113+
use crate::models::{DeletedKeyBundle, DeletedKeyItem, KeyBundle, KeyItem};
114+
115+
pub trait AsId {
116+
fn as_id(&self) -> Option<&String>;
117+
}
118+
119+
impl AsId for KeyBundle {
120+
fn as_id(&self) -> Option<&String> {
121+
self.key.as_ref()?.kid.as_ref()
122+
}
123+
}
124+
125+
impl AsId for KeyItem {
126+
fn as_id(&self) -> Option<&String> {
127+
self.kid.as_ref()
128+
}
129+
}
130+
131+
impl AsId for DeletedKeyBundle {
132+
fn as_id(&self) -> Option<&String> {
133+
self.key.as_ref()?.kid.as_ref()
134+
}
135+
}
136+
137+
impl AsId for DeletedKeyItem {
138+
fn as_id(&self) -> Option<&String> {
139+
self.kid.as_ref()
140+
}
141+
}
142+
}
143+
144+
#[cfg(test)]
145+
mod tests {
146+
use crate::models::{JsonWebKey, KeyBundle};
147+
148+
use super::*;
149+
150+
#[test]
151+
fn test_deconstruct() {
152+
deconstruct("file:///tmp".parse().unwrap()).expect_err("cannot-be-base url");
153+
deconstruct("https://vault.azure.net/".parse().unwrap()).expect_err("missing collection");
154+
deconstruct("https://vault.azure.net/collection/".parse().unwrap())
155+
.expect_err("invalid collection");
156+
deconstruct("https://vault.azure.net/keys/".parse().unwrap()).expect_err("missing name");
157+
158+
let url: Url = "https://vault.azure.net/keys/name".parse().unwrap();
159+
assert_eq!(
160+
deconstruct(url.clone()).unwrap(),
161+
ResourceId {
162+
source_id: url.to_string(),
163+
vault_url: "https://vault.azure.net".into(),
164+
name: "name".into(),
165+
version: None
166+
}
167+
);
168+
169+
let url: Url = "https://vault.azure.net/keys/name/version".parse().unwrap();
170+
assert_eq!(
171+
deconstruct(url.clone()).unwrap(),
172+
ResourceId {
173+
source_id: url.to_string(),
174+
vault_url: "https://vault.azure.net".into(),
175+
name: "name".into(),
176+
version: Some("version".into()),
177+
}
178+
);
179+
180+
let url: Url = "https://vault.azure.net:443/keys/name/version"
181+
.parse()
182+
.unwrap();
183+
assert_eq!(
184+
deconstruct(url.clone()).unwrap(),
185+
ResourceId {
186+
source_id: url.to_string(),
187+
vault_url: "https://vault.azure.net".into(),
188+
name: "name".into(),
189+
version: Some("version".into()),
190+
}
191+
);
192+
193+
let url: Url = "https://vault.azure.net:8443/keys/name/version"
194+
.parse()
195+
.unwrap();
196+
assert_eq!(
197+
deconstruct(url.clone()).unwrap(),
198+
ResourceId {
199+
source_id: url.to_string(),
200+
vault_url: "https://vault.azure.net:8443".into(),
201+
name: "name".into(),
202+
version: Some("version".into()),
203+
}
204+
);
205+
}
206+
207+
#[test]
208+
fn from_secret_bundle() {
209+
let mut key = KeyBundle {
210+
key: None,
211+
..Default::default()
212+
};
213+
key.resource_id().expect_err("missing resource id");
214+
215+
let mut jwk = JsonWebKey {
216+
kid: None,
217+
..Default::default()
218+
};
219+
key.key = Some(jwk.clone());
220+
key.resource_id().expect_err("missing resource id");
221+
222+
let url: Url = "https://vault.azure.net/keys/name/version".parse().unwrap();
223+
jwk.kid = Some(url.to_string());
224+
key.key = Some(jwk);
225+
assert_eq!(
226+
key.resource_id().unwrap(),
227+
ResourceId {
228+
source_id: url.to_string(),
229+
vault_url: "https://vault.azure.net".into(),
230+
name: "name".into(),
231+
version: Some("version".into()),
232+
}
233+
);
234+
}
235+
}

0 commit comments

Comments
 (0)