use momento::cache::{
    DeleteResponse, GetResponse, SetIfAbsentOrEqualRequest, SetIfAbsentOrEqualResponse,
    SetIfAbsentRequest, SetIfAbsentResponse, SetIfEqualRequest, SetIfEqualResponse,
    SetIfNotEqualRequest, SetIfNotEqualResponse, SetIfPresentAndNotEqualRequest,
    SetIfPresentAndNotEqualResponse, SetIfPresentRequest, SetIfPresentResponse, SetRequest,
    SetResponse,
};
use momento::{MomentoErrorCode, MomentoResult};
use momento_test_util::{
    unique_cache_name, unique_key, unique_string, TestScalar, CACHE_TEST_STATE,
};
use std::{convert::TryInto, time::Duration};

mod get_set_delete {
    use std::time::Duration;

    use super::*;

    #[tokio::test]
    async fn get_set_string() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();

        // Getting a key that doesn't exist should return a miss
        let result = client.get(cache_name, item.key()).await?;
        assert_eq!(
            result,
            GetResponse::Miss,
            "Expected miss for key '{}' in cache {}, got {:?}",
            item.key(),
            cache_name,
            result
        );

        // Setting a key should return a hit
        let result = client.set(cache_name, item.key(), item.value()).await?;
        assert_eq!(
            result,
            SetResponse {},
            "Expected successful Set of key '{}' in cache {}, got {:?}",
            item.key(),
            cache_name,
            result
        );

        // Getting the key should return a hit with the value
        let result = client.get(cache_name, item.key()).await?;
        assert_eq!(
            result,
            GetResponse::from(&item),
            "Expected hit for key '{}' in cache {}, got {:?}",
            item.key(),
            cache_name,
            result
        );

        Ok(())
    }

    #[tokio::test]
    async fn set_with_nonnegative_ttl_is_ok() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();
        let set_request =
            SetRequest::new(cache_name, item.key(), item.value()).ttl(Duration::from_secs(1));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetResponse {});
        Ok(())
    }

    #[tokio::test]
    async fn set_with_ttl_is_miss_after_expiration() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();
        let ttl = Duration::from_secs(1);
        let set_request = SetRequest::new(cache_name, item.key(), item.value()).ttl(ttl);
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetResponse {});

        // Wait for the TTL to expire
        tokio::time::sleep(ttl).await;

        // Getting the key should return a miss
        let result = client.get(cache_name, item.key()).await?;
        assert_eq!(
            result,
            GetResponse::Miss,
            "Expected miss for key '{}' in cache {}, got {:?}",
            item.key(),
            cache_name,
            result
        );

        Ok(())
    }

    #[tokio::test]
    async fn delete_invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client.delete("   ", "key").await.unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn delete_nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_cache_name();
        let result = client.delete(cache_name, "key").await.unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn delete_happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();

        // Deleting a key that doesn't exist should not error
        let result = client.delete(cache_name, item.key()).await?;
        assert_eq!(
            result,
            DeleteResponse {},
            "Expected successful Delete of nonexistent key, got {result:?}"
        );

        // Deleting a key that exists should delete it
        client.set(cache_name, item.key(), item.value()).await?;
        let result = client.delete(cache_name, item.key()).await?;
        assert_eq!(
            result,
            DeleteResponse {},
            "Expected successful Delete of existing key, got {result:?}"
        );

        // Key should not exist after deletion
        let result = client.key_exists(cache_name, item.key()).await?;
        assert!(
            !result.exists,
            "Expected key 'key' to not exist in cache {} after deletion, but it does",
            cache_name
        );

        Ok(())
    }
}

mod increment {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client.increment("   ", "key", 1).await.unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_cache_name();
        let result = client.increment(cache_name, "key", 1).await.unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let key = unique_key();
        let key = key.as_str();

        // Incrementing a key that doesn't exist should create it
        let result = client.increment(cache_name, key, 1).await?;
        assert_eq!(result.value, 1);

        // Incrementing an existing key should increment it
        let result = client.increment(cache_name, key, 1).await?;
        assert_eq!(result.value, 2);

        // Incrementing by a negative number should decrement the value
        let result = client.increment(cache_name, key, -2).await?;
        assert_eq!(result.value, 0);

