Skip to content
Merged
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
2 changes: 1 addition & 1 deletion policies/docs/event-channel-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Reference implementations:

- Rust: `policies/rust/now-policy-api/src/event_channel.rs`
- .NET: `policies/dotnet/Devolutions.Now.Policy.Api/EventChannel.cs`
- Shared test fixture: `policies/rust/now-policy-server-template/assets/samples/frames/event-channel.frames.bin`
- Shared test fixture: `policies/test-data/package-broker/frames/event-channel.frames.bin`

## Purpose

Expand Down
17 changes: 8 additions & 9 deletions policies/dotnet/Devolutions.Now.Policy.Client.Tests/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@
namespace Devolutions.Now.Policy.Client.Tests;

/// <summary>
/// Resolves the shared schema artifacts and sample files that the Rust crate uses, so the
/// C# client is validated against the exact same fixtures. The request/response wire schemas
/// are sourced from the OpenAPI spec generated by the Rust API crate.
/// Resolves the shared schema artifacts and sample files, so the C# client is validated against
/// the exact same fixtures as the Rust implementation. The request/response wire schemas are
/// sourced from the OpenAPI spec generated by the Rust API crate.
/// </summary>
public static class TestData
{
/// <summary>Absolute path to the Rust package broker server template crate root.</summary>
public static string CrateRoot { get; } = ResolveCrateRoot();
private static string PoliciesRoot { get; } = ResolvePoliciesRoot();

public static string SamplesDir => Path.Combine(CrateRoot, "assets", "samples");
public static string SamplesDir => Path.Combine(PoliciesRoot, "test-data", "package-broker");

/// <summary>The OpenAPI specification: the source of truth for the request/response wire schemas.</summary>
public static string OpenApiSpec =>
Path.Combine(CrateRoot, "..", "now-policy-api", "openapi", "now-policy-api.yaml");
Path.Combine(PoliciesRoot, "rust", "now-policy-api", "openapi", "now-policy-api.yaml");

private static readonly SemaphoreSlim s_docLock = new(1, 1);
private static OpenApiDocument? s_doc;
Expand Down Expand Up @@ -202,9 +201,9 @@ private static IEnumerable<string> JsonFiles(string dir) =>
private static bool IsInvalidRequestSample(string path) =>
Path.GetFileName(path).Equals("missing-package-id.request.json", StringComparison.Ordinal);

private static string ResolveCrateRoot([CallerFilePath] string thisFile = "")
private static string ResolvePoliciesRoot([CallerFilePath] string thisFile = "")
{
var testsDir = Path.GetDirectoryName(thisFile)!;
return Path.GetFullPath(Path.Combine(testsDir, "..", "..", "rust", "now-policy-server-template"));
return Path.GetFullPath(Path.Combine(testsDir, "..", ".."));
}
}
2 changes: 1 addition & 1 deletion policies/rust/now-policy-server-template/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ homepage.workspace = true
repository.workspace = true
authors.workspace = true
readme = "README.md"
description = "Reusable Devolutions NOW package broker server facade, mock, and OpenAPI generator"
description = "Reusable Devolutions NOW package broker server facade and OpenAPI generator"
publish = true

[lints]
Expand Down
20 changes: 2 additions & 18 deletions policies/rust/now-policy-server-template/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Devolutions NOW policy server template
======================================

`now-policy-server-template` is the reusable server facade, HTTP route binding, mock implementation, OpenAPI generator, and sample fixture owner for the Devolutions NOW package broker API.
`now-policy-server-template` is the reusable server facade, HTTP route binding, and OpenAPI generator for the Devolutions NOW package broker API.

Purpose
-------
Expand All @@ -15,16 +15,13 @@ The crate is used to:
- expose the `PackageBrokerServer` trait implemented by runtime brokers;
- bind that trait to the canonical HTTP endpoints;
- keep runtime routing and OpenAPI generation based on the same route definitions;
- provide a deterministic mock server for tests and client development;
- store sample request/response documents used by Rust and .NET validation.

Architecture
------------

The crate has three main surfaces:
The crate has two main components:

- `server.rs` defines `PackageBrokerServer`, router builders, endpoint handlers, error-to-HTTP status mapping, and OpenAPI generation.
- `mock.rs` provides `MockPackageBrokerServer`, a deterministic in-memory implementation backed by registered sample responses and default health/capability data.
- `tools/generate_openapi.rs` generates the OpenAPI YAML file into the sibling `now-policy-api/openapi/` directory.

The crate re-exports `now-policy-api`, so tests and consumers that need the server template can import both server utilities and API DTOs from `now_policy_server_template`.
Expand Down Expand Up @@ -59,19 +56,6 @@ Then they pass the implementation to `api_router` or `api_router_from_shared`. T

This keeps route dispatch, error responses, and OpenAPI operation metadata in one place.

Mock and fixtures
-----------------

`MockPackageBrokerServer` is intended for protocol tests, sample validation, and client development. It returns deterministic health/capabilities responses and can be configured with policy, evaluation, execution, and status responses loaded from fixture files.

Sample documents live under:

```text
assets/samples/
```

