Skip to content

Conversation

@rlinehan
Copy link
Contributor

@rlinehan rlinehan commented Jan 20, 2022

Closes #3.

Flesh out integration tests to add full coverage for all public methods.

A few notes:

  • there isn't really any organization here, just one test file, although organized by method being tested. I'm happy to split this up across multiple files if folks have input on this.
  • I updated the doc strings for the methods to match the current behavior for what errors are raised when, however there are a few cases where that behavior doesn't entirely make sense to me:

1. ClientSdkError is raised when cache name is an invalid type (e.g. an integer)

However, InvalidInputError is raised for most other situations where cache name, key, or value is not a non-empty string. I would think this should be an InvalidInputError too?

2. The only place CacheValueError seems to be used is if create_cache or delete_cache is called with an empty string as cache_name

The name CacheValueError feels like something should be wrong with the value being cached on a set, not with the cache name? It's also confusing because if the cache name is None in these cases then the error is InvalidInputError and (as noted above) if it's an integer then it's ClientSdkError. It would be more intuitive to me if all of these caused the same type of error.

3. If the cache name is empty string on get or set then a PermissionError is thrown with Cache header is empty.

I think this is coming from the grpc server, but that doesn't feel like the right error, especially since it's not the same error that's thrown for other operations with an empty string for cache name (although as noted in 2 above, I don't think CacheValueError is really correct either).

I left comments inline about these cases too. I don't think these need to be fixed/changed in this PR necessarily, but wanted to at least note what I found unexpected.

Expand the existing test file from one happy path test to add tests for all sdk functionality.
Update doc strings for all methods to match what the tests say is the current behavior for raising errors.

def test_create_cache_throws_exception_for_empty_cache_name(self):
with self.assertRaises(errors.CacheValueError):
self.client.create_cache("")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was surprised that this was CacheValueError for create_cache("") when in the next test below it's InvalidInputError for create_cache(None)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. This is pretty unfortunate. The custom 'None' check is needed to protect SDK from failing due to protobuf errors. The distinction between CacheValueError and InvalidInputError is that while the former is from the service while the later comes from the client.

with self.assertRaises(errors.ClientSdkError) as cm:
self.client.create_cache(1)
self.assertEqual('{}'.format(cm.exception),
"Operation failed with error: 1 has type int, but expected one of: bytes, unicode")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Was surprised that create_cache(1) is a ClientSdkError rather than InvalidInputError.

Copy link
Contributor

Choose a reason for hiding this comment

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

Type safety - it would be totally reasonable to add a bug that states the cache name check should ensure non- None string type.

For any unknown failures or conditions that SDK doesn't understand, we default to ClientSdkError.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened #24


def test_delete_cache_throws_exception_for_empty_cache_name(self):
with self.assertRaises(errors.CacheValueError):
self.client.delete_cache("")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similarly here and the test above and below this: delete_cache("") is CacheValueError, delete_cache(None) is InvalidInputError, delete_cache(1) is ClientSdkError.

cache_name = str(uuid.uuid4())
with self.assertRaises(errors.PermissionError) as cm:
self.client.set("", "foo", "bar")
self.assertEqual('{}'.format(cm.exception), "Cache header is empty")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

set with cache_name as "" throwing a PermissionError is IMO confusing as a user.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

with self.assertRaises(errors.ClientSdkError) as cm:
self.client.set(1, "foo", "bar")
self.assertEqual('{}'.format(cm.exception),
"Operation failed with error: Expected str, not <class 'int'>")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another case where an integer cache_name raises ClientSdkError, but empty string or None raises InvalidInputError.

with self.assertRaises(errors.ClientSdkError) as cm:
self.client.get(1, "foo")
self.assertEqual('{}'.format(cm.exception),
"Operation failed with error: Expected str, not <class 'int'>")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another case where an integer cache_name raises ClientSdkError, but empty string or None raises InvalidInputError.

cache_name = str(uuid.uuid4())
with self.assertRaises(errors.PermissionError) as cm:
self.client.get("", "foo")
self.assertEqual('{}'.format(cm.exception), "Cache header is empty")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I find get with cache_name as "" throwing a PermissionError confusing as a user.

@rlinehan rlinehan marked this pull request as ready for review January 20, 2022 00:28
@rlinehan rlinehan requested a review from gautamomento January 20, 2022 00:28
Copy link
Contributor

@gautamomento gautamomento left a comment

Choose a reason for hiding this comment

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

Thank you @rlinehan - this is great.

You have identified a pattern in our SDK side input checks. The way these validations are written always expect users to provide a correct type. Then when users don't, we end up sending a generic ClientSdkException.

I see two ways to handle this:

  1. Embed type-safety checks in our custom validations
  2. When we wrap unknown exceptions - also send include the root exception

I think we should file these bugs and make improvements.


def test_create_cache_throws_exception_for_empty_cache_name(self):
with self.assertRaises(errors.CacheValueError):
self.client.create_cache("")
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. This is pretty unfortunate. The custom 'None' check is needed to protect SDK from failing due to protobuf errors. The distinction between CacheValueError and InvalidInputError is that while the former is from the service while the later comes from the client.

with self.assertRaises(errors.ClientSdkError) as cm:
self.client.create_cache(1)
self.assertEqual('{}'.format(cm.exception),
"Operation failed with error: 1 has type int, but expected one of: bytes, unicode")
Copy link
Contributor

Choose a reason for hiding this comment

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

Type safety - it would be totally reasonable to add a bug that states the cache name check should ensure non- None string type.

For any unknown failures or conditions that SDK doesn't understand, we default to ClientSdkError.

cache_name = str(uuid.uuid4())
with self.assertRaises(errors.PermissionError) as cm:
self.client.set("", "foo", "bar")
self.assertEqual('{}'.format(cm.exception), "Cache header is empty")
Copy link
Contributor

Choose a reason for hiding this comment

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

@gautamomento
Copy link
Contributor

The name CacheValueError feels like something should be wrong with the value being cached on a set, not with the cache name?

This name was derived from in-built ValueError in python. I see how this can cause confusion. Also, the exception is generic on purpose to also account for any INVALID_ARGUMENT grpc status codes. We can totally updates this to InvalidArgumentError or something similar.

@rlinehan
Copy link
Contributor Author

This name was derived from in-built ValueError in python. I see how this can cause confusion. Also, the exception is generic on purpose to also account for any INVALID_ARGUMENT grpc status codes. We can totally updates this to InvalidArgumentError or something similar.

Opened #23 for this

@rlinehan rlinehan merged commit 2dca2aa into main Jan 20, 2022
@rlinehan rlinehan deleted the rlinehan/more-integration-tests branch January 20, 2022 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Integration Tests

3 participants