diff --git a/inngest/_internal/comm_lib/handler.py b/inngest/_internal/comm_lib/handler.py index 2f8f1127..424a7397 100644 --- a/inngest/_internal/comm_lib/handler.py +++ b/inngest/_internal/comm_lib/handler.py @@ -43,10 +43,6 @@ def __init__( framework: server_lib.Framework, functions: list[function.Function], ) -> None: - # TODO: Default to true once in-band syncing is stable - self._allow_in_band_sync = env_lib.is_true( - const.EnvKey.ALLOW_IN_BAND_SYNC, - ) self._client = client self._mode = client._mode self._api_origin = client.api_origin @@ -363,24 +359,21 @@ async def put( """Handle a PUT request.""" self._client.logger.info("Syncing app") + + allow_in_band_sync = req.allow_in_band_sync + if req.allow_in_band_sync is None: + # TODO: Default to true once in-band syncing is stable + allow_in_band_sync = env_lib.is_true( + const.EnvKey.ALLOW_IN_BAND_SYNC, + ) + syncer = _Syncer(logger=self._client.logger) if ( req.headers.get(server_lib.HeaderKey.SYNC_KIND.value) == server_lib.SyncKind.IN_BAND.value - and self._allow_in_band_sync + and allow_in_band_sync is True ): - err: typing.Optional[Exception] = None - if isinstance(request_signing_key, Exception): - err = request_signing_key - elif request_signing_key is None: - err = Exception("request must be signed for in-band sync") - if err is not None: - return CommResponse.from_error( - self._client.logger, - err, - status=http.HTTPStatus.UNAUTHORIZED, - ) return syncer.in_band(self, req, request_signing_key) return await syncer.out_of_band(self, req) @@ -394,25 +387,21 @@ def put_sync( """Handle a PUT request.""" self._client.logger.info("Syncing app") + + allow_in_band_sync = req.allow_in_band_sync + if req.allow_in_band_sync is None: + # TODO: Default to true once in-band syncing is stable + allow_in_band_sync = env_lib.is_true( + const.EnvKey.ALLOW_IN_BAND_SYNC, + ) + syncer = _Syncer(logger=self._client.logger) if ( req.headers.get(server_lib.HeaderKey.SYNC_KIND.value) == server_lib.SyncKind.IN_BAND.value - and self._allow_in_band_sync + and allow_in_band_sync is True ): - err: typing.Optional[Exception] = None - if isinstance(request_signing_key, Exception): - err = request_signing_key - elif request_signing_key is None: - err = Exception("request must be signed for in-band sync") - if err is not None: - return CommResponse.from_error( - self._client.logger, - err, - status=http.HTTPStatus.UNAUTHORIZED, - ) - return syncer.in_band(self, req, request_signing_key) return syncer.out_of_band_sync(self, req) @@ -496,10 +485,19 @@ def in_band( req: CommRequest, request_signing_key: types.MaybeError[typing.Optional[str]], ) -> types.MaybeError[CommResponse]: - if not isinstance(request_signing_key, str): - # This should be checked earlier, but we'll also check it here since - # it's critical - return Exception("request must be signed for in-band sync") + if handler._signing_key is not None: + if isinstance(request_signing_key, Exception): + return CommResponse.from_error( + self._logger, + request_signing_key, + status=http.HTTPStatus.UNAUTHORIZED, + ) + if request_signing_key is None: + return CommResponse.from_error( + self._logger, + Exception("request must be signed for in-band sync"), + status=http.HTTPStatus.UNAUTHORIZED, + ) req_body = server_lib.InBandSynchronizeRequest.from_raw(req.body) if isinstance(req_body, Exception): @@ -522,9 +520,6 @@ def in_band( ) if isinstance(inspection, Exception): return inspection - if isinstance(inspection, server_lib.UnauthenticatedInspection): - # Unreachable - return Exception("request must be signed for in-band sync") res_body = server_lib.InBandSynchronizeResponse( app_id=handler._client.app_id, diff --git a/inngest/_internal/comm_lib/models.py b/inngest/_internal/comm_lib/models.py index f497b042..0882cacb 100644 --- a/inngest/_internal/comm_lib/models.py +++ b/inngest/_internal/comm_lib/models.py @@ -14,6 +14,7 @@ class CommRequest(types.BaseModel): + allow_in_band_sync: typing.Optional[bool] body: bytes headers: typing.Union[dict[str, str], dict[str, str]] query_params: typing.Union[dict[str, str], dict[str, list[str]]] diff --git a/inngest/_internal/server_lib/registration.py b/inngest/_internal/server_lib/registration.py index 63b86897..155ab3f4 100644 --- a/inngest/_internal/server_lib/registration.py +++ b/inngest/_internal/server_lib/registration.py @@ -8,7 +8,11 @@ from inngest._internal import const, errors, transforms, types from .consts import DeployType, Framework -from .inspection import AuthenticatedInspection, Capabilities +from .inspection import ( + AuthenticatedInspection, + Capabilities, + UnauthenticatedInspection, +) class _BaseConfig(types.BaseModel): @@ -185,7 +189,7 @@ class InBandSynchronizeResponse(types.BaseModel): env: typing.Optional[str] framework: Framework functions: list[FunctionConfig] - inspection: AuthenticatedInspection + inspection: typing.Union[AuthenticatedInspection, UnauthenticatedInspection] platform: typing.Optional[str] sdk_author: str = const.AUTHOR sdk_language: str = const.LANGUAGE diff --git a/inngest/digital_ocean.py b/inngest/digital_ocean.py index 65bf9bac..1b230c38 100644 --- a/inngest/digital_ocean.py +++ b/inngest/digital_ocean.py @@ -17,6 +17,7 @@ def serve( client: client_lib.Inngest, functions: list[function.Function], *, + allow_in_band_sync: typing.Optional[bool] = None, serve_origin: typing.Optional[str] = None, serve_path: typing.Optional[str] = None, ) -> typing.Callable[[dict[str, object], _Context], _Response]: @@ -28,6 +29,7 @@ def serve( client: Inngest client. functions: List of functions to serve. + allow_in_band_sync: Whether to allow in-band syncing. serve_origin: Origin to serve the functions from. serve_path: The entire function path (e.g. /api/v1/web/fn-b094417f/sample/hello). """ @@ -71,6 +73,7 @@ def main(event: dict[str, object], context: _Context) -> _Response: request_url = urllib.parse.urljoin(context.api_host, path) comm_req = comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=_to_body_bytes(http.body), headers=http.headers, query_params=query_params, diff --git a/inngest/django.py b/inngest/django.py index 0be0db7b..e1a21670 100644 --- a/inngest/django.py +++ b/inngest/django.py @@ -28,6 +28,7 @@ def serve( client: client_lib.Inngest, functions: list[function.Function], *, + allow_in_band_sync: typing.Optional[bool] = None, serve_origin: typing.Optional[str] = None, serve_path: typing.Optional[str] = None, ) -> django.urls.URLPattern: @@ -39,7 +40,7 @@ def serve( client: Inngest client. functions: List of functions to serve. - async_mode: [DEPRECATED] Whether to serve functions asynchronously. + allow_in_band_sync: Whether to allow in-band syncing. serve_origin: Origin to serve Inngest from. serve_path: Path to serve Inngest from. """ @@ -59,6 +60,7 @@ def serve( return _create_handler_async( client, handler, + allow_in_band_sync=allow_in_band_sync, serve_origin=serve_origin, serve_path=serve_path, ) @@ -66,6 +68,7 @@ def serve( return _create_handler_sync( client, handler, + allow_in_band_sync=allow_in_band_sync, serve_origin=serve_origin, serve_path=serve_path, ) @@ -75,6 +78,7 @@ def _create_handler_sync( client: client_lib.Inngest, handler: comm_lib.CommHandler, *, + allow_in_band_sync: typing.Optional[bool], serve_origin: typing.Optional[str], serve_path: typing.Optional[str], ) -> django.urls.URLPattern: @@ -82,6 +86,7 @@ def inngest_api( request: django.http.HttpRequest, ) -> django.http.HttpResponse: comm_req = comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=request.body, headers=dict(request.headers.items()), query_params=dict(request.GET.items()), @@ -126,6 +131,7 @@ def _create_handler_async( client: client_lib.Inngest, handler: comm_lib.CommHandler, *, + allow_in_band_sync: typing.Optional[bool], serve_origin: typing.Optional[str], serve_path: typing.Optional[str], ) -> django.urls.URLPattern: @@ -143,6 +149,7 @@ async def inngest_api( request: django.http.HttpRequest, ) -> django.http.HttpResponse: comm_req = comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=request.body, headers=dict(request.headers.items()), query_params=dict(request.GET.items()), diff --git a/inngest/fast_api.py b/inngest/fast_api.py index f1209514..48a9219a 100644 --- a/inngest/fast_api.py +++ b/inngest/fast_api.py @@ -23,6 +23,7 @@ def serve( client: client_lib.Inngest, functions: list[function.Function], *, + allow_in_band_sync: typing.Optional[bool] = None, serve_origin: typing.Optional[str] = None, serve_path: typing.Optional[str] = None, ) -> None: @@ -35,6 +36,7 @@ def serve( client: Inngest client. functions: List of functions to serve. + allow_in_band_sync: Whether to allow in-band syncing. serve_origin: Origin to serve the functions from. serve_path: Path to serve the functions from. """ @@ -53,6 +55,7 @@ async def get_api_inngest( client, handler.get_sync( comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=await request.body(), headers=dict(request.headers.items()), query_params=dict(request.query_params.items()), @@ -72,6 +75,7 @@ async def post_inngest_api( client, await handler.post( comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=await request.body(), headers=dict(request.headers.items()), query_params=dict(request.query_params.items()), @@ -91,6 +95,7 @@ async def put_inngest_api( client, await handler.put( comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=await request.body(), headers=dict(request.headers.items()), query_params=dict(request.query_params.items()), diff --git a/inngest/flask.py b/inngest/flask.py index b082385c..8050c06d 100644 --- a/inngest/flask.py +++ b/inngest/flask.py @@ -22,6 +22,7 @@ def serve( client: client_lib.Inngest, functions: list[function.Function], *, + allow_in_band_sync: typing.Optional[bool] = None, serve_origin: typing.Optional[str] = None, serve_path: typing.Optional[str] = None, ) -> None: @@ -34,6 +35,7 @@ def serve( client: Inngest client. functions: List of functions to serve. + allow_in_band_sync: Whether to allow in-band syncing. serve_origin: Origin to serve the functions from. serve_path: Path to serve the functions from. """ @@ -53,6 +55,7 @@ def serve( app, client, handler, + allow_in_band_sync=allow_in_band_sync, serve_origin=serve_origin, serve_path=serve_path, ) @@ -61,6 +64,7 @@ def serve( app, client, handler, + allow_in_band_sync=allow_in_band_sync, serve_origin=serve_origin, serve_path=serve_path, ) @@ -71,6 +75,7 @@ def _create_handler_async( client: client_lib.Inngest, handler: comm_lib.CommHandler, *, + allow_in_band_sync: typing.Optional[bool], serve_origin: typing.Optional[str], serve_path: typing.Optional[str], ) -> None: @@ -80,6 +85,7 @@ def _create_handler_async( ) async def inngest_api() -> typing.Union[flask.Response, str]: comm_req = comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=_get_body_bytes(), headers=dict(flask.request.headers.items()), query_params=flask.request.args, @@ -116,6 +122,7 @@ def _create_handler_sync( client: client_lib.Inngest, handler: comm_lib.CommHandler, *, + allow_in_band_sync: typing.Optional[bool], serve_origin: typing.Optional[str], serve_path: typing.Optional[str], ) -> None: @@ -125,6 +132,7 @@ def _create_handler_sync( ) def inngest_api() -> typing.Union[flask.Response, str]: comm_req = comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=_get_body_bytes(), headers=dict(flask.request.headers.items()), query_params=flask.request.args, diff --git a/inngest/tornado.py b/inngest/tornado.py index 93bdcfb7..2729be7d 100644 --- a/inngest/tornado.py +++ b/inngest/tornado.py @@ -23,6 +23,7 @@ def serve( client: client_lib.Inngest, functions: list[function.Function], *, + allow_in_band_sync: typing.Optional[bool] = None, serve_origin: typing.Optional[str] = None, serve_path: typing.Optional[str] = None, ) -> None: @@ -35,6 +36,7 @@ def serve( client: Inngest client. functions: List of functions to serve. + allow_in_band_sync: Whether to allow in-band syncing. serve_origin: Origin to serve the functions from. serve_path: Path to serve the functions from. """ @@ -54,6 +56,7 @@ def data_received( def get(self) -> None: comm_res = handler.get_sync( comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=self.request.body, headers=dict(self.request.headers.items()), query_params=_parse_query_params( @@ -71,6 +74,7 @@ def get(self) -> None: def post(self) -> None: comm_res = handler.post_sync( comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=self.request.body, headers=dict(self.request.headers.items()), query_params=_parse_query_params( @@ -88,6 +92,7 @@ def post(self) -> None: def put(self) -> None: comm_res = handler.put_sync( comm_lib.CommRequest( + allow_in_band_sync=allow_in_band_sync, body=self.request.body, headers=dict(self.request.headers.items()), query_params=_parse_query_params( diff --git a/tests/test_registration/base.py b/tests/test_registration/base.py index 23e57dc5..9843478b 100644 --- a/tests/test_registration/base.py +++ b/tests/test_registration/base.py @@ -28,5 +28,6 @@ def serve( self, client: inngest.Inngest, fns: list[inngest.Function], + allow_in_band_sync: typing.Optional[bool] = None, ) -> None: raise NotImplementedError() diff --git a/tests/test_registration/cases/__init__.py b/tests/test_registration/cases/__init__.py index fe2221bb..05bd1f15 100644 --- a/tests/test_registration/cases/__init__.py +++ b/tests/test_registration/cases/__init__.py @@ -2,17 +2,23 @@ from . import ( base, - cloud_branch_env, - in_band_invalid_sig, - in_band_missing_sig, + cloud_mode_in_band_invalid_sig, + cloud_mode_in_band_missing_sig, + cloud_mode_in_band_valid_sig, + dev_mode_in_band, + in_band_disallowed, + missing_sync_kind, out_of_band, server_kind_mismatch, ) _modules = ( - cloud_branch_env, - in_band_invalid_sig, - in_band_missing_sig, + cloud_mode_in_band_invalid_sig, + cloud_mode_in_band_missing_sig, + cloud_mode_in_band_valid_sig, + dev_mode_in_band, + in_band_disallowed, + missing_sync_kind, out_of_band, server_kind_mismatch, ) diff --git a/tests/test_registration/cases/in_band_invalid_sig.py b/tests/test_registration/cases/cloud_mode_in_band_invalid_sig.py similarity index 90% rename from tests/test_registration/cases/in_band_invalid_sig.py rename to tests/test_registration/cases/cloud_mode_in_band_invalid_sig.py index c41cfad1..15656f62 100644 --- a/tests/test_registration/cases/in_band_invalid_sig.py +++ b/tests/test_registration/cases/cloud_mode_in_band_invalid_sig.py @@ -12,8 +12,12 @@ def create(framework: server_lib.Framework) -> base.Case: def run_test(self: base.TestCase) -> None: """ - Test that the SDK correctly syncs itself with Cloud when using a branch - environment. + Given: + SDK mode: cloud + Sync kind: in_band + Request sig: invalid + + Respond with 401. """ signing_key = "signkey-prod-000000" @@ -35,7 +39,7 @@ def fn( ) -> None: pass - self.serve(client, [fn]) + self.serve(client, [fn], allow_in_band_sync=True) req_body = json.dumps( server_lib.InBandSynchronizeRequest( diff --git a/tests/test_registration/cases/in_band_missing_sig.py b/tests/test_registration/cases/cloud_mode_in_band_missing_sig.py similarity index 89% rename from tests/test_registration/cases/in_band_missing_sig.py rename to tests/test_registration/cases/cloud_mode_in_band_missing_sig.py index c610a46c..e34b5363 100644 --- a/tests/test_registration/cases/in_band_missing_sig.py +++ b/tests/test_registration/cases/cloud_mode_in_band_missing_sig.py @@ -12,8 +12,12 @@ def create(framework: server_lib.Framework) -> base.Case: def run_test(self: base.TestCase) -> None: """ - Test that the SDK correctly syncs itself with Cloud when using a branch - environment. + Given: + SDK mode: cloud + Sync kind: in_band + Request sig: missing + + Respond with 401. """ signing_key = "signkey-prod-000000" @@ -35,13 +39,14 @@ def fn( ) -> None: pass + self.serve(client, [fn], allow_in_band_sync=True) + req_body = json.dumps( server_lib.InBandSynchronizeRequest( url="http://test.local" ).to_dict() ).encode("utf-8") - self.serve(client, [fn]) res = self.put( body=req_body, headers={ diff --git a/tests/test_registration/cases/cloud_branch_env.py b/tests/test_registration/cases/cloud_mode_in_band_valid_sig.py similarity index 95% rename from tests/test_registration/cases/cloud_branch_env.py rename to tests/test_registration/cases/cloud_mode_in_band_valid_sig.py index b359acef..a0e9b3ed 100644 --- a/tests/test_registration/cases/cloud_branch_env.py +++ b/tests/test_registration/cases/cloud_mode_in_band_valid_sig.py @@ -12,8 +12,12 @@ def create(framework: server_lib.Framework) -> base.Case: def run_test(self: base.TestCase) -> None: """ - Test that the SDK correctly syncs itself with Cloud when using a branch - environment. + Given: + SDK mode: cloud + Sync kind: in_band + Request sig: valid + + Perform in-band sync. """ signing_key = "signkey-prod-000000" @@ -35,7 +39,7 @@ def fn( ) -> None: pass - self.serve(client, [fn]) + self.serve(client, [fn], allow_in_band_sync=True) req_body = json.dumps( server_lib.InBandSynchronizeRequest( diff --git a/tests/test_registration/cases/dev_mode_in_band.py b/tests/test_registration/cases/dev_mode_in_band.py new file mode 100644 index 00000000..2344c45e --- /dev/null +++ b/tests/test_registration/cases/dev_mode_in_band.py @@ -0,0 +1,108 @@ +import json + +import inngest +import inngest.fast_api +from inngest._internal import const, server_lib + +from . import base + +_TEST_NAME = base.create_test_name(__file__) + + +def create(framework: server_lib.Framework) -> base.Case: + def run_test(self: base.TestCase) -> None: + """ + Given: + SDK mode: dev + Sync kind: in_band + + Perform in-band sync. Request signature doesn't matter. + """ + + client = inngest.Inngest( + app_id=f"{framework.value}-{_TEST_NAME}", + env="my-env", + is_production=False, + ) + + @client.create_function( + fn_id="foo", + retries=0, + trigger=inngest.TriggerEvent(event="app/foo"), + ) + def fn( + ctx: inngest.Context, + step: inngest.StepSync, + ) -> None: + pass + + self.serve(client, [fn], allow_in_band_sync=True) + + req_body = json.dumps( + server_lib.InBandSynchronizeRequest( + url="http://test.local" + ).to_dict() + ).encode("utf-8") + + res = self.put( + body=req_body, + headers={ + server_lib.HeaderKey.SERVER_KIND.value: server_lib.ServerKind.CLOUD.value, + server_lib.HeaderKey.SYNC_KIND.value: server_lib.SyncKind.IN_BAND.value, + }, + ) + assert res.status_code == 200 + assert res.headers["x-inngest-env"] == "my-env" + assert res.headers["x-inngest-expected-server-kind"] == "dev" + assert res.headers["x-inngest-sync-kind"] == "in_band" + + assert json.loads(res.body.decode("utf-8")) == { + "app_id": client.app_id, + "env": "my-env", + "framework": framework.value, + "functions": [ + { + "batchEvents": None, + "cancel": None, + "concurrency": None, + "debounce": None, + "id": fn.id, + "idempotency": None, + "name": "foo", + "priority": None, + "rateLimit": None, + "steps": { + "step": { + "id": "step", + "name": "step", + "retries": {"attempts": 0}, + "runtime": { + "type": "http", + "url": f"http://test.local?fnId={fn.id}&stepId=step", + }, + } + }, + "throttle": None, + "triggers": [{"event": "app/foo", "expression": None}], + } + ], + "inspection": { + "schema_version": "2024-05-24", + "authentication_succeeded": None, + "function_count": 1, + "has_event_key": False, + "has_signing_key": False, + "has_signing_key_fallback": False, + "mode": "dev", + }, + "platform": None, + "sdk_author": "inngest", + "sdk_language": "py", + "sdk_version": const.VERSION, + "url": "http://test.local", + } + + return base.Case( + name=_TEST_NAME, + run_test=run_test, + ) diff --git a/tests/test_registration/cases/in_band_disallowed.py b/tests/test_registration/cases/in_band_disallowed.py new file mode 100644 index 00000000..4a23fa22 --- /dev/null +++ b/tests/test_registration/cases/in_band_disallowed.py @@ -0,0 +1,135 @@ +import dataclasses +import json +import typing + +import inngest +import inngest.fast_api +from inngest._internal import const, server_lib +from tests import http_proxy + +from . import base + +_TEST_NAME = base.create_test_name(__file__) + + +def create(framework: server_lib.Framework) -> base.Case: + def run_test(self: base.TestCase) -> None: + """ + Never perform an in-band sync if in-band syncing is disabled. + """ + + signing_key = "signkey-prod-000000" + + @dataclasses.dataclass + class State: + body: typing.Optional[bytes] + headers: dict[str, list[str]] + + state = State( + body=None, + headers={}, + ) + + def on_request( + *, + body: typing.Optional[bytes], + headers: dict[str, list[str]], + method: str, + path: str, + ) -> http_proxy.Response: + for k, v in headers.items(): + state.headers[k] = v + + if body is not None: + state.body = body + + return http_proxy.Response( + body=json.dumps({}).encode("utf-8"), + headers={}, + status_code=200, + ) + + mock_cloud = http_proxy.Proxy(on_request).start() + self.addCleanup(mock_cloud.stop) + + client = inngest.Inngest( + api_base_url=f"http://localhost:{mock_cloud.port}", + app_id=f"{framework.value}-{_TEST_NAME}", + env="my-env", + signing_key=signing_key, + ) + + @client.create_function( + fn_id="foo", + retries=0, + trigger=inngest.TriggerEvent(event="app/foo"), + ) + def fn( + ctx: inngest.Context, + step: inngest.StepSync, + ) -> None: + pass + + self.serve(client, [fn]) + + req_body = json.dumps( + server_lib.InBandSynchronizeRequest( + url="http://test.local" + ).to_dict() + ).encode("utf-8") + + res = self.put( + body=req_body, + headers={ + server_lib.HeaderKey.SYNC_KIND.value: server_lib.SyncKind.OUT_OF_BAND.value, + }, + ) + assert res.status_code == 200 + assert json.loads(res.body.decode("utf-8")) == {} + + assert state.headers.get("authorization") is not None + assert state.headers.get("x-inngest-env") == ["my-env"] + assert state.headers.get("x-inngest-framework") == [framework.value] + assert state.headers.get("x-inngest-sdk") == [ + f"inngest-py:v{const.VERSION}" + ] + + host: str + if framework == server_lib.Framework.FAST_API: + host = "http://testserver" + elif framework == server_lib.Framework.FLASK: + host = "http://localhost" + + assert state.body is not None + assert json.loads(state.body.decode("utf-8")) == { + "appname": client.app_id, + "capabilities": {"in_band_sync": "v1", "trust_probe": "v1"}, + "deploy_type": "ping", + "framework": framework.value, + "functions": [ + { + "id": fn.id, + "name": "foo", + "steps": { + "step": { + "id": "step", + "name": "step", + "retries": {"attempts": 0}, + "runtime": { + "type": "http", + "url": f"{host}/api/inngest?fnId={fn.id}&stepId=step", + }, + } + }, + "triggers": [{"event": "app/foo"}], + } + ], + "sdk": f"py:v{const.VERSION}", + "url": f"{host}/api/inngest", + "v": "0.1", + } + + return base.Case( + name=_TEST_NAME, + run_test=run_test, + ) diff --git a/tests/test_registration/cases/missing_sync_kind.py b/tests/test_registration/cases/missing_sync_kind.py new file mode 100644 index 00000000..c80c8962 --- /dev/null +++ b/tests/test_registration/cases/missing_sync_kind.py @@ -0,0 +1,123 @@ +import dataclasses +import json +import typing + +import inngest +import inngest.fast_api +from inngest._internal import const, server_lib +from tests import http_proxy + +from . import base + +_TEST_NAME = base.create_test_name(__file__) + + +def create(framework: server_lib.Framework) -> base.Case: + def run_test(self: base.TestCase) -> None: + """ + Perform an out-of-band sync if the request doesn't specify a sync kind. + """ + + signing_key = "signkey-prod-000000" + + @dataclasses.dataclass + class State: + body: typing.Optional[bytes] + headers: dict[str, list[str]] + + state = State( + body=None, + headers={}, + ) + + def on_request( + *, + body: typing.Optional[bytes], + headers: dict[str, list[str]], + method: str, + path: str, + ) -> http_proxy.Response: + for k, v in headers.items(): + state.headers[k] = v + + if body is not None: + state.body = body + + return http_proxy.Response( + body=json.dumps({}).encode("utf-8"), + headers={}, + status_code=200, + ) + + mock_cloud = http_proxy.Proxy(on_request).start() + self.addCleanup(mock_cloud.stop) + + client = inngest.Inngest( + api_base_url=f"http://localhost:{mock_cloud.port}", + app_id=f"{framework.value}-{_TEST_NAME}", + env="my-env", + signing_key=signing_key, + ) + + @client.create_function( + fn_id="foo", + retries=0, + trigger=inngest.TriggerEvent(event="app/foo"), + ) + def fn( + ctx: inngest.Context, + step: inngest.StepSync, + ) -> None: + pass + + self.serve(client, [fn], allow_in_band_sync=True) + res = self.put(body={}) + assert res.status_code == 200 + assert json.loads(res.body.decode("utf-8")) == {} + + assert state.headers.get("authorization") is not None + assert state.headers.get("x-inngest-env") == ["my-env"] + assert state.headers.get("x-inngest-framework") == [framework.value] + assert state.headers.get("x-inngest-sdk") == [ + f"inngest-py:v{const.VERSION}" + ] + + host: str + if framework == server_lib.Framework.FAST_API: + host = "http://testserver" + elif framework == server_lib.Framework.FLASK: + host = "http://localhost" + + assert state.body is not None + assert json.loads(state.body.decode("utf-8")) == { + "appname": client.app_id, + "capabilities": {"in_band_sync": "v1", "trust_probe": "v1"}, + "deploy_type": "ping", + "framework": framework.value, + "functions": [ + { + "id": fn.id, + "name": "foo", + "steps": { + "step": { + "id": "step", + "name": "step", + "retries": {"attempts": 0}, + "runtime": { + "type": "http", + "url": f"{host}/api/inngest?fnId={fn.id}&stepId=step", + }, + } + }, + "triggers": [{"event": "app/foo"}], + } + ], + "sdk": f"py:v{const.VERSION}", + "url": f"{host}/api/inngest", + "v": "0.1", + } + + return base.Case( + name=_TEST_NAME, + run_test=run_test, + ) diff --git a/tests/test_registration/cases/out_of_band.py b/tests/test_registration/cases/out_of_band.py index 5de3dbfd..75f732a8 100644 --- a/tests/test_registration/cases/out_of_band.py +++ b/tests/test_registration/cases/out_of_band.py @@ -15,13 +15,16 @@ def create(framework: server_lib.Framework) -> base.Case: def run_test(self: base.TestCase) -> None: """ - Test that the SDK correctly syncs itself with Cloud when using a branch - environment. + Given: + SDK mode: cloud + Sync kind: out_of_band + Request sig: valid - We need to use a mock Cloud since the Dev Server doesn't have a mode - that simulates Cloud. + Perform out-of-band sync. """ + signing_key = "signkey-prod-000000" + @dataclasses.dataclass class State: body: typing.Optional[bytes] @@ -58,7 +61,7 @@ def on_request( api_base_url=f"http://localhost:{mock_cloud.port}", app_id=f"{framework.value}-{_TEST_NAME}", env="my-env", - signing_key="signkey-prod-0486c9", + signing_key=signing_key, ) @client.create_function( @@ -72,8 +75,13 @@ def fn( ) -> None: pass - self.serve(client, [fn]) - res = self.put(body={}) + self.serve(client, [fn], allow_in_band_sync=True) + res = self.put( + body={}, + headers={ + server_lib.HeaderKey.SYNC_KIND.value: server_lib.SyncKind.OUT_OF_BAND.value, + }, + ) assert res.status_code == 200 assert json.loads(res.body.decode("utf-8")) == {} diff --git a/tests/test_registration/test_fast_api.py b/tests/test_registration/test_fast_api.py index 554eacab..fabe2d88 100644 --- a/tests/test_registration/test_fast_api.py +++ b/tests/test_registration/test_fast_api.py @@ -1,5 +1,4 @@ import json -import os import typing import unittest @@ -8,7 +7,7 @@ import inngest import inngest.fast_api -from inngest._internal import const, server_lib +from inngest._internal import server_lib from . import base, cases @@ -18,19 +17,9 @@ class TestRegistration(base.TestCase): def setUp(self) -> None: super().setUp() - - # TODO: Delete this when we default to allowing in-band sync - os.environ[const.EnvKey.ALLOW_IN_BAND_SYNC.value] = "1" - self.app = fastapi.FastAPI() self.app_client = fastapi.testclient.TestClient(self.app) - def tearDown(self) -> None: - super().tearDown() - - # TODO: Delete this when we default to allowing in-band sync - os.environ.pop(const.EnvKey.ALLOW_IN_BAND_SYNC.value, None) - def put( self, *, @@ -58,11 +47,13 @@ def serve( self, client: inngest.Inngest, fns: list[inngest.Function], + allow_in_band_sync: typing.Optional[bool] = None, ) -> None: inngest.fast_api.serve( self.app, client, fns, + allow_in_band_sync=allow_in_band_sync, ) diff --git a/tests/test_registration/test_flask.py b/tests/test_registration/test_flask.py index fc72724b..de88888c 100644 --- a/tests/test_registration/test_flask.py +++ b/tests/test_registration/test_flask.py @@ -1,4 +1,3 @@ -import os import typing import unittest @@ -8,7 +7,7 @@ import inngest import inngest.flask -from inngest._internal import const, server_lib +from inngest._internal import server_lib from . import base, cases @@ -18,19 +17,9 @@ class TestRegistration(base.TestCase): def setUp(self) -> None: super().setUp() - - # TODO: Delete this when we default to allowing in-band sync - os.environ[const.EnvKey.ALLOW_IN_BAND_SYNC.value] = "1" - self.app = flask.Flask(__name__) self.app_client = self.app.test_client() - def tearDown(self) -> None: - super().tearDown() - - # TODO: Delete this when we default to allowing in-band sync - os.environ.pop(const.EnvKey.ALLOW_IN_BAND_SYNC.value, None) - def put( self, *, @@ -55,11 +44,13 @@ def serve( self, client: inngest.Inngest, fns: list[inngest.Function], + allow_in_band_sync: typing.Optional[bool] = None, ) -> None: inngest.flask.serve( self.app, client, fns, + allow_in_band_sync=allow_in_band_sync, )