diff --git a/google/cloud/storage/emulator/database.py b/google/cloud/storage/emulator/database.py index b45e5ef5c669..f0bef7fb34ed 100644 --- a/google/cloud/storage/emulator/database.py +++ b/google/cloud/storage/emulator/database.py @@ -100,12 +100,9 @@ def insert_test_bucket(self, context): "GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME", "bucket" ) if self.buckets.get(bucket_name) is None: - if context is not None: - request = storage_pb2.InsertBucketRequest(bucket={"name": bucket_name}) - else: - request = utils.common.FakeRequest( - args={}, data=json.dumps({"name": bucket_name}) - ) + request = utils.common.FakeRequest( + args={}, data=json.dumps({"name": bucket_name}) + ) bucket_test, _ = gcs.bucket.Bucket.init(request, context) self.insert_bucket(request, bucket_test, context) bucket_test.metadata.metageneration = 4 diff --git a/google/cloud/storage/emulator/emulator.py b/google/cloud/storage/emulator/emulator.py index c21efc588acf..fc14619234fa 100644 --- a/google/cloud/storage/emulator/emulator.py +++ b/google/cloud/storage/emulator/emulator.py @@ -363,32 +363,21 @@ def bucket_default_object_acl_delete(bucket_name, entity): @retry_test(method="storage.notifications.list") def bucket_notification_list(bucket_name): bucket = db.get_bucket(flask.request, bucket_name, None) - response = {"kind": "storage#notifications", "items": []} - for notification in bucket.notifications.values(): - response["items"].append( - json_format.MessageToDict(notification, preserving_proto_field_name=True) - ) - return response + return bucket.list_notifications(None) @gcs.route("/b//notificationConfigs", methods=["POST"]) @retry_test(method="storage.notifications.insert") def bucket_notification_insert(bucket_name): bucket = db.get_bucket(flask.request, bucket_name, None) - notification = bucket.insert_notification(flask.request, None) - response = json_format.MessageToDict(notification, preserving_proto_field_name=True) - response["kind"] = "storage#notification" - return response + return bucket.insert_notification(flask.request, None) @gcs.route("/b//notificationConfigs/") @retry_test(method="storage.notifications.get") def bucket_notification_get(bucket_name, notification_id): bucket = db.get_bucket(flask.request, bucket_name, None) - notification = bucket.get_notification(notification_id, None) - response = json_format.MessageToDict(notification, preserving_proto_field_name=True) - response["kind"] = "storage#notification" - return response + return bucket.get_notification(notification_id, None) @gcs.route("/b//notificationConfigs/", methods=["DELETE"]) diff --git a/google/cloud/storage/emulator/gcs/bucket.py b/google/cloud/storage/emulator/gcs/bucket.py index 824711c91f6f..7cdf03a1ffa0 100644 --- a/google/cloud/storage/emulator/gcs/bucket.py +++ b/google/cloud/storage/emulator/gcs/bucket.py @@ -460,15 +460,24 @@ def delete_default_object_acl(self, entity, context): # === NOTIFICATIONS === # def insert_notification(self, request, context): - notification = None - if context is not None: - notification = request.notification - else: - notification = json_format.ParseDict( - json.loads(request.data), resources_pb2.Notification() - ) - notification.id = "notification-%d" % random.getrandbits(16) - self.notifications[notification.id] = notification + notification = { + "kind": "storage#notification", + "id": "notification-%d" % random.getrandbits(16), + } + data = json.loads(request.data) + for required_key in {"topic", "payload_format"}: + value = data.pop(required_key, None) + if value is not None: + notification[required_key] = value + else: + utils.error.invalid( + "Missing field in notification %s" % required_key, context + ) + for key in {"event_types", "custom_attributes", "object_name_prefix"}: + value = data.pop(key, None) + if value is not None: + notification[key] = value + self.notifications[notification["id"]] = notification return notification def get_notification(self, notification_id, context): @@ -477,6 +486,12 @@ def get_notification(self, notification_id, context): def delete_notification(self, notification_id, context): del self.notifications[notification_id] + def list_notifications(self, context): + response = {"kind": "storage#notifications", "items": []} + for notification in self.notifications.values(): + response["items"].append(notification) + return response + # === RESPONSE === # def rest(self): diff --git a/google/cloud/storage/emulator/grpc_server.py b/google/cloud/storage/emulator/grpc_server.py index da60017169ff..d7e051ab3969 100644 --- a/google/cloud/storage/emulator/grpc_server.py +++ b/google/cloud/storage/emulator/grpc_server.py @@ -30,136 +30,6 @@ class StorageServicer(storage_pb2_grpc.StorageServicer): - # === BUCKET ===# - - def ListBuckets(self, request, context): - db.insert_test_bucket(context) - result = resources_pb2.ListBucketsResponse(next_page_token="", items=[]) - for bucket in db.list_bucket(request, request.project, context): - result.items.append(bucket.metadata) - return result - - def InsertBucket(self, request, context): - db.insert_test_bucket(context) - bucket, projection = gcs_type.bucket.Bucket.init(request, context) - db.insert_bucket(request, bucket, context) - return bucket.metadata - - def GetBucket(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.metadata - - def UpdateBucket(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - bucket.update(request, context) - return bucket.metadata - - def DeleteBucket(self, request, context): - bucket_name = request.bucket - db.delete_bucket(request, bucket_name, context) - return Empty() - - def ListBucketAccessControls(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - result = resources_pb2.ListBucketAccessControlsResponse( - items=bucket.metadata.acl - ) - return result - - def InsertBucketAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.insert_acl(request, context) - - def GetBucketAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.get_acl(request.entity, context) - - def UpdateBucketAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.update_acl(request, request.entity, context) - - def DeleteBucketAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - bucket.delete_acl(request.entity, context) - return Empty() - - def ListDefaultObjectAccessControls(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - result = resources_pb2.ListObjectAccessControlsResponse( - items=bucket.metadata.default_object_acl - ) - return result - - def InsertDefaultObjectAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.insert_default_object_acl(request, context) - - def GetDefaultObjectAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.get_default_object_acl(request.entity, context) - - def UpdateDefaultObjectAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.update_default_object_acl(request, request.entity, context) - - def DeleteDefaultObjectAccessControl(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - bucket.delete_default_object_acl(request.entity, context) - return Empty() - - def InsertNotification(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - return bucket.insert_notification(request, context) - - def ListNotifications(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - result = resources_pb2.ListNotificationsResponse( - items=bucket.notifications.values() - ) - return result - - def GetNotification(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - notification_id = request.notification - return bucket.get_notification(notification_id, context) - - def DeleteNotification(self, request, context): - bucket_name = request.bucket - bucket = db.get_bucket(request, bucket_name, context) - notification_id = request.notification - bucket.delete_notification(notification_id, context) - return Empty() - - def GetBucketIamPolicy(self, request, context): - bucket_name = request.iam_request.resource - bucket = db.get_bucket(request, bucket_name, context) - return bucket.get_iam_policy(request, context) - - def SetBucketIamPolicy(self, request, context): - bucket_name = request.iam_request.resource - bucket = db.get_bucket(request, bucket_name, context) - return bucket.set_iam_policy(request, context) - - def TestBucketIamPermissions(self, request, context): - return iam_policy_pb2.TestIamPermissionsResponse( - permissions=request.iam_request.permissions - ) - # === OBJECT === # def handle_insert_object_streaming_rpc(self, request_iterator, context): diff --git a/google/cloud/storage/emulator/tests/test_bucket.py b/google/cloud/storage/emulator/tests/test_bucket.py index 1fd0a28ad7eb..f0159faf37cd 100644 --- a/google/cloud/storage/emulator/tests/test_bucket.py +++ b/google/cloud/storage/emulator/tests/test_bucket.py @@ -27,125 +27,6 @@ class TestBucket(unittest.TestCase): - def test_init_grpc(self): - request = storage_pb2.InsertBucketRequest(bucket={"name": "bucket"}) - bucket, projection = gcs.bucket.Bucket.init(request, "") - self.assertEqual(bucket.metadata.name, "bucket") - self.assertEqual(projection, CommonEnums.Projection.NO_ACL) - self.assertListEqual( - list(bucket.metadata.acl), - utils.acl.compute_predefined_bucket_acl( - "bucket", CommonEnums.PredefinedBucketAcl.BUCKET_ACL_PROJECT_PRIVATE, "" - ), - ) - self.assertListEqual( - list(bucket.metadata.default_object_acl), - utils.acl.compute_predefined_default_object_acl( - "bucket", CommonEnums.PredefinedObjectAcl.OBJECT_ACL_PROJECT_PRIVATE, "" - ), - ) - - # WITH ACL - request = storage_pb2.InsertBucketRequest( - bucket={ - "name": "bucket", - "acl": utils.acl.compute_predefined_bucket_acl( - "bucket", - CommonEnums.PredefinedBucketAcl.BUCKET_ACL_AUTHENTICATED_READ, - "", - ), - } - ) - bucket, projection = gcs.bucket.Bucket.init(request, "") - self.assertEqual(bucket.metadata.name, "bucket") - self.assertEqual(projection, CommonEnums.Projection.FULL) - self.assertEqual( - list(bucket.metadata.acl), - utils.acl.compute_predefined_bucket_acl( - "bucket", - CommonEnums.PredefinedBucketAcl.BUCKET_ACL_AUTHENTICATED_READ, - "", - ), - ) - self.assertListEqual( - list(bucket.metadata.default_object_acl), - utils.acl.compute_predefined_default_object_acl( - "bucket", CommonEnums.PredefinedObjectAcl.OBJECT_ACL_PROJECT_PRIVATE, "" - ), - ) - - # WITH PREDEFINED ACL - request = storage_pb2.InsertBucketRequest( - bucket={"name": "bucket"}, - predefined_acl=CommonEnums.PredefinedBucketAcl.BUCKET_ACL_PUBLIC_READ_WRITE, - ) - bucket, projection = gcs.bucket.Bucket.init(request, "") - self.assertEqual(bucket.metadata.name, "bucket") - self.assertEqual(projection, CommonEnums.Projection.NO_ACL) - self.assertEqual( - list(bucket.metadata.acl), - utils.acl.compute_predefined_bucket_acl( - "bucket", - CommonEnums.PredefinedBucketAcl.BUCKET_ACL_PUBLIC_READ_WRITE, - "", - ), - ) - self.assertListEqual( - list(bucket.metadata.default_object_acl), - utils.acl.compute_predefined_default_object_acl( - "bucket", CommonEnums.PredefinedObjectAcl.OBJECT_ACL_PROJECT_PRIVATE, "" - ), - ) - - # WITH ACL AND PREDEFINED ACL - request = storage_pb2.InsertBucketRequest( - bucket={ - "name": "bucket", - "acl": utils.acl.compute_predefined_bucket_acl( - "bucket", - CommonEnums.PredefinedBucketAcl.BUCKET_ACL_AUTHENTICATED_READ, - "", - ), - }, - predefined_acl=CommonEnums.PredefinedBucketAcl.BUCKET_ACL_PRIVATE, - ) - bucket, projection = gcs.bucket.Bucket.init(request, "") - self.assertEqual(bucket.metadata.name, "bucket") - self.assertEqual(projection, CommonEnums.Projection.FULL) - self.assertEqual( - list(bucket.metadata.acl), - utils.acl.compute_predefined_bucket_acl( - "bucket", - CommonEnums.PredefinedBucketAcl.BUCKET_ACL_AUTHENTICATED_READ, - "", - ), - ) - self.assertListEqual( - list(bucket.metadata.default_object_acl), - utils.acl.compute_predefined_default_object_acl( - "bucket", CommonEnums.PredefinedObjectAcl.OBJECT_ACL_PROJECT_PRIVATE, "" - ), - ) - - def test_grpc_to_rest(self): - request = storage_pb2.InsertBucketRequest(bucket={"name": "bucket"}) - bucket, projection = gcs.bucket.Bucket.init(request, "") - self.assertEqual(bucket.metadata.name, "bucket") - - # `REST` GET - - rest_metadata = bucket.rest() - self.assertEqual(rest_metadata["name"], "bucket") - self.assertIsNone(bucket.metadata.labels.get("method")) - - # `REST` PATCH - - request = utils.common.FakeRequest( - args={}, data=json.dumps({"labels": {"method": "rest"}}) - ) - bucket.patch(request, None) - self.assertEqual(bucket.metadata.labels["method"], "rest") - def test_init_rest(self): metadata = { "name": "test-bucket-name", @@ -203,9 +84,7 @@ def test_init_rest(self): # Verify the BucketAccessControl entries have the desired fields metadata.pop("acl") acl = bucket_rest.pop("acl", None) - self.assertLessEqual( - set(["allAuthenticatedUsers"]), set([e["entity"] for e in acl]) - ) + self.assertLessEqual({"allAuthenticatedUsers"}, {e["entity"] for e in acl}) self.assertIsNotNone(acl) for entry in acl: self.assertEqual(entry.pop("kind", None), "storage#bucketAccessControl") @@ -215,17 +94,15 @@ def test_init_rest(self): # Verify the remaining keys are a subset of the expected keys self.assertLessEqual( set(entry.keys()), - set( - [ - "id", - "selfLink", - "email", - "entityId", - "domain", - "projectTeam", - "etag", - ] - ), + { + "id", + "selfLink", + "email", + "entityId", + "domain", + "projectTeam", + "etag", + }, ) # Verify the BucketAccessControl entries have the desired fields metadata.pop("defaultObjectAcl") @@ -412,6 +289,36 @@ def test_default_object_acl(self): with self.assertRaises(Exception): bucket.get_default_object_acl(entity, None) + def test_notification(self): + metadata = { + "name": "test-bucket-name", + "location": "us-central1", + "locationType": "REGIONAL", + "storageClass": "regional", + } + request = utils.common.FakeRequest(args={}, data=json.dumps(metadata)) + bucket, _ = gcs.bucket.Bucket.init(request, None) + + expected = [] + for topic in ["test-topic-1", "test-topic-2"]: + request = utils.common.FakeRequest( + args={}, + data=json.dumps({"topic": topic, "payload_format": "JSON_API_V1"}), + ) + notification = bucket.insert_notification(request, None) + self.assertEqual(notification["topic"], topic) + + get_result = bucket.get_notification(notification["id"], None) + self.assertEqual(notification, get_result) + expected.append(notification) + + list_result = bucket.list_notifications(None) + self.assertDictEqual( + list_result, {"kind": "storage#notifications", "items": expected} + ) + for n in expected: + bucket.delete_notification(n["id"], None) + if __name__ == "__main__": unittest.main()