        Ok(())
    }

    #[tokio::test]
    async fn fails_when_value_is_not_an_integer() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();

        // Set a non-string value
        client.set(cache_name, item.key(), item.value()).await?;

        // Incrementing the key should fail
        let result = client
            .increment(cache_name, item.key(), 1)
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::FailedPreconditionError);

        Ok(())
    }
}

mod set_if_absent {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client
            .set_if_absent("   ", "key", "value")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_string("fake-cache");
        let result = client
            .set_if_absent(cache_name, "key", "value")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item1 = TestScalar::new();
        let item2 = TestScalar::new();

        // Setting a key that doesn't exist should create it
        let result = client
            .set_if_absent(cache_name, item1.key(), item1.value())
            .await?;
        assert_eq!(result, SetIfAbsentResponse::Stored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item1));

        // Setting a key that exists should not overwrite it
        let result = client
            .set_if_absent(cache_name, item1.key(), item2.value())
            .await?;
        assert_eq!(result, SetIfAbsentResponse::NotStored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item1));

        Ok(())
    }

    // string key and string value with ttl before and after expiration
    #[tokio::test]
    async fn happy_path_with_ttl() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();

        let set_request = SetIfAbsentRequest::new(cache_name, item.key(), item.value())
            .ttl(Duration::from_secs(2));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetIfAbsentResponse::Stored);

        // Should have remaining ttl > 0
        let result_ttl: Duration = client
            .item_get_ttl(cache_name, item.key())
            .await?
            .try_into()
            .expect("Expected to get an item ttl");
        assert!(
            result_ttl.as_millis() > 0,
            "Expected ttl > 0, got {:?}",
            result_ttl
        );

        // Wait for ttl to expire
        tokio::time::sleep(Duration::from_secs(2)).await;

        // Should get a cache miss
        let result = client.get(cache_name, item.key()).await?;
        assert_eq!(result, GetResponse::Miss {});

        Ok(())
    }
}

mod set_if_present {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client
            .set_if_present("   ", "key", "value")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_string("fake-cache");
        let result = client
            .set_if_present(cache_name, "key", "value")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item1 = TestScalar::new();
        let item2 = TestScalar::new();

        // Setting a key that doesn't exist should not create it
        let result = client
            .set_if_present(cache_name, item1.key(), item1.value())
            .await?;
        assert_eq!(result, SetIfPresentResponse::NotStored);

        let result = client.set(cache_name, item1.key(), item1.value()).await?;
        assert_eq!(result, SetResponse {});

        // Setting a key that exists should overwrite it
        let result = client
            .set_if_present(cache_name, item1.key(), item2.value())
            .await?;
        assert_eq!(result, SetIfPresentResponse::Stored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        Ok(())
    }

    // string key and string value with ttl before and after expiration
    #[tokio::test]
    async fn happy_path_with_ttl() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();

        let result = client.set(cache_name, item.key(), item.value()).await?;
        assert_eq!(result, SetResponse {});

        let set_request = SetIfPresentRequest::new(cache_name, item.key(), "second_value")
            .ttl(Duration::from_secs(2));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetIfPresentResponse::Stored);

        // Should have remaining ttl > 0
        let result_ttl: Duration = client
            .item_get_ttl(cache_name, item.key())
            .await?
            .try_into()
            .expect("Expected to get an item ttl");
        assert!(
            result_ttl.as_millis() > 0,
            "Expected ttl > 0, got {:?}",
            result_ttl
        );

        // Wait for ttl to expire
        tokio::time::sleep(Duration::from_secs(2)).await;

        // Should get a cache miss
        let result = client.get(cache_name, item.key()).await?;
        assert_eq!(result, GetResponse::Miss {});

        Ok(())
    }
}

