Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion sdk/core/azure_core_test_macros/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use syn::{
};

const INVALID_RECORDED_ATTRIBUTE_MESSAGE: &str =
"expected `#[recorded::test]` or `#[recorded::test(live)]`";
"expected `#[recorded::test]`, `#[recorded::test(live)]`, or `#[recorded::test(playback)]`";
const INVALID_RECORDED_FUNCTION_MESSAGE: &str =
"expected `async fn(TestContext)` function signature with `Result<T, E>` return";

Expand Down Expand Up @@ -50,6 +50,13 @@ pub fn parse_test(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
});
}

// Ignore playback-only tests if not running playback tests.
if recorded_attrs.playback && test_mode != TestMode::Playback {
test_attr.extend(quote! {
#[ignore = "skipping playback-only tests"]
});
}
Comment on lines +53 to +58
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the use case for this? How do you record this in the first place then? If you don't need a recording, don't use #[recorded::test]. Use a normal #[tokio::test].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran it first with #[recorded::test] and AZURE_TEST_MODE=record, to generate the initial recording, then I marked it as #[recorded::test(playback)] so that this test will not execute in the live test pipeline and only in playback mode.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what it does. I'm asking: why? You can use mock tests. This feels like a lot of work to re-record tests and I don't necessarily want to support a "record-only" concept for other services.


let fn_name = &original_sig.ident;
let mut inputs = original_sig.inputs.iter();
let setup = match inputs.next() {
Expand Down Expand Up @@ -110,6 +117,7 @@ static TEST_MODE: LazyLock<TestMode> = LazyLock::new(|| {
#[derive(Debug, Default)]
struct Attributes {
live: bool,
playback: bool,
}

impl Parse for Attributes {
Expand All @@ -123,6 +131,7 @@ impl Parse for Attributes {
})?;
match ident.to_string().as_str() {
"live" => attrs.live = true,
"playback" => attrs.playback = true,
_ => {
return Err(syn::Error::new(
arg.span(),
Expand All @@ -140,6 +149,13 @@ impl Parse for Attributes {
}
}

if attrs.live && attrs.playback {
return Err(syn::Error::new(
input.span(),
"cannot specify both 'live' and 'playback' attributes",
));
}

Ok(attrs)
}
}
Expand Down Expand Up @@ -274,4 +290,33 @@ mod tests {
};
parse_test(attr, item).unwrap();
}

#[test]
fn attributes_parse_playback() {
let attr: Attribute = syn::parse_quote! {
#[recorded(playback)]
};
let attrs: Attributes = attr.parse_args().unwrap();
assert!(attrs.playback);
assert!(!attrs.live);
}

#[test]
fn attributes_parse_conflicting() {
let attr: Attribute = syn::parse_quote! {
#[recorded(live, playback)]
};
attr.parse_args::<Attributes>().unwrap_err();
}

#[test]
fn parse_recorded_playback_only() {
let attr = quote! { playback };
let item = quote! {
async fn playback_only(ctx: TestContext) -> azure_core::Result<()> {
todo!()
}
};
parse_test(attr, item).unwrap();
}
}
6 changes: 2 additions & 4 deletions sdk/storage/azure_storage_blob/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Added support for `set_legal_hold` to `BlobClient`.

### Breaking Changes

### Bugs Fixed
Expand Down Expand Up @@ -35,10 +37,6 @@
- The `credential` parameter is now `Optional` on `new()` client constructors, allowing for construction of public access clients.
- Renamed `Response<T, F>::into_body(self) -> Result<Response<T>>` to `into_model(self) -> Result<Response<T>>`. `into_body(self)` now returns a `ResponseBody`.

### Bugs Fixed

### Other Changes

## 0.6.0 (2025-10-06)

### Features Added
Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azure_storage_blob/assets.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "rust",
"Tag": "rust/azure_storage_blob_1e5e3b2c6c",
"Tag": "rust/azure_storage_blob_b49e4a659b",
"TagPrefix": "rust/azure_storage_blob"
}
20 changes: 17 additions & 3 deletions sdk/storage/azure_storage_blob/src/clients/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use crate::{
AccessTier, BlobClientAcquireLeaseOptions, BlobClientBreakLeaseOptions,
BlobClientChangeLeaseOptions, BlobClientDeleteOptions, BlobClientDownloadOptions,
BlobClientGetAccountInfoOptions, BlobClientGetPropertiesOptions, BlobClientGetTagsOptions,
BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, BlobClientSetMetadataOptions,
BlobClientSetPropertiesOptions, BlobClientSetTagsOptions, BlobClientSetTierOptions,
BlobTags, BlockBlobClientUploadOptions, StorageErrorCode,
BlobClientReleaseLeaseOptions, BlobClientRenewLeaseOptions, BlobClientSetLegalHoldOptions,
BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTagsOptions,
BlobClientSetTierOptions, BlobTags, BlockBlobClientUploadOptions, StorageErrorCode,
},
pipeline::StorageHeadersPolicy,
AppendBlobClient, BlobClientOptions, BlockBlobClient, PageBlobClient,
Expand Down Expand Up @@ -436,4 +436,18 @@ impl BlobClient {
Err(e) => Err(e),
}
}