They are treated as protocol fixtures rather than implementation fixtures. Rust tests deserialize them through the API model, exercise the mock/router path, and .NET tests validate equivalent DTOs against the generated OpenAPI schema.

OpenAPI generation
------------------

Expand Down
4 changes: 1 addition & 3 deletions policies/rust/now-policy-server-template/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
//! Devolutions NOW package broker server facade, mock, and OpenAPI generator.
//! Devolutions NOW package broker server facade and OpenAPI generator.

#![allow(clippy::std_instead_of_core, unused_qualifications)]

pub mod mock;
pub mod server;

pub use mock::*;
pub use now_policy_api::*;
pub use server::*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ use axum::http::{Request, StatusCode};
use now_policy_server_template::{
API_VERSION_STR, CancelRequest, CancelResponse, CapabilitiesResponse, CapabilitiesResponseKind, DEFAULT_PIPE_NAME,
ErrorCode, ErrorResponse, ErrorResponseKind, EvaluationResponse, ExecutionResponse, HealthResponse,
HealthResponseKind, HealthStatus, MAX_REQUEST_BODY_BYTES, ManagerName, MockPackageBrokerServer, Operation,
PackageBrokerServer, PackageRequest, PolicyResponse, PolicyResponseKind, Scope, ServerContext, StatusRequest,
StatusRequestKind, StatusResponse, Transport, api_router,
HealthResponseKind, HealthStatus, MAX_REQUEST_BODY_BYTES, ManagerName, Operation, PackageBrokerServer,
PackageRequest, PolicyResponse, PolicyResponseKind, Scope, ServerContext, StatusRequest, StatusRequestKind,
StatusResponse, Transport, api_router,
};
use tower::ServiceExt;

use support::mock::MockPackageBrokerServer;

mod support;

fn samples_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("assets/samples")
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../test-data/package-broker")
}

fn load_text_file(path: &Path) -> String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ use std::collections::BTreeMap;

use async_trait::async_trait;

use crate::server::{MAX_REQUEST_BODY_BYTES, PackageBrokerServer};
use now_policy_api::{
use now_policy_server_template::{
API_VERSION_STR, Architecture, CancelRequest, CancelResponse, CapabilitiesResponse, CapabilitiesResponseKind,
ErrorCode, ErrorResponse, ErrorResponseKind, EvaluationResponse, ExecutionResponse, HealthResponse,
HealthResponseKind, HealthStatus, ManagerCapability, ManagerName, Operation, PackageRequest, PolicyResponse, Scope,
ServerContext, StatusRequest, StatusResponse, Transport,
HealthResponseKind, HealthStatus, MAX_REQUEST_BODY_BYTES, ManagerCapability, ManagerName, Operation,
PackageBrokerServer, PackageRequest, PolicyResponse, Scope, ServerContext, StatusRequest, StatusResponse,
Transport,
};

/// Deterministic mock broker backed by caller-provided sample responses.
#[derive(Debug, Clone)]
pub struct MockPackageBrokerServer {
pub(crate) struct MockPackageBrokerServer {
health: HealthResponse,
capabilities: CapabilitiesResponse,
policy_response: Option<PolicyResponse>,
Expand All @@ -26,7 +26,7 @@ pub struct MockPackageBrokerServer {
}

impl MockPackageBrokerServer {
pub fn new(_pipe_name: impl Into<String>) -> Self {
pub(crate) fn new(_pipe_name: impl Into<String>) -> Self {
let server = server_context();
Self {
health: HealthResponse {
Expand Down Expand Up @@ -54,42 +54,42 @@ impl MockPackageBrokerServer {
}

#[must_use]
pub fn with_evaluation_response(mut self, response: EvaluationResponse) -> Self {
pub(crate) fn with_evaluation_response(mut self, response: EvaluationResponse) -> Self {
self.evaluation_responses
.insert(response.request_id.to_string(), response);
self
}

#[must_use]
pub fn with_policy_response(mut self, response: PolicyResponse) -> Self {
pub(crate) fn with_policy_response(mut self, response: PolicyResponse) -> Self {
self.policy_response = Some(response);
self.policy_error = None;
self
}

#[must_use]
pub fn with_policy_error(mut self, error: ErrorResponse) -> Self {
pub(crate) fn with_policy_error(mut self, error: ErrorResponse) -> Self {
self.policy_response = None;
self.policy_error = Some(error);
self
}

#[must_use]
pub fn with_execution_response(mut self, response: ExecutionResponse) -> Self {
pub(crate) fn with_execution_response(mut self, response: ExecutionResponse) -> Self {
self.execution_responses
.insert(response.request_id.to_string(), response);
self
}

#[must_use]
pub fn with_status_response(mut self, response: StatusResponse) -> Self {
pub(crate) fn with_status_response(mut self, response: StatusResponse) -> Self {
self.status_responses
.insert(response.operation_id.to_string(), response);
self
}

#[must_use]
pub fn with_cancel_response(mut self, response: CancelResponse) -> Self {
pub(crate) fn with_cancel_response(mut self, response: CancelResponse) -> Self {
self.cancel_responses
.insert(response.operation_id.to_string(), response);
self
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod mock;