mod set_if_equal {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client
            .set_if_equal("   ", "key", "value", "equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_string("fake-cache");
        let result = client
            .set_if_equal(cache_name, "key", "value", "equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item1 = TestScalar::new();
        let item2 = TestScalar::new();

        // Setting a key that doesn't exist should not create it
        let result = client
            .set_if_equal(cache_name, item1.key(), item1.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfEqualResponse::NotStored);

        let result = client.set(cache_name, item1.key(), item1.value()).await?;
        assert_eq!(result, SetResponse {});

        // Setting a key that exists and equals the value should overwrite it
        let result = client
            .set_if_equal(cache_name, item1.key(), item2.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfEqualResponse::Stored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        // Setting a key that exists and does NOT equal the value should NOT overwrite it
        let result = client
            .set_if_equal(cache_name, item1.key(), item1.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfEqualResponse::NotStored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        Ok(())
    }

    // string key and string value with ttl before and after expiration
    #[tokio::test]
    async fn happy_path_with_ttl() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let key_uuid = unique_string("key");
        let key = key_uuid.as_str();

        let result = client.set(cache_name, key, "first_value").await?;
        assert_eq!(result, SetResponse {});

        let set_request = SetIfEqualRequest::new(cache_name, key, "second_value", "first_value")
            .ttl(Duration::from_secs(2));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetIfEqualResponse::Stored);

        // Should have remaining ttl > 0
        let result_ttl: Duration = client
            .item_get_ttl(cache_name, key)
            .await?
            .try_into()
            .expect("Expected to get an item ttl");
        assert!(
            result_ttl.as_millis() > 0,
            "Expected ttl > 0, got {:?}",
            result_ttl
        );

        // Wait for ttl to expire
        tokio::time::sleep(Duration::from_secs(2)).await;

        // Should get a cache miss
        let result = client.get(cache_name, key).await?;
        assert_eq!(result, GetResponse::Miss {});

        Ok(())
    }
}

mod set_if_not_equal {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client
            .set_if_not_equal("   ", "key", "value", "not-equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_string("fake-cache");
        let result = client
            .set_if_not_equal(cache_name, "key", "value", "not-equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item1 = TestScalar::new();
        let item2 = TestScalar::new();

        // Setting a key that doesn't exist should create it
        let result = client
            .set_if_not_equal(cache_name, item1.key(), item1.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfNotEqualResponse::Stored);

        let result = client.set(cache_name, item1.key(), item1.value()).await?;
        assert_eq!(result, SetResponse {});

        // Setting a key that exists and does not equal the value should overwrite it
        let result = client
            .set_if_not_equal(cache_name, item1.key(), item2.value(), item2.value())
            .await?;
        assert_eq!(result, SetIfNotEqualResponse::Stored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        // Setting a key that exists and equals the value should NOT overwrite it
        let result = client
            .set_if_not_equal(cache_name, item1.key(), item1.value(), item2.value())
            .await?;
        assert_eq!(result, SetIfNotEqualResponse::NotStored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        Ok(())
    }

    // string key and string value with ttl before and after expiration
    #[tokio::test]
    async fn happy_path_with_ttl() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item = TestScalar::new();

        let result = client.set(cache_name, item.key(), item.value()).await?;
        assert_eq!(result, SetResponse {});

        let set_request =
            SetIfNotEqualRequest::new(cache_name, item.key(), "second_value", "second_value")
                .ttl(Duration::from_secs(2));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetIfNotEqualResponse::Stored);

        // Should have remaining ttl > 0
        let result_ttl: Duration = client
            .item_get_ttl(cache_name, item.key())
            .await?
            .try_into()
            .expect("Expected to get an item ttl");
        assert!(
            result_ttl.as_millis() > 0,
            "Expected ttl > 0, got {:?}",
            result_ttl
        );

        // Wait for ttl to expire
        tokio::time::sleep(Duration::from_secs(2)).await;

        // Should get a cache miss
        let result = client.get(cache_name, item.key()).await?;
        assert_eq!(result, GetResponse::Miss {});

        Ok(())
    }
}

mod set_if_present_and_not_equal {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client
            .set_if_present_and_not_equal("   ", "key", "value", "not-equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_string("fake-cache");
        let result = client
            .set_if_present_and_not_equal(cache_name, "key", "value", "not-equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item1 = TestScalar::new();
        let item2 = TestScalar::new();

        // Setting a key that doesn't exist should not create it
        let result = client
            .set_if_present_and_not_equal(cache_name, item1.key(), item1.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfPresentAndNotEqualResponse::NotStored);

        let result = client.set(cache_name, item1.key(), item1.value()).await?;
        assert_eq!(result, SetResponse {});

        // Setting a key that exists and does not equal the value should overwrite it
        let result = client
            .set_if_present_and_not_equal(cache_name, item1.key(), item2.value(), item2.value())
            .await?;
        assert_eq!(result, SetIfPresentAndNotEqualResponse::Stored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        // Setting a key that exists and equals the value should NOT overwrite it
        let result = client
            .set_if_present_and_not_equal(cache_name, item1.key(), item1.value(), item2.value())
            .await?;
        assert_eq!(result, SetIfPresentAndNotEqualResponse::NotStored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        Ok(())
    }

    // string key and string value with ttl before and after expiration
    #[tokio::test]
    async fn happy_path_with_ttl() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let key_uuid = unique_string("key");
        let key = key_uuid.as_str();

        let result = client.set(cache_name, key, "first_value").await?;
        assert_eq!(result, SetResponse {});

        let set_request =
            SetIfPresentAndNotEqualRequest::new(cache_name, key, "second_value", "second_value")
                .ttl(Duration::from_secs(2));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetIfPresentAndNotEqualResponse::Stored);

        // Should have remaining ttl > 0
        let result_ttl: Duration = client
            .item_get_ttl(cache_name, key)
            .await?
            .try_into()
            .expect("Expected to get an item ttl");
        assert!(
            result_ttl.as_millis() > 0,
            "Expected ttl > 0, got {:?}",
            result_ttl
        );

        // Wait for ttl to expire
        tokio::time::sleep(Duration::from_secs(2)).await;

        // Should get a cache miss
        let result = client.get(cache_name, key).await?;
        assert_eq!(result, GetResponse::Miss {});

        Ok(())
    }
}

mod set_if_absent_or_equal {
    use super::*;

