Skip to content

Commit c31870d

Browse files
feat: update tests and fix api path bug (#105)
68% coverage -> 95%
2 parents 7f04d24 + 4e17012 commit c31870d

15 files changed

Lines changed: 696 additions & 33 deletions

.coveragerc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[run]
2+
omit =
3+
./nitric/proto/*

nitric/api/storage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
StorageListFilesRequest,
3232
)
3333
from enum import Enum
34+
from warnings import warn
3435

3536

3637
class Storage(object):
@@ -140,6 +141,7 @@ async def download_url(self, expiry: int = 600):
140141

141142
async def sign_url(self, mode: FileMode = FileMode.READ, expiry: int = 3600):
142143
"""Generate a signed URL for reading or writing to a file."""
144+
warn("File.sign_url() is deprecated, use upload_url() or download_url() instead", DeprecationWarning)
143145
try:
144146
response = await self._storage._storage_stub.pre_sign_url(
145147
storage_pre_sign_url_request=StoragePreSignUrlRequest(

nitric/application.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ def _create_resource(cls, resource: Type[BT], name: str, *args, **kwargs) -> BT:
6767
)
6868

6969
@classmethod
70-
def _create_tracer(cls) -> TracerProvider:
71-
local_run = "OTELCOL_BIN" not in environ
72-
samplePercent = int(getenv("NITRIC_TRACE_SAMPLE_PERCENT", "100")) / 100.0
70+
def _create_tracer(cls, local: bool = True, sampler: int = 100) -> TracerProvider:
71+
local_run = local or "OTELCOL_BIN" not in environ
72+
samplePercent = int(getenv("NITRIC_TRACE_SAMPLE_PERCENT", sampler)) / 100.0
7373

7474
# If its a local run use a console exporter, otherwise export using OTEL Protocol
7575
exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)

nitric/faas.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,10 @@ class Frequency(Enum):
395395
@staticmethod
396396
def from_str(value: str) -> Frequency:
397397
"""Convert a string frequency value to a Frequency."""
398-
return Frequency[value.strip().lower()]
398+
try:
399+
return Frequency[value.strip().lower()]
400+
except Exception:
401+
raise ValueError(f"{value} is not valid frequency")
399402

400403
@staticmethod
401404
def as_str_list() -> List[str]:

nitric/resources/apis.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ class ApiDetails:
5252

5353
@dataclass
5454
class JwtSecurityDefinition:
55-
"""Represents the JWT security definition for an API."""
55+
"""
56+
Represents the JWT security definition for an API.
57+
58+
issuer (str): the JWT issuer
59+
audiences (List[str]): a list of the allowed audiences for the API
60+
"""
5661

5762
issuer: str
5863
audiences: List[str]
@@ -68,16 +73,16 @@ class ApiOptions:
6873
"""Represents options when creating an API, such as middleware to be applied to all HTTP request to the API."""
6974

7075
path: str
71-
middleware: Union[HttpMiddleware, List[HttpMiddleware], None]
72-
security_definitions: Union[dict[str, SecurityDefinition], None]
73-
security: Union[dict[str, List[str]], None]
76+
middleware: Union[HttpMiddleware, List[HttpMiddleware]]
77+
security_definitions: dict[str, SecurityDefinition]
78+
security: dict[str, List[str]]
7479

7580
def __init__(
7681
self,
7782
path: str = "",
7883
middleware: List[Middleware] = [],
79-
security_definitions: dict[str, SecurityDefinition] = None,
80-
security: dict[str, List[str]] = None,
84+
security_definitions: dict[str, SecurityDefinition] = {},
85+
security: dict[str, List[str]] = {},
8186
):
8287
"""Construct a new API options object."""
8388
self.middleware = middleware
@@ -102,18 +107,18 @@ def _to_resource(b: Api) -> Resource:
102107

103108
def _security_definition_to_grpc_declaration(
104109
security_definitions: dict[str, SecurityDefinition]
105-
) -> Union[dict[str, ApiSecurityDefinition], None]:
110+
) -> dict[str, ApiSecurityDefinition]:
106111
if security_definitions is None or len(security_definitions) == 0:
107-
return None
112+
return {}
108113
return {
109114
k: ApiSecurityDefinition(jwt=ApiSecurityDefinitionJwt(issuer=v.issuer, audiences=v.audiences))
110115
for k, v in security_definitions.items()
111116
}
112117

113118

114-
def _security_to_grpc_declaration(security: dict[str, List[str]]) -> dict[str, ApiScopes] | None:
119+
def _security_to_grpc_declaration(security: dict[str, List[str]]) -> dict[str, ApiScopes]:
115120
if security is None or len(security) == 0:
116-
return None
121+
return {}
117122
return {k: ApiScopes(v) for k, v in security.items()}
118123

119124

@@ -187,7 +192,7 @@ def decorator(function: HttpMiddleware):
187192
return decorator
188193

189194
def methods(self, methods: List[HttpMethod], match: str, opts: MethodOptions = None):
190-
"""Define an HTTP route which will respond to HTTP GET requests."""
195+
"""Define an HTTP route which will respond to specific HTTP requests defined by a list of verbs."""
191196
if opts is None:
192197
opts = MethodOptions()
193198

@@ -275,7 +280,7 @@ async def _details(self) -> ApiDetails:
275280
except GRPCError as grpc_err:
276281
raise exception_from_grpc_error(grpc_err)
277282

278-
async def URL(self) -> str:
283+
async def url(self) -> str:
279284
"""Get the APIs live URL."""
280285
details = await self._details()
281286
return details.url
@@ -291,7 +296,7 @@ class Route:
291296
def __init__(self, api: Api, path: str, opts: RouteOptions):
292297
"""Define a route to be handled by the provided API."""
293298
self.api = api
294-
self.path = api.path.join(path)
299+
self.path = (api.path + path).replace("//", "/")
295300
self.middleware = opts.middleware if opts.middleware is not None else []
296301

297302
def method(self, methods: List[HttpMethod], *middleware: HttpMiddleware, opts: MethodOptions = None):

nitric/resources/buckets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async def _register(self):
6262
except GRPCError as grpc_err:
6363
raise exception_from_grpc_error(grpc_err)
6464

65-
def _perms_to_actions(self, *args: [Union[BucketPermission, str]]) -> List[Action]:
65+
def _perms_to_actions(self, *args: List[Union[BucketPermission, str]]) -> List[Action]:
6666
permission_actions_map = {
6767
BucketPermission.reading: [Action.BucketFileGet, Action.BucketFileList],
6868
BucketPermission.writing: [Action.BucketFilePut],

nitric/resources/schedules.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,15 @@ def every(self, rate_description: str, *middleware: EventMiddleware):
4747
# handle singular frequencies. e.g. every('day')
4848
rate_description = f"1 {rate_description}s" # 'day' becomes '1 days'
4949

50-
rate, freq_str = rate_description.split(" ")
51-
freq = Frequency.from_str(freq_str)
50+
try:
51+
rate, freq_str = rate_description.split(" ")
52+
freq = Frequency.from_str(freq_str)
53+
except Exception:
54+
raise Exception(f"invalid rate expression, frequency must be one of {Frequency.as_str_list()}")
5255

5356
if not rate.isdigit():
5457
raise Exception("invalid rate expression, expression must begin with a positive integer")
5558

56-
if not freq:
57-
raise Exception(
58-
f"invalid rate expression, frequency must be one of ${Frequency.as_str_list()}, received ${freq_str}"
59-
)
60-
6159
opts = RateWorkerOptions(self.description, int(rate), freq)
6260

6361
self.server = FunctionServer(opts)
@@ -73,4 +71,4 @@ def decorator(func: EventMiddleware):
7371
r.every(every, func)
7472
return r
7573

76-
return decorator
74+
return decorator

tests/api/test_documents.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
DocumentQueryStreamRequest,
5151
)
5252
from nitric.proto.nitric.event.v1 import TopicListResponse, NitricTopic
53-
from nitric.utils import _struct_from_dict
5453

5554

5655
class Object(object):

tests/api/test_exception.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666

6767

6868
class TestException:
69-
@pytest.yield_fixture(autouse=True)
69+
@pytest.fixture(autouse=True)
7070
def init_exceptions(self):
7171
# Status codes that can be automatically converted to exceptions
7272
self.accepted_status_codes = set(_exception_code_map.keys())

tests/api/test_queues.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,24 @@ async def test_send(self):
6363
)
6464
)
6565

66+
async def test_send_with_only_payload(self):
67+
mock_send = AsyncMock()
68+
mock_response = Object()
69+
mock_send.return_value = mock_response
70+
71+
payload = {"content": "of task"}
72+
73+
with patch("nitric.proto.nitric.queue.v1.QueueServiceStub.send", mock_send):
74+
queue = Queues().queue("test-queue")
75+
await queue.send(payload)
76+
77+
# Check expected values were passed to Stub
78+
mock_send.assert_called_once_with(
79+
queue_send_request=QueueSendRequest(
80+
queue="test-queue", task=NitricTask(id=None, payload_type=None, payload=_struct_from_dict(payload))
81+
)
82+
)
83+
6684
async def test_send_with_failed(self):
6785
payload = {"content": "of task"}
6886

0 commit comments

Comments
 (0)