Add Outline API client library foundation
Implements a first vertical slice of the Outline RPC API (auth, documents, collections, users) covering the structural patterns used across the whole API: single object, paginated list, tree, create/update, delete, search. Chosen as the basis for future CLI and GUI clients built on top of this crate. - Handwritten client (not codegen) against the vendored OpenAPI spec, since Outline's API is uniformly POST /api/<resource>.<action> with JSON bodies and inline/anonymous schemas that generators handle poorly - Async (reqwest + tokio) Client, cheaply cloneable, no &mut self methods - thiserror-based Error with ErrorKind classification and boxed API error context; forward-compatible models (unknown fields ignored, unknown string-enum values preserved via a catch-all variant) since the API is unversioned and self-hosted instances vary in age - Pagination via Page<T>/Paginator with next_page/collect_all/into_stream - wiremock-based test suite plus a spec-coverage test guarding against typos in RPC method names
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
mod common;
|
||||
|
||||
use serde_json::json;
|
||||
use wiremock::matchers::{body_json, header, method, path};
|
||||
use wiremock::{Mock, MockServer, ResponseTemplate};
|
||||
|
||||
#[tokio::test]
|
||||
async fn auth_info_returns_user_and_team() {
|
||||
let server = MockServer::start().await;
|
||||
let client = common::client_for(&server).await;
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/auth.info"))
|
||||
.and(header(
|
||||
"authorization",
|
||||
"Bearer ol_api_test000000000000000000000000000000",
|
||||
))
|
||||
.and(body_json(json!({})))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"ok": true,
|
||||
"status": 200,
|
||||
"data": {
|
||||
"user": {
|
||||
"id": "5c3fa3dd-eb47-4239-8a5f-de5b5d6bf6e2",
|
||||
"name": "Jane Doe",
|
||||
"email": "jane@example.com",
|
||||
"role": "admin",
|
||||
"isSuspended": false
|
||||
},
|
||||
"team": {
|
||||
"id": "9d3d1b2e-3c9a-4c67-8a2a-7f5d3f0e9c11",
|
||||
"name": "Acme Inc",
|
||||
"sharing": true
|
||||
}
|
||||
}
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let auth = client.auth().info().await.unwrap();
|
||||
assert_eq!(auth.user.name, "Jane Doe");
|
||||
assert_eq!(auth.team.name, "Acme Inc");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn auth_config_requires_no_authorization_header() {
|
||||
let server = MockServer::start().await;
|
||||
let client = common::client_for(&server).await;
|
||||
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/api/auth.config"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"ok": true,
|
||||
"status": 200,
|
||||
"data": {
|
||||
"name": "Acme Inc",
|
||||
"hostname": "acme-inc.getoutline.com",
|
||||
"services": [
|
||||
{ "id": "slack", "name": "Slack", "authUrl": "https://acme-inc.getoutline.com/auth/slack" }
|
||||
]
|
||||
}
|
||||
})))
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let config = client.auth().config().await.unwrap();
|
||||
assert_eq!(config.name.as_deref(), Some("Acme Inc"));
|
||||
assert_eq!(config.services.len(), 1);
|
||||
assert_eq!(config.services[0].id.as_deref(), Some("slack"));
|
||||
}
|
||||
Reference in New Issue
Block a user