mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
fix(sdk-rs): send headers correctly for usage reporting (#7364)
This commit is contained in:
parent
6adbbb26f3
commit
69e2f74ab8
8 changed files with 430 additions and 58 deletions
5
.changeset/rude-cats-lead.md
Normal file
5
.changeset/rude-cats-lead.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'hive-console-sdk-rs': patch
|
||||
---
|
||||
|
||||
Fix the bug where reports were not being sent correctly due to missing headers
|
||||
36
.github/workflows/publish-rust.yaml
vendored
36
.github/workflows/publish-rust.yaml
vendored
|
|
@ -32,9 +32,43 @@ jobs:
|
|||
echo 'rust_changed=true' >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
publish-rust:
|
||||
test-rust:
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.rust_changed == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: setup environment
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
actor: test-rust
|
||||
codegen: false
|
||||
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1
|
||||
with:
|
||||
toolchain: '1.90.0'
|
||||
default: true
|
||||
override: true
|
||||
|
||||
- name: Cache Rust
|
||||
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test
|
||||
|
||||
publish-rust:
|
||||
needs: [detect-changes, test-rust]
|
||||
if: needs.detect-changes.outputs.rust_changed == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
|
|
|||
66
.github/workflows/release-alpha.yaml
vendored
66
.github/workflows/release-alpha.yaml
vendored
|
|
@ -138,3 +138,69 @@ jobs:
|
|||
cliVersion: ${{ needs.extract-cli-version.outputs.version }}
|
||||
publishLatest: false
|
||||
secrets: inherit
|
||||
|
||||
cargo:
|
||||
needs: [npm]
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
actions: write
|
||||
contents: write
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
|
||||
- name: setup environment
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
codegen: false # no need to run because release script will run it anyway
|
||||
actor: alpha-rust
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: extract published plugin version
|
||||
id: plugin-crate
|
||||
if:
|
||||
needs.npm.outputs.published && contains(needs.npm.outputs.publishedPackages,
|
||||
'"hive-apollo-router-plugin"')
|
||||
run: |
|
||||
cargo install set-cargo-version
|
||||
echo '${{needs.npm.outputs.publishedPackages}}' > published.json
|
||||
|
||||
VERSION=`echo $(jq -r '.[] | select(.name | endswith("hive-apollo-router-plugin")).version' published.json)`
|
||||
set-cargo-version ./packages/libraries/router/Cargo.toml $VERSION
|
||||
|
||||
- name: extract published sdk version
|
||||
id: sdk-crate
|
||||
if:
|
||||
needs.npm.outputs.published && contains(needs.npm.outputs.publishedPackages,
|
||||
'"hive-console-sdk-rs"')
|
||||
run: |
|
||||
cargo install set-cargo-version
|
||||
echo '${{needs.npm.outputs.publishedPackages}}' > published.json
|
||||
|
||||
VERSION=`echo $(jq -r '.[] | select(.name | endswith("hive-console-sdk-rs")).version' published.json)`
|
||||
set-cargo-version ./packages/libraries/sdk-rs/Cargo.toml $VERSION
|
||||
|
||||
echo "crate_publish=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Find and replace the version of "hive-console-sdk" dependency in router Cargo.toml
|
||||
if [ -f ./packages/libraries/router/Cargo.toml ]; then
|
||||
SDK_VERSION_LINE=$(grep -n 'hive-console-sdk' ./packages/libraries/router/Cargo.toml | head -n 1 | cut -d: -f1)
|
||||
if [ ! -z "$SDK_VERSION_LINE" ]; then
|
||||
sed -i "${SDK_VERSION_LINE}s/version = \".*\"/version = \"$VERSION\"/" ./packages/libraries/router/Cargo.toml
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: release to Crates.io
|
||||
if:
|
||||
steps.sdk-crate.outputs.crate_publish == 'true' ||
|
||||
steps.plugin-crate.outputs.crate_publish == 'true'
|
||||
run: |
|
||||
cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
cargo publish --allow-dirty --no-verify
|
||||
|
|
|
|||
11
.github/workflows/release-stable.yaml
vendored
11
.github/workflows/release-stable.yaml
vendored
|
|
@ -112,19 +112,10 @@ jobs:
|
|||
VERSION: ${{ steps.cli.outputs.version }}
|
||||
run: pnpm oclif promote --no-xz --sha ${GITHUB_SHA:0:7} --version $VERSION
|
||||
|
||||
- name: extract published Crate version
|
||||
id: rust-crate
|
||||
- name: release to Crates.io
|
||||
if:
|
||||
steps.changesets.outputs.published && contains(steps.changesets.outputs.publishedPackages,
|
||||
'"hive-apollo-router-plugin"')
|
||||
run: |
|
||||
echo '${{steps.changesets.outputs.publishedPackages}}' > published.json
|
||||
VERSION=`echo $(jq -r '.[] | select(.name | endswith("hive-apollo-router-plugin")).version' published.json)`
|
||||
echo "crate_version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "crate_publish=true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: release to Crates.io
|
||||
if: steps.rust-crate.outputs.crate_publish == 'true'
|
||||
run: |
|
||||
cargo login ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
cargo publish --allow-dirty --no-verify
|
||||
|
|
|
|||
95
configs/cargo/Cargo.lock
generated
95
configs/cargo/Cargo.lock
generated
|
|
@ -1326,6 +1326,15 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
|
|
@ -2625,7 +2634,7 @@ dependencies = [
|
|||
"rand 0.9.2",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"reqwest-retry",
|
||||
"reqwest-retry 0.7.0",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2646,10 +2655,11 @@ dependencies = [
|
|||
"graphql-parser",
|
||||
"graphql-tools",
|
||||
"md5",
|
||||
"mockito",
|
||||
"moka",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"reqwest-retry",
|
||||
"reqwest-retry 0.8.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
@ -3590,6 +3600,31 @@ dependencies = [
|
|||
"syn 2.0.108",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mockito"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"bytes",
|
||||
"colored",
|
||||
"futures-core",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.7.0",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"similar",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moka"
|
||||
version = "0.12.11"
|
||||
|
|
@ -4451,7 +4486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"multimap 0.10.1",
|
||||
"once_cell",
|
||||
|
|
@ -4471,7 +4506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.14.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.108",
|
||||
|
|
@ -4846,13 +4881,34 @@ dependencies = [
|
|||
"parking_lot 0.11.2",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"retry-policies",
|
||||
"retry-policies 0.4.0",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wasm-timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-retry"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "105747e3a037fe5bf17458d794de91149e575b6183fc72c85623a44abb9683f5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"futures",
|
||||
"getrandom 0.2.16",
|
||||
"http 1.3.1",
|
||||
"hyper 1.7.0",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"retry-policies 0.5.1",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wasmtimer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.7.5"
|
||||
|
|
@ -4868,6 +4924,15 @@ dependencies = [
|
|||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "retry-policies"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a4bd6027df676bcb752d3724db0ea3c0c5fc1dd0376fec51ac7dcaf9cc69be"
|
||||
dependencies = [
|
||||
"rand 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.21.0"
|
||||
|
|
@ -6365,13 +6430,13 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.1"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
|
|
@ -6530,6 +6595,20 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtimer"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"js-sys",
|
||||
"parking_lot 0.12.5",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.82"
|
||||
|
|
|
|||
|
|
@ -432,11 +432,14 @@ mod hive_usage_tests {
|
|||
plugin::{test::MockSupergraphService, Plugin, PluginInit},
|
||||
services::supergraph,
|
||||
};
|
||||
use http::header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
|
||||
use httpmock::{Method::POST, Mock, MockServer};
|
||||
use jsonschema::Validator;
|
||||
use serde_json::json;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use crate::consts::PLUGIN_VERSION;
|
||||
|
||||
use super::{Config, UsagePlugin};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
|
@ -481,17 +484,26 @@ mod hive_usage_tests {
|
|||
|
||||
fn activate_usage_mock(&'_ self) -> Mock<'_> {
|
||||
self.mocked_upstream.mock(|when, then| {
|
||||
when.method(POST).path("/usage").matches(|r| {
|
||||
// This mock also validates that the content of the reported usage is valid
|
||||
// when it comes to the JSON schema validation.
|
||||
// if it does not match, the request matching will fail and this will lead
|
||||
// to a failed assertion
|
||||
let body = r.body.as_ref().unwrap();
|
||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||
let body = serde_json::from_str(&body).unwrap();
|
||||
when.method(POST)
|
||||
.path("/usage")
|
||||
.header(CONTENT_TYPE.as_str(), "application/json")
|
||||
.header(
|
||||
USER_AGENT.as_str(),
|
||||
format!("hive-apollo-router/{}", PLUGIN_VERSION),
|
||||
)
|
||||
.header(AUTHORIZATION.as_str(), "Bearer 123")
|
||||
.header("X-Usage-API-Version", "2")
|
||||
.matches(|r| {
|
||||
// This mock also validates that the content of the reported usage is valid
|
||||
// when it comes to the JSON schema validation.
|
||||
// if it does not match, the request matching will fail and this will lead
|
||||
// to a failed assertion
|
||||
let body = r.body.as_ref().unwrap();
|
||||
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||
let body = serde_json::from_str(&body).unwrap();
|
||||
|
||||
SCHEMA_VALIDATOR.is_valid(&body)
|
||||
});
|
||||
SCHEMA_VALIDATOR.is_valid(&body)
|
||||
});
|
||||
then.status(200);
|
||||
})
|
||||
}
|
||||
|
|
@ -576,21 +588,4 @@ mod hive_usage_tests {
|
|||
mock.assert();
|
||||
mock.assert_hits(1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_query_reported() {
|
||||
let instance = UsageTestHelper::new().await;
|
||||
let req = supergraph::Request::fake_builder()
|
||||
.query("query {")
|
||||
.build()
|
||||
.unwrap();
|
||||
let mock = instance.activate_usage_mock();
|
||||
|
||||
instance.execute_operation(req).await.next_response().await;
|
||||
|
||||
instance.wait_for_processing().await;
|
||||
|
||||
mock.assert();
|
||||
mock.assert_hits(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ path = "src/lib.rs"
|
|||
async-trait = "0.1.77"
|
||||
axum-core = "0.5"
|
||||
thiserror = "2.0.11"
|
||||
reqwest = { version = "0.12.0", default-features = false, features = [
|
||||
reqwest = { version = "0.12.24", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
"blocking",
|
||||
"json",
|
||||
] }
|
||||
reqwest-retry = "0.7.0"
|
||||
reqwest-middleware = "0.4.0"
|
||||
reqwest-retry = "0.8.0"
|
||||
reqwest-middleware = "0.4.2"
|
||||
anyhow = "1"
|
||||
tracing = "0.1"
|
||||
serde = "1"
|
||||
|
|
@ -34,3 +33,5 @@ moka = { version = "0.12.10", features = ["future", "sync"] }
|
|||
sha2 = { version = "0.10.8", features = ["std"] }
|
||||
tokio-util = "0.7.16"
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "1.7.0"
|
||||
|
|
@ -3,7 +3,7 @@ use graphql_parser::schema::Document;
|
|||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||
use serde::Serialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::{Arc, Mutex},
|
||||
|
|
@ -12,7 +12,7 @@ use std::{
|
|||
use thiserror::Error;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Report {
|
||||
size: usize,
|
||||
map: HashMap<String, OperationMapRecord>,
|
||||
|
|
@ -20,7 +20,7 @@ pub struct Report {
|
|||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct OperationMapRecord {
|
||||
operation: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
@ -29,7 +29,7 @@ struct OperationMapRecord {
|
|||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Operation {
|
||||
operationMapKey: String,
|
||||
timestamp: u64,
|
||||
|
|
@ -41,20 +41,20 @@ struct Operation {
|
|||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Execution {
|
||||
ok: bool,
|
||||
duration: u128,
|
||||
errorsTotal: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Metadata {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
client: Option<ClientInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ClientInfo {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
|
|
@ -114,7 +114,7 @@ pub struct UsageAgent {
|
|||
}
|
||||
|
||||
fn non_empty_string(value: Option<String>) -> Option<String> {
|
||||
value.filter(|str| str.is_empty())
|
||||
value.filter(|str| !str.is_empty())
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
@ -171,6 +171,7 @@ impl UsageAgent {
|
|||
.connect_timeout(connect_timeout)
|
||||
.timeout(request_timeout)
|
||||
.user_agent(user_agent)
|
||||
.default_headers(default_headers)
|
||||
.build()
|
||||
.map_err(AgentError::HTTPClientCreationError)?;
|
||||
let client = ClientBuilder::new(reqwest_agent)
|
||||
|
|
@ -269,6 +270,9 @@ impl UsageAgent {
|
|||
}
|
||||
|
||||
pub async fn send_report(&self, report: Report) -> Result<(), AgentError> {
|
||||
if report.size == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let report_body =
|
||||
serde_json::to_vec(&report).map_err(|e| AgentError::Unknown(e.to_string()))?;
|
||||
// Based on https://the-guild.dev/graphql/hive/docs/specs/usage-reports#data-structure
|
||||
|
|
@ -357,3 +361,200 @@ impl UsageAgentExt for Arc<UsageAgent> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use graphql_parser::{parse_query, parse_schema};
|
||||
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
|
||||
|
||||
use crate::agent::{ExecutionReport, Report, UsageAgent, UsageAgentExt};
|
||||
|
||||
const CONTENT_TYPE_VALUE: &'static str = "application/json";
|
||||
const GRAPHQL_CLIENT_NAME: &'static str = "Hive Client";
|
||||
const GRAPHQL_CLIENT_VERSION: &'static str = "1.0.0";
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_send_data_to_hive() {
|
||||
let token = "Token";
|
||||
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
|
||||
let server_url = server.url();
|
||||
|
||||
let timestamp = 1625247600;
|
||||
let duration = Duration::from_millis(20);
|
||||
let user_agent = format!("hive-router-sdk-test");
|
||||
|
||||
let mock = server
|
||||
.mock("POST", "/200")
|
||||
.match_header(AUTHORIZATION, format!("Bearer {}", token).as_str())
|
||||
.match_header(CONTENT_TYPE, CONTENT_TYPE_VALUE)
|
||||
.match_header(USER_AGENT, user_agent.as_str())
|
||||
.match_header("X-Usage-API-Version", "2")
|
||||
.match_request(move |request| {
|
||||
let request_body = request.body().expect("Failed to extract body");
|
||||
let report: Report = serde_json::from_slice(request_body)
|
||||
.expect("Failed to parse request body as JSON");
|
||||
assert_eq!(report.size, 1);
|
||||
let record = report.map.values().next().expect("No operation record");
|
||||
// operation
|
||||
assert!(record.operation.contains("mutation deleteProject"));
|
||||
assert_eq!(record.operationName.as_deref(), Some("deleteProject"));
|
||||
// fields
|
||||
let expected_fields = vec![
|
||||
"Mutation.deleteProject",
|
||||
"Mutation.deleteProject.selector",
|
||||
"Mutation.deleteProject.selector!",
|
||||
"DeleteProjectPayload.selector",
|
||||
"ProjectSelector.organization",
|
||||
"ProjectSelector.project",
|
||||
"DeleteProjectPayload.deletedProject",
|
||||
"Project.id",
|
||||
"Project.cleanId",
|
||||
"Project.name",
|
||||
"Project.type",
|
||||
"ProjectType.FEDERATION",
|
||||
"ProjectType.STITCHING",
|
||||
"ProjectType.SINGLE",
|
||||
"ProjectType.CUSTOM",
|
||||
"ProjectSelectorInput.organization",
|
||||
"ID",
|
||||
"ProjectSelectorInput.project",
|
||||
];
|
||||
for field in &expected_fields {
|
||||
assert!(record.fields.contains(&field.to_string()));
|
||||
}
|
||||
assert_eq!(record.fields.len(), expected_fields.len());
|
||||
|
||||
// Operations
|
||||
let operations = report.operations;
|
||||
assert_eq!(operations.len(), 1); // one operation
|
||||
|
||||
let operation = &operations[0];
|
||||
let key = report.map.keys().next().expect("No operation key");
|
||||
assert_eq!(&operation.operationMapKey, key);
|
||||
assert_eq!(operation.timestamp, timestamp);
|
||||
assert_eq!(operation.execution.duration, duration.as_nanos());
|
||||
assert_eq!(operation.execution.ok, true);
|
||||
assert_eq!(operation.execution.errorsTotal, 0);
|
||||
true
|
||||
})
|
||||
.expect(1)
|
||||
.with_status(200)
|
||||
.create_async()
|
||||
.await;
|
||||
let schema: graphql_tools::static_graphql::schema::Document = parse_schema(
|
||||
r#"
|
||||
type Query {
|
||||
project(selector: ProjectSelectorInput!): Project
|
||||
projectsByType(type: ProjectType!): [Project!]!
|
||||
projects(filter: FilterInput): [Project!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
deleteProject(selector: ProjectSelectorInput!): DeleteProjectPayload!
|
||||
}
|
||||
|
||||
input ProjectSelectorInput {
|
||||
organization: ID!
|
||||
project: ID!
|
||||
}
|
||||
|
||||
input FilterInput {
|
||||
type: ProjectType
|
||||
pagination: PaginationInput
|
||||
}
|
||||
|
||||
input PaginationInput {
|
||||
limit: Int
|
||||
offset: Int
|
||||
}
|
||||
|
||||
type ProjectSelector {
|
||||
organization: ID!
|
||||
project: ID!
|
||||
}
|
||||
|
||||
type DeleteProjectPayload {
|
||||
selector: ProjectSelector!
|
||||
deletedProject: Project!
|
||||
}
|
||||
|
||||
type Project {
|
||||
id: ID!
|
||||
cleanId: ID!
|
||||
name: String!
|
||||
type: ProjectType!
|
||||
buildUrl: String
|
||||
validationUrl: String
|
||||
}
|
||||
|
||||
enum ProjectType {
|
||||
FEDERATION
|
||||
STITCHING
|
||||
SINGLE
|
||||
CUSTOM
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to parse schema");
|
||||
|
||||
let op: graphql_tools::static_graphql::query::Document = parse_query(
|
||||
r#"
|
||||
mutation deleteProject($selector: ProjectSelectorInput!) {
|
||||
deleteProject(selector: $selector) {
|
||||
selector {
|
||||
organization
|
||||
project
|
||||
}
|
||||
deletedProject {
|
||||
...ProjectFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ProjectFields on Project {
|
||||
id
|
||||
cleanId
|
||||
name
|
||||
type
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.expect("Failed to parse query");
|
||||
|
||||
let usage_agent = UsageAgent::try_new(
|
||||
token,
|
||||
format!("{}/200", server_url),
|
||||
None,
|
||||
10,
|
||||
Duration::from_millis(500),
|
||||
Duration::from_millis(500),
|
||||
false,
|
||||
Duration::from_millis(10),
|
||||
user_agent,
|
||||
)
|
||||
.expect("Failed to create UsageAgent");
|
||||
|
||||
usage_agent
|
||||
.add_report(ExecutionReport {
|
||||
schema: Arc::new(schema),
|
||||
operation_body: op.to_string(),
|
||||
operation_name: Some("deleteProject".to_string()),
|
||||
client_name: Some(GRAPHQL_CLIENT_NAME.to_string()),
|
||||
client_version: Some(GRAPHQL_CLIENT_VERSION.to_string()),
|
||||
timestamp,
|
||||
duration,
|
||||
ok: true,
|
||||
errors: 0,
|
||||
persisted_document_hash: None,
|
||||
})
|
||||
.expect("Failed to add report");
|
||||
|
||||
usage_agent.flush().await;
|
||||
|
||||
mock.assert_async().await;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue