• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

divviup / divviup-api / 26256510268

21 May 2026 10:21PM UTC coverage: 68.771% (+0.4%) from 68.351%
26256510268

push

github

web-flow
Migrate from Trillium [part 9B]: Rewrite test infrastructure + remove all trillium deps (#2266)

Replace the entire trillium-based test harness with axum-native equivalents and remove every
trillium dependency from the workspace. Are you ready for a PARTY? This one removes Trillium.
Say goodnight.

test-support/* rewrite:
- TestRequest/TestResponse types using tower::ServiceExt::oneshot()
- ClientLogs rewritten as axum middleware capturing request/response bodies
- ApiMocks rewritten with host-based axum::Router dispatch
- User injection via X-Integration-Testing-User header (was in the trillium conn state)

src/ crate cleanup:
- Delete handler::origin_router, handler::proxy, ErrorHandler, and the DivviupApi trillium
  Handler struct
- Delete inject_integration_testing_user middleware (the header-based inject_test_header_user is
  sufficient)
- Rewrote src/api_mocks/*

tests/integration/*:
- All response_json() calls are now synchronous (body eagerly consumed)

Here we see the final destruction of the trillium alliance, and the end of its insignificant
rebellion against our dependency tree.

I've left some more cleanup for Part 10, including refactoring 'with_state' to 'with_user' in
~100 callsites, and the restoration of remaining HTTP metrics.

345 of 366 new or added lines in 14 files covered. (94.26%)

2 existing lines in 2 files now uncovered.

4422 of 6430 relevant lines covered (68.77%)

69.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.87
/src/api_mocks.rs
1
use axum::{
2
    extract::{Request, State},
3
    response::IntoResponse,
4
    Router,
5
};
6
use std::{collections::HashMap, sync::Arc};
7
use tower::ServiceExt;
8
use url::Url;
9

10
pub mod aggregator_api;
11
pub mod auth0;
12
pub mod postmark;
13

14
fn random_chars(n: usize) -> String {
141✔
15
    std::iter::repeat_with(fastrand::alphabetic)
141✔
16
        .take(n)
141✔
17
        .collect()
141✔
18
}
141✔
19

20
#[derive(Clone)]
21
struct HostMap {
22
    routers: HashMap<String, Router>,
23
    fallback: Router,
24
}
25

26
#[derive(Debug)]
27
pub struct ApiMocks {
28
    router: Router,
29
}
30

31
impl ApiMocks {
32
    pub fn new(postmark_url: &str, auth0_url: &str) -> Self {
274✔
33
        let mut routers = HashMap::new();
274✔
34
        routers.insert(extract_host(postmark_url), postmark::mock());
274✔
35
        routers.insert(extract_host(auth0_url), auth0::mock(auth0_url));
274✔
36

37
        let host_map = Arc::new(HostMap {
274✔
38
            routers,
274✔
39
            fallback: aggregator_api::mock(),
274✔
40
        });
274✔
41

42
        let router = Router::new()
274✔
43
            .fallback(dispatch_by_host)
274✔
44
            .with_state(host_map);
274✔
45

46
        Self { router }
274✔
47
    }
274✔
48

49
    pub fn into_router(self) -> Router {
274✔
50
        self.router
274✔
51
    }
274✔
52
}
53

54
// NB: Url::host_str() strips IPv6 brackets, but the Host header keeps them.
55
// Current callers only pass hostname URLs, so this doesn't bite today.
56
fn extract_host(url: &str) -> String {
548✔
57
    Url::parse(url)
548✔
58
        .ok()
548✔
59
        .and_then(|u| {
548✔
60
            u.host_str().map(|h| match u.port() {
548✔
NEW
61
                Some(p) => format!("{h}:{p}"),
×
62
                None => h.to_string(),
548✔
63
            })
548✔
64
        })
548✔
65
        .unwrap_or_default()
548✔
66
        .to_lowercase()
548✔
67
}
548✔
68

69
async fn dispatch_by_host(State(host_map): State<Arc<HostMap>>, req: Request) -> impl IntoResponse {
79✔
70
    let host = req
79✔
71
        .headers()
79✔
72
        .get("host")
79✔
73
        .and_then(|h| h.to_str().ok())
79✔
74
        .unwrap_or("")
79✔
75
        .to_lowercase();
79✔
76

77
    let router = host_map
79✔
78
        .routers
79✔
79
        .get(&host)
79✔
80
        .unwrap_or(&host_map.fallback)
79✔
81
        .clone();
79✔
82

83
    router.oneshot(req).await.into_response()
79✔
84
}
79✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc