1pub mod plugin_in_memory;
27pub mod plugin_redis;
28
29pub use plugin_in_memory::*;
30pub use plugin_redis::*;
31
32use crate::utils::RedisConnections;
33use async_trait::async_trait;
34use std::{sync::Arc, time::Duration};
35
36#[cfg(test)]
37use mockall::automock;
38
39use crate::{
40 config::PluginFileConfig,
41 constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS,
42 models::{PaginationQuery, PluginModel, RepositoryError},
43 repositories::{ConversionError, PaginatedResult},
44};
45
46#[async_trait]
47#[allow(dead_code)]
48#[cfg_attr(test, automock)]
49pub trait PluginRepositoryTrait {
50 async fn get_by_id(&self, id: &str) -> Result<Option<PluginModel>, RepositoryError>;
52 async fn add(&self, plugin: PluginModel) -> Result<(), RepositoryError>;
53 async fn update(&self, plugin: PluginModel) -> Result<PluginModel, RepositoryError>;
55 async fn list_paginated(
56 &self,
57 query: PaginationQuery,
58 ) -> Result<PaginatedResult<PluginModel>, RepositoryError>;
59 async fn count(&self) -> Result<usize, RepositoryError>;
60 async fn has_entries(&self) -> Result<bool, RepositoryError>;
61 async fn drop_all_entries(&self) -> Result<(), RepositoryError>;
62
63 async fn get_compiled_code(&self, plugin_id: &str) -> Result<Option<String>, RepositoryError>;
66 async fn store_compiled_code(
68 &self,
69 plugin_id: &str,
70 compiled_code: &str,
71 source_hash: Option<&str>,
72 ) -> Result<(), RepositoryError>;
73 async fn invalidate_compiled_code(&self, plugin_id: &str) -> Result<(), RepositoryError>;
75 async fn invalidate_all_compiled_code(&self) -> Result<(), RepositoryError>;
77 async fn has_compiled_code(&self, plugin_id: &str) -> Result<bool, RepositoryError>;
79 async fn get_source_hash(&self, plugin_id: &str) -> Result<Option<String>, RepositoryError>;
81}
82
83#[derive(Debug, Clone)]
85pub enum PluginRepositoryStorage {
86 InMemory(InMemoryPluginRepository),
87 Redis(RedisPluginRepository),
88}
89
90impl PluginRepositoryStorage {
91 pub fn new_in_memory() -> Self {
92 Self::InMemory(InMemoryPluginRepository::new())
93 }
94
95 pub fn new_redis(
96 connections: Arc<RedisConnections>,
97 key_prefix: String,
98 ) -> Result<Self, RepositoryError> {
99 let redis_repo = RedisPluginRepository::new(connections, key_prefix)?;
100 Ok(Self::Redis(redis_repo))
101 }
102}
103
104#[async_trait]
105impl PluginRepositoryTrait for PluginRepositoryStorage {
106 async fn get_by_id(&self, id: &str) -> Result<Option<PluginModel>, RepositoryError> {
107 match self {
108 PluginRepositoryStorage::InMemory(repo) => repo.get_by_id(id).await,
109 PluginRepositoryStorage::Redis(repo) => repo.get_by_id(id).await,
110 }
111 }
112
113 async fn add(&self, plugin: PluginModel) -> Result<(), RepositoryError> {
114 match self {
115 PluginRepositoryStorage::InMemory(repo) => repo.add(plugin).await,
116 PluginRepositoryStorage::Redis(repo) => repo.add(plugin).await,
117 }
118 }
119
120 async fn update(&self, plugin: PluginModel) -> Result<PluginModel, RepositoryError> {
121 match self {
122 PluginRepositoryStorage::InMemory(repo) => repo.update(plugin).await,
123 PluginRepositoryStorage::Redis(repo) => repo.update(plugin).await,
124 }
125 }
126
127 async fn list_paginated(
128 &self,
129 query: PaginationQuery,
130 ) -> Result<PaginatedResult<PluginModel>, RepositoryError> {
131 match self {
132 PluginRepositoryStorage::InMemory(repo) => repo.list_paginated(query).await,
133 PluginRepositoryStorage::Redis(repo) => repo.list_paginated(query).await,
134 }
135 }
136
137 async fn count(&self) -> Result<usize, RepositoryError> {
138 match self {
139 PluginRepositoryStorage::InMemory(repo) => repo.count().await,
140 PluginRepositoryStorage::Redis(repo) => repo.count().await,
141 }
142 }
143
144 async fn has_entries(&self) -> Result<bool, RepositoryError> {
145 match self {
146 PluginRepositoryStorage::InMemory(repo) => repo.has_entries().await,
147 PluginRepositoryStorage::Redis(repo) => repo.has_entries().await,
148 }
149 }
150
151 async fn drop_all_entries(&self) -> Result<(), RepositoryError> {
152 match self {
153 PluginRepositoryStorage::InMemory(repo) => repo.drop_all_entries().await,
154 PluginRepositoryStorage::Redis(repo) => repo.drop_all_entries().await,
155 }
156 }
157
158 async fn get_compiled_code(&self, plugin_id: &str) -> Result<Option<String>, RepositoryError> {
159 match self {
160 PluginRepositoryStorage::InMemory(repo) => repo.get_compiled_code(plugin_id).await,
161 PluginRepositoryStorage::Redis(repo) => repo.get_compiled_code(plugin_id).await,
162 }
163 }
164
165 async fn store_compiled_code(
166 &self,
167 plugin_id: &str,
168 compiled_code: &str,
169 source_hash: Option<&str>,
170 ) -> Result<(), RepositoryError> {
171 match self {
172 PluginRepositoryStorage::InMemory(repo) => {
173 repo.store_compiled_code(plugin_id, compiled_code, source_hash)
174 .await
175 }
176 PluginRepositoryStorage::Redis(repo) => {
177 repo.store_compiled_code(plugin_id, compiled_code, source_hash)
178 .await
179 }
180 }
181 }
182
183 async fn invalidate_compiled_code(&self, plugin_id: &str) -> Result<(), RepositoryError> {
184 match self {
185 PluginRepositoryStorage::InMemory(repo) => {
186 repo.invalidate_compiled_code(plugin_id).await
187 }
188 PluginRepositoryStorage::Redis(repo) => repo.invalidate_compiled_code(plugin_id).await,
189 }
190 }
191
192 async fn invalidate_all_compiled_code(&self) -> Result<(), RepositoryError> {
193 match self {
194 PluginRepositoryStorage::InMemory(repo) => repo.invalidate_all_compiled_code().await,
195 PluginRepositoryStorage::Redis(repo) => repo.invalidate_all_compiled_code().await,
196 }
197 }
198
199 async fn has_compiled_code(&self, plugin_id: &str) -> Result<bool, RepositoryError> {
200 match self {
201 PluginRepositoryStorage::InMemory(repo) => repo.has_compiled_code(plugin_id).await,
202 PluginRepositoryStorage::Redis(repo) => repo.has_compiled_code(plugin_id).await,
203 }
204 }
205
206 async fn get_source_hash(&self, plugin_id: &str) -> Result<Option<String>, RepositoryError> {
207 match self {
208 PluginRepositoryStorage::InMemory(repo) => repo.get_source_hash(plugin_id).await,
209 PluginRepositoryStorage::Redis(repo) => repo.get_source_hash(plugin_id).await,
210 }
211 }
212}
213
214impl TryFrom<PluginFileConfig> for PluginModel {
215 type Error = ConversionError;
216
217 fn try_from(config: PluginFileConfig) -> Result<Self, Self::Error> {
218 let timeout = Duration::from_secs(config.timeout.unwrap_or(DEFAULT_PLUGIN_TIMEOUT_SECONDS));
219
220 Ok(PluginModel {
221 id: config.id.clone(),
222 path: config.path.clone(),
223 timeout,
224 emit_logs: config.emit_logs,
225 emit_traces: config.emit_traces,
226 raw_response: config.raw_response,
227 allow_get_invocation: config.allow_get_invocation,
228 config: config.config,
229 forward_logs: config.forward_logs,
230 })
231 }
232}
233
234impl PartialEq for PluginModel {
235 fn eq(&self, other: &Self) -> bool {
236 self.id == other.id && self.path == other.path
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use crate::{config::PluginFileConfig, constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS};
243 use std::time::Duration;
244
245 use super::*;
246
247 fn create_test_plugin(id: &str, path: &str) -> PluginModel {
252 PluginModel {
253 id: id.to_string(),
254 path: path.to_string(),
255 timeout: Duration::from_secs(30),
256 emit_logs: false,
257 emit_traces: false,
258 raw_response: false,
259 allow_get_invocation: false,
260 config: None,
261 forward_logs: false,
262 }
263 }
264
265 fn create_test_plugin_with_options(
266 id: &str,
267 path: &str,
268 emit_logs: bool,
269 emit_traces: bool,
270 raw_response: bool,
271 ) -> PluginModel {
272 PluginModel {
273 id: id.to_string(),
274 path: path.to_string(),
275 timeout: Duration::from_secs(30),
276 emit_logs,
277 emit_traces,
278 raw_response,
279 allow_get_invocation: false,
280 config: None,
281 forward_logs: false,
282 }
283 }
284
285 #[tokio::test]
290 async fn test_try_from_default_timeout() {
291 let config = PluginFileConfig {
292 id: "test-plugin".to_string(),
293 path: "test-path".to_string(),
294 timeout: None,
295 emit_logs: false,
296 emit_traces: false,
297 raw_response: false,
298 allow_get_invocation: false,
299 config: None,
300 forward_logs: false,
301 };
302
303 let result = PluginModel::try_from(config);
304 assert!(result.is_ok());
305
306 let plugin = result.unwrap();
307 assert_eq!(plugin.id, "test-plugin");
308 assert_eq!(plugin.path, "test-path");
309 assert_eq!(
310 plugin.timeout,
311 Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS)
312 );
313 }
314
315 #[tokio::test]
316 async fn test_try_from_custom_timeout() {
317 let config = PluginFileConfig {
318 id: "test-plugin".to_string(),
319 path: "test-path".to_string(),
320 timeout: Some(120),
321 emit_logs: false,
322 emit_traces: false,
323 raw_response: false,
324 allow_get_invocation: false,
325 config: None,
326 forward_logs: false,
327 };
328
329 let result = PluginModel::try_from(config);
330 assert!(result.is_ok());
331
332 let plugin = result.unwrap();
333 assert_eq!(plugin.timeout, Duration::from_secs(120));
334 }
335
336 #[tokio::test]
337 async fn test_try_from_all_options_enabled() {
338 let mut config_map = serde_json::Map::new();
339 config_map.insert("key".to_string(), serde_json::json!("value"));
340
341 let config = PluginFileConfig {
342 id: "full-plugin".to_string(),
343 path: "/scripts/full.js".to_string(),
344 timeout: Some(60),
345 emit_logs: true,
346 emit_traces: true,
347 raw_response: true,
348 allow_get_invocation: true,
349 config: Some(config_map),
350 forward_logs: true,
351 };
352
353 let result = PluginModel::try_from(config);
354 assert!(result.is_ok());
355
356 let plugin = result.unwrap();
357 assert_eq!(plugin.id, "full-plugin");
358 assert!(plugin.emit_logs);
359 assert!(plugin.emit_traces);
360 assert!(plugin.raw_response);
361 assert!(plugin.allow_get_invocation);
362 assert!(plugin.config.is_some());
363 assert!(plugin.forward_logs);
364 }
365
366 #[tokio::test]
367 async fn test_try_from_zero_timeout() {
368 let config = PluginFileConfig {
369 id: "test".to_string(),
370 path: "path".to_string(),
371 timeout: Some(0),
372 emit_logs: false,
373 emit_traces: false,
374 raw_response: false,
375 allow_get_invocation: false,
376 config: None,
377 forward_logs: false,
378 };
379
380 let result = PluginModel::try_from(config);
381 assert!(result.is_ok());
382 assert_eq!(result.unwrap().timeout, Duration::from_secs(0));
383 }
384
385 #[test]
390 fn test_plugin_model_equality_same_id_and_path() {
391 let plugin1 = create_test_plugin("plugin-1", "/path/script.js");
392 let plugin2 = create_test_plugin("plugin-1", "/path/script.js");
393
394 assert_eq!(plugin1, plugin2);
395 }
396
397 #[test]
398 fn test_plugin_model_equality_different_id() {
399 let plugin1 = create_test_plugin("plugin-1", "/path/script.js");
400 let plugin2 = create_test_plugin("plugin-2", "/path/script.js");
401
402 assert_ne!(plugin1, plugin2);
403 }
404
405 #[test]
406 fn test_plugin_model_equality_different_path() {
407 let plugin1 = create_test_plugin("plugin-1", "/path/script1.js");
408 let plugin2 = create_test_plugin("plugin-1", "/path/script2.js");
409
410 assert_ne!(plugin1, plugin2);
411 }
412
413 #[test]
414 fn test_plugin_model_equality_ignores_other_fields() {
415 let plugin1 =
417 create_test_plugin_with_options("plugin-1", "/path/script.js", false, false, false);
418 let plugin2 =
419 create_test_plugin_with_options("plugin-1", "/path/script.js", true, true, true);
420
421 assert_eq!(plugin1, plugin2);
423 }
424
425 #[test]
426 fn test_plugin_model_equality_different_timeout() {
427 let mut plugin1 = create_test_plugin("plugin-1", "/path/script.js");
428 plugin1.timeout = Duration::from_secs(30);
429
430 let mut plugin2 = create_test_plugin("plugin-1", "/path/script.js");
431 plugin2.timeout = Duration::from_secs(60);
432
433 assert_eq!(plugin1, plugin2);
435 }
436
437 #[tokio::test]
442 async fn test_new_in_memory_creates_empty_storage() {
443 let storage = PluginRepositoryStorage::new_in_memory();
444
445 assert_eq!(storage.count().await.unwrap(), 0);
446 assert!(!storage.has_entries().await.unwrap());
447 }
448
449 #[test]
450 fn test_storage_enum_debug() {
451 let storage = PluginRepositoryStorage::new_in_memory();
452 let debug_str = format!("{storage:?}");
453 assert!(debug_str.contains("InMemory"));
454 }
455
456 #[tokio::test]
461 async fn test_plugin_repository_storage_get_by_id_existing() {
462 let storage = PluginRepositoryStorage::new_in_memory();
463 let plugin = create_test_plugin("test-plugin", "/path/to/script.js");
464
465 storage.add(plugin.clone()).await.unwrap();
466
467 let result = storage.get_by_id("test-plugin").await.unwrap();
468 assert_eq!(result, Some(plugin));
469 }
470
471 #[tokio::test]
472 async fn test_plugin_repository_storage_get_by_id_non_existing() {
473 let storage = PluginRepositoryStorage::new_in_memory();
474
475 let result = storage.get_by_id("non-existent").await.unwrap();
476 assert_eq!(result, None);
477 }
478
479 #[tokio::test]
480 async fn test_plugin_repository_storage_add_success() {
481 let storage = PluginRepositoryStorage::new_in_memory();
482 let plugin = create_test_plugin("test-plugin", "/path/to/script.js");
483
484 let result = storage.add(plugin).await;
485 assert!(result.is_ok());
486 }
487
488 #[tokio::test]
489 async fn test_plugin_repository_storage_add_duplicate() {
490 let storage = PluginRepositoryStorage::new_in_memory();
491 let plugin = create_test_plugin("test-plugin", "/path/to/script.js");
492
493 storage.add(plugin.clone()).await.unwrap();
494
495 let result = storage.add(plugin).await;
497 assert!(result.is_ok());
498 }
499
500 #[tokio::test]
501 async fn test_plugin_repository_storage_add_multiple() {
502 let storage = PluginRepositoryStorage::new_in_memory();
503
504 for i in 1..=10 {
505 let plugin = create_test_plugin(&format!("plugin-{i}"), &format!("/path/{i}.js"));
506 storage.add(plugin).await.unwrap();
507 }
508
509 assert_eq!(storage.count().await.unwrap(), 10);
510 }
511
512 #[tokio::test]
517 async fn test_plugin_repository_storage_update_existing() {
518 let storage = PluginRepositoryStorage::new_in_memory();
519
520 let plugin =
521 create_test_plugin_with_options("test-plugin", "/path/script.js", false, false, false);
522 storage.add(plugin).await.unwrap();
523
524 let updated =
525 create_test_plugin_with_options("test-plugin", "/path/script.js", true, true, true);
526 let result = storage.update(updated.clone()).await;
527
528 assert!(result.is_ok());
529 let returned = result.unwrap();
530 assert!(returned.emit_logs);
531 assert!(returned.emit_traces);
532 assert!(returned.raw_response);
533 }
534
535 #[tokio::test]
536 async fn test_plugin_repository_storage_update_nonexistent() {
537 let storage = PluginRepositoryStorage::new_in_memory();
538
539 let plugin = create_test_plugin("nonexistent", "/path/script.js");
540 let result = storage.update(plugin).await;
541
542 assert!(result.is_err());
543 match result {
544 Err(RepositoryError::NotFound(msg)) => {
545 assert!(msg.contains("nonexistent"));
546 }
547 _ => panic!("Expected NotFound error"),
548 }
549 }
550
551 #[tokio::test]
552 async fn test_plugin_repository_storage_update_persists_changes() {
553 let storage = PluginRepositoryStorage::new_in_memory();
554
555 let plugin = create_test_plugin("test-plugin", "/path/script.js");
556 storage.add(plugin).await.unwrap();
557
558 let mut updated = create_test_plugin("test-plugin", "/path/updated.js");
559 updated.emit_logs = true;
560 storage.update(updated).await.unwrap();
561
562 let retrieved = storage.get_by_id("test-plugin").await.unwrap().unwrap();
564 assert!(retrieved.emit_logs);
565 assert_eq!(retrieved.path, "/path/updated.js");
566 }
567
568 #[tokio::test]
569 async fn test_plugin_repository_storage_update_does_not_affect_others() {
570 let storage = PluginRepositoryStorage::new_in_memory();
571
572 storage
573 .add(create_test_plugin("plugin-1", "/path/1.js"))
574 .await
575 .unwrap();
576 storage
577 .add(create_test_plugin("plugin-2", "/path/2.js"))
578 .await
579 .unwrap();
580 storage
581 .add(create_test_plugin("plugin-3", "/path/3.js"))
582 .await
583 .unwrap();
584
585 let mut updated = create_test_plugin("plugin-2", "/path/updated.js");
586 updated.emit_logs = true;
587 storage.update(updated).await.unwrap();
588
589 let p1 = storage.get_by_id("plugin-1").await.unwrap().unwrap();
591 assert_eq!(p1.path, "/path/1.js");
592 assert!(!p1.emit_logs);
593
594 let p3 = storage.get_by_id("plugin-3").await.unwrap().unwrap();
595 assert_eq!(p3.path, "/path/3.js");
596 assert!(!p3.emit_logs);
597 }
598
599 #[tokio::test]
604 async fn test_plugin_repository_storage_count_empty() {
605 let storage = PluginRepositoryStorage::new_in_memory();
606
607 let count = storage.count().await.unwrap();
608 assert_eq!(count, 0);
609 }
610
611 #[tokio::test]
612 async fn test_plugin_repository_storage_count_with_plugins() {
613 let storage = PluginRepositoryStorage::new_in_memory();
614
615 storage
616 .add(create_test_plugin("plugin1", "/path/1.js"))
617 .await
618 .unwrap();
619 storage
620 .add(create_test_plugin("plugin2", "/path/2.js"))
621 .await
622 .unwrap();
623 storage
624 .add(create_test_plugin("plugin3", "/path/3.js"))
625 .await
626 .unwrap();
627
628 let count = storage.count().await.unwrap();
629 assert_eq!(count, 3);
630 }
631
632 #[tokio::test]
633 async fn test_plugin_repository_storage_count_after_drop() {
634 let storage = PluginRepositoryStorage::new_in_memory();
635
636 for i in 1..=5 {
637 storage
638 .add(create_test_plugin(&format!("p{i}"), &format!("/{i}.js")))
639 .await
640 .unwrap();
641 }
642
643 assert_eq!(storage.count().await.unwrap(), 5);
644
645 storage.drop_all_entries().await.unwrap();
646
647 assert_eq!(storage.count().await.unwrap(), 0);
648 }
649
650 #[tokio::test]
655 async fn test_plugin_repository_storage_has_entries_empty() {
656 let storage = PluginRepositoryStorage::new_in_memory();
657
658 let has_entries = storage.has_entries().await.unwrap();
659 assert!(!has_entries);
660 }
661
662 #[tokio::test]
663 async fn test_plugin_repository_storage_has_entries_with_plugins() {
664 let storage = PluginRepositoryStorage::new_in_memory();
665
666 storage
667 .add(create_test_plugin("plugin1", "/path/1.js"))
668 .await
669 .unwrap();
670
671 let has_entries = storage.has_entries().await.unwrap();
672 assert!(has_entries);
673 }
674
675 #[tokio::test]
680 async fn test_plugin_repository_storage_drop_all_entries_empty() {
681 let storage = PluginRepositoryStorage::new_in_memory();
682
683 let result = storage.drop_all_entries().await;
684 assert!(result.is_ok());
685
686 let count = storage.count().await.unwrap();
687 assert_eq!(count, 0);
688 }
689
690 #[tokio::test]
691 async fn test_plugin_repository_storage_drop_all_entries_with_plugins() {
692 let storage = PluginRepositoryStorage::new_in_memory();
693
694 storage
695 .add(create_test_plugin("plugin1", "/path/1.js"))
696 .await
697 .unwrap();
698 storage
699 .add(create_test_plugin("plugin2", "/path/2.js"))
700 .await
701 .unwrap();
702
703 let result = storage.drop_all_entries().await;
704 assert!(result.is_ok());
705
706 let count = storage.count().await.unwrap();
707 assert_eq!(count, 0);
708
709 let has_entries = storage.has_entries().await.unwrap();
710 assert!(!has_entries);
711 }
712
713 #[tokio::test]
718 async fn test_plugin_repository_storage_list_paginated_empty() {
719 let storage = PluginRepositoryStorage::new_in_memory();
720
721 let query = PaginationQuery {
722 page: 1,
723 per_page: 10,
724 };
725 let result = storage.list_paginated(query).await.unwrap();
726
727 assert_eq!(result.items.len(), 0);
728 assert_eq!(result.total, 0);
729 assert_eq!(result.page, 1);
730 assert_eq!(result.per_page, 10);
731 }
732
733 #[tokio::test]
734 async fn test_plugin_repository_storage_list_paginated_first_page() {
735 let storage = PluginRepositoryStorage::new_in_memory();
736
737 for i in 1..=10 {
738 storage
739 .add(create_test_plugin(
740 &format!("plugin{i}"),
741 &format!("/{i}.js"),
742 ))
743 .await
744 .unwrap();
745 }
746
747 let query = PaginationQuery {
748 page: 1,
749 per_page: 3,
750 };
751 let result = storage.list_paginated(query).await.unwrap();
752
753 assert_eq!(result.items.len(), 3);
754 assert_eq!(result.total, 10);
755 assert_eq!(result.page, 1);
756 assert_eq!(result.per_page, 3);
757 }
758
759 #[tokio::test]
760 async fn test_plugin_repository_storage_list_paginated_middle_page() {
761 let storage = PluginRepositoryStorage::new_in_memory();
762
763 for i in 1..=10 {
764 storage
765 .add(create_test_plugin(
766 &format!("plugin{i}"),
767 &format!("/{i}.js"),
768 ))
769 .await
770 .unwrap();
771 }
772
773 let query = PaginationQuery {
774 page: 2,
775 per_page: 3,
776 };
777 let result = storage.list_paginated(query).await.unwrap();
778
779 assert_eq!(result.items.len(), 3);
780 assert_eq!(result.total, 10);
781 assert_eq!(result.page, 2);
782 }
783
784 #[tokio::test]
785 async fn test_plugin_repository_storage_list_paginated_last_partial_page() {
786 let storage = PluginRepositoryStorage::new_in_memory();
787
788 for i in 1..=10 {
789 storage
790 .add(create_test_plugin(
791 &format!("plugin{i}"),
792 &format!("/{i}.js"),
793 ))
794 .await
795 .unwrap();
796 }
797
798 let query = PaginationQuery {
800 page: 4,
801 per_page: 3,
802 };
803 let result = storage.list_paginated(query).await.unwrap();
804
805 assert_eq!(result.items.len(), 1);
806 assert_eq!(result.total, 10);
807 }
808
809 #[tokio::test]
810 async fn test_plugin_repository_storage_list_paginated_beyond_data() {
811 let storage = PluginRepositoryStorage::new_in_memory();
812
813 for i in 1..=5 {
814 storage
815 .add(create_test_plugin(
816 &format!("plugin{i}"),
817 &format!("/{i}.js"),
818 ))
819 .await
820 .unwrap();
821 }
822
823 let query = PaginationQuery {
824 page: 100,
825 per_page: 10,
826 };
827 let result = storage.list_paginated(query).await.unwrap();
828
829 assert_eq!(result.items.len(), 0);
830 assert_eq!(result.total, 5);
831 }
832
833 #[tokio::test]
834 async fn test_plugin_repository_storage_list_paginated_large_per_page() {
835 let storage = PluginRepositoryStorage::new_in_memory();
836
837 for i in 1..=5 {
838 storage
839 .add(create_test_plugin(
840 &format!("plugin{i}"),
841 &format!("/{i}.js"),
842 ))
843 .await
844 .unwrap();
845 }
846
847 let query = PaginationQuery {
848 page: 1,
849 per_page: 100,
850 };
851 let result = storage.list_paginated(query).await.unwrap();
852
853 assert_eq!(result.items.len(), 5);
854 assert_eq!(result.total, 5);
855 }
856
857 #[tokio::test]
862 async fn test_store_and_get_compiled_code() {
863 let storage = PluginRepositoryStorage::new_in_memory();
864
865 storage
866 .store_compiled_code("plugin-1", "compiled code", None)
867 .await
868 .unwrap();
869
870 let code = storage.get_compiled_code("plugin-1").await.unwrap();
871 assert_eq!(code, Some("compiled code".to_string()));
872 }
873
874 #[tokio::test]
875 async fn test_get_compiled_code_nonexistent() {
876 let storage = PluginRepositoryStorage::new_in_memory();
877
878 let code = storage.get_compiled_code("nonexistent").await.unwrap();
879 assert_eq!(code, None);
880 }
881
882 #[tokio::test]
883 async fn test_store_compiled_code_with_source_hash() {
884 let storage = PluginRepositoryStorage::new_in_memory();
885
886 storage
887 .store_compiled_code("plugin-1", "code", Some("sha256:abc123"))
888 .await
889 .unwrap();
890
891 let code = storage.get_compiled_code("plugin-1").await.unwrap();
892 assert_eq!(code, Some("code".to_string()));
893
894 let hash = storage.get_source_hash("plugin-1").await.unwrap();
895 assert_eq!(hash, Some("sha256:abc123".to_string()));
896 }
897
898 #[tokio::test]
899 async fn test_store_compiled_code_overwrites() {
900 let storage = PluginRepositoryStorage::new_in_memory();
901
902 storage
903 .store_compiled_code("plugin-1", "old code", Some("old-hash"))
904 .await
905 .unwrap();
906 storage
907 .store_compiled_code("plugin-1", "new code", Some("new-hash"))
908 .await
909 .unwrap();
910
911 let code = storage.get_compiled_code("plugin-1").await.unwrap();
912 assert_eq!(code, Some("new code".to_string()));
913
914 let hash = storage.get_source_hash("plugin-1").await.unwrap();
915 assert_eq!(hash, Some("new-hash".to_string()));
916 }
917
918 #[tokio::test]
919 async fn test_has_compiled_code() {
920 let storage = PluginRepositoryStorage::new_in_memory();
921
922 assert!(!storage.has_compiled_code("plugin-1").await.unwrap());
923
924 storage
925 .store_compiled_code("plugin-1", "code", None)
926 .await
927 .unwrap();
928
929 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
930 assert!(!storage.has_compiled_code("plugin-2").await.unwrap());
931 }
932
933 #[tokio::test]
934 async fn test_invalidate_compiled_code() {
935 let storage = PluginRepositoryStorage::new_in_memory();
936
937 storage
938 .store_compiled_code("plugin-1", "code1", None)
939 .await
940 .unwrap();
941 storage
942 .store_compiled_code("plugin-2", "code2", None)
943 .await
944 .unwrap();
945
946 storage.invalidate_compiled_code("plugin-1").await.unwrap();
947
948 assert!(!storage.has_compiled_code("plugin-1").await.unwrap());
949 assert!(storage.has_compiled_code("plugin-2").await.unwrap());
950 }
951
952 #[tokio::test]
953 async fn test_invalidate_compiled_code_nonexistent() {
954 let storage = PluginRepositoryStorage::new_in_memory();
955
956 let result = storage.invalidate_compiled_code("nonexistent").await;
958 assert!(result.is_ok());
959 }
960
961 #[tokio::test]
962 async fn test_invalidate_all_compiled_code() {
963 let storage = PluginRepositoryStorage::new_in_memory();
964
965 for i in 1..=5 {
966 storage
967 .store_compiled_code(&format!("plugin-{i}"), &format!("code-{i}"), None)
968 .await
969 .unwrap();
970 }
971
972 storage.invalidate_all_compiled_code().await.unwrap();
973
974 for i in 1..=5 {
975 assert!(!storage
976 .has_compiled_code(&format!("plugin-{i}"))
977 .await
978 .unwrap());
979 }
980 }
981
982 #[tokio::test]
983 async fn test_invalidate_all_compiled_code_empty() {
984 let storage = PluginRepositoryStorage::new_in_memory();
985
986 let result = storage.invalidate_all_compiled_code().await;
988 assert!(result.is_ok());
989 }
990
991 #[tokio::test]
992 async fn test_get_source_hash() {
993 let storage = PluginRepositoryStorage::new_in_memory();
994
995 storage
997 .store_compiled_code("plugin-1", "code", None)
998 .await
999 .unwrap();
1000 let hash = storage.get_source_hash("plugin-1").await.unwrap();
1001 assert_eq!(hash, None);
1002
1003 storage
1005 .store_compiled_code("plugin-2", "code", Some("hash123"))
1006 .await
1007 .unwrap();
1008 let hash = storage.get_source_hash("plugin-2").await.unwrap();
1009 assert_eq!(hash, Some("hash123".to_string()));
1010 }
1011
1012 #[tokio::test]
1013 async fn test_get_source_hash_nonexistent() {
1014 let storage = PluginRepositoryStorage::new_in_memory();
1015
1016 let hash = storage.get_source_hash("nonexistent").await.unwrap();
1017 assert_eq!(hash, None);
1018 }
1019
1020 #[tokio::test]
1025 async fn test_compiled_cache_independent_of_plugin_store() {
1026 let storage = PluginRepositoryStorage::new_in_memory();
1027
1028 storage
1030 .store_compiled_code("plugin-1", "code", None)
1031 .await
1032 .unwrap();
1033
1034 assert!(storage.get_by_id("plugin-1").await.unwrap().is_none());
1036
1037 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
1039 }
1040
1041 #[tokio::test]
1042 async fn test_drop_all_does_not_clear_compiled_cache() {
1043 let storage = PluginRepositoryStorage::new_in_memory();
1044
1045 storage
1046 .add(create_test_plugin("plugin-1", "/path.js"))
1047 .await
1048 .unwrap();
1049 storage
1050 .store_compiled_code("plugin-1", "code", None)
1051 .await
1052 .unwrap();
1053
1054 storage.drop_all_entries().await.unwrap();
1055
1056 assert!(storage.get_by_id("plugin-1").await.unwrap().is_none());
1058
1059 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
1061 }
1062
1063 #[tokio::test]
1064 async fn test_invalidate_all_compiled_does_not_clear_store() {
1065 let storage = PluginRepositoryStorage::new_in_memory();
1066
1067 storage
1068 .add(create_test_plugin("plugin-1", "/path.js"))
1069 .await
1070 .unwrap();
1071 storage
1072 .store_compiled_code("plugin-1", "code", None)
1073 .await
1074 .unwrap();
1075
1076 storage.invalidate_all_compiled_code().await.unwrap();
1077
1078 assert!(!storage.has_compiled_code("plugin-1").await.unwrap());
1080
1081 assert!(storage.get_by_id("plugin-1").await.unwrap().is_some());
1083 }
1084
1085 #[tokio::test]
1090 async fn test_plugin_repository_storage_workflow() {
1091 let storage = PluginRepositoryStorage::new_in_memory();
1092
1093 assert!(!storage.has_entries().await.unwrap());
1095 assert_eq!(storage.count().await.unwrap(), 0);
1096
1097 let plugin1 = create_test_plugin("auth-plugin", "/scripts/auth.js");
1099 let plugin2 = create_test_plugin("email-plugin", "/scripts/email.js");
1100
1101 storage.add(plugin1.clone()).await.unwrap();
1102 storage.add(plugin2.clone()).await.unwrap();
1103
1104 assert!(storage.has_entries().await.unwrap());
1106 assert_eq!(storage.count().await.unwrap(), 2);
1107
1108 let retrieved = storage.get_by_id("auth-plugin").await.unwrap();
1110 assert_eq!(retrieved, Some(plugin1));
1111
1112 let mut updated = create_test_plugin("auth-plugin", "/scripts/auth_v2.js");
1114 updated.emit_logs = true;
1115 storage.update(updated).await.unwrap();
1116
1117 let after_update = storage.get_by_id("auth-plugin").await.unwrap().unwrap();
1118 assert_eq!(after_update.path, "/scripts/auth_v2.js");
1119 assert!(after_update.emit_logs);
1120
1121 let query = PaginationQuery {
1123 page: 1,
1124 per_page: 10,
1125 };
1126 let result = storage.list_paginated(query).await.unwrap();
1127 assert_eq!(result.items.len(), 2);
1128 assert_eq!(result.total, 2);
1129
1130 storage.drop_all_entries().await.unwrap();
1132 assert!(!storage.has_entries().await.unwrap());
1133 assert_eq!(storage.count().await.unwrap(), 0);
1134 }
1135
1136 #[tokio::test]
1137 async fn test_compiled_code_workflow() {
1138 let storage = PluginRepositoryStorage::new_in_memory();
1139
1140 storage
1142 .add(create_test_plugin("my-plugin", "/scripts/plugin.js"))
1143 .await
1144 .unwrap();
1145
1146 assert!(!storage.has_compiled_code("my-plugin").await.unwrap());
1148
1149 storage
1151 .store_compiled_code("my-plugin", "compiled JS", Some("hash-v1"))
1152 .await
1153 .unwrap();
1154
1155 assert!(storage.has_compiled_code("my-plugin").await.unwrap());
1157 assert_eq!(
1158 storage.get_compiled_code("my-plugin").await.unwrap(),
1159 Some("compiled JS".to_string())
1160 );
1161 assert_eq!(
1162 storage.get_source_hash("my-plugin").await.unwrap(),
1163 Some("hash-v1".to_string())
1164 );
1165
1166 storage
1168 .store_compiled_code("my-plugin", "updated JS", Some("hash-v2"))
1169 .await
1170 .unwrap();
1171
1172 assert_eq!(
1173 storage.get_compiled_code("my-plugin").await.unwrap(),
1174 Some("updated JS".to_string())
1175 );
1176 assert_eq!(
1177 storage.get_source_hash("my-plugin").await.unwrap(),
1178 Some("hash-v2".to_string())
1179 );
1180
1181 storage.invalidate_compiled_code("my-plugin").await.unwrap();
1183
1184 assert!(!storage.has_compiled_code("my-plugin").await.unwrap());
1185 assert_eq!(storage.get_compiled_code("my-plugin").await.unwrap(), None);
1186 }
1187
1188 #[tokio::test]
1189 async fn test_multiple_plugins_compiled_code() {
1190 let storage = PluginRepositoryStorage::new_in_memory();
1191
1192 for i in 1..=5 {
1194 storage
1195 .store_compiled_code(
1196 &format!("plugin-{i}"),
1197 &format!("code for plugin {i}"),
1198 Some(&format!("hash-{i}")),
1199 )
1200 .await
1201 .unwrap();
1202 }
1203
1204 for i in 1..=5 {
1206 assert!(storage
1207 .has_compiled_code(&format!("plugin-{i}"))
1208 .await
1209 .unwrap());
1210 assert_eq!(
1211 storage
1212 .get_compiled_code(&format!("plugin-{i}"))
1213 .await
1214 .unwrap(),
1215 Some(format!("code for plugin {i}"))
1216 );
1217 assert_eq!(
1218 storage
1219 .get_source_hash(&format!("plugin-{i}"))
1220 .await
1221 .unwrap(),
1222 Some(format!("hash-{i}"))
1223 );
1224 }
1225
1226 storage.invalidate_compiled_code("plugin-3").await.unwrap();
1228
1229 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
1231 assert!(storage.has_compiled_code("plugin-2").await.unwrap());
1232 assert!(!storage.has_compiled_code("plugin-3").await.unwrap());
1233 assert!(storage.has_compiled_code("plugin-4").await.unwrap());
1234 assert!(storage.has_compiled_code("plugin-5").await.unwrap());
1235 }
1236}