1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use fakecloud_aws::arn::Arn;
5use parking_lot::RwLock;
6
7pub type SharedElastiCacheState = Arc<RwLock<ElastiCacheState>>;
8
9#[derive(Debug, Clone)]
10pub struct CacheEngineVersion {
11 pub engine: String,
12 pub engine_version: String,
13 pub cache_parameter_group_family: String,
14 pub cache_engine_description: String,
15 pub cache_engine_version_description: String,
16}
17
18#[derive(Debug, Clone)]
19pub struct CacheParameterGroup {
20 pub cache_parameter_group_name: String,
21 pub cache_parameter_group_family: String,
22 pub description: String,
23 pub is_global: bool,
24 pub arn: String,
25}
26
27#[derive(Debug, Clone)]
28pub struct EngineDefaultParameter {
29 pub parameter_name: String,
30 pub parameter_value: String,
31 pub description: String,
32 pub source: String,
33 pub data_type: String,
34 pub allowed_values: String,
35 pub is_modifiable: bool,
36 pub minimum_engine_version: String,
37}
38
39#[derive(Debug, Clone)]
40pub struct CacheSubnetGroup {
41 pub cache_subnet_group_name: String,
42 pub cache_subnet_group_description: String,
43 pub vpc_id: String,
44 pub subnet_ids: Vec<String>,
45 pub arn: String,
46}
47
48#[derive(Debug, Clone)]
49pub struct RecurringCharge {
50 pub recurring_charge_amount: f64,
51 pub recurring_charge_frequency: String,
52}
53
54#[derive(Debug, Clone)]
55pub struct ReservedCacheNode {
56 pub reserved_cache_node_id: String,
57 pub reserved_cache_nodes_offering_id: String,
58 pub cache_node_type: String,
59 pub start_time: String,
60 pub duration: i32,
61 pub fixed_price: f64,
62 pub usage_price: f64,
63 pub cache_node_count: i32,
64 pub product_description: String,
65 pub offering_type: String,
66 pub state: String,
67 pub recurring_charges: Vec<RecurringCharge>,
68 pub reservation_arn: String,
69}
70
71#[derive(Debug, Clone)]
72pub struct ReservedCacheNodesOffering {
73 pub reserved_cache_nodes_offering_id: String,
74 pub cache_node_type: String,
75 pub duration: i32,
76 pub fixed_price: f64,
77 pub usage_price: f64,
78 pub product_description: String,
79 pub offering_type: String,
80 pub recurring_charges: Vec<RecurringCharge>,
81}
82
83#[derive(Debug, Clone)]
84pub struct CacheCluster {
85 pub cache_cluster_id: String,
86 pub cache_node_type: String,
87 pub engine: String,
88 pub engine_version: String,
89 pub cache_cluster_status: String,
90 pub num_cache_nodes: i32,
91 pub preferred_availability_zone: String,
92 pub cache_subnet_group_name: Option<String>,
93 pub auto_minor_version_upgrade: bool,
94 pub arn: String,
95 pub created_at: String,
96 pub endpoint_address: String,
97 pub endpoint_port: u16,
98 pub container_id: String,
99 pub host_port: u16,
100 pub replication_group_id: Option<String>,
101}
102
103#[derive(Debug, Clone)]
104pub struct ReplicationGroup {
105 pub replication_group_id: String,
106 pub description: String,
107 pub global_replication_group_id: Option<String>,
108 pub global_replication_group_role: Option<String>,
109 pub status: String,
110 pub cache_node_type: String,
111 pub engine: String,
112 pub engine_version: String,
113 pub num_cache_clusters: i32,
114 pub automatic_failover_enabled: bool,
115 pub endpoint_address: String,
116 pub endpoint_port: u16,
117 pub arn: String,
118 pub created_at: String,
119 pub container_id: String,
120 pub host_port: u16,
121 pub member_clusters: Vec<String>,
122 pub snapshot_retention_limit: i32,
123 pub snapshot_window: String,
124}
125
126#[derive(Debug, Clone)]
127pub struct GlobalReplicationGroupMember {
128 pub replication_group_id: String,
129 pub replication_group_region: String,
130 pub role: String,
131 pub automatic_failover: bool,
132 pub status: String,
133}
134
135#[derive(Debug, Clone)]
136pub struct GlobalReplicationGroup {
137 pub global_replication_group_id: String,
138 pub global_replication_group_description: String,
139 pub status: String,
140 pub cache_node_type: String,
141 pub engine: String,
142 pub engine_version: String,
143 pub members: Vec<GlobalReplicationGroupMember>,
144 pub cluster_enabled: bool,
145 pub arn: String,
146}
147
148#[derive(Debug, Clone)]
149pub struct ElastiCacheUser {
150 pub user_id: String,
151 pub user_name: String,
152 pub engine: String,
153 pub access_string: String,
154 pub status: String,
155 pub authentication_type: String,
156 pub password_count: i32,
157 pub arn: String,
158 pub minimum_engine_version: String,
159 pub user_group_ids: Vec<String>,
160}
161
162#[derive(Debug, Clone)]
163pub struct ElastiCacheUserGroup {
164 pub user_group_id: String,
165 pub engine: String,
166 pub status: String,
167 pub user_ids: Vec<String>,
168 pub arn: String,
169 pub minimum_engine_version: String,
170 pub pending_changes: Option<UserGroupPendingChanges>,
171 pub replication_groups: Vec<String>,
172}
173
174#[derive(Debug, Clone)]
175pub struct UserGroupPendingChanges {
176 pub user_ids_to_add: Vec<String>,
177 pub user_ids_to_remove: Vec<String>,
178}
179
180#[derive(Debug, Clone)]
181pub struct CacheSnapshot {
182 pub snapshot_name: String,
183 pub replication_group_id: String,
184 pub replication_group_description: String,
185 pub snapshot_status: String,
186 pub cache_node_type: String,
187 pub engine: String,
188 pub engine_version: String,
189 pub num_cache_clusters: i32,
190 pub arn: String,
191 pub created_at: String,
192 pub snapshot_source: String,
193}
194
195#[derive(Debug, Clone, Default)]
196pub struct ServerlessCacheUsageLimits {
197 pub data_storage: Option<ServerlessCacheDataStorage>,
198 pub ecpu_per_second: Option<ServerlessCacheEcpuPerSecond>,
199}
200
201#[derive(Debug, Clone)]
202pub struct ServerlessCacheDataStorage {
203 pub maximum: Option<i32>,
204 pub minimum: Option<i32>,
205 pub unit: Option<String>,
206}
207
208#[derive(Debug, Clone)]
209pub struct ServerlessCacheEcpuPerSecond {
210 pub maximum: Option<i32>,
211 pub minimum: Option<i32>,
212}
213
214#[derive(Debug, Clone)]
215pub struct ServerlessCacheEndpoint {
216 pub address: String,
217 pub port: u16,
218}
219
220#[derive(Debug, Clone)]
221pub struct ServerlessCache {
222 pub serverless_cache_name: String,
223 pub description: String,
224 pub engine: String,
225 pub major_engine_version: String,
226 pub full_engine_version: String,
227 pub status: String,
228 pub endpoint: ServerlessCacheEndpoint,
229 pub reader_endpoint: ServerlessCacheEndpoint,
230 pub arn: String,
231 pub created_at: String,
232 pub cache_usage_limits: Option<ServerlessCacheUsageLimits>,
233 pub security_group_ids: Vec<String>,
234 pub subnet_ids: Vec<String>,
235 pub kms_key_id: Option<String>,
236 pub user_group_id: Option<String>,
237 pub snapshot_retention_limit: Option<i32>,
238 pub daily_snapshot_time: Option<String>,
239 pub container_id: String,
240 pub host_port: u16,
241}
242
243#[derive(Debug, Clone)]
244pub struct ServerlessCacheSnapshot {
245 pub serverless_cache_snapshot_name: String,
246 pub arn: String,
247 pub kms_key_id: Option<String>,
248 pub snapshot_type: String,
249 pub status: String,
250 pub create_time: String,
251 pub expiry_time: Option<String>,
252 pub bytes_used_for_cache: Option<String>,
253 pub serverless_cache_name: String,
254 pub engine: String,
255 pub major_engine_version: String,
256}
257
258#[derive(Debug)]
259pub struct ElastiCacheState {
260 pub account_id: String,
261 pub region: String,
262 pub parameter_groups: Vec<CacheParameterGroup>,
263 pub subnet_groups: HashMap<String, CacheSubnetGroup>,
264 pub reserved_cache_nodes: HashMap<String, ReservedCacheNode>,
265 pub reserved_cache_nodes_offerings: Vec<ReservedCacheNodesOffering>,
266 pub cache_clusters: HashMap<String, CacheCluster>,
267 pub replication_groups: HashMap<String, ReplicationGroup>,
268 pub global_replication_groups: HashMap<String, GlobalReplicationGroup>,
269 pub users: HashMap<String, ElastiCacheUser>,
270 pub user_groups: HashMap<String, ElastiCacheUserGroup>,
271 pub snapshots: HashMap<String, CacheSnapshot>,
272 pub serverless_caches: HashMap<String, ServerlessCache>,
273 pub serverless_cache_snapshots: HashMap<String, ServerlessCacheSnapshot>,
274 pub tags: HashMap<String, Vec<(String, String)>>,
275 in_progress_cache_cluster_ids: HashSet<String>,
276 in_progress_replication_group_ids: HashSet<String>,
277 in_progress_serverless_cache_names: HashSet<String>,
278}
279
280impl ElastiCacheState {
281 pub fn new(account_id: &str, region: &str) -> Self {
282 let parameter_groups = default_parameter_groups(account_id, region);
283 let subnet_groups = default_subnet_groups(account_id, region);
284 let users = default_users(account_id, region);
285 let mut tags: HashMap<String, Vec<(String, String)>> = subnet_groups
286 .values()
287 .map(|g| (g.arn.clone(), Vec::new()))
288 .collect();
289 for user in users.values() {
290 tags.insert(user.arn.clone(), Vec::new());
291 }
292 Self {
293 account_id: account_id.to_string(),
294 region: region.to_string(),
295 parameter_groups,
296 subnet_groups,
297 reserved_cache_nodes: HashMap::new(),
298 reserved_cache_nodes_offerings: default_reserved_cache_nodes_offerings(),
299 cache_clusters: HashMap::new(),
300 replication_groups: HashMap::new(),
301 global_replication_groups: HashMap::new(),
302 users,
303 user_groups: HashMap::new(),
304 snapshots: HashMap::new(),
305 serverless_caches: HashMap::new(),
306 serverless_cache_snapshots: HashMap::new(),
307 tags,
308 in_progress_cache_cluster_ids: HashSet::new(),
309 in_progress_replication_group_ids: HashSet::new(),
310 in_progress_serverless_cache_names: HashSet::new(),
311 }
312 }
313
314 pub fn reset(&mut self) {
315 self.parameter_groups = default_parameter_groups(&self.account_id, &self.region);
316 self.subnet_groups = default_subnet_groups(&self.account_id, &self.region);
317 self.reserved_cache_nodes.clear();
318 self.reserved_cache_nodes_offerings = default_reserved_cache_nodes_offerings();
319 self.cache_clusters.clear();
320 self.replication_groups.clear();
321 self.global_replication_groups.clear();
322 self.users = default_users(&self.account_id, &self.region);
323 self.user_groups.clear();
324 self.snapshots.clear();
325 self.serverless_caches.clear();
326 self.serverless_cache_snapshots.clear();
327 self.tags.clear();
328 for g in self.subnet_groups.values() {
329 self.tags.insert(g.arn.clone(), Vec::new());
330 }
331 for user in self.users.values() {
332 self.tags.insert(user.arn.clone(), Vec::new());
333 }
334 self.in_progress_cache_cluster_ids.clear();
335 self.in_progress_replication_group_ids.clear();
336 self.in_progress_serverless_cache_names.clear();
337 }
338
339 pub fn begin_cache_cluster_creation(&mut self, cache_cluster_id: &str) -> bool {
340 if self.cache_clusters.contains_key(cache_cluster_id)
341 || self
342 .in_progress_cache_cluster_ids
343 .contains(cache_cluster_id)
344 {
345 return false;
346 }
347 self.in_progress_cache_cluster_ids
348 .insert(cache_cluster_id.to_string());
349 true
350 }
351
352 pub fn finish_cache_cluster_creation(&mut self, cluster: CacheCluster) {
353 self.in_progress_cache_cluster_ids
354 .remove(&cluster.cache_cluster_id);
355 self.tags.insert(cluster.arn.clone(), Vec::new());
356 self.cache_clusters
357 .insert(cluster.cache_cluster_id.clone(), cluster);
358 }
359
360 pub fn cancel_cache_cluster_creation(&mut self, cache_cluster_id: &str) {
361 self.in_progress_cache_cluster_ids.remove(cache_cluster_id);
362 }
363
364 pub fn begin_replication_group_creation(&mut self, replication_group_id: &str) -> bool {
365 if self.replication_groups.contains_key(replication_group_id)
366 || self
367 .in_progress_replication_group_ids
368 .contains(replication_group_id)
369 {
370 return false;
371 }
372 self.in_progress_replication_group_ids
373 .insert(replication_group_id.to_string());
374 true
375 }
376
377 pub fn finish_replication_group_creation(&mut self, group: ReplicationGroup) {
378 self.in_progress_replication_group_ids
379 .remove(&group.replication_group_id);
380 self.tags.insert(group.arn.clone(), Vec::new());
381 self.replication_groups
382 .insert(group.replication_group_id.clone(), group);
383 }
384
385 pub fn cancel_replication_group_creation(&mut self, replication_group_id: &str) {
386 self.in_progress_replication_group_ids
387 .remove(replication_group_id);
388 }
389
390 pub fn begin_serverless_cache_creation(&mut self, serverless_cache_name: &str) -> bool {
391 if self.serverless_caches.contains_key(serverless_cache_name)
392 || self
393 .in_progress_serverless_cache_names
394 .contains(serverless_cache_name)
395 {
396 return false;
397 }
398 self.in_progress_serverless_cache_names
399 .insert(serverless_cache_name.to_string());
400 true
401 }
402
403 pub fn finish_serverless_cache_creation(&mut self, cache: ServerlessCache) {
404 self.in_progress_serverless_cache_names
405 .remove(&cache.serverless_cache_name);
406 self.tags.insert(cache.arn.clone(), Vec::new());
407 self.serverless_caches
408 .insert(cache.serverless_cache_name.clone(), cache);
409 }
410
411 pub fn cancel_serverless_cache_creation(&mut self, serverless_cache_name: &str) {
412 self.in_progress_serverless_cache_names
413 .remove(serverless_cache_name);
414 }
415
416 pub fn register_arn(&mut self, arn: &str) {
417 self.tags.entry(arn.to_string()).or_default();
418 }
419
420 pub fn has_arn(&self, arn: &str) -> bool {
421 self.tags.contains_key(arn)
422 }
423}
424
425fn default_reserved_cache_nodes_offerings() -> Vec<ReservedCacheNodesOffering> {
426 vec![
427 ReservedCacheNodesOffering {
428 reserved_cache_nodes_offering_id: "off-cache-t3-micro-redis-1yr-no-upfront".to_string(),
429 cache_node_type: "cache.t3.micro".to_string(),
430 duration: 31_536_000,
431 fixed_price: 0.0,
432 usage_price: 0.011,
433 product_description: "redis".to_string(),
434 offering_type: "No Upfront".to_string(),
435 recurring_charges: Vec::new(),
436 },
437 ReservedCacheNodesOffering {
438 reserved_cache_nodes_offering_id: "off-cache-t3-small-redis-1yr-partial-upfront"
439 .to_string(),
440 cache_node_type: "cache.t3.small".to_string(),
441 duration: 31_536_000,
442 fixed_price: 120.0,
443 usage_price: 0.007,
444 product_description: "redis".to_string(),
445 offering_type: "Partial Upfront".to_string(),
446 recurring_charges: Vec::new(),
447 },
448 ReservedCacheNodesOffering {
449 reserved_cache_nodes_offering_id: "off-cache-m5-large-memcached-3yr-no-upfront"
450 .to_string(),
451 cache_node_type: "cache.m5.large".to_string(),
452 duration: 94_608_000,
453 fixed_price: 0.0,
454 usage_price: 0.033,
455 product_description: "memcached".to_string(),
456 offering_type: "No Upfront".to_string(),
457 recurring_charges: Vec::new(),
458 },
459 ReservedCacheNodesOffering {
460 reserved_cache_nodes_offering_id: "off-cache-r6g-large-redis-3yr-all-upfront"
461 .to_string(),
462 cache_node_type: "cache.r6g.large".to_string(),
463 duration: 94_608_000,
464 fixed_price: 1_550.0,
465 usage_price: 0.0,
466 product_description: "redis".to_string(),
467 offering_type: "All Upfront".to_string(),
468 recurring_charges: vec![RecurringCharge {
469 recurring_charge_amount: 0.0,
470 recurring_charge_frequency: "Hourly".to_string(),
471 }],
472 },
473 ]
474}
475
476pub fn default_engine_versions() -> Vec<CacheEngineVersion> {
477 vec![
478 CacheEngineVersion {
479 engine: "redis".to_string(),
480 engine_version: "7.1".to_string(),
481 cache_parameter_group_family: "redis7".to_string(),
482 cache_engine_description: "Redis".to_string(),
483 cache_engine_version_description: "Redis 7.1".to_string(),
484 },
485 CacheEngineVersion {
486 engine: "valkey".to_string(),
487 engine_version: "8.0".to_string(),
488 cache_parameter_group_family: "valkey8".to_string(),
489 cache_engine_description: "Valkey".to_string(),
490 cache_engine_version_description: "Valkey 8.0".to_string(),
491 },
492 ]
493}
494
495fn default_parameter_groups(account_id: &str, region: &str) -> Vec<CacheParameterGroup> {
496 vec![
497 CacheParameterGroup {
498 cache_parameter_group_name: "default.redis7".to_string(),
499 cache_parameter_group_family: "redis7".to_string(),
500 description: "Default parameter group for redis7".to_string(),
501 is_global: false,
502 arn: Arn::new(
503 "elasticache",
504 region,
505 account_id,
506 "parametergroup:default.redis7",
507 )
508 .to_string(),
509 },
510 CacheParameterGroup {
511 cache_parameter_group_name: "default.valkey8".to_string(),
512 cache_parameter_group_family: "valkey8".to_string(),
513 description: "Default parameter group for valkey8".to_string(),
514 is_global: false,
515 arn: Arn::new(
516 "elasticache",
517 region,
518 account_id,
519 "parametergroup:default.valkey8",
520 )
521 .to_string(),
522 },
523 ]
524}
525
526fn default_subnet_groups(account_id: &str, region: &str) -> HashMap<String, CacheSubnetGroup> {
527 let default_group = CacheSubnetGroup {
528 cache_subnet_group_name: "default".to_string(),
529 cache_subnet_group_description: "Default CacheSubnetGroup".to_string(),
530 vpc_id: "vpc-00000000".to_string(),
531 subnet_ids: vec!["subnet-00000000".to_string()],
532 arn: Arn::new("elasticache", region, account_id, "subnetgroup:default").to_string(),
533 };
534 let mut map = HashMap::new();
535 map.insert("default".to_string(), default_group);
536 map
537}
538
539pub fn default_parameters_for_family(family: &str) -> Vec<EngineDefaultParameter> {
540 match family {
541 "redis7" => vec![
542 EngineDefaultParameter {
543 parameter_name: "maxmemory-policy".to_string(),
544 parameter_value: "volatile-lru".to_string(),
545 description: "Max memory policy".to_string(),
546 source: "system".to_string(),
547 data_type: "string".to_string(),
548 allowed_values: "volatile-lru,allkeys-lru,volatile-lfu,allkeys-lfu,volatile-random,allkeys-random,volatile-ttl,noeviction".to_string(),
549 is_modifiable: true,
550 minimum_engine_version: "7.0.0".to_string(),
551 },
552 EngineDefaultParameter {
553 parameter_name: "cluster-enabled".to_string(),
554 parameter_value: "no".to_string(),
555 description: "Enable or disable Redis Cluster mode".to_string(),
556 source: "system".to_string(),
557 data_type: "string".to_string(),
558 allowed_values: "yes,no".to_string(),
559 is_modifiable: false,
560 minimum_engine_version: "7.0.0".to_string(),
561 },
562 EngineDefaultParameter {
563 parameter_name: "activedefrag".to_string(),
564 parameter_value: "no".to_string(),
565 description: "Enable active defragmentation".to_string(),
566 source: "system".to_string(),
567 data_type: "string".to_string(),
568 allowed_values: "yes,no".to_string(),
569 is_modifiable: true,
570 minimum_engine_version: "7.0.0".to_string(),
571 },
572 ],
573 "valkey8" => vec![
574 EngineDefaultParameter {
575 parameter_name: "maxmemory-policy".to_string(),
576 parameter_value: "volatile-lru".to_string(),
577 description: "Max memory policy".to_string(),
578 source: "system".to_string(),
579 data_type: "string".to_string(),
580 allowed_values: "volatile-lru,allkeys-lru,volatile-lfu,allkeys-lfu,volatile-random,allkeys-random,volatile-ttl,noeviction".to_string(),
581 is_modifiable: true,
582 minimum_engine_version: "8.0.0".to_string(),
583 },
584 EngineDefaultParameter {
585 parameter_name: "cluster-enabled".to_string(),
586 parameter_value: "no".to_string(),
587 description: "Enable or disable cluster mode".to_string(),
588 source: "system".to_string(),
589 data_type: "string".to_string(),
590 allowed_values: "yes,no".to_string(),
591 is_modifiable: false,
592 minimum_engine_version: "8.0.0".to_string(),
593 },
594 EngineDefaultParameter {
595 parameter_name: "activedefrag".to_string(),
596 parameter_value: "no".to_string(),
597 description: "Enable active defragmentation".to_string(),
598 source: "system".to_string(),
599 data_type: "string".to_string(),
600 allowed_values: "yes,no".to_string(),
601 is_modifiable: true,
602 minimum_engine_version: "8.0.0".to_string(),
603 },
604 ],
605 _ => Vec::new(),
606 }
607}
608
609fn default_users(account_id: &str, region: &str) -> HashMap<String, ElastiCacheUser> {
610 let mut map = HashMap::new();
611 map.insert(
612 "default".to_string(),
613 ElastiCacheUser {
614 user_id: "default".to_string(),
615 user_name: "default".to_string(),
616 engine: "redis".to_string(),
617 access_string: "on ~* +@all".to_string(),
618 status: "active".to_string(),
619 authentication_type: "no-password".to_string(),
620 password_count: 0,
621 arn: Arn::new("elasticache", region, account_id, "user:default").to_string(),
622 minimum_engine_version: "6.0".to_string(),
623 user_group_ids: Vec::new(),
624 },
625 );
626 map
627}
628
629#[cfg(test)]
630mod tests {
631 use super::*;
632
633 #[test]
634 fn default_engine_versions_contains_redis_and_valkey() {
635 let versions = default_engine_versions();
636 assert_eq!(versions.len(), 2);
637 assert_eq!(versions[0].engine, "redis");
638 assert_eq!(versions[0].engine_version, "7.1");
639 assert_eq!(versions[1].engine, "valkey");
640 assert_eq!(versions[1].engine_version, "8.0");
641 }
642
643 #[test]
644 fn state_new_creates_default_parameter_groups() {
645 let state = ElastiCacheState::new("123456789012", "us-east-1");
646 assert_eq!(state.parameter_groups.len(), 2);
647 assert_eq!(
648 state.parameter_groups[0].cache_parameter_group_name,
649 "default.redis7"
650 );
651 assert_eq!(
652 state.parameter_groups[1].cache_parameter_group_name,
653 "default.valkey8"
654 );
655 }
656
657 #[test]
658 fn state_new_creates_default_subnet_group() {
659 let state = ElastiCacheState::new("123456789012", "us-east-1");
660 assert_eq!(state.subnet_groups.len(), 1);
661 let default = state.subnet_groups.get("default").unwrap();
662 assert_eq!(default.cache_subnet_group_name, "default");
663 assert_eq!(
664 default.cache_subnet_group_description,
665 "Default CacheSubnetGroup"
666 );
667 assert_eq!(default.vpc_id, "vpc-00000000");
668 assert!(!default.subnet_ids.is_empty());
669 assert!(default.arn.contains("subnetgroup:default"));
670 }
671
672 #[test]
673 fn reset_restores_default_parameter_groups() {
674 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
675 state.parameter_groups.clear();
676 assert!(state.parameter_groups.is_empty());
677 state.reset();
678 assert_eq!(state.parameter_groups.len(), 2);
679 }
680
681 #[test]
682 fn reset_restores_default_subnet_groups() {
683 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
684 state.subnet_groups.clear();
685 assert!(state.subnet_groups.is_empty());
686 state.reset();
687 assert_eq!(state.subnet_groups.len(), 1);
688 assert!(state.subnet_groups.contains_key("default"));
689 }
690
691 #[test]
692 fn default_parameters_for_redis7_returns_parameters() {
693 let params = default_parameters_for_family("redis7");
694 assert_eq!(params.len(), 3);
695 assert_eq!(params[0].parameter_name, "maxmemory-policy");
696 }
697
698 #[test]
699 fn default_parameters_for_unknown_family_returns_empty() {
700 let params = default_parameters_for_family("unknown");
701 assert!(params.is_empty());
702 }
703
704 #[test]
705 fn state_new_has_empty_replication_groups() {
706 let state = ElastiCacheState::new("123456789012", "us-east-1");
707 assert!(state.replication_groups.is_empty());
708 }
709
710 #[test]
711 fn state_new_has_empty_global_replication_groups() {
712 let state = ElastiCacheState::new("123456789012", "us-east-1");
713 assert!(state.global_replication_groups.is_empty());
714 }
715
716 #[test]
717 fn state_new_has_empty_cache_clusters() {
718 let state = ElastiCacheState::new("123456789012", "us-east-1");
719 assert!(state.cache_clusters.is_empty());
720 }
721
722 #[test]
723 fn state_new_has_empty_serverless_caches() {
724 let state = ElastiCacheState::new("123456789012", "us-east-1");
725 assert!(state.serverless_caches.is_empty());
726 assert!(state.serverless_cache_snapshots.is_empty());
727 }
728
729 #[test]
730 fn begin_cache_cluster_creation_rejects_duplicate_ids() {
731 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
732
733 assert!(state.begin_cache_cluster_creation("cluster-1"));
734 assert!(!state.begin_cache_cluster_creation("cluster-1"));
735
736 state.cancel_cache_cluster_creation("cluster-1");
737 assert!(state.begin_cache_cluster_creation("cluster-1"));
738 }
739
740 #[test]
741 fn begin_replication_group_creation_rejects_duplicate_ids() {
742 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
743
744 assert!(state.begin_replication_group_creation("rg-1"));
745 assert!(!state.begin_replication_group_creation("rg-1"));
746
747 state.cancel_replication_group_creation("rg-1");
748 assert!(state.begin_replication_group_creation("rg-1"));
749 }
750
751 #[test]
752 fn begin_serverless_cache_creation_rejects_duplicate_names() {
753 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
754
755 assert!(state.begin_serverless_cache_creation("cache-1"));
756 assert!(!state.begin_serverless_cache_creation("cache-1"));
757
758 state.cancel_serverless_cache_creation("cache-1");
759 assert!(state.begin_serverless_cache_creation("cache-1"));
760 }
761
762 #[test]
763 fn finish_serverless_cache_creation_registers_cache_and_tags() {
764 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
765 assert!(state.begin_serverless_cache_creation("cache-1"));
766
767 let cache = ServerlessCache {
768 serverless_cache_name: "cache-1".to_string(),
769 description: "test".to_string(),
770 engine: "redis".to_string(),
771 major_engine_version: "7.1".to_string(),
772 full_engine_version: "7.1".to_string(),
773 status: "available".to_string(),
774 endpoint: ServerlessCacheEndpoint {
775 address: "127.0.0.1".to_string(),
776 port: 6379,
777 },
778 reader_endpoint: ServerlessCacheEndpoint {
779 address: "127.0.0.1".to_string(),
780 port: 6379,
781 },
782 arn: "arn:aws:elasticache:us-east-1:123456789012:serverlesscache:cache-1".to_string(),
783 created_at: "2024-01-01T00:00:00Z".to_string(),
784 cache_usage_limits: None,
785 security_group_ids: Vec::new(),
786 subnet_ids: Vec::new(),
787 kms_key_id: None,
788 user_group_id: None,
789 snapshot_retention_limit: None,
790 daily_snapshot_time: None,
791 container_id: "cid".to_string(),
792 host_port: 6379,
793 };
794
795 state.finish_serverless_cache_creation(cache.clone());
796
797 assert!(state.serverless_caches.contains_key("cache-1"));
798 assert!(state.tags.contains_key(&cache.arn));
799 }
800
801 #[test]
802 fn state_new_creates_default_user() {
803 let state = ElastiCacheState::new("123456789012", "us-east-1");
804 assert_eq!(state.users.len(), 1);
805 let default = state.users.get("default").unwrap();
806 assert_eq!(default.user_id, "default");
807 assert_eq!(default.user_name, "default");
808 assert_eq!(default.engine, "redis");
809 assert_eq!(default.access_string, "on ~* +@all");
810 assert_eq!(default.status, "active");
811 assert_eq!(default.authentication_type, "no-password");
812 assert_eq!(default.password_count, 0);
813 assert!(default.arn.contains("user:default"));
814 }
815
816 #[test]
817 fn state_new_has_empty_user_groups() {
818 let state = ElastiCacheState::new("123456789012", "us-east-1");
819 assert!(state.user_groups.is_empty());
820 }
821
822 #[test]
823 fn reset_restores_default_user() {
824 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
825 state.users.clear();
826 assert!(state.users.is_empty());
827 state.reset();
828 assert_eq!(state.users.len(), 1);
829 assert!(state.users.contains_key("default"));
830 }
831
832 #[test]
833 fn reset_clears_user_groups() {
834 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
835 state.user_groups.insert(
836 "my-group".to_string(),
837 ElastiCacheUserGroup {
838 user_group_id: "my-group".to_string(),
839 engine: "redis".to_string(),
840 status: "active".to_string(),
841 user_ids: vec!["default".to_string()],
842 arn: "arn:aws:elasticache:us-east-1:123456789012:usergroup:my-group".to_string(),
843 minimum_engine_version: "6.0".to_string(),
844 pending_changes: None,
845 replication_groups: Vec::new(),
846 },
847 );
848 assert_eq!(state.user_groups.len(), 1);
849 state.reset();
850 assert!(state.user_groups.is_empty());
851 }
852
853 #[test]
854 fn state_new_has_empty_snapshots() {
855 let state = ElastiCacheState::new("123456789012", "us-east-1");
856 assert!(state.snapshots.is_empty());
857 }
858
859 #[test]
860 fn reset_clears_snapshots() {
861 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
862 state.snapshots.insert(
863 "my-snapshot".to_string(),
864 CacheSnapshot {
865 snapshot_name: "my-snapshot".to_string(),
866 replication_group_id: "rg-1".to_string(),
867 replication_group_description: "test".to_string(),
868 snapshot_status: "available".to_string(),
869 cache_node_type: "cache.t3.micro".to_string(),
870 engine: "redis".to_string(),
871 engine_version: "7.1".to_string(),
872 num_cache_clusters: 1,
873 arn: "arn:aws:elasticache:us-east-1:123456789012:snapshot:my-snapshot".to_string(),
874 created_at: "2024-01-01T00:00:00Z".to_string(),
875 snapshot_source: "manual".to_string(),
876 },
877 );
878 assert_eq!(state.snapshots.len(), 1);
879 state.reset();
880 assert!(state.snapshots.is_empty());
881 }
882
883 #[test]
884 fn reset_clears_replication_groups() {
885 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
886 state.replication_groups.insert(
887 "my-group".to_string(),
888 ReplicationGroup {
889 replication_group_id: "my-group".to_string(),
890 description: "test".to_string(),
891 global_replication_group_id: None,
892 global_replication_group_role: None,
893 status: "available".to_string(),
894 cache_node_type: "cache.t3.micro".to_string(),
895 engine: "redis".to_string(),
896 engine_version: "7.1".to_string(),
897 num_cache_clusters: 1,
898 automatic_failover_enabled: false,
899 endpoint_address: "127.0.0.1".to_string(),
900 endpoint_port: 6379,
901 arn: "arn:aws:elasticache:us-east-1:123456789012:replicationgroup:my-group"
902 .to_string(),
903 created_at: "2024-01-01T00:00:00Z".to_string(),
904 container_id: "abc123".to_string(),
905 host_port: 12345,
906 member_clusters: vec!["my-group-001".to_string()],
907 snapshot_retention_limit: 0,
908 snapshot_window: "05:00-09:00".to_string(),
909 },
910 );
911 assert_eq!(state.replication_groups.len(), 1);
912 state.reset();
913 assert!(state.replication_groups.is_empty());
914 }
915
916 #[test]
917 fn reset_clears_global_replication_groups() {
918 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
919 state.global_replication_groups.insert(
920 "global-rg".to_string(),
921 GlobalReplicationGroup {
922 global_replication_group_id: "global-rg".to_string(),
923 global_replication_group_description: "test".to_string(),
924 status: "available".to_string(),
925 cache_node_type: "cache.t3.micro".to_string(),
926 engine: "redis".to_string(),
927 engine_version: "7.1".to_string(),
928 members: vec![GlobalReplicationGroupMember {
929 replication_group_id: "rg-1".to_string(),
930 replication_group_region: "us-east-1".to_string(),
931 role: "primary".to_string(),
932 automatic_failover: false,
933 status: "associated".to_string(),
934 }],
935 cluster_enabled: false,
936 arn: "arn:aws:elasticache:us-east-1:123456789012:globalreplicationgroup:global-rg"
937 .to_string(),
938 },
939 );
940 assert_eq!(state.global_replication_groups.len(), 1);
941 state.reset();
942 assert!(state.global_replication_groups.is_empty());
943 }
944
945 #[test]
946 fn reset_clears_cache_clusters() {
947 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
948 state.cache_clusters.insert(
949 "classic-cluster".to_string(),
950 CacheCluster {
951 cache_cluster_id: "classic-cluster".to_string(),
952 cache_node_type: "cache.t3.micro".to_string(),
953 engine: "redis".to_string(),
954 engine_version: "7.1".to_string(),
955 cache_cluster_status: "available".to_string(),
956 num_cache_nodes: 1,
957 preferred_availability_zone: "us-east-1a".to_string(),
958 cache_subnet_group_name: Some("default".to_string()),
959 auto_minor_version_upgrade: true,
960 arn: "arn:aws:elasticache:us-east-1:123456789012:cluster:classic-cluster"
961 .to_string(),
962 created_at: "2024-01-01T00:00:00Z".to_string(),
963 endpoint_address: "127.0.0.1".to_string(),
964 endpoint_port: 6379,
965 container_id: "abc123".to_string(),
966 host_port: 12345,
967 replication_group_id: None,
968 },
969 );
970 assert_eq!(state.cache_clusters.len(), 1);
971 state.reset();
972 assert!(state.cache_clusters.is_empty());
973 }
974
975 #[test]
976 fn reset_restores_reserved_cache_node_metadata() {
977 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
978 state.reserved_cache_nodes.insert(
979 "rcn-a".to_string(),
980 ReservedCacheNode {
981 reserved_cache_node_id: "rcn-a".to_string(),
982 reserved_cache_nodes_offering_id: "offering-a".to_string(),
983 cache_node_type: "cache.t3.micro".to_string(),
984 start_time: "2024-01-01T00:00:00Z".to_string(),
985 duration: 31_536_000,
986 fixed_price: 0.0,
987 usage_price: 0.011,
988 cache_node_count: 1,
989 product_description: "redis".to_string(),
990 offering_type: "No Upfront".to_string(),
991 state: "payment-pending".to_string(),
992 recurring_charges: Vec::new(),
993 reservation_arn:
994 "arn:aws:elasticache:us-east-1:123456789012:reserved-instance:test".to_string(),
995 },
996 );
997 state.reserved_cache_nodes_offerings.clear();
998
999 state.reset();
1000
1001 assert!(state.reserved_cache_nodes.is_empty());
1002 assert!(!state.reserved_cache_nodes_offerings.is_empty());
1003 }
1004
1005 #[test]
1006 fn reset_clears_serverless_cache_state() {
1007 let mut state = ElastiCacheState::new("123456789012", "us-east-1");
1008 state.serverless_caches.insert(
1009 "serverless".to_string(),
1010 ServerlessCache {
1011 serverless_cache_name: "serverless".to_string(),
1012 description: "test".to_string(),
1013 engine: "redis".to_string(),
1014 major_engine_version: "7.1".to_string(),
1015 full_engine_version: "7.1".to_string(),
1016 status: "available".to_string(),
1017 endpoint: ServerlessCacheEndpoint {
1018 address: "127.0.0.1".to_string(),
1019 port: 6379,
1020 },
1021 reader_endpoint: ServerlessCacheEndpoint {
1022 address: "127.0.0.1".to_string(),
1023 port: 6379,
1024 },
1025 arn: "arn:aws:elasticache:us-east-1:123456789012:serverlesscache:serverless"
1026 .to_string(),
1027 created_at: "2024-01-01T00:00:00Z".to_string(),
1028 cache_usage_limits: None,
1029 security_group_ids: Vec::new(),
1030 subnet_ids: Vec::new(),
1031 kms_key_id: None,
1032 user_group_id: None,
1033 snapshot_retention_limit: None,
1034 daily_snapshot_time: None,
1035 container_id: "cid".to_string(),
1036 host_port: 6379,
1037 },
1038 );
1039 state.serverless_cache_snapshots.insert(
1040 "snap-1".to_string(),
1041 ServerlessCacheSnapshot {
1042 serverless_cache_snapshot_name: "snap-1".to_string(),
1043 arn: "arn:aws:elasticache:us-east-1:123456789012:serverlesssnapshot:snap-1"
1044 .to_string(),
1045 kms_key_id: None,
1046 snapshot_type: "manual".to_string(),
1047 status: "available".to_string(),
1048 create_time: "2024-01-01T00:00:00Z".to_string(),
1049 expiry_time: None,
1050 bytes_used_for_cache: None,
1051 serverless_cache_name: "serverless".to_string(),
1052 engine: "redis".to_string(),
1053 major_engine_version: "7.1".to_string(),
1054 },
1055 );
1056
1057 state.reset();
1058
1059 assert!(state.serverless_caches.is_empty());
1060 assert!(state.serverless_cache_snapshots.is_empty());
1061 }
1062}