1use crate::{
8 jobs::JobProducerTrait,
9 models::{
10 ApiError, ApiResponse, NetworkRepoModel, NotificationRepoModel, PaginationMeta,
11 PaginationQuery, PluginCallRequest, PluginModel, PluginValidationError, RelayerRepoModel,
12 SignerRepoModel, ThinDataAppState, TransactionRepoModel, UpdatePluginRequest,
13 },
14 repositories::{
15 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository,
16 Repository, TransactionCounterTrait, TransactionRepository,
17 },
18 services::plugins::{
19 PluginCallResponse, PluginCallResult, PluginHandlerResponse, PluginRunner, PluginService,
20 PluginServiceTrait,
21 },
22};
23use actix_web::{http::StatusCode, HttpResponse};
24use eyre::Result;
25use std::sync::Arc;
26
27pub async fn call_plugin<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
39 plugin_id: String,
40 plugin_call_request: PluginCallRequest,
41 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
42) -> Result<HttpResponse, ApiError>
43where
44 J: JobProducerTrait + Send + Sync + 'static,
45 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
46 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
47 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
48 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
49 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
50 TCR: TransactionCounterTrait + Send + Sync + 'static,
51 PR: PluginRepositoryTrait + Send + Sync + 'static,
52 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
53{
54 let plugin = state
55 .plugin_repository
56 .get_by_id(&plugin_id)
57 .await?
58 .ok_or_else(|| ApiError::NotFound(format!("Plugin with id {plugin_id} not found")))?;
59
60 let plugin_runner = PluginRunner;
61 let plugin_service = PluginService::new(plugin_runner);
62 let raw_response = plugin.raw_response;
63 let result = plugin_service
64 .call_plugin(plugin, plugin_call_request, Arc::new(state))
65 .await;
66
67 match result {
68 PluginCallResult::Success(plugin_result) => {
69 let PluginCallResponse { result, metadata } = plugin_result;
70
71 if raw_response {
72 Ok(HttpResponse::Ok().json(result))
74 } else {
75 let mut response = ApiResponse::success(result);
77 response.metadata = metadata;
78 Ok(HttpResponse::Ok().json(response))
79 }
80 }
81 PluginCallResult::Handler(handler) => {
82 let PluginHandlerResponse {
83 status,
84 message,
85 error,
86 metadata,
87 } = handler;
88
89 let log_count = metadata
90 .as_ref()
91 .and_then(|meta| meta.logs.as_ref().map(|logs| logs.len()))
92 .unwrap_or(0);
93 let trace_count = metadata
94 .as_ref()
95 .and_then(|meta| meta.traces.as_ref().map(|traces| traces.len()))
96 .unwrap_or(0);
97
98 let http_status =
99 StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
100
101 tracing::debug!(
103 status,
104 message = %message,
105 code = ?error.code.as_ref(),
106 details = ?error.details.as_ref(),
107 log_count,
108 trace_count,
109 "Plugin handler error"
110 );
111
112 if raw_response {
113 Ok(HttpResponse::build(http_status).json(error))
115 } else {
116 let mut response = ApiResponse::new(Some(error), Some(message.clone()), None);
118 response.metadata = metadata;
119 Ok(HttpResponse::build(http_status).json(response))
120 }
121 }
122 PluginCallResult::Fatal(error) => {
123 tracing::error!("Plugin error: {:?}", error);
124 Ok(HttpResponse::InternalServerError()
125 .json(ApiResponse::<String>::error("Internal server error")))
126 }
127 }
128}
129
130pub async fn list_plugins<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
143 query: PaginationQuery,
144 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
145) -> Result<HttpResponse, ApiError>
146where
147 J: JobProducerTrait + Send + Sync + 'static,
148 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
149 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
150 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
151 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
152 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
153 TCR: TransactionCounterTrait + Send + Sync + 'static,
154 PR: PluginRepositoryTrait + Send + Sync + 'static,
155 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
156{
157 let plugins = state.plugin_repository.list_paginated(query).await?;
158
159 let plugin_items: Vec<PluginModel> = plugins.items.into_iter().collect();
160
161 Ok(HttpResponse::Ok().json(ApiResponse::paginated(
162 plugin_items,
163 PaginationMeta {
164 total_items: plugins.total,
165 current_page: plugins.page,
166 per_page: plugins.per_page,
167 },
168 )))
169}
170
171pub async fn get_plugin<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
182 plugin_id: String,
183 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
184) -> Result<HttpResponse, ApiError>
185where
186 J: JobProducerTrait + Send + Sync + 'static,
187 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
188 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
189 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
190 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
191 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
192 TCR: TransactionCounterTrait + Send + Sync + 'static,
193 PR: PluginRepositoryTrait + Send + Sync + 'static,
194 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
195{
196 let plugin = state
197 .plugin_repository
198 .get_by_id(&plugin_id)
199 .await?
200 .ok_or_else(|| ApiError::NotFound(format!("Plugin with id {plugin_id} not found")))?;
201
202 Ok(HttpResponse::Ok().json(ApiResponse::success(plugin)))
203}
204
205pub async fn update_plugin<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
221 plugin_id: String,
222 update_request: UpdatePluginRequest,
223 state: ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
224) -> Result<HttpResponse, ApiError>
225where
226 J: JobProducerTrait + Send + Sync + 'static,
227 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
228 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
229 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
230 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
231 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
232 TCR: TransactionCounterTrait + Send + Sync + 'static,
233 PR: PluginRepositoryTrait + Send + Sync + 'static,
234 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
235{
236 let plugin = state
238 .plugin_repository
239 .get_by_id(&plugin_id)
240 .await?
241 .ok_or_else(|| ApiError::NotFound(format!("Plugin with id {plugin_id} not found")))?;
242
243 let updated_plugin = plugin.apply_update(update_request).map_err(|e| match e {
245 PluginValidationError::InvalidTimeout(msg) => ApiError::BadRequest(msg),
246 })?;
247
248 let saved_plugin = state.plugin_repository.update(updated_plugin).await?;
250
251 tracing::info!(plugin_id = %plugin_id, "Plugin configuration updated");
252
253 Ok(HttpResponse::Ok().json(ApiResponse::success(saved_plugin)))
254}
255
256#[cfg(test)]
257mod tests {
258 use std::time::Duration;
259
260 use super::*;
261 use actix_web::web;
262
263 use crate::{
264 constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS, models::PluginModel,
265 utils::mocks::mockutils::create_mock_app_state,
266 };
267
268 #[actix_web::test]
269 async fn test_call_plugin_execution_failure() {
270 let plugin = PluginModel {
272 id: "test-plugin".to_string(),
273 path: "test-path".to_string(),
274 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
275 emit_logs: false,
276 emit_traces: false,
277 raw_response: false,
278 allow_get_invocation: false,
279 config: None,
280 forward_logs: false,
281 };
282 let app_state =
283 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
284 let plugin_call_request = PluginCallRequest {
285 params: serde_json::json!({"key":"value"}),
286 headers: None,
287 route: None,
288 method: Some("POST".to_string()),
289 query: None,
290 };
291 let response = call_plugin(
292 "test-plugin".to_string(),
293 plugin_call_request,
294 web::ThinData(app_state),
295 )
296 .await;
297 assert!(response.is_ok());
298 let http_response = response.unwrap();
299 assert_eq!(http_response.status(), StatusCode::INTERNAL_SERVER_ERROR);
301 }
302
303 #[actix_web::test]
304 async fn test_call_plugin_not_found() {
305 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
307 let plugin_call_request = PluginCallRequest {
308 params: serde_json::json!({"key":"value"}),
309 headers: None,
310 route: None,
311 method: Some("POST".to_string()),
312 query: None,
313 };
314 let response = call_plugin(
315 "non-existent".to_string(),
316 plugin_call_request,
317 web::ThinData(app_state),
318 )
319 .await;
320 assert!(response.is_err());
321 match response.unwrap_err() {
322 ApiError::NotFound(msg) => assert!(msg.contains("non-existent")),
323 _ => panic!("Expected NotFound error"),
324 }
325 }
326
327 #[actix_web::test]
328 async fn test_call_plugin_with_logs_and_traces_enabled() {
329 let plugin = PluginModel {
331 id: "test-plugin-logs".to_string(),
332 path: "test-path".to_string(),
333 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
334 emit_logs: true,
335 emit_traces: true,
336 raw_response: false,
337 allow_get_invocation: false,
338 config: None,
339 forward_logs: false,
340 };
341 let app_state =
342 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
343 let plugin_call_request = PluginCallRequest {
344 params: serde_json::json!({}),
345 headers: None,
346 route: None,
347 method: Some("POST".to_string()),
348 query: None,
349 };
350 let response = call_plugin(
351 "test-plugin-logs".to_string(),
352 plugin_call_request,
353 web::ThinData(app_state),
354 )
355 .await;
356 assert!(response.is_ok());
357 }
358
359 #[actix_web::test]
360 async fn test_list_plugins() {
361 let plugin1 = PluginModel {
363 id: "plugin1".to_string(),
364 path: "path1".to_string(),
365 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
366 emit_logs: false,
367 emit_traces: false,
368 raw_response: false,
369 allow_get_invocation: false,
370 config: None,
371 forward_logs: false,
372 };
373 let plugin2 = PluginModel {
374 id: "plugin2".to_string(),
375 path: "path2".to_string(),
376 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
377 emit_logs: true,
378 emit_traces: true,
379 raw_response: false,
380 allow_get_invocation: false,
381 config: None,
382 forward_logs: false,
383 };
384 let app_state =
385 create_mock_app_state(None, None, None, None, Some(vec![plugin1, plugin2]), None).await;
386
387 let query = PaginationQuery {
388 page: 1,
389 per_page: 10,
390 };
391
392 let response = list_plugins(query, web::ThinData(app_state)).await;
393 assert!(response.is_ok());
394 let http_response = response.unwrap();
395 assert_eq!(http_response.status(), StatusCode::OK);
396 }
397
398 #[actix_web::test]
399 async fn test_list_plugins_empty() {
400 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
402
403 let query = PaginationQuery {
404 page: 1,
405 per_page: 10,
406 };
407
408 let response = list_plugins(query, web::ThinData(app_state)).await;
409 assert!(response.is_ok());
410 let http_response = response.unwrap();
411 assert_eq!(http_response.status(), StatusCode::OK);
412 }
413
414 #[actix_web::test]
415 async fn test_get_plugin_success() {
416 let plugin = PluginModel {
418 id: "test-plugin".to_string(),
419 path: "test-path".to_string(),
420 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
421 emit_logs: true,
422 emit_traces: false,
423 raw_response: false,
424 allow_get_invocation: true,
425 config: None,
426 forward_logs: true,
427 };
428 let app_state =
429 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
430
431 let response = get_plugin("test-plugin".to_string(), web::ThinData(app_state)).await;
432 assert!(response.is_ok());
433 let http_response = response.unwrap();
434 assert_eq!(http_response.status(), StatusCode::OK);
435 }
436
437 #[actix_web::test]
438 async fn test_get_plugin_not_found() {
439 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
441
442 let response = get_plugin("non-existent".to_string(), web::ThinData(app_state)).await;
443 assert!(response.is_err());
444 match response.unwrap_err() {
445 ApiError::NotFound(msg) => assert!(msg.contains("non-existent")),
446 _ => panic!("Expected NotFound error"),
447 }
448 }
449
450 #[actix_web::test]
451 async fn test_call_plugin_with_raw_response() {
452 let plugin = PluginModel {
454 id: "test-plugin-raw".to_string(),
455 path: "test-path".to_string(),
456 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
457 emit_logs: false,
458 emit_traces: false,
459 raw_response: true,
460 allow_get_invocation: false,
461 config: None,
462 forward_logs: false,
463 };
464 let app_state =
465 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
466 let plugin_call_request = PluginCallRequest {
467 params: serde_json::json!({"test": "data"}),
468 headers: None,
469 route: None,
470 method: Some("POST".to_string()),
471 query: None,
472 };
473 let response = call_plugin(
474 "test-plugin-raw".to_string(),
475 plugin_call_request,
476 web::ThinData(app_state),
477 )
478 .await;
479 assert!(response.is_ok());
480 }
483
484 #[actix_web::test]
485 async fn test_call_plugin_with_config() {
486 let config_value = serde_json::json!({
488 "apiKey": "test-key",
489 "webhookUrl": "https://example.com/webhook"
490 });
491 let plugin = PluginModel {
492 id: "test-plugin-config".to_string(),
493 path: "test-path".to_string(),
494 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
495 emit_logs: false,
496 emit_traces: false,
497 raw_response: false,
498 allow_get_invocation: false,
499 config: config_value.as_object().cloned(),
500 forward_logs: false,
501 };
502 let app_state =
503 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
504 let plugin_call_request = PluginCallRequest {
505 params: serde_json::json!({"action": "test"}),
506 headers: None,
507 route: None,
508 method: Some("POST".to_string()),
509 query: None,
510 };
511 let response = call_plugin(
512 "test-plugin-config".to_string(),
513 plugin_call_request,
514 web::ThinData(app_state),
515 )
516 .await;
517 assert!(response.is_ok());
518 }
521
522 #[actix_web::test]
523 async fn test_call_plugin_with_raw_response_and_config() {
524 let config_value = serde_json::json!({
526 "setting": "value"
527 });
528 let plugin = PluginModel {
529 id: "test-plugin-raw-config".to_string(),
530 path: "test-path".to_string(),
531 timeout: Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS),
532 emit_logs: false,
533 emit_traces: false,
534 raw_response: true,
535 allow_get_invocation: false,
536 config: config_value.as_object().cloned(),
537 forward_logs: false,
538 };
539 let app_state =
540 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
541 let plugin_call_request = PluginCallRequest {
542 params: serde_json::json!({"data": "test"}),
543 headers: None,
544 route: None,
545 method: Some("POST".to_string()),
546 query: None,
547 };
548 let response = call_plugin(
549 "test-plugin-raw-config".to_string(),
550 plugin_call_request,
551 web::ThinData(app_state),
552 )
553 .await;
554 assert!(response.is_ok());
555 }
556
557 #[actix_web::test]
562 async fn test_call_plugin_success_with_standard_response() {
563 use crate::models::PluginMetadata;
564 use crate::services::plugins::PluginCallResponse;
565
566 let plugin_result = PluginCallResponse {
568 result: serde_json::json!({"status": "success", "data": "test"}),
569 metadata: Some(PluginMetadata {
570 logs: Some(vec![]),
571 traces: Some(vec![]),
572 }),
573 };
574
575 let mut response = ApiResponse::success(plugin_result.result.clone());
577 response.metadata = plugin_result.metadata.clone();
578
579 assert!(response.success);
581 assert_eq!(response.data, Some(plugin_result.result));
582 assert!(response.metadata.is_some());
583 assert!(response.error.is_none());
584
585 let metadata = response.metadata.unwrap();
587 assert!(metadata.logs.is_some());
588 assert!(metadata.traces.is_some());
589 }
590
591 #[actix_web::test]
594 async fn test_call_plugin_success_with_raw_response() {
595 use crate::models::PluginMetadata;
596 use crate::services::plugins::PluginCallResponse;
597
598 let plugin_result = PluginCallResponse {
600 result: serde_json::json!({"status": "success", "data": "test"}),
601 metadata: Some(PluginMetadata {
602 logs: Some(vec![]),
603 traces: Some(vec![]),
604 }),
605 };
606
607 let raw_result = plugin_result.result.clone();
610
611 assert!(raw_result.is_object());
613 assert_eq!(
614 raw_result.get("status"),
615 Some(&serde_json::json!("success"))
616 );
617 assert_eq!(raw_result.get("data"), Some(&serde_json::json!("test")));
618
619 }
622
623 #[actix_web::test]
626 async fn test_call_plugin_success_metadata_included() {
627 use crate::models::PluginMetadata;
628 use crate::services::plugins::script_executor::LogLevel;
629 use crate::services::plugins::{LogEntry, PluginCallResponse};
630
631 let plugin_result = PluginCallResponse {
633 result: serde_json::json!({"result": "ok"}),
634 metadata: Some(PluginMetadata {
635 logs: Some(vec![
636 LogEntry {
637 level: LogLevel::Log,
638 message: "test log message".to_string(),
639 },
640 LogEntry {
641 level: LogLevel::Error,
642 message: "test error".to_string(),
643 },
644 ]),
645 traces: Some(vec![
646 serde_json::json!({"step": 1, "action": "start"}),
647 serde_json::json!({"step": 2, "action": "complete"}),
648 ]),
649 }),
650 };
651
652 let mut response = ApiResponse::success(plugin_result.result.clone());
654 response.metadata = plugin_result.metadata.clone();
655
656 assert!(response.metadata.is_some());
658 let metadata = response.metadata.unwrap();
659 assert_eq!(metadata.logs.as_ref().unwrap().len(), 2);
660 assert_eq!(metadata.traces.as_ref().unwrap().len(), 2);
661 assert_eq!(
662 metadata.logs.as_ref().unwrap()[0].message,
663 "test log message"
664 );
665 assert_eq!(
666 metadata.traces.as_ref().unwrap()[0].get("step"),
667 Some(&serde_json::json!(1))
668 );
669 }
670
671 #[actix_web::test]
674 async fn test_call_plugin_success_without_metadata() {
675 use crate::services::plugins::PluginCallResponse;
676
677 let plugin_result = PluginCallResponse {
679 result: serde_json::json!({"result": "ok"}),
680 metadata: None,
681 };
682
683 let mut response = ApiResponse::success(plugin_result.result.clone());
685 response.metadata = plugin_result.metadata.clone();
686
687 assert!(response.success);
689 assert_eq!(response.data, Some(plugin_result.result));
690 assert!(response.metadata.is_none());
691 assert!(response.error.is_none());
692 }
693
694 #[actix_web::test]
699 async fn test_update_plugin_success() {
700 let plugin = PluginModel {
702 id: "test-plugin".to_string(),
703 path: "test-path".to_string(),
704 timeout: Duration::from_secs(30),
705 emit_logs: false,
706 emit_traces: false,
707 raw_response: false,
708 allow_get_invocation: false,
709 config: None,
710 forward_logs: false,
711 };
712 let app_state =
713 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
714
715 let update_request = UpdatePluginRequest {
716 timeout: Some(60),
717 emit_logs: Some(true),
718 forward_logs: Some(true),
719 ..Default::default()
720 };
721
722 let response = update_plugin(
723 "test-plugin".to_string(),
724 update_request,
725 web::ThinData(app_state),
726 )
727 .await;
728
729 assert!(response.is_ok());
730 let http_response = response.unwrap();
731 assert_eq!(http_response.status(), StatusCode::OK);
732 }
733
734 #[actix_web::test]
735 async fn test_update_plugin_not_found() {
736 let app_state = create_mock_app_state(None, None, None, None, None, None).await;
738
739 let update_request = UpdatePluginRequest {
740 timeout: Some(60),
741 ..Default::default()
742 };
743
744 let response = update_plugin(
745 "non-existent".to_string(),
746 update_request,
747 web::ThinData(app_state),
748 )
749 .await;
750
751 assert!(response.is_err());
752 match response.unwrap_err() {
753 ApiError::NotFound(msg) => assert!(msg.contains("non-existent")),
754 _ => panic!("Expected NotFound error"),
755 }
756 }
757
758 #[actix_web::test]
759 async fn test_update_plugin_invalid_timeout() {
760 let plugin = PluginModel {
762 id: "test-plugin".to_string(),
763 path: "test-path".to_string(),
764 timeout: Duration::from_secs(30),
765 emit_logs: false,
766 emit_traces: false,
767 raw_response: false,
768 allow_get_invocation: false,
769 config: None,
770 forward_logs: false,
771 };
772 let app_state =
773 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
774
775 let update_request = UpdatePluginRequest {
776 timeout: Some(0), ..Default::default()
778 };
779
780 let response = update_plugin(
781 "test-plugin".to_string(),
782 update_request,
783 web::ThinData(app_state),
784 )
785 .await;
786
787 assert!(response.is_err());
788 match response.unwrap_err() {
789 ApiError::BadRequest(msg) => assert!(msg.contains("Timeout")),
790 _ => panic!("Expected BadRequest error"),
791 }
792 }
793
794 #[actix_web::test]
795 async fn test_update_plugin_with_config() {
796 let plugin = PluginModel {
798 id: "test-plugin".to_string(),
799 path: "test-path".to_string(),
800 timeout: Duration::from_secs(30),
801 emit_logs: false,
802 emit_traces: false,
803 raw_response: false,
804 allow_get_invocation: false,
805 config: None,
806 forward_logs: false,
807 };
808 let app_state =
809 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
810
811 let mut config_map = serde_json::Map::new();
812 config_map.insert("feature_flag".to_string(), serde_json::json!(true));
813 config_map.insert("api_key".to_string(), serde_json::json!("secret123"));
814
815 let update_request = UpdatePluginRequest {
816 config: Some(Some(config_map)),
817 ..Default::default()
818 };
819
820 let response = update_plugin(
821 "test-plugin".to_string(),
822 update_request,
823 web::ThinData(app_state),
824 )
825 .await;
826
827 assert!(response.is_ok());
828 let http_response = response.unwrap();
829 assert_eq!(http_response.status(), StatusCode::OK);
830 }
831
832 #[actix_web::test]
833 async fn test_update_plugin_clear_config() {
834 let mut initial_config = serde_json::Map::new();
836 initial_config.insert("existing".to_string(), serde_json::json!("value"));
837
838 let plugin = PluginModel {
839 id: "test-plugin".to_string(),
840 path: "test-path".to_string(),
841 timeout: Duration::from_secs(30),
842 emit_logs: false,
843 emit_traces: false,
844 raw_response: false,
845 allow_get_invocation: false,
846 config: Some(initial_config),
847 forward_logs: false,
848 };
849 let app_state =
850 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
851
852 let update_request = UpdatePluginRequest {
854 config: Some(None),
855 ..Default::default()
856 };
857
858 let response = update_plugin(
859 "test-plugin".to_string(),
860 update_request,
861 web::ThinData(app_state),
862 )
863 .await;
864
865 assert!(response.is_ok());
866 let http_response = response.unwrap();
867 assert_eq!(http_response.status(), StatusCode::OK);
868 }
869
870 #[actix_web::test]
871 async fn test_update_plugin_all_fields() {
872 let plugin = PluginModel {
874 id: "test-plugin".to_string(),
875 path: "test-path".to_string(),
876 timeout: Duration::from_secs(30),
877 emit_logs: false,
878 emit_traces: false,
879 raw_response: false,
880 allow_get_invocation: false,
881 config: None,
882 forward_logs: false,
883 };
884 let app_state =
885 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
886
887 let mut config_map = serde_json::Map::new();
888 config_map.insert("key".to_string(), serde_json::json!("value"));
889
890 let update_request = UpdatePluginRequest {
891 timeout: Some(120),
892 emit_logs: Some(true),
893 emit_traces: Some(true),
894 raw_response: Some(true),
895 allow_get_invocation: Some(true),
896 config: Some(Some(config_map)),
897 forward_logs: Some(true),
898 };
899
900 let response = update_plugin(
901 "test-plugin".to_string(),
902 update_request,
903 web::ThinData(app_state),
904 )
905 .await;
906
907 assert!(response.is_ok());
908 let http_response = response.unwrap();
909 assert_eq!(http_response.status(), StatusCode::OK);
910 }
911
912 #[actix_web::test]
913 async fn test_update_plugin_empty_request() {
914 let plugin = PluginModel {
916 id: "test-plugin".to_string(),
917 path: "test-path".to_string(),
918 timeout: Duration::from_secs(30),
919 emit_logs: true,
920 emit_traces: true,
921 raw_response: false,
922 allow_get_invocation: false,
923 config: None,
924 forward_logs: true,
925 };
926 let app_state =
927 create_mock_app_state(None, None, None, None, Some(vec![plugin]), None).await;
928
929 let update_request = UpdatePluginRequest::default();
931
932 let response = update_plugin(
933 "test-plugin".to_string(),
934 update_request,
935 web::ThinData(app_state),
936 )
937 .await;
938
939 assert!(response.is_ok());
940 let http_response = response.unwrap();
941 assert_eq!(http_response.status(), StatusCode::OK);
942 }
943}