Add hermetic coverage for service boundaries

This commit is contained in:
ms2k 2026-08-24 16:39:13 +09:00
commit de4f990492
3 changed files with 137 additions and 0 deletions

View file

@ -332,6 +332,38 @@ fn random_hex<const N: usize>() -> String {
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{http::StatusCode, test as actix_test};
#[actix_web::test]
async fn trace_header() {
let app = actix_test::init_service(
App::new()
.wrap(from_fn(trace_request))
.route("/", web::get().to(live)),
)
.await;
let trace_id = "4bf92f3577b34da6a3ce929d0e0e4736";
let request = actix_test::TestRequest::get()
.uri("/")
.insert_header(("traceparent", format!("00-{trace_id}-00f067aa0ba902b7-01")))
.to_request();
let response = actix_test::call_service(&app, request).await;
assert_eq!(response.status(), StatusCode::OK);
let trace = response
.headers()
.get("traceparent")
.unwrap()
.to_str()
.unwrap();
assert!(trace.starts_with(&format!("00-{trace_id}-")));
assert!(trace.ends_with("-01"));
assert_eq!(trace.len(), 55);
assert_eq!(
actix_test::read_body(response).await,
r#"{"status":"live"}"#
);
}
#[test]
fn escapes_prometheus_labels() {

View file

@ -250,3 +250,69 @@ fn generated_id(prefix: char) -> OpaqueId {
}
OpaqueId::parse(value).expect("generated identifier is valid")
}
#[cfg(test)]
mod tests {
use super::*;
fn response(status: StatusCode) -> Response {
http::Response::builder()
.status(status)
.body(String::new())
.unwrap()
.into()
}
#[test]
fn classifies_statuses() {
assert!(classify(response(StatusCode::NO_CONTENT)).is_ok());
assert!(matches!(
classify(response(StatusCode::BAD_REQUEST)),
Err(WorkerError::InvalidRequest)
));
for status in [StatusCode::UNAUTHORIZED, StatusCode::FORBIDDEN] {
assert!(matches!(
classify(response(status)),
Err(WorkerError::Unauthenticated)
));
}
assert!(matches!(
classify(response(StatusCode::CONFLICT)),
Err(WorkerError::PreconditionFailed)
));
for status in [
StatusCode::NOT_FOUND,
StatusCode::TOO_MANY_REQUESTS,
StatusCode::SERVICE_UNAVAILABLE,
] {
assert!(matches!(
classify(response(status)),
Err(WorkerError::ApiUnavailable)
));
}
}
#[test]
fn validates_base_url() {
let client = Client::new();
let valid = WorkerApiClient::new("https://api.example.test/", client.clone()).unwrap();
assert_eq!(valid.base_url, "https://api.example.test");
for url in ["http://api.example.test", "https://", "not-a-url"] {
assert!(matches!(
WorkerApiClient::new(url, client.clone()),
Err(WorkerError::InvalidConfiguration)
));
}
}
#[test]
fn ids_are_unique() {
let operation = operation_id();
let second = operation_id();
let incarnation = incarnation_id();
assert!(operation.as_str().starts_with('o'));
assert!(incarnation.as_str().starts_with('w'));
assert_ne!(operation, second);
assert_ne!(operation, incarnation);
}
}

View file

@ -0,0 +1,39 @@
import { afterEach, expect, test, vi } from 'vitest'
import { installClientErrorReporting } from './observability'
afterEach(() => vi.restoreAllMocks())
test('reports and cleans up', () => {
const beacon = vi.spyOn(navigator, 'sendBeacon').mockReturnValue(true)
const added = vi.spyOn(window, 'addEventListener')
const removed = vi.spyOn(window, 'removeEventListener')
const remove = installClientErrorReporting()
const error = added.mock.calls.find(([kind]) => kind === 'error')?.[1]
const rejection = added.mock.calls.find(
([kind]) => kind === 'unhandledrejection',
)?.[1]
expect(error).toEqual(expect.any(Function))
expect(rejection).toEqual(expect.any(Function))
;(error as EventListener)(new Event('error'))
;(rejection as EventListener)(new Event('unhandledrejection'))
expect(beacon.mock.calls).toEqual([
[
'/client-errors',
JSON.stringify({ kind: 'error', path: window.location.pathname }),
],
[
'/client-errors',
JSON.stringify({
kind: 'unhandled_rejection',
path: window.location.pathname,
}),
],
])
remove()
expect(removed).toHaveBeenCalledWith('error', error)
expect(removed).toHaveBeenCalledWith('unhandledrejection', rejection)
})