From 6cc769140e704d159b0a215a9c7097f82010491b Mon Sep 17 00:00:00 2001 From: Phoebe Goldman Date: Mon, 9 Jun 2025 11:27:23 -0400 Subject: [PATCH] Add an HTTP route to get the current `Timestamp` Under a new router, `/unstable`, as `/unstable/timestamp`. Also add a smoketest that this route is reachable and returns a JSON-encoded `Timestamp`. --- crates/client-api/src/routes/mod.rs | 2 ++ crates/client-api/src/routes/unstable.rs | 21 +++++++++++++++++++++ smoketests/__init__.py | 5 +++-- smoketests/tests/timestamp_route.py | 19 +++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 crates/client-api/src/routes/unstable.rs create mode 100644 smoketests/tests/timestamp_route.py diff --git a/crates/client-api/src/routes/mod.rs b/crates/client-api/src/routes/mod.rs index f0930eefb4c..16fe55022e7 100644 --- a/crates/client-api/src/routes/mod.rs +++ b/crates/client-api/src/routes/mod.rs @@ -12,6 +12,7 @@ mod internal; pub mod metrics; pub mod prometheus; pub mod subscribe; +pub mod unstable; /// This API call is just designed to allow clients to determine whether or not they can /// establish a connection to SpacetimeDB. This API call doesn't actually do anything. @@ -40,4 +41,5 @@ where axum::Router::new() .nest("/v1", router.layer(cors)) .nest("/internal", internal::router()) + .nest("/unstable", unstable::router()) } diff --git a/crates/client-api/src/routes/unstable.rs b/crates/client-api/src/routes/unstable.rs new file mode 100644 index 00000000000..0b0b86fc274 --- /dev/null +++ b/crates/client-api/src/routes/unstable.rs @@ -0,0 +1,21 @@ +use axum::response::IntoResponse; +use spacetimedb_lib::{sats, Timestamp}; + +use crate::NodeDelegate; + +/// Returns the database's view of the current time, +/// as a SATS-JSON encoded [`Timestamp`]. +async fn get_timestamp() -> impl IntoResponse { + axum::Json(sats::serde::SerdeWrapper(Timestamp::now())).into_response() +} + +/// The internal router is for routes which are early in design, +/// and may incompatibly change or be removed without a major version bump. +pub fn router() -> axum::Router +where + S: NodeDelegate + Clone + 'static, +{ + use axum::routing::get; + + axum::Router::new().route("/timestamp", get(get_timestamp)) +} diff --git a/smoketests/__init__.py b/smoketests/__init__.py index 82f7bf426d6..ba924d5da00 100644 --- a/smoketests/__init__.py +++ b/smoketests/__init__.py @@ -264,10 +264,11 @@ def api_call(self, method, path, body = None, headers = {}): log_cmd([method, path]) conn.request(method, path, body, headers) resp = conn.getresponse() - logging.debug(f"{resp.status} {resp.read()}") + body = resp.read() + logging.debug(f"{resp.status} {body}") if resp.status != 200: raise resp - resp + return body @classmethod diff --git a/smoketests/tests/timestamp_route.py b/smoketests/tests/timestamp_route.py new file mode 100644 index 00000000000..1fc1fe759c6 --- /dev/null +++ b/smoketests/tests/timestamp_route.py @@ -0,0 +1,19 @@ +from .. import Smoketest, random_string +import unittest +import json +import io + +TIMESTAMP_TAG = "__timestamp_micros_since_unix_epoch__" + +class TimestampRoute(Smoketest): + AUTOPUBLISH = False + + def test_timestamp_route(self): + resp = self.api_call( + "GET", + "/unstable/timestamp", + ) + timestamp = json.load(io.BytesIO(resp)) + self.assertIsInstance(timestamp, dict) + self.assertIn(TIMESTAMP_TAG, timestamp) + self.assertIsInstance(timestamp[TIMESTAMP_TAG], int)