1use std::process::Command;
4use std::process::Stdio;
5
6use anyhow::anyhow;
7use async_trait::async_trait;
8use log::max_level;
9use os_info::Type;
10use prost::Message;
11use std::path::PathBuf;
12use sysinfo::{Pid, System};
13use tonic::codegen::InterceptedService;
14use tonic::metadata::{Ascii, MetadataValue};
15use tonic::service::Interceptor;
16use tonic::transport::Channel;
17use tonic::{Request, Status};
18use tracing::{debug, warn};
19use uuid::Uuid;
20
21use crate::child_process::ChildPluginProcess;
22use crate::plugin_models::{
23 PactPlugin, PactPluginManifest, PactPluginRpc, PluginInitRequest, PluginInitResponse,
24 PluginInstance, PluginInterfaceVersion,
25};
26use crate::proto::pact_plugin_client::PactPluginClient as PactPluginClientV1;
27use crate::proto::*;
28use crate::proto_v2::{self, pact_plugin_client::PactPluginClient as PactPluginClientV2};
29
30pub(crate) enum PluginClient {
31 V1(PactPluginClientV1<InterceptedService<Channel, PactPluginInterceptor>>),
32 V2(PactPluginClientV2<InterceptedService<Channel, PactPluginInterceptor>>),
33}
34
35impl PluginClient {
36 pub(crate) fn convert_message<T, U>(message: T) -> Result<U, Status>
37 where
38 T: Message,
39 U: Message + Default,
40 {
41 U::decode(message.encode_to_vec().as_slice()).map_err(|err| {
42 Status::internal(format!(
43 "Failed to convert between plugin interface message versions: {}",
44 err
45 ))
46 })
47 }
48
49 async fn init_plugin(
50 &mut self,
51 request: PluginInitRequest,
52 ) -> Result<PluginInitResponse, Status> {
53 match self {
54 PluginClient::V1(client) => client
55 .init_plugin(Request::new(InitPluginRequest {
56 implementation: request.implementation,
57 version: request.version,
58 }))
59 .await
60 .map(|response| PluginInitResponse {
61 catalogue: response.into_inner().catalogue,
62 plugin_capabilities: vec![],
63 }),
64 PluginClient::V2(client) => client
65 .init_plugin(Request::new(proto_v2::InitPluginRequest {
66 implementation: request.implementation,
67 version: request.version,
68 host_capabilities: request.host_capabilities,
69 plugin_instance_id: request.plugin_instance_id,
70 }))
71 .await
72 .and_then(|response| match response.into_inner().response {
73 Some(proto_v2::init_plugin_response::Response::Success(success)) => {
74 Ok(PluginInitResponse {
75 catalogue: success
76 .catalogue
77 .into_iter()
78 .map(Self::convert_message)
79 .collect::<Result<Vec<CatalogueEntry>, Status>>()?,
80 plugin_capabilities: success.plugin_capabilities,
81 })
82 }
83 Some(proto_v2::init_plugin_response::Response::Failure(failure)) => {
84 let mut error = failure.error;
85 if !failure.missing_host_capabilities.is_empty() {
86 error.push_str(" (missing host capabilities: ");
87 error.push_str(failure.missing_host_capabilities.join(", ").as_str());
88 error.push(')');
89 }
90 Err(Status::failed_precondition(error))
91 }
92 None => Err(Status::internal(
93 "Plugin returned an invalid V2 InitPlugin response",
94 )),
95 }),
96 }
97 }
98
99 async fn compare_contents(
100 &mut self,
101 request: CompareContentsRequest,
102 ) -> Result<CompareContentsResponse, Status> {
103 match self {
104 PluginClient::V1(client) => client
105 .compare_contents(Request::new(request))
106 .await
107 .map(|response| response.into_inner()),
108 PluginClient::V2(client) => {
109 let mut v2_req = Self::convert_message::<_, proto_v2::CompareContentsRequest>(request)?;
110 if let Some(id) = crate::test_context::current_test_run_id() {
111 let ctx = v2_req.test_context.get_or_insert_with(prost_types::Struct::default);
112 ctx.fields.entry("testRunId".to_string()).or_insert_with(|| prost_types::Value {
113 kind: Some(prost_types::value::Kind::StringValue(id)),
114 });
115 }
116 client
117 .compare_contents(Request::new(v2_req))
118 .await
119 .and_then(|response| Self::convert_message(response.into_inner()))
120 }
121 }
122 }
123
124 async fn compare_contents_with_metadata(
125 &mut self,
126 request: CompareContentsRequest,
127 chain_id: &str,
128 deadline_ms: u64,
129 ) -> Result<CompareContentsResponse, Status> {
130 match self {
131 PluginClient::V1(client) => {
132 let mut req = Request::new(request);
133 insert_chain_metadata(&mut req, chain_id, deadline_ms)?;
134 client.compare_contents(req).await.map(|response| response.into_inner())
135 }
136 PluginClient::V2(client) => {
137 let mut v2_req = Self::convert_message::<_, proto_v2::CompareContentsRequest>(request)?;
138 if let Some(id) = crate::test_context::current_test_run_id() {
139 let ctx = v2_req.test_context.get_or_insert_with(prost_types::Struct::default);
140 ctx.fields.entry("testRunId".to_string()).or_insert_with(|| prost_types::Value {
141 kind: Some(prost_types::value::Kind::StringValue(id)),
142 });
143 }
144 let mut req = Request::new(v2_req);
145 insert_chain_metadata(&mut req, chain_id, deadline_ms)?;
146 client
147 .compare_contents(req)
148 .await
149 .and_then(|response| Self::convert_message(response.into_inner()))
150 }
151 }
152 }
153
154 async fn generate_content_with_metadata(
155 &mut self,
156 request: GenerateContentRequest,
157 chain_id: &str,
158 deadline_ms: u64,
159 ) -> Result<GenerateContentResponse, Status> {
160 match self {
161 PluginClient::V1(client) => {
162 let mut req = Request::new(request);
163 insert_chain_metadata(&mut req, chain_id, deadline_ms)?;
164 client.generate_content(req).await.map(|response| response.into_inner())
165 }
166 PluginClient::V2(client) => {
167 let mut req = Request::new(Self::convert_message::<_, proto_v2::GenerateContentRequest>(request)?);
168 insert_chain_metadata(&mut req, chain_id, deadline_ms)?;
169 client
170 .generate_content(req)
171 .await
172 .and_then(|response| Self::convert_message(response.into_inner()))
173 }
174 }
175 }
176
177 async fn configure_interaction(
178 &mut self,
179 request: ConfigureInteractionRequest,
180 ) -> Result<ConfigureInteractionResponse, Status> {
181 match self {
182 PluginClient::V1(client) => client
183 .configure_interaction(Request::new(request))
184 .await
185 .map(|response| response.into_inner()),
186 PluginClient::V2(client) => {
187 let mut v2_req =
188 Self::convert_message::<_, proto_v2::ConfigureInteractionRequest>(request)?;
189 if let Some(id) = crate::test_context::current_test_run_id() {
190 let ctx = v2_req.test_context.get_or_insert_with(prost_types::Struct::default);
191 ctx.fields.entry("testRunId".to_string()).or_insert_with(|| prost_types::Value {
192 kind: Some(prost_types::value::Kind::StringValue(id)),
193 });
194 }
195 client
196 .configure_interaction(Request::new(v2_req))
197 .await
198 .and_then(|response| Self::convert_message(response.into_inner()))
199 }
200 }
201 }
202
203 async fn generate_content(
204 &mut self,
205 request: GenerateContentRequest,
206 ) -> Result<GenerateContentResponse, Status> {
207 match self {
208 PluginClient::V1(client) => client
209 .generate_content(Request::new(request))
210 .await
211 .map(|response| response.into_inner()),
212 PluginClient::V2(client) => client
213 .generate_content(Request::new(Self::convert_message::<
214 _,
215 proto_v2::GenerateContentRequest,
216 >(request)?))
217 .await
218 .and_then(|response| Self::convert_message(response.into_inner())),
219 }
220 }
221
222 async fn start_mock_server(
223 &mut self,
224 request: StartMockServerRequest,
225 ) -> Result<StartMockServerResponse, Status> {
226 match self {
227 PluginClient::V1(client) => client
228 .start_mock_server(Request::new(request))
229 .await
230 .map(|response| response.into_inner()),
231 PluginClient::V2(client) => client
232 .start_mock_server(Request::new(Self::convert_message::<
233 _,
234 proto_v2::StartMockServerRequest,
235 >(request)?))
236 .await
237 .and_then(|response| Self::convert_message(response.into_inner())),
238 }
239 }
240
241 async fn shutdown_mock_server(
242 &mut self,
243 request: ShutdownMockServerRequest,
244 ) -> Result<ShutdownMockServerResponse, Status> {
245 match self {
246 PluginClient::V1(client) => client
247 .shutdown_mock_server(Request::new(request))
248 .await
249 .map(|response| response.into_inner()),
250 PluginClient::V2(client) => client
251 .shutdown_mock_server(Request::new(Self::convert_message::<
252 _,
253 proto_v2::MockServerRequest,
254 >(request)?))
255 .await
256 .and_then(|response| {
257 Self::convert_message::<_, ShutdownMockServerResponse>(response.into_inner())
258 }),
259 }
260 }
261
262 async fn start_mock_server_v2(
263 &mut self,
264 request: proto_v2::StartMockServerRequest,
265 ) -> Result<StartMockServerResponse, Status> {
266 match self {
267 PluginClient::V1(_) => Err(Status::unimplemented("V2 interface not supported on V1 plugin")),
268 PluginClient::V2(client) => client
269 .start_mock_server(Request::new(request))
270 .await
271 .and_then(|response| Self::convert_message(response.into_inner())),
272 }
273 }
274
275 async fn prepare_interaction_for_verification_v2(
276 &mut self,
277 request: proto_v2::VerificationPreparationRequest,
278 ) -> Result<VerificationPreparationResponse, Status> {
279 match self {
280 PluginClient::V1(_) => Err(Status::unimplemented("V2 interface not supported on V1 plugin")),
281 PluginClient::V2(client) => client
282 .prepare_interaction_for_verification(Request::new(request))
283 .await
284 .and_then(|response| Self::convert_message(response.into_inner())),
285 }
286 }
287
288 async fn verify_interaction_v2(
289 &mut self,
290 request: proto_v2::VerifyInteractionRequest,
291 ) -> Result<VerifyInteractionResponse, Status> {
292 match self {
293 PluginClient::V1(_) => Err(Status::unimplemented("V2 interface not supported on V1 plugin")),
294 PluginClient::V2(client) => client
295 .verify_interaction(Request::new(request))
296 .await
297 .and_then(|response| Self::convert_message(response.into_inner())),
298 }
299 }
300
301 async fn get_mock_server_results(
302 &mut self,
303 request: MockServerRequest,
304 ) -> Result<MockServerResults, Status> {
305 match self {
306 PluginClient::V1(client) => client
307 .get_mock_server_results(Request::new(request))
308 .await
309 .map(|response| response.into_inner()),
310 PluginClient::V2(client) => client
311 .get_mock_server_results(Request::new(Self::convert_message::<
312 _,
313 proto_v2::MockServerRequest,
314 >(request)?))
315 .await
316 .and_then(|response| Self::convert_message(response.into_inner())),
317 }
318 }
319
320 async fn prepare_interaction_for_verification(
321 &mut self,
322 request: VerificationPreparationRequest,
323 ) -> Result<VerificationPreparationResponse, Status> {
324 match self {
325 PluginClient::V1(client) => client
326 .prepare_interaction_for_verification(Request::new(request))
327 .await
328 .map(|response| response.into_inner()),
329 PluginClient::V2(client) => client
330 .prepare_interaction_for_verification(Request::new(Self::convert_message::<
331 _,
332 proto_v2::VerificationPreparationRequest,
333 >(request)?))
334 .await
335 .and_then(|response| Self::convert_message(response.into_inner())),
336 }
337 }
338
339 async fn verify_interaction(
340 &mut self,
341 request: VerifyInteractionRequest,
342 ) -> Result<VerifyInteractionResponse, Status> {
343 match self {
344 PluginClient::V1(client) => client
345 .verify_interaction(Request::new(request))
346 .await
347 .map(|response| response.into_inner()),
348 PluginClient::V2(client) => client
349 .verify_interaction(Request::new(Self::convert_message::<
350 _,
351 proto_v2::VerifyInteractionRequest,
352 >(request)?))
353 .await
354 .and_then(|response| Self::convert_message(response.into_inner())),
355 }
356 }
357
358 async fn update_catalogue(&mut self, request: Catalogue) -> Result<(), Status> {
359 match self {
360 PluginClient::V1(client) => client
361 .update_catalogue(Request::new(request))
362 .await
363 .map(|_| ()),
364 PluginClient::V2(client) => client
365 .update_catalogue(Request::new(
366 Self::convert_message::<_, proto_v2::Catalogue>(request)?,
367 ))
368 .await
369 .map(|_| ()),
370 }
371 }
372}
373
374fn insert_chain_metadata<T>(request: &mut Request<T>, chain_id: &str, deadline_ms: u64) -> Result<(), Status> {
378 let chain_value = MetadataValue::try_from(chain_id)
379 .map_err(|err| Status::internal(format!("Invalid call chain id '{}': {}", chain_id, err)))?;
380 let deadline_value = MetadataValue::try_from(deadline_ms.to_string())
381 .map_err(|err| Status::internal(format!("Invalid deadline value '{}': {}", deadline_ms, err)))?;
382 request.metadata_mut().insert(crate::call_chain::CALL_CHAIN_ID_METADATA_KEY, chain_value);
383 request.metadata_mut().insert(crate::call_chain::DEADLINE_METADATA_KEY, deadline_value);
384 request.set_timeout(crate::call_chain::remaining(deadline_ms));
385 Ok(())
386}
387
388#[derive(Clone, Debug)]
390pub(crate) struct PactPluginInterceptor {
391 server_key: MetadataValue<Ascii>,
393}
394
395impl PactPluginInterceptor {
396 pub(crate) fn new(server_key: &str) -> anyhow::Result<Self> {
397 let token = MetadataValue::try_from(server_key)?;
398 Ok(PactPluginInterceptor { server_key: token })
399 }
400}
401
402impl Interceptor for PactPluginInterceptor {
403 fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
404 request
405 .metadata_mut()
406 .insert("authorization", self.server_key.clone());
407 Ok(request)
408 }
409}
410
411#[derive(Debug, Clone)]
413pub struct GrpcPactPlugin {
414 pub plugin: PactPlugin,
415}
416
417impl GrpcPactPlugin {
418 pub fn new(plugin: PactPlugin) -> Self {
419 GrpcPactPlugin { plugin }
420 }
421
422 #[allow(deprecated)]
423 async fn connect_channel(&self) -> anyhow::Result<Channel> {
424 let port = self.plugin.child.port();
425 match Channel::from_shared(format!("http://[::1]:{}", port))?
426 .connect()
427 .await
428 {
429 Ok(channel) => Ok(channel),
430 Err(err) => {
431 debug!("IP6 connection failed, will try IP4 address - {err}");
432 Channel::from_shared(format!("http://127.0.0.1:{}", port))?
433 .connect()
434 .await
435 .map_err(|err| anyhow!(err))
436 }
437 }
438 }
439
440 #[allow(deprecated)]
441 async fn get_plugin_client(&self) -> anyhow::Result<PluginClient> {
442 let channel = self.connect_channel().await?;
443 let interceptor =
444 PactPluginInterceptor::new(self.plugin.child.plugin_info.server_key.as_str())?;
445 match self.plugin.interface_version {
446 PluginInterfaceVersion::V1 => Ok(PluginClient::V1(PactPluginClientV1::with_interceptor(
447 channel,
448 interceptor,
449 ))),
450 PluginInterfaceVersion::V2 => Ok(PluginClient::V2(PactPluginClientV2::with_interceptor(
451 channel,
452 interceptor,
453 ))),
454 }
455 }
456}
457
458#[async_trait]
459impl PactPluginRpc for GrpcPactPlugin {
460 async fn init_plugin(
461 &mut self,
462 request: PluginInitRequest,
463 ) -> anyhow::Result<PluginInitResponse> {
464 let mut client = self.get_plugin_client().await?;
465 client.init_plugin(request).await.map_err(anyhow::Error::from)
466 }
467}
468
469#[async_trait]
470impl PluginInstance for GrpcPactPlugin {
471 fn manifest(&self) -> &PactPluginManifest {
472 &self.plugin.manifest
473 }
474
475 fn instance_id(&self) -> &str {
476 &self.plugin.instance_id
477 }
478
479 fn has_capability(&self, capability: &str) -> bool {
480 self.plugin.has_capability(capability)
481 }
482
483 #[allow(deprecated)]
484 fn kill(&self) {
485 self.plugin.child.kill();
486 }
487
488 async fn compare_contents(
489 &self,
490 request: CompareContentsRequest,
491 ) -> anyhow::Result<CompareContentsResponse> {
492 let mut client = self.get_plugin_client().await?;
493 client.compare_contents(request).await.map_err(anyhow::Error::from)
494 }
495
496 async fn compare_contents_with_chain(
497 &self,
498 request: CompareContentsRequest,
499 chain_id: &str,
500 deadline_ms: u64,
501 ) -> anyhow::Result<CompareContentsResponse> {
502 let mut client = self.get_plugin_client().await?;
503 client
504 .compare_contents_with_metadata(request, chain_id, deadline_ms)
505 .await
506 .map_err(anyhow::Error::from)
507 }
508
509 async fn configure_interaction(
510 &self,
511 request: ConfigureInteractionRequest,
512 ) -> anyhow::Result<ConfigureInteractionResponse> {
513 let mut client = self.get_plugin_client().await?;
514 client.configure_interaction(request).await.map_err(anyhow::Error::from)
515 }
516
517 async fn generate_content(
518 &self,
519 request: GenerateContentRequest,
520 ) -> anyhow::Result<GenerateContentResponse> {
521 let mut client = self.get_plugin_client().await?;
522 client.generate_content(request).await.map_err(anyhow::Error::from)
523 }
524
525 async fn generate_content_with_chain(
526 &self,
527 request: GenerateContentRequest,
528 chain_id: &str,
529 deadline_ms: u64,
530 ) -> anyhow::Result<GenerateContentResponse> {
531 let mut client = self.get_plugin_client().await?;
532 client
533 .generate_content_with_metadata(request, chain_id, deadline_ms)
534 .await
535 .map_err(anyhow::Error::from)
536 }
537
538 async fn start_mock_server(
539 &self,
540 request: StartMockServerRequest,
541 ) -> anyhow::Result<StartMockServerResponse> {
542 let mut client = self.get_plugin_client().await?;
543 client.start_mock_server(request).await.map_err(anyhow::Error::from)
544 }
545
546 async fn start_mock_server_v2(
547 &self,
548 request: proto_v2::StartMockServerRequest,
549 ) -> anyhow::Result<StartMockServerResponse> {
550 let mut client = self.get_plugin_client().await?;
551 client.start_mock_server_v2(request).await.map_err(anyhow::Error::from)
552 }
553
554 async fn shutdown_mock_server(
555 &self,
556 request: ShutdownMockServerRequest,
557 ) -> anyhow::Result<ShutdownMockServerResponse> {
558 let mut client = self.get_plugin_client().await?;
559 client.shutdown_mock_server(request).await.map_err(anyhow::Error::from)
560 }
561
562 async fn get_mock_server_results(
563 &self,
564 request: MockServerRequest,
565 ) -> anyhow::Result<MockServerResults> {
566 let mut client = self.get_plugin_client().await?;
567 client.get_mock_server_results(request).await.map_err(anyhow::Error::from)
568 }
569
570 async fn prepare_interaction_for_verification(
571 &self,
572 request: VerificationPreparationRequest,
573 ) -> anyhow::Result<VerificationPreparationResponse> {
574 let mut client = self.get_plugin_client().await?;
575 client
576 .prepare_interaction_for_verification(request)
577 .await
578 .map_err(anyhow::Error::from)
579 }
580
581 async fn prepare_interaction_for_verification_v2(
582 &self,
583 request: proto_v2::VerificationPreparationRequest,
584 ) -> anyhow::Result<VerificationPreparationResponse> {
585 let mut client = self.get_plugin_client().await?;
586 client
587 .prepare_interaction_for_verification_v2(request)
588 .await
589 .map_err(anyhow::Error::from)
590 }
591
592 async fn verify_interaction(
593 &self,
594 request: VerifyInteractionRequest,
595 ) -> anyhow::Result<VerifyInteractionResponse> {
596 let mut client = self.get_plugin_client().await?;
597 client.verify_interaction(request).await.map_err(anyhow::Error::from)
598 }
599
600 async fn verify_interaction_v2(
601 &self,
602 request: proto_v2::VerifyInteractionRequest,
603 ) -> anyhow::Result<VerifyInteractionResponse> {
604 let mut client = self.get_plugin_client().await?;
605 client.verify_interaction_v2(request).await.map_err(anyhow::Error::from)
606 }
607
608 async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()> {
609 let mut client = self.get_plugin_client().await?;
610 client.update_catalogue(request).await.map_err(anyhow::Error::from)
611 }
612}
613
614pub(crate) async fn start_plugin_process(manifest: &PactPluginManifest) -> anyhow::Result<PactPlugin> {
616 debug!("Starting plugin with manifest {:?}", manifest);
617
618 let os_info = os_info::get();
619 debug!("Detected OS: {}", os_info);
620 let mut path = if let Some(entry_point) = manifest.entry_points.get(&os_info.to_string()) {
621 PathBuf::from(entry_point)
622 } else if os_info.os_type() == Type::Windows && manifest.entry_points.contains_key("windows") {
623 PathBuf::from(manifest.entry_points.get("windows").unwrap())
624 } else {
625 PathBuf::from(&manifest.entry_point)
626 };
627 if !path.is_absolute() || !path.exists() {
628 path = PathBuf::from(manifest.plugin_dir.clone()).join(path);
629 }
630 debug!("Starting plugin using {:?}", &path);
631
632 let host_port = match crate::plugin_host::ensure_plugin_host_running().await {
633 Ok(port) => Some(port),
634 Err(err) => {
635 warn!("Could not start PluginHost server, Log RPC forwarding will be unavailable: {}", err);
636 None
637 }
638 };
639
640 let log_level = max_level();
641 let mut child_command = Command::new(path.clone());
642 let mut child_command = child_command
643 .env("LOG_LEVEL", log_level.to_string())
644 .env("RUST_LOG", log_level.to_string())
645 .current_dir(manifest.plugin_dir.clone());
646
647 let instance_id = Uuid::new_v4().to_string();
648
649 child_command = child_command.env("PACT_PLUGIN_INSTANCE_ID", &instance_id);
650 if let Some(port) = host_port {
651 child_command = child_command.env("PACT_PLUGIN_HOST", format!("127.0.0.1:{}", port));
652 }
653
654 if let Some(args) = &manifest.args {
655 child_command = child_command.args(args);
656 }
657
658 let child = child_command
659 .stdout(Stdio::piped())
660 .stderr(Stdio::piped())
661 .spawn()
662 .map_err(|err| {
663 anyhow!(
664 "Was not able to start plugin process for '{}' - {}",
665 path.to_string_lossy(),
666 err
667 )
668 })?;
669 let child_pid = child.id();
670 debug!("Plugin {} started with PID {} (instance {})", manifest.name, child_pid, instance_id);
671 crate::plugin_manager::register_plugin_instance(&instance_id, &manifest.name);
672
673 match ChildPluginProcess::new(child, manifest, instance_id.clone()).await {
674 Ok(child) => {
675 let plugin = PactPlugin::new(manifest, child)?;
676 Ok(plugin)
677 }
678 Err(err) => {
679 crate::plugin_manager::deregister_plugin_instance(&instance_id);
680 let mut s = System::new();
681 s.refresh_processes();
682 if let Some(process) = s.process(Pid::from_u32(child_pid)) {
683 #[cfg(not(windows))]
684 process.kill();
685 #[cfg(windows)]
687 let _ = Command::new("taskkill.exe")
688 .arg("/PID")
689 .arg(child_pid.to_string())
690 .arg("/F")
691 .arg("/T")
692 .output();
693 } else {
694 warn!("Child process with PID {} was not found", child_pid);
695 }
696 Err(err)
697 }
698 }
699}
700
701#[cfg(test)]
702pub(crate) mod tests {
703 use tonic::Status;
704
705 use crate::plugin_models::PluginInitResponse;
706 use crate::proto::*;
707
708 use super::PluginClient;
709
710 #[test]
711 fn converts_between_v1_and_v2_messages() {
712 use std::collections::HashMap;
713 use crate::plugin_models::PluginInitRequest;
714 use crate::proto_v2;
715
716 let request = PluginInitRequest {
717 implementation: "plugin-driver-rust".to_string(),
718 version: "1.0.0-beta.1".to_string(),
719 host_capabilities: vec!["interaction/request-response".to_string()],
720 plugin_instance_id: "test-instance-id".to_string(),
721 };
722
723 let converted_request = proto_v2::InitPluginRequest {
724 implementation: request.implementation,
725 version: request.version,
726 host_capabilities: request.host_capabilities,
727 plugin_instance_id: request.plugin_instance_id,
728 };
729 assert_eq!(converted_request.implementation, "plugin-driver-rust");
730 assert_eq!(converted_request.version, "1.0.0-beta.1");
731 assert_eq!(
732 converted_request.host_capabilities,
733 vec!["interaction/request-response"]
734 );
735
736 let response = proto_v2::InitPluginResponse {
737 response: Some(proto_v2::init_plugin_response::Response::Success(
738 proto_v2::InitPluginSuccess {
739 catalogue: vec![proto_v2::CatalogueEntry {
740 r#type: proto_v2::catalogue_entry::EntryType::ContentMatcher as i32,
741 key: "test".to_string(),
742 values: HashMap::new(),
743 }],
744 plugin_capabilities: vec!["plugin/verification".to_string()],
745 },
746 )),
747 };
748
749 let converted_response = match response.response.unwrap() {
750 proto_v2::init_plugin_response::Response::Success(success) => PluginInitResponse {
751 catalogue: success
752 .catalogue
753 .into_iter()
754 .map(PluginClient::convert_message)
755 .collect::<Result<Vec<CatalogueEntry>, Status>>()
756 .unwrap(),
757 plugin_capabilities: success.plugin_capabilities,
758 },
759 _ => unreachable!(),
760 };
761 assert_eq!(converted_response.catalogue.len(), 1);
762 assert_eq!(converted_response.catalogue[0].key, "test");
763 assert_eq!(
764 converted_response.catalogue[0].r#type,
765 catalogue_entry::EntryType::ContentMatcher as i32
766 );
767 assert_eq!(converted_response.plugin_capabilities, vec!["plugin/verification"]);
768 }
769}