diff --git a/Cargo.lock b/Cargo.lock index bf26040978..96e43cf87e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,6 +449,7 @@ dependencies = [ "azure_core", "azure_core_test", "azure_identity", + "azure_storage_blob_test", "azure_storage_common", "serde", "time", @@ -458,6 +459,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "azure_storage_blob_test" +version = "0.1.0" +dependencies = [ + "azure_core", + "azure_core_test", + "azure_storage_blob", + "tokio", +] + [[package]] name = "azure_storage_common" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a81b9b737a..7728c11e7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,10 @@ path = "sdk/identity/azure_identity" version = "0.1.0" path = "sdk/storage" +[workspace.dependencies.azure_storage_blob] +version = "0.1.0" +path = "sdk/storage/azure_storage_blob" + [workspace.dependencies] async-lock = "3.0" async-process = "2.0" diff --git a/sdk/storage/.dict.txt b/sdk/storage/.dict.txt index 9df290e72e..8b87472a8a 100644 --- a/sdk/storage/.dict.txt +++ b/sdk/storage/.dict.txt @@ -1,3 +1,4 @@ +AAABBBCCC appendblock appendpos blockid diff --git a/sdk/storage/assets.json b/sdk/storage/assets.json index 91d3d3d3a0..dc5a008c38 100644 --- a/sdk/storage/assets.json +++ b/sdk/storage/assets.json @@ -1,6 +1,6 @@ { "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "rust", - "Tag": "rust/azure_storage_blob_d76e2bb82c", + "Tag": "rust/azure_storage_blob_d1ca6ac4ca", "TagPrefix": "rust/azure_storage_blob" } \ No newline at end of file diff --git a/sdk/storage/azure_storage_blob/Cargo.toml b/sdk/storage/azure_storage_blob/Cargo.toml index ca25867785..f4923cc783 100644 --- a/sdk/storage/azure_storage_blob/Cargo.toml +++ b/sdk/storage/azure_storage_blob/Cargo.toml @@ -11,18 +11,19 @@ rust-version.workspace = true [dependencies] async-trait.workspace = true -azure_storage_common.workspace = true azure_core = { workspace = true, features = ["xml"] } +azure_storage_common.workspace = true serde.workspace = true time.workspace = true typespec_client_core = { workspace = true, features = ["derive"] } -uuid.workspace = true url.workspace = true +uuid.workspace = true [lints] workspace = true [dev-dependencies] -tokio = { workspace = true, features = ["macros"] } -azure_identity.workspace = true azure_core_test.workspace = true +azure_identity.workspace = true +azure_storage_blob_test.path = "../azure_storage_blob_test" +tokio = { workspace = true, features = ["macros"] } diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs index 56e4c9b5f1..ccb083af34 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_client.rs @@ -5,14 +5,15 @@ use crate::{ clients::GeneratedBlobClient, models::{ BlobBlobClientDownloadOptions, BlobBlobClientGetPropertiesOptions, - BlobBlockBlobClientUploadOptions, BlobProperties, + BlobBlockBlobClientCommitBlockListOptions, BlobBlockBlobClientStageBlockOptions, + BlobBlockBlobClientUploadOptions, BlobProperties, BlockLookupList, }, pipeline::StorageHeadersPolicy, BlobClientOptions, }; use azure_core::{ - credentials::TokenCredential, BearerTokenCredentialPolicy, Bytes, Policy, RequestContent, - Response, Result, Url, + base64, credentials::TokenCredential, BearerTokenCredentialPolicy, Bytes, Policy, + RequestContent, Response, Result, Url, }; use std::sync::Arc; @@ -117,4 +118,33 @@ impl BlobClient { .await?; Ok(response) } + + pub async fn commit_block_list( + &self, + blocks: RequestContent, + options: Option>, + ) -> Result> { + let response = self + .client + .get_blob_block_blob_client(self.container_name.clone(), self.blob_name.clone()) + .commit_block_list(blocks, options) + .await?; + Ok(response) + } + + pub async fn stage_block( + &self, + block_id: &str, + content_length: i64, + body: RequestContent, + options: Option>, + ) -> Result> { + let block_id = base64::encode(block_id); + let response = self + .client + .get_blob_block_blob_client(self.container_name.clone(), self.blob_name.clone()) + .stage_block(&block_id, content_length, body, options) + .await?; + Ok(response) + } } diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs index d757cfc231..bf75cb64d3 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_container_client.rs @@ -2,7 +2,10 @@ // Licensed under the MIT License. use crate::clients::GeneratedBlobClient; -use crate::models::{BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions}; +use crate::models::{ + BlobContainerClientCreateOptions, BlobContainerClientDeleteOptions, + BlobContainerClientGetPropertiesOptions, ContainerProperties, +}; use crate::pipeline::StorageHeadersPolicy; use crate::BlobClientOptions; use azure_core::{ @@ -80,4 +83,18 @@ impl BlobContainerClient { .await?; Ok(response) } + + pub async fn get_container_properties( + &self, + options: Option>, + ) -> Result { + let response = self + .client + .get_blob_container_client(self.container_name.clone()) + .get_properties(options) + .await?; + + let container_properties: ContainerProperties = response.headers().get()?; + Ok(container_properties) + } } diff --git a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs index d194bed477..b8e0e26207 100644 --- a/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs +++ b/sdk/storage/azure_storage_blob/src/clients/blob_service_client.rs @@ -1,2 +1,66 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +use crate::{ + clients::GeneratedBlobClient, + models::{BlobServiceClientGetPropertiesOptions, StorageServiceProperties}, + pipeline::StorageHeadersPolicy, + BlobClientOptions, +}; +use azure_core::{ + credentials::TokenCredential, BearerTokenCredentialPolicy, Policy, Response, Result, Url, +}; +use std::sync::Arc; + +pub struct BlobServiceClient { + endpoint: Url, + client: GeneratedBlobClient, +} + +impl BlobServiceClient { + pub fn new( + endpoint: &str, + credential: Arc, + options: Option, + ) -> Result { + let mut options = options.unwrap_or_default(); + + let storage_headers_policy = Arc::new(StorageHeadersPolicy); + options + .client_options + .per_call_policies + .push(storage_headers_policy); + + let oauth_token_policy = BearerTokenCredentialPolicy::new( + credential.clone(), + ["https://storage.azure.com/.default"], + ); + options + .client_options + .per_try_policies + .push(Arc::new(oauth_token_policy) as Arc); + + let client = GeneratedBlobClient::new(endpoint, credential, Some(options))?; + + Ok(Self { + endpoint: endpoint.parse()?, + client, + }) + } + + pub fn endpoint(&self) -> &Url { + &self.endpoint + } + + pub async fn get_service_properties( + &self, + options: Option>, + ) -> Result> { + let response = self + .client + .get_blob_service_client() + .get_properties(options) + .await?; + Ok(response) + } +} diff --git a/sdk/storage/azure_storage_blob/src/lib.rs b/sdk/storage/azure_storage_blob/src/lib.rs index d3130d4332..9e44af2d30 100644 --- a/sdk/storage/azure_storage_blob/src/lib.rs +++ b/sdk/storage/azure_storage_blob/src/lib.rs @@ -9,9 +9,11 @@ mod generated; pub mod clients { pub mod blob_client; pub mod blob_container_client; + pub mod blob_service_client; pub use blob_client::BlobClient; pub use blob_container_client::BlobContainerClient as ContainerClient; + pub use blob_service_client::BlobServiceClient as ServiceClient; pub use crate::generated::clients::{ BlobAppendBlobClient, BlobBlobClient, BlobBlockBlobClient, @@ -60,6 +62,9 @@ pub mod models { mod blob_properties; pub use blob_properties::BlobProperties; + + mod container_properties; + pub use container_properties::ContainerProperties; } pub use crate::generated::clients::{BlobClient, BlobClientOptions}; diff --git a/sdk/storage/azure_storage_blob/src/models/container_properties.rs b/sdk/storage/azure_storage_blob/src/models/container_properties.rs new file mode 100644 index 0000000000..b55d81e6ca --- /dev/null +++ b/sdk/storage/azure_storage_blob/src/models/container_properties.rs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use azure_core::{ + headers::{ + FromHeaders, HeaderName, Headers, ETAG, HAS_IMMUTABILITY_POLICY, HAS_LEGAL_HOLD, + LEASE_STATE, LEASE_STATUS, + }, + Error, Etag, LeaseStatus, +}; +use typespec_client_core::fmt::SafeDebug; + +use crate::models::LeaseState; + +pub const LAST_MODIFIED: HeaderName = HeaderName::from_static("last-modified"); +pub const IMMUTABLE_STORAGE_WITH_VERSIONING_ENABLED: HeaderName = + HeaderName::from_static("x-ms-immutable-storage-with-versioning-enabled"); + +/// Properties of an Azure Storage container. +/// +#[derive(Clone, Default, SafeDebug)] +pub struct ContainerProperties { + pub last_modified: Option, + pub lease_state: Option, + pub lease_status: Option, + pub has_immutability_policy: Option, + pub has_legal_hold: Option, + pub immutable_storage_with_versioning_enabled: Option, + pub etag: Option, +} + +impl FromHeaders for ContainerProperties { + type Error = Error; + fn header_names() -> &'static [&'static str] { + &[ + "etag", + "last-modified", + "x-ms-lease-state", + "x-ms-lease-status", + "x-ms-immutable-storage-with-versioning-enabled", + "x-ms-has-immutability-policy", + "x-ms-has-legal-hold", + ] + } + + fn from_headers(headers: &Headers) -> Result, Error> { + let mut properties = ContainerProperties { + ..Default::default() + }; + + let last_modified = headers.get_optional_str(&LAST_MODIFIED); + properties.last_modified = last_modified.map(|s| s.to_string()); + + let lease_state: Option = headers.get_optional_as(&LEASE_STATE)?; + properties.lease_state = lease_state; + + let lease_status: Option = headers.get_optional_as(&LEASE_STATUS)?; + properties.lease_status = lease_status; + + let has_immutability_policy: Option = + headers.get_optional_as(&HAS_IMMUTABILITY_POLICY)?; + properties.has_immutability_policy = has_immutability_policy; + + let has_legal_hold: Option = headers.get_optional_as(&HAS_LEGAL_HOLD)?; + properties.has_legal_hold = has_legal_hold; + + let immutable_storage_with_versioning_enabled = + headers.get_optional_str(&IMMUTABLE_STORAGE_WITH_VERSIONING_ENABLED); + properties.immutable_storage_with_versioning_enabled = + immutable_storage_with_versioning_enabled.map(|s| s.to_string()); + + let etag: Option = headers.get_optional_as(&ETAG)?; + properties.etag = etag; + + let last_modified = headers.get_optional_str(&LAST_MODIFIED); + properties.last_modified = last_modified.map(|s| s.to_string()); + + let lease_state: Option = headers.get_optional_as(&LEASE_STATE)?; + properties.lease_state = lease_state; + + let lease_status: Option = headers.get_optional_as(&LEASE_STATUS)?; + properties.lease_status = lease_status; + + Ok(Some(properties)) + } +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_client.rs b/sdk/storage/azure_storage_blob/tests/blob_client.rs index f22a28f536..c1e430027c 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_client.rs @@ -1,32 +1,32 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -use azure_core::{headers::HeaderName, Bytes, RequestContent, StatusCode}; +use azure_core::{base64, headers::HeaderName, Bytes, RequestContent, StatusCode}; use azure_core_test::{recorded, TestContext}; use azure_storage_blob::{ clients::{BlobClient, ContainerClient}, - models::{BlobBlobClientDownloadOptions, BlobBlobClientGetPropertiesOptions, BlobType}, + models::{BlobType, BlockLookupList}, BlobClientOptions, }; -use std::{env, error::Error}; +use azure_storage_blob_test::recorded_test_setup; +use std::error::Error; #[recorded::test] async fn test_get_blob_properties(ctx: TestContext) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - let mut options = BlobClientOptions::default(); - recording.instrument(&mut options.client_options); - - // Setup - let endpoint = format!( - "https://{}.blob.core.windows.net/", - recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() - ); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + let blob_name = recording + .random_string::<12>(Some("blob")) + .to_ascii_lowercase(); // Act let container_client = ContainerClient::new( &endpoint, - String::from("testcontainer1"), + container_name.clone(), recording.credential(), Some(options.clone()), )?; @@ -34,8 +34,8 @@ async fn test_get_blob_properties(ctx: TestContext) -> Result<(), Box let blob_client = BlobClient::new( &endpoint, - String::from("testcontainer1"), - String::from("test_blob.txt"), + container_name, + blob_name, recording.credential(), Some(options), )?; @@ -48,9 +48,7 @@ async fn test_get_blob_properties(ctx: TestContext) -> Result<(), Box None, ) .await?; - let response = blob_client - .get_blob_properties(Some(BlobBlobClientGetPropertiesOptions::default())) - .await; + let response = blob_client.get_blob_properties(None).await; // Assert assert!(response.is_ok()); @@ -69,26 +67,23 @@ async fn test_get_blob_properties_invalid_container( ) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - let mut options = BlobClientOptions::default(); - recording.instrument(&mut options.client_options); - - // Setup - let endpoint = format!( - "https://{}.blob.core.windows.net/", - recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() - ); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + let blob_name = recording + .random_string::<12>(Some("blob")) + .to_ascii_lowercase(); // Act let blob_client = BlobClient::new( &endpoint, - String::from("missingcontainer"), - String::from("test_blob.txt"), + container_name, + blob_name, recording.credential(), Some(options), )?; - let response = blob_client - .get_blob_properties(Some(BlobBlobClientGetPropertiesOptions::default())) - .await; + let response = blob_client.get_blob_properties(None).await; // Assert assert_eq!( @@ -103,19 +98,18 @@ async fn test_get_blob_properties_invalid_container( async fn test_download_blob(ctx: TestContext) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - let mut options = BlobClientOptions::default(); - recording.instrument(&mut options.client_options); - - // Setup - let endpoint = format!( - "https://{}.blob.core.windows.net/", - recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() - ); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + let blob_name = recording + .random_string::<12>(Some("blob")) + .to_ascii_lowercase(); // Act let container_client = ContainerClient::new( &endpoint, - String::from("testcontainer2"), + container_name.clone(), recording.credential(), Some(options.clone()), )?; @@ -123,8 +117,8 @@ async fn test_download_blob(ctx: TestContext) -> Result<(), Box> { let blob_client = BlobClient::new( &endpoint, - String::from("testcontainer2"), - String::from("test_blob.txt"), + container_name, + blob_name, recording.credential(), Some(options), )?; @@ -137,9 +131,7 @@ async fn test_download_blob(ctx: TestContext) -> Result<(), Box> { None, ) .await?; - let response = blob_client - .download_blob(Some(BlobBlobClientDownloadOptions::default())) - .await?; + let response = blob_client.download_blob(None).await?; // Assert let (status_code, headers, response_body) = response.deconstruct(); @@ -158,19 +150,18 @@ async fn test_download_blob(ctx: TestContext) -> Result<(), Box> { async fn test_upload_blob(ctx: TestContext) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - let mut options = BlobClientOptions::default(); - recording.instrument(&mut options.client_options); - - // Setup - let endpoint = format!( - "https://{}.blob.core.windows.net/", - recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() - ); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + let blob_name = recording + .random_string::<12>(Some("blob")) + .to_ascii_lowercase(); // Act let container_client = ContainerClient::new( &endpoint, - String::from("testcontainer3"), + container_name.clone(), recording.credential(), Some(options.clone()), )?; @@ -178,8 +169,8 @@ async fn test_upload_blob(ctx: TestContext) -> Result<(), Box> { let blob_client = BlobClient::new( &endpoint, - String::from("testcontainer3"), - String::from("test_upload_blob.txt"), + container_name, + blob_name, recording.credential(), Some(options), )?; @@ -205,19 +196,18 @@ async fn test_upload_blob(ctx: TestContext) -> Result<(), Box> { async fn test_upload_blob_overwrite(ctx: TestContext) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - let mut options = BlobClientOptions::default(); - recording.instrument(&mut options.client_options); - - // Setup - let endpoint = format!( - "https://{}.blob.core.windows.net/", - recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() - ); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + let blob_name = recording + .random_string::<12>(Some("blob")) + .to_ascii_lowercase(); // Act let container_client = ContainerClient::new( &endpoint, - String::from("testcontainer4"), + container_name.clone(), recording.credential(), Some(options.clone()), )?; @@ -225,8 +215,8 @@ async fn test_upload_blob_overwrite(ctx: TestContext) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + let blob_name = recording + .random_string::<12>(Some("blob")) + .to_ascii_lowercase(); + + // Act + let container_client = ContainerClient::new( + &endpoint, + container_name.clone(), + recording.credential(), + Some(options.clone()), + )?; + container_client.create_container(None).await?; + + let blob_client = BlobClient::new( + &endpoint, + container_name, + blob_name, + recording.credential(), + Some(options), + )?; + + let block_1 = b"AAA"; + let block_2 = b"BBB"; + let block_3 = b"CCC"; + + blob_client + .stage_block( + "1", + i64::try_from(block_1.len())?, + RequestContent::from(block_1.to_vec()), + None, + ) + .await?; + + blob_client + .stage_block( + "2", + i64::try_from(block_2.len())?, + RequestContent::from(block_2.to_vec()), + None, + ) + .await?; + blob_client + .stage_block( + "3", + i64::try_from(block_3.len())?, + RequestContent::from(block_3.to_vec()), + None, + ) + .await?; + + let latest_blocks: Vec = vec![ + base64::encode("1"), + base64::encode("2"), + base64::encode("3"), + ]; + + let block_lookup_list = BlockLookupList { + committed: None, + latest: Some(latest_blocks), + uncommitted: None, + }; + + let request_content = RequestContent::try_from(block_lookup_list)?; + + blob_client.commit_block_list(request_content, None).await?; + + let response = blob_client.download_blob(None).await?; + + // Assert + let (status_code, headers, response_body) = response.deconstruct(); + assert!(status_code.is_success()); + assert_eq!( + "9", + headers.get_str(&HeaderName::from_static("content-length"))? + ); + assert_eq!( + Bytes::from_static(b"AAABBBCCC"), + response_body.collect().await? + ); + + container_client.delete_container(None).await?; + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs index 5e0a1c5a4f..edc4632724 100644 --- a/sdk/storage/azure_storage_blob/tests/blob_container_client.rs +++ b/sdk/storage/azure_storage_blob/tests/blob_container_client.rs @@ -1,27 +1,25 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +use azure_core::StatusCode; use azure_core_test::{recorded, TestContext}; -use azure_storage_blob::{clients::ContainerClient, BlobClientOptions}; -use std::{env, error::Error}; +use azure_storage_blob::{clients::ContainerClient, models::LeaseState, BlobClientOptions}; +use azure_storage_blob_test::recorded_test_setup; +use std::error::Error; #[recorded::test] async fn test_create_container(ctx: TestContext) -> Result<(), Box> { // Recording Setup let recording = ctx.recording(); - let mut options = BlobClientOptions::default(); - recording.instrument(&mut options.client_options); - - // Setup - let endpoint = format!( - "https://{}.blob.core.windows.net/", - recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() - ); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); // Act let container_client = ContainerClient::new( &endpoint, - String::from("testcontainer11"), + container_name, recording.credential(), Some(options), )?; @@ -32,3 +30,61 @@ async fn test_create_container(ctx: TestContext) -> Result<(), Box> { container_client.delete_container(None).await?; Ok(()) } + +#[recorded::test] +async fn test_get_container_properties(ctx: TestContext) -> Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + + // Act + let container_client = ContainerClient::new( + &endpoint, + container_name, + recording.credential(), + Some(options), + )?; + container_client.create_container(None).await?; + let container_properties = container_client.get_container_properties(None).await?; + + // Assert + assert_eq!( + container_properties.lease_state, + Some(LeaseState::Available) + ); + assert_eq!(container_properties.has_immutability_policy, Some(false)); + + container_client.delete_container(None).await?; + Ok(()) +} + +#[recorded::test] +async fn test_get_container_properties_invalid_container( + ctx: TestContext, +) -> Result<(), Box> { + // Recording Setup + let recording = ctx.recording(); + let (options, endpoint) = recorded_test_setup(recording, BlobClientOptions::default()).await; + let container_name = recording + .random_string::<17>(Some("container")) + .to_ascii_lowercase(); + + // Act + let container_client = ContainerClient::new( + &endpoint, + container_name, + recording.credential(), + Some(options), + )?; + let response = container_client.get_container_properties(None).await; + + // Assert + assert!(response.is_err()); + let error = response.unwrap_err().http_status(); + assert_eq!(Some(StatusCode::NotFound), error); + + Ok(()) +} diff --git a/sdk/storage/azure_storage_blob_test/Cargo.toml b/sdk/storage/azure_storage_blob_test/Cargo.toml new file mode 100644 index 0000000000..be988c8e4f --- /dev/null +++ b/sdk/storage/azure_storage_blob_test/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "azure_storage_blob_test" +version = "0.1.0" +description = "Common utilities for Key Vault tests" +readme = "README.md" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +homepage = "https://github.com/azure/azure-sdk-for-rust" +keywords = ["sdk", "azure", "rest", "cloud", "test"] +categories = ["development-tools"] +publish = false + +[dependencies] +azure_core = { workspace = true, features = ["xml"] } +azure_core_test.path = "../../core/azure_core_test" +azure_storage_blob = { workspace = true } +tokio = { workspace = true, features = ["rt"] } + +[lints] +workspace = true diff --git a/sdk/storage/azure_storage_blob_test/src/lib.rs b/sdk/storage/azure_storage_blob_test/src/lib.rs new file mode 100644 index 0000000000..af1bffb16c --- /dev/null +++ b/sdk/storage/azure_storage_blob_test/src/lib.rs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +use azure_core_test::Recording; +use azure_storage_blob::BlobClientOptions; + +pub async fn recorded_test_setup( + recording: &Recording, + mut options: BlobClientOptions, +) -> (BlobClientOptions, String) { + recording.instrument(&mut options.client_options); + let endpoint = format!( + "https://{}.blob.core.windows.net/", + recording.var("AZURE_STORAGE_ACCOUNT_NAME", None).as_str() + ); + + (options, endpoint) +}