Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,89 @@ This reduces the number of copies of this string we keep in memory, as schemas c

By [@SimonSapin](https://github.com/SimonSapin)

### Changes to `PluginTestHarness` ([PR #1468](https://github.com/apollographql/router/pull/1468))

The `plugin` method of the builder for `apollo_router::plugin::test::PluginTestHarness` was removed.
Users of `PluginTestHarness` don’t create plugin instances themselves anymore.

Instead, the builder has a new mandatory `configuration` method,
which takes the full Router configuration as would be found in a `router.yaml` file.
Through that configuration, plugins can be enabled (and configured) by name.
Instead of YAML syntax though, the method takes a `serde_json::Value`.
A convenient way to create such a value in Rust code is with the `json!` macro.

The `IntoSchema` enum has been removed.
The `schema` method of the builder is now optional and takes a `&str`.
If not provided, the canned testing schema is used by default.

Additionally, `PluginTestHarness` internally creates a Tower `Service`
that is closer to a “full” Router than before:
Apollo plugins that are enabled by default (such as CSRF protection)
will be enabled in the test harness,
and all enabled plugins will have their `Plugin::activate` method called
during harness creation.

Changes to tests for an example plugin:

```diff
-use apollo_router::plugin::test::IntoSchema::Canned;
use apollo_router::plugin::test::PluginTestHarness;
-use apollo_router::plugin::Plugin;
-use apollo_router::plugin::PluginInit;
+use serde_json::json;

-let conf = MyPluginConfig {
- something: "something".to_string(),
-};
-let plugin = MyPlugin::new(PluginInit::new(conf, Default::default()))
- .await
- .unwrap();
+let conf = json!({
+ "plugins": {
+ "example.my_plugin": {
+ "something": "something"
+ }
+ }
+});
let test_harness = PluginTestHarness::builder()
- .plugin(plugin)
- .schema(Canned)
+ .configuration(conf)
.build()
.await
.unwarp();
```

By [@SimonSapin](https://github.com/SimonSapin)

### Changes to `RouterRequest::fake_builder` defaults to `Content-Type: application/json` ([PR #1468](https://github.com/apollographql/router/pull/1468))

Because of the change above, tests that use `PluginTestHarness` will now go through
CSRF-protection, which might reject some requests.
`apollo_router::services::RouterRequest` has a builder for creating a “fake” request during tests.
When no `Content-Type` header is specified, this builder will now default to `application/json`
which makes the request be accepted by CSRF protection.
If a test requires a request specifically *without* a `Content-Type` header,
this default can be removed from a `RouterRequest` after building it:

```rust
let mut router_request = RouterRequesT::fake_builder().build();
router_request.originating_request.headers_mut().remove("content-type");
```

By [@SimonSapin](https://github.com/SimonSapin)

## 🚀 Features

### `mock_execution_service` for `PluginTestHarness` ([PR #1468](https://github.com/apollographql/router/pull/1468))

The builder for `apollo_router::plugin::test::PluginTestHarness`
has a new `mock_execution_service` method.
This allows adding a mock at the execution stage of the pipeline,
similar to the other `mock_*` methods taht were already present.

By [@SimonSapin](https://github.com/SimonSapin)

## 🐛 Fixes

### Configuration handling enhancements ([PR #1454](https://github.com/apollographql/router/pull/1454))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,7 @@ register_plugin!("{{project_name}}", "{{snake_name}}", {{pascal_name}});

#[cfg(test)]
mod tests {
use super::{Conf, {{pascal_name}}};

use apollo_router::plugin::test::IntoSchema::Canned;
use apollo_router::plugin::test::PluginTestHarness;
use apollo_router::plugin::Plugin;
use apollo_router::plugin::PluginInit;
use tower::BoxError;

#[tokio::test]
Expand All @@ -177,18 +172,18 @@ mod tests {

#[tokio::test]
async fn basic_test() -> Result<(), BoxError> {
// Define a configuration to use with our plugin
let conf = Conf {
message: "Starting my plugin".to_string(),
};

// Build an instance of our plugin to use in the test harness
let plugin = {{pascal_name}}::new(PluginInit::new(conf, Default::default())).await.expect("created plugin");
// Define a configuration that enables our plugin
let conf = serde_json::json!({
"plugins": {
"{{project_name}}.{{snake_name}}": {
"message": "Starting my plugin"
}
}
});

// Create the test harness. You can add mocks for individual services, or use prebuilt canned services.
let mut test_harness = PluginTestHarness::builder()
.plugin(plugin)
.schema(Canned)
.configuration(conf)
.build()
.await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,9 @@ expression: "&schema"
}
}
},
"test.empty_plugin": {
"type": "null"
},
"traffic_shaping": {
"type": "object",
"properties": {
Expand Down
19 changes: 19 additions & 0 deletions apollo-router/src/plugin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
pub mod serde;
pub mod test;

use std::any::TypeId;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
Expand Down Expand Up @@ -265,6 +266,13 @@ pub trait DynPlugin: Send + Sync + 'static {

/// Return the name of the plugin.
fn name(&self) -> &'static str;

fn type_id(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<Self>()
}
}

#[async_trait]
Expand Down Expand Up @@ -316,6 +324,17 @@ where
}
}

impl dyn DynPlugin {
// Same pattern as https://github.com/rust-lang/rust/blob/1.62.1/library/std/src/error.rs#L675-L685
pub(crate) fn downcast_ref<T: 'static>(&self) -> Option<&T> {
if TypeId::of::<T>() == self.type_id() {
Some(unsafe { &*(self as *const dyn DynPlugin as *const T) })
} else {
None
}
}
}

/// Register a plugin with a group and a name
/// Grouping prevent name clashes for plugins, so choose something unique, like your domain name.
/// Plugins will appear in the configuration as a layer property called: {group}.{name}
Expand Down
Loading