Skip to content

Commit

Permalink
feat: initial version of flask application (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
coryan authored Aug 13, 2021
1 parent 89da42c commit 3c5726e
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 1 deletion.
140 changes: 140 additions & 0 deletions emulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import flask
import httpbin
from functools import wraps
from werkzeug.middleware.dispatcher import DispatcherMiddleware

from google.cloud.storage_v1.proto.storage_resources_pb2 import CommonEnums

import gcs as gcs_type
import testbench


db = testbench.database.Database.init()
grpc_port = 0
supported_methods = []

# === DEFAULT ENTRY FOR REST SERVER === #
root = flask.Flask(__name__)
root.debug = False
root.register_error_handler(Exception, testbench.error.RestException.handler)


@root.route("/")
def index():
return "OK"


# === WSGI APP TO HANDLE JSON API === #
GCS_HANDLER_PATH = "/storage/v1"
gcs = flask.Flask(__name__)
gcs.debug = False
gcs.register_error_handler(Exception, testbench.error.RestException.handler)


# === BUCKET === #


@gcs.route("/b", methods=["GET"])
def bucket_list():
db.insert_test_bucket(None)
project = flask.request.args.get("project")
projection = flask.request.args.get("projection", "noAcl")
fields = flask.request.args.get("fields", None)
response = {
"kind": "storage#buckets",
"items": [
bucket.rest() for bucket in db.list_bucket(flask.request, project, None)
],
}
return testbench.common.filter_response_rest(response, projection, fields)


@gcs.route("/b", methods=["POST"])
def bucket_insert():
db.insert_test_bucket(None)
bucket, projection = gcs_type.bucket.Bucket.init(flask.request, None)
fields = flask.request.args.get("fields", None)
db.insert_bucket(flask.request, bucket, None)
return testbench.common.filter_response_rest(bucket.rest(), projection, fields)


@gcs.route("/b/<bucket_name>")
def bucket_get(bucket_name):
db.insert_test_bucket(None)
db.insert_test_bucket(None)
bucket = db.get_bucket(flask.request, bucket_name, None)
projection = testbench.common.extract_projection(
flask.request, CommonEnums.Projection.NO_ACL, None
)
fields = flask.request.args.get("fields", None)
return testbench.common.filter_response_rest(bucket.rest(), projection, fields)


@gcs.route("/b/<bucket_name>", methods=["PUT"])
def bucket_update(bucket_name):
db.insert_test_bucket(None)
bucket = db.get_bucket(flask.request, bucket_name, None)
bucket.update(flask.request, None)
projection = testbench.common.extract_projection(
flask.request, CommonEnums.Projection.FULL, None
)
fields = flask.request.args.get("fields", None)
return testbench.common.filter_response_rest(bucket.rest(), projection, fields)


@gcs.route("/b/<bucket_name>", methods=["PATCH", "POST"])
def bucket_patch(bucket_name):
testbench.common.enforce_patch_override(flask.request)
bucket = db.get_bucket(flask.request, bucket_name, None)
bucket.patch(flask.request, None)
projection = testbench.common.extract_projection(
flask.request, CommonEnums.Projection.FULL, None
)
fields = flask.request.args.get("fields", None)
return testbench.common.filter_response_rest(bucket.rest(), projection, fields)


@gcs.route("/b/<bucket_name>", methods=["DELETE"])
def bucket_delete(bucket_name):
db.delete_bucket(flask.request, bucket_name, None)
return ""


# === SERVER === #

# Define the WSGI application to handle HMAC key requests
(PROJECTS_HANDLER_PATH, projects_app) = gcs_type.project.get_projects_app()

# Define the WSGI application to handle IAM requests
(IAM_HANDLER_PATH, iam_app) = gcs_type.iam.get_iam_app()

server = flask.Flask(__name__)
server.debug = False
server.register_error_handler(Exception, testbench.error.RestException.handler)
server.wsgi_app = testbench.handle_gzip.HandleGzipMiddleware(
DispatcherMiddleware(
root,
{
"/httpbin": httpbin.app,
GCS_HANDLER_PATH: gcs,
PROJECTS_HANDLER_PATH: projects_app,
IAM_HANDLER_PATH: iam_app,
},
)
)

httpbin.app.register_error_handler(Exception, testbench.error.RestException.handler)
2 changes: 1 addition & 1 deletion gcs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from gcs import bucket, holder, object, project
from gcs import bucket, holder, iam, object, project
116 changes: 116 additions & 0 deletions tests/test_emulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
#
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Unit test for emulator."""

import json
import os
import unittest

import emulator


class TestEmulator(unittest.TestCase):
def setUp(self):
self.client = emulator.server.test_client()
# Avoid magic buckets in the test
os.environ.pop("GOOGLE_CLOUD_CPP_STORAGE_TEST_BUCKET_NAME", None)

def test_root(self):
response = self.client.get("/")
self.assertEqual(response.data, b"OK")

def test_bucket_insert(self):
insert_response = self.client.post(
"/storage/v1/b", data=json.dumps({"name": "bucket-name"})
)
self.assertEqual(insert_response.status_code, 200)
self.assertEqual(
insert_response.headers.get("content-type"), "application/json"
)
insert_rest = json.loads(insert_response.data)
self.assertEqual(insert_rest.get("name"), "bucket-name")

get_response = self.client.get("/storage/v1/b/bucket-name")
self.assertEqual(get_response.status_code, 200)
self.assertEqual(get_response.headers.get("content-type"), "application/json")
get_rest = json.loads(get_response.data)
self.assertEqual(insert_rest, get_rest)

patch_response = self.client.patch(
"/storage/v1/b/bucket-name", data=json.dumps({"labels": {"key": "value"}})
)
self.assertEqual(patch_response.status_code, 200)
self.assertEqual(patch_response.headers.get("content-type"), "application/json")
patch_rest = json.loads(patch_response.data)
self.assertEqual(patch_rest.get("labels"), {"key": "value"})

# This is a bit terrible, but we need to send only the fields that are updatable with any update request.
modifiable_fields = {
"acl",
"default_object_acl",
"lifecycle",
"cors",
"storage_class",
"default_event_based_hold",
"labels",
"website",
"versioning",
"logging",
"encryption",
"billing",
"retention_policy",
"location_type",
"iam_configuration",
}
update_request = patch_rest.copy()
for fixed in [k for k in update_request.keys() if k not in modifiable_fields]:
update_request.pop(fixed, None)
for acl in update_request.get("acl", []):
acl.pop("kind", None)
for acl in update_request.get("defaultObjectAcl", []):
acl.pop("kind", None)
patch_rest["labels"]["key"] = "new-value"
update_response = self.client.put(
"/storage/v1/b/bucket-name", data=json.dumps(update_request)
)
self.assertEqual(update_response.status_code, 200, msg=update_response.data)
self.assertEqual(
update_response.headers.get("content-type"), "application/json"
)
update_rest = json.loads(update_response.data)
self.assertEqual(update_rest.get("labels"), {"key": "new-value"})

list_response = self.client.get(
"/storage/v1/b", query_string={"project": "test-project-unused"}
)
self.assertEqual(list_response.status_code, 200)
self.assertEqual(list_response.headers.get("content-type"), "application/json")
list_rest = json.loads(list_response.data)
names = [b.get("name") for b in list_rest.get("items")]
self.assertEqual(names, ["bucket-name"])

delete_response = self.client.delete("/storage/v1/b/bucket-name")
self.assertEqual(delete_response.status_code, 200)

list_response = self.client.get(
"/storage/v1/b", query_string={"project": "test-project-unused"}
)
self.assertEqual(list_response.status_code, 200)
self.assertEqual(list_response.headers.get("content-type"), "application/json")
list_rest = json.loads(list_response.data)
names = [b.get("name") for b in list_rest.get("items")]
self.assertEqual(names, [])

0 comments on commit 3c5726e

Please sign in to comment.