/// Sets a legal hold on the blob.
///
/// # Arguments
///
/// * `legal_hold` - Specifies the legal hold status to set on the blob.
/// * `options` - Optional configuration for the request.
pub async fn set_legal_hold(
&self,
legal_hold: bool,
options: Option<BlobClientSetLegalHoldOptions<'_>>,
) -> Result<Response<(), NoFormat>> {
self.client.set_legal_hold(legal_hold, options).await
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions sdk/storage/azure_storage_blob/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ pub use crate::generated::models::{
BlobClientReleaseLeaseResultHeaders, BlobClientRenewLeaseOptions, BlobClientRenewLeaseResult,
BlobClientRenewLeaseResultHeaders, BlobClientSetExpiryResult, BlobClientSetExpiryResultHeaders,
BlobClientSetImmutabilityPolicyResult, BlobClientSetImmutabilityPolicyResultHeaders,
BlobClientSetLegalHoldResult, BlobClientSetLegalHoldResultHeaders,
BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions, BlobClientSetTagsOptions,
BlobClientSetTierOptions, BlobClientStartCopyFromUrlResult,
BlobClientSetLegalHoldOptions, BlobClientSetMetadataOptions, BlobClientSetPropertiesOptions,
BlobClientSetTagsOptions, BlobClientSetTierOptions, BlobClientStartCopyFromUrlResult,
BlobClientStartCopyFromUrlResultHeaders, BlobClientUndeleteResult,
BlobClientUndeleteResultHeaders, BlobContainerClientAcquireLeaseOptions,
BlobContainerClientAcquireLeaseResult, BlobContainerClientAcquireLeaseResultHeaders,
Expand Down
34 changes: 34 additions & 0 deletions sdk/storage/azure_storage_blob/tests/blob_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,3 +644,37 @@ async fn test_encoding_edge_cases(ctx: TestContext) -> Result<(), Box<dyn Error>

Ok(())
}

#[recorded::test(playback)]
async fn test_set_legal_hold(ctx: TestContext) -> Result<(), Box<dyn Error>> {
// Recording Setup
let recording = ctx.recording();
let container_client = get_container_client(recording, false).await?;
let blob_client = container_client.blob_client(&get_blob_name(recording));
container_client.create_container(None).await?;
create_test_blob(&blob_client, None, None).await?;

// Set Legal Hold
blob_client.set_legal_hold(true, None).await?;
let response = blob_client.get_properties(None).await?;
// Assert
let legal_hold = response.legal_hold()?;
assert!(legal_hold.unwrap());

// Attempt Operation While Legal Hold Active
let response = blob_client.delete(None).await;
// Assert
let error = response.unwrap_err().http_status();
assert_eq!(StatusCode::Conflict, error.unwrap());

// Remove Legal Hold
blob_client.set_legal_hold(false, None).await?;
let response = blob_client.get_properties(None).await?;
// Assert
let legal_hold = response.legal_hold()?;
assert!(!legal_hold.unwrap());

blob_client.delete(None).await?;

Ok(())
}
2 changes: 1 addition & 1 deletion sdk/storage/azure_storage_blob/tsp-location.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
directory: specification/storage/Microsoft.BlobStorage
commit: 6b5a0fdadca03abe0b8e91a68a62cd1e639ed55e
commit: 70d88b18082af98a786943f6260bd2dd9ce27179
repo: Azure/azure-rest-api-specs
additionalDirectories:
Loading