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/{}.js", i));
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(
639 &format!("p{}", i),
640 &format!("/{}.js", i),
641 ))
642 .await
643 .unwrap();
644 }
645
646 assert_eq!(storage.count().await.unwrap(), 5);
647
648 storage.drop_all_entries().await.unwrap();
649
650 assert_eq!(storage.count().await.unwrap(), 0);
651 }
652
653 #[tokio::test]
658 async fn test_plugin_repository_storage_has_entries_empty() {
659 let storage = PluginRepositoryStorage::new_in_memory();
660
661 let has_entries = storage.has_entries().await.unwrap();
662 assert!(!has_entries);
663 }
664
665 #[tokio::test]
666 async fn test_plugin_repository_storage_has_entries_with_plugins() {
667 let storage = PluginRepositoryStorage::new_in_memory();
668
669 storage
670 .add(create_test_plugin("plugin1", "/path/1.js"))
671 .await
672 .unwrap();
673
674 let has_entries = storage.has_entries().await.unwrap();
675 assert!(has_entries);
676 }
677
678 #[tokio::test]
683 async fn test_plugin_repository_storage_drop_all_entries_empty() {
684 let storage = PluginRepositoryStorage::new_in_memory();
685
686 let result = storage.drop_all_entries().await;
687 assert!(result.is_ok());
688
689 let count = storage.count().await.unwrap();
690 assert_eq!(count, 0);
691 }
692
693 #[tokio::test]
694 async fn test_plugin_repository_storage_drop_all_entries_with_plugins() {
695 let storage = PluginRepositoryStorage::new_in_memory();
696
697 storage
698 .add(create_test_plugin("plugin1", "/path/1.js"))
699 .await
700 .unwrap();
701 storage
702 .add(create_test_plugin("plugin2", "/path/2.js"))
703 .await
704 .unwrap();
705
706 let result = storage.drop_all_entries().await;
707 assert!(result.is_ok());
708
709 let count = storage.count().await.unwrap();
710 assert_eq!(count, 0);
711
712 let has_entries = storage.has_entries().await.unwrap();
713 assert!(!has_entries);
714 }
715
716 #[tokio::test]
721 async fn test_plugin_repository_storage_list_paginated_empty() {
722 let storage = PluginRepositoryStorage::new_in_memory();
723
724 let query = PaginationQuery {
725 page: 1,
726 per_page: 10,
727 };
728 let result = storage.list_paginated(query).await.unwrap();
729
730 assert_eq!(result.items.len(), 0);
731 assert_eq!(result.total, 0);
732 assert_eq!(result.page, 1);
733 assert_eq!(result.per_page, 10);
734 }
735
736 #[tokio::test]
737 async fn test_plugin_repository_storage_list_paginated_first_page() {
738 let storage = PluginRepositoryStorage::new_in_memory();
739
740 for i in 1..=10 {
741 storage
742 .add(create_test_plugin(
743 &format!("plugin{}", i),
744 &format!("/{}.js", i),
745 ))
746 .await
747 .unwrap();
748 }
749
750 let query = PaginationQuery {
751 page: 1,
752 per_page: 3,
753 };
754 let result = storage.list_paginated(query).await.unwrap();
755
756 assert_eq!(result.items.len(), 3);
757 assert_eq!(result.total, 10);
758 assert_eq!(result.page, 1);
759 assert_eq!(result.per_page, 3);
760 }
761
762 #[tokio::test]
763 async fn test_plugin_repository_storage_list_paginated_middle_page() {
764 let storage = PluginRepositoryStorage::new_in_memory();
765
766 for i in 1..=10 {
767 storage
768 .add(create_test_plugin(
769 &format!("plugin{}", i),
770 &format!("/{}.js", i),
771 ))
772 .await
773 .unwrap();
774 }
775
776 let query = PaginationQuery {
777 page: 2,
778 per_page: 3,
779 };
780 let result = storage.list_paginated(query).await.unwrap();
781
782 assert_eq!(result.items.len(), 3);
783 assert_eq!(result.total, 10);
784 assert_eq!(result.page, 2);
785 }
786
787 #[tokio::test]
788 async fn test_plugin_repository_storage_list_paginated_last_partial_page() {
789 let storage = PluginRepositoryStorage::new_in_memory();
790
791 for i in 1..=10 {
792 storage
793 .add(create_test_plugin(
794 &format!("plugin{}", i),
795 &format!("/{}.js", i),
796 ))
797 .await
798 .unwrap();
799 }
800
801 let query = PaginationQuery {
803 page: 4,
804 per_page: 3,
805 };
806 let result = storage.list_paginated(query).await.unwrap();
807
808 assert_eq!(result.items.len(), 1);
809 assert_eq!(result.total, 10);
810 }
811
812 #[tokio::test]
813 async fn test_plugin_repository_storage_list_paginated_beyond_data() {
814 let storage = PluginRepositoryStorage::new_in_memory();
815
816 for i in 1..=5 {
817 storage
818 .add(create_test_plugin(
819 &format!("plugin{}", i),
820 &format!("/{}.js", i),
821 ))
822 .await
823 .unwrap();
824 }
825
826 let query = PaginationQuery {
827 page: 100,
828 per_page: 10,
829 };
830 let result = storage.list_paginated(query).await.unwrap();
831
832 assert_eq!(result.items.len(), 0);
833 assert_eq!(result.total, 5);
834 }
835
836 #[tokio::test]
837 async fn test_plugin_repository_storage_list_paginated_large_per_page() {
838 let storage = PluginRepositoryStorage::new_in_memory();
839
840 for i in 1..=5 {
841 storage
842 .add(create_test_plugin(
843 &format!("plugin{}", i),
844 &format!("/{}.js", i),
845 ))
846 .await
847 .unwrap();
848 }
849
850 let query = PaginationQuery {
851 page: 1,
852 per_page: 100,
853 };
854 let result = storage.list_paginated(query).await.unwrap();
855
856 assert_eq!(result.items.len(), 5);
857 assert_eq!(result.total, 5);
858 }
859
860 #[tokio::test]
865 async fn test_store_and_get_compiled_code() {
866 let storage = PluginRepositoryStorage::new_in_memory();
867
868 storage
869 .store_compiled_code("plugin-1", "compiled code", None)
870 .await
871 .unwrap();
872
873 let code = storage.get_compiled_code("plugin-1").await.unwrap();
874 assert_eq!(code, Some("compiled code".to_string()));
875 }
876
877 #[tokio::test]
878 async fn test_get_compiled_code_nonexistent() {
879 let storage = PluginRepositoryStorage::new_in_memory();
880
881 let code = storage.get_compiled_code("nonexistent").await.unwrap();
882 assert_eq!(code, None);
883 }
884
885 #[tokio::test]
886 async fn test_store_compiled_code_with_source_hash() {
887 let storage = PluginRepositoryStorage::new_in_memory();
888
889 storage
890 .store_compiled_code("plugin-1", "code", Some("sha256:abc123"))
891 .await
892 .unwrap();
893
894 let code = storage.get_compiled_code("plugin-1").await.unwrap();
895 assert_eq!(code, Some("code".to_string()));
896
897 let hash = storage.get_source_hash("plugin-1").await.unwrap();
898 assert_eq!(hash, Some("sha256:abc123".to_string()));
899 }
900
901 #[tokio::test]
902 async fn test_store_compiled_code_overwrites() {
903 let storage = PluginRepositoryStorage::new_in_memory();
904
905 storage
906 .store_compiled_code("plugin-1", "old code", Some("old-hash"))
907 .await
908 .unwrap();
909 storage
910 .store_compiled_code("plugin-1", "new code", Some("new-hash"))
911 .await
912 .unwrap();
913
914 let code = storage.get_compiled_code("plugin-1").await.unwrap();
915 assert_eq!(code, Some("new code".to_string()));
916
917 let hash = storage.get_source_hash("plugin-1").await.unwrap();
918 assert_eq!(hash, Some("new-hash".to_string()));
919 }
920
921 #[tokio::test]
922 async fn test_has_compiled_code() {
923 let storage = PluginRepositoryStorage::new_in_memory();
924
925 assert!(!storage.has_compiled_code("plugin-1").await.unwrap());
926
927 storage
928 .store_compiled_code("plugin-1", "code", None)
929 .await
930 .unwrap();
931
932 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
933 assert!(!storage.has_compiled_code("plugin-2").await.unwrap());
934 }
935
936 #[tokio::test]
937 async fn test_invalidate_compiled_code() {
938 let storage = PluginRepositoryStorage::new_in_memory();
939
940 storage
941 .store_compiled_code("plugin-1", "code1", None)
942 .await
943 .unwrap();
944 storage
945 .store_compiled_code("plugin-2", "code2", None)
946 .await
947 .unwrap();
948
949 storage.invalidate_compiled_code("plugin-1").await.unwrap();
950
951 assert!(!storage.has_compiled_code("plugin-1").await.unwrap());
952 assert!(storage.has_compiled_code("plugin-2").await.unwrap());
953 }
954
955 #[tokio::test]
956 async fn test_invalidate_compiled_code_nonexistent() {
957 let storage = PluginRepositoryStorage::new_in_memory();
958
959 let result = storage.invalidate_compiled_code("nonexistent").await;
961 assert!(result.is_ok());
962 }
963
964 #[tokio::test]
965 async fn test_invalidate_all_compiled_code() {
966 let storage = PluginRepositoryStorage::new_in_memory();
967
968 for i in 1..=5 {
969 storage
970 .store_compiled_code(&format!("plugin-{}", i), &format!("code-{}", i), None)
971 .await
972 .unwrap();
973 }
974
975 storage.invalidate_all_compiled_code().await.unwrap();
976
977 for i in 1..=5 {
978 assert!(!storage
979 .has_compiled_code(&format!("plugin-{}", i))
980 .await
981 .unwrap());
982 }
983 }
984
985 #[tokio::test]
986 async fn test_invalidate_all_compiled_code_empty() {
987 let storage = PluginRepositoryStorage::new_in_memory();
988
989 let result = storage.invalidate_all_compiled_code().await;
991 assert!(result.is_ok());
992 }
993
994 #[tokio::test]
995 async fn test_get_source_hash() {
996 let storage = PluginRepositoryStorage::new_in_memory();
997
998 storage
1000 .store_compiled_code("plugin-1", "code", None)
1001 .await
1002 .unwrap();
1003 let hash = storage.get_source_hash("plugin-1").await.unwrap();
1004 assert_eq!(hash, None);
1005
1006 storage
1008 .store_compiled_code("plugin-2", "code", Some("hash123"))
1009 .await
1010 .unwrap();
1011 let hash = storage.get_source_hash("plugin-2").await.unwrap();
1012 assert_eq!(hash, Some("hash123".to_string()));
1013 }
1014
1015 #[tokio::test]
1016 async fn test_get_source_hash_nonexistent() {
1017 let storage = PluginRepositoryStorage::new_in_memory();
1018
1019 let hash = storage.get_source_hash("nonexistent").await.unwrap();
1020 assert_eq!(hash, None);
1021 }
1022
1023 #[tokio::test]
1028 async fn test_compiled_cache_independent_of_plugin_store() {
1029 let storage = PluginRepositoryStorage::new_in_memory();
1030
1031 storage
1033 .store_compiled_code("plugin-1", "code", None)
1034 .await
1035 .unwrap();
1036
1037 assert!(storage.get_by_id("plugin-1").await.unwrap().is_none());
1039
1040 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
1042 }
1043
1044 #[tokio::test]
1045 async fn test_drop_all_does_not_clear_compiled_cache() {
1046 let storage = PluginRepositoryStorage::new_in_memory();
1047
1048 storage
1049 .add(create_test_plugin("plugin-1", "/path.js"))
1050 .await
1051 .unwrap();
1052 storage
1053 .store_compiled_code("plugin-1", "code", None)
1054 .await
1055 .unwrap();
1056
1057 storage.drop_all_entries().await.unwrap();
1058
1059 assert!(storage.get_by_id("plugin-1").await.unwrap().is_none());
1061
1062 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
1064 }
1065
1066 #[tokio::test]
1067 async fn test_invalidate_all_compiled_does_not_clear_store() {
1068 let storage = PluginRepositoryStorage::new_in_memory();
1069
1070 storage
1071 .add(create_test_plugin("plugin-1", "/path.js"))
1072 .await
1073 .unwrap();
1074 storage
1075 .store_compiled_code("plugin-1", "code", None)
1076 .await
1077 .unwrap();
1078
1079 storage.invalidate_all_compiled_code().await.unwrap();
1080
1081 assert!(!storage.has_compiled_code("plugin-1").await.unwrap());
1083
1084 assert!(storage.get_by_id("plugin-1").await.unwrap().is_some());
1086 }
1087
1088 #[tokio::test]
1093 async fn test_plugin_repository_storage_workflow() {
1094 let storage = PluginRepositoryStorage::new_in_memory();
1095
1096 assert!(!storage.has_entries().await.unwrap());
1098 assert_eq!(storage.count().await.unwrap(), 0);
1099
1100 let plugin1 = create_test_plugin("auth-plugin", "/scripts/auth.js");
1102 let plugin2 = create_test_plugin("email-plugin", "/scripts/email.js");
1103
1104 storage.add(plugin1.clone()).await.unwrap();
1105 storage.add(plugin2.clone()).await.unwrap();
1106
1107 assert!(storage.has_entries().await.unwrap());
1109 assert_eq!(storage.count().await.unwrap(), 2);
1110
1111 let retrieved = storage.get_by_id("auth-plugin").await.unwrap();
1113 assert_eq!(retrieved, Some(plugin1));
1114
1115 let mut updated = create_test_plugin("auth-plugin", "/scripts/auth_v2.js");
1117 updated.emit_logs = true;
1118 storage.update(updated).await.unwrap();
1119
1120 let after_update = storage.get_by_id("auth-plugin").await.unwrap().unwrap();
1121 assert_eq!(after_update.path, "/scripts/auth_v2.js");
1122 assert!(after_update.emit_logs);
1123
1124 let query = PaginationQuery {
1126 page: 1,
1127 per_page: 10,
1128 };
1129 let result = storage.list_paginated(query).await.unwrap();
1130 assert_eq!(result.items.len(), 2);
1131 assert_eq!(result.total, 2);
1132
1133 storage.drop_all_entries().await.unwrap();
1135 assert!(!storage.has_entries().await.unwrap());
1136 assert_eq!(storage.count().await.unwrap(), 0);
1137 }
1138
1139 #[tokio::test]
1140 async fn test_compiled_code_workflow() {
1141 let storage = PluginRepositoryStorage::new_in_memory();
1142
1143 storage
1145 .add(create_test_plugin("my-plugin", "/scripts/plugin.js"))
1146 .await
1147 .unwrap();
1148
1149 assert!(!storage.has_compiled_code("my-plugin").await.unwrap());
1151
1152 storage
1154 .store_compiled_code("my-plugin", "compiled JS", Some("hash-v1"))
1155 .await
1156 .unwrap();
1157
1158 assert!(storage.has_compiled_code("my-plugin").await.unwrap());
1160 assert_eq!(
1161 storage.get_compiled_code("my-plugin").await.unwrap(),
1162 Some("compiled JS".to_string())
1163 );
1164 assert_eq!(
1165 storage.get_source_hash("my-plugin").await.unwrap(),
1166 Some("hash-v1".to_string())
1167 );
1168
1169 storage
1171 .store_compiled_code("my-plugin", "updated JS", Some("hash-v2"))
1172 .await
1173 .unwrap();
1174
1175 assert_eq!(
1176 storage.get_compiled_code("my-plugin").await.unwrap(),
1177 Some("updated JS".to_string())
1178 );
1179 assert_eq!(
1180 storage.get_source_hash("my-plugin").await.unwrap(),
1181 Some("hash-v2".to_string())
1182 );
1183
1184 storage.invalidate_compiled_code("my-plugin").await.unwrap();
1186
1187 assert!(!storage.has_compiled_code("my-plugin").await.unwrap());
1188 assert_eq!(storage.get_compiled_code("my-plugin").await.unwrap(), None);
1189 }
1190
1191 #[tokio::test]
1192 async fn test_multiple_plugins_compiled_code() {
1193 let storage = PluginRepositoryStorage::new_in_memory();
1194
1195 for i in 1..=5 {
1197 storage
1198 .store_compiled_code(
1199 &format!("plugin-{}", i),
1200 &format!("code for plugin {}", i),
1201 Some(&format!("hash-{}", i)),
1202 )
1203 .await
1204 .unwrap();
1205 }
1206
1207 for i in 1..=5 {
1209 assert!(storage
1210 .has_compiled_code(&format!("plugin-{}", i))
1211 .await
1212 .unwrap());
1213 assert_eq!(
1214 storage
1215 .get_compiled_code(&format!("plugin-{}", i))
1216 .await
1217 .unwrap(),
1218 Some(format!("code for plugin {}", i))
1219 );
1220 assert_eq!(
1221 storage
1222 .get_source_hash(&format!("plugin-{}", i))
1223 .await
1224 .unwrap(),
1225 Some(format!("hash-{}", i))
1226 );
1227 }
1228
1229 storage.invalidate_compiled_code("plugin-3").await.unwrap();
1231
1232 assert!(storage.has_compiled_code("plugin-1").await.unwrap());
1234 assert!(storage.has_compiled_code("plugin-2").await.unwrap());
1235 assert!(!storage.has_compiled_code("plugin-3").await.unwrap());
1236 assert!(storage.has_compiled_code("plugin-4").await.unwrap());
1237 assert!(storage.has_compiled_code("plugin-5").await.unwrap());
1238 }
1239}