Skip to main content

max / synckit

Cover validate_api_key including its 401 branch Three cases against wiremock: a 200 yields the app name, a 401 is a Server error carrying that status, and a closed port is an error that is not the 401 variant, so a setup UI can tell a bad key from a down server.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-23 22:00 UTC
Signed with PGP, not checked
Commit: 72d024e47b0f60534d97f688923ed57449727c4b
Parent: 943c269
1 file changed, +56 insertions, -0 deletions
@@ -292,3 +292,59 @@
292 292 "Restored expired token should return TokenExpired, got: {err:?}"
293 293 );
294 294 }
295 +
296 + // ── API-key validation ──
297 + //
298 + // `validate_api_key` is a free function: it builds its own HTTP client rather
299 + // than going through `SyncKitClient`, so nothing else in the suite covers it.
300 + // A setup UI calls it before saving a key, and it has to tell "wrong key"
301 + // (401) apart from "server unreachable" or the UI reports the wrong problem.
302 +
303 + const VALIDATE_PATH: &str = "/api/v1/sync/validate-app";
304 +
305 + #[tokio::test]
306 + async fn validate_api_key_returns_the_app_name() {
307 + ensure_crypto_provider();
308 + let kit = MockKit::start().await;
309 + kit.post(VALIDATE_PATH)
310 + .json(json!({"app_name": "goingson"}))
311 + .await;
312 +
313 + let name = synckit_client::validate_api_key(&kit.uri(), "sk_live_whatever")
314 + .await
315 + .expect("a 200 carrying app_name validates");
316 + assert_eq!(name, "goingson");
317 + }
318 +
319 + #[tokio::test]
320 + async fn validate_api_key_reports_401_as_a_server_error() {
321 + ensure_crypto_provider();
322 + let kit = MockKit::start().await;
323 + kit.post(VALIDATE_PATH).code(401).text("nope").await;
324 +
325 + let err = synckit_client::validate_api_key(&kit.uri(), "sk_live_wrong")
326 + .await
327 + .expect_err("a rejected key is an error");
328 + assert!(
329 + matches!(err, SyncKitError::Server { status: 401, .. }),
330 + "expected a 401 Server error, got {err:?}"
331 + );
332 + }
333 +
334 + #[tokio::test]
335 + async fn validate_api_key_unreachable_server_is_not_the_401_variant() {
336 + ensure_crypto_provider();
337 + // Bind then drop, so the port is closed and nothing is listening. A setup UI
338 + // must not tell the user their key is wrong when the server is down.
339 + let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind a loopback port");
340 + let port = listener.local_addr().expect("the bound address").port();
341 + drop(listener);
342 +
343 + let err = synckit_client::validate_api_key(&format!("http://127.0.0.1:{port}"), "sk_live_any")
344 + .await
345 + .expect_err("a closed port cannot validate anything");
346 + assert!(
347 + !matches!(err, SyncKitError::Server { status: 401, .. }),
348 + "an unreachable server must not read as a rejected key, got {err:?}"
349 + );
350 + }