    #[tokio::test]
    async fn invalid_cache_name() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let result = client
            .set_if_absent_or_equal("   ", "key", "value", "equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::InvalidArgumentError);
        Ok(())
    }

    #[tokio::test]
    async fn nonexistent_cache() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = unique_string("fake-cache");
        let result = client
            .set_if_absent_or_equal(cache_name, "key", "value", "equal")
            .await
            .unwrap_err();
        assert_eq!(result.error_code, MomentoErrorCode::CacheNotFoundError);
        Ok(())
    }

    #[tokio::test]
    async fn happy_path() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let item1 = TestScalar::new();
        let item2 = TestScalar::new();

        // Setting a key that doesn't exist should create it
        let result = client
            .set_if_absent_or_equal(cache_name, item1.key(), item1.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfAbsentOrEqualResponse::Stored);

        let result = client.set(cache_name, item1.key(), item1.value()).await?;
        assert_eq!(result, SetResponse {});

        // Setting a key that exists and equals the value should overwrite it
        let result = client
            .set_if_absent_or_equal(cache_name, item1.key(), item2.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfAbsentOrEqualResponse::Stored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        // Setting a key that exists and does NOT equal the value should NOT overwrite it
        let result = client
            .set_if_absent_or_equal(cache_name, item1.key(), item1.value(), item1.value())
            .await?;
        assert_eq!(result, SetIfAbsentOrEqualResponse::NotStored);
        let result = client.get(cache_name, item1.key()).await?;
        assert_eq!(result, GetResponse::from(&item2));

        Ok(())
    }

    // string key and string value with ttl before and after expiration
    #[tokio::test]
    async fn happy_path_with_ttl() -> MomentoResult<()> {
        let client = &CACHE_TEST_STATE.client;
        let cache_name = CACHE_TEST_STATE.cache_name.as_str();
        let key_uuid = unique_string("key");
        let key = key_uuid.as_str();

        let set_request =
            SetIfAbsentOrEqualRequest::new(cache_name, key, "first_value", "first_value")
                .ttl(Duration::from_secs(2));
        let result = client.send_request(set_request).await?;
        assert_eq!(result, SetIfAbsentOrEqualResponse::Stored);

        // Should have remaining ttl > 0
        let result_ttl: Duration = client
            .item_get_ttl(cache_name, key)
            .await?
            .try_into()
            .expect("Expected to get an item ttl");
        assert!(
            result_ttl.as_millis() > 0,
            "Expected ttl > 0, got {:?}",
            result_ttl
        );

        // Wait for ttl to expire
        tokio::time::sleep(Duration::from_secs(2)).await;

        // Should get a cache miss
        let result = client.get(cache_name, key).await?;
        assert_eq!(result, GetResponse::Miss {});

        Ok(())
    }
}
