1use std::collections::{HashMap, HashSet};
2use std::fmt;
3use std::sync::Arc;
4
5use chrono::{DateTime, Utc};
6use fakecloud_aws::arn::Arn;
7use parking_lot::RwLock;
8use uuid::Uuid;
9
10pub type SharedRdsState = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<RdsState>>>;
11
12impl fakecloud_core::multi_account::AccountState for RdsState {
13 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
14 Self::new(account_id, region)
15 }
16}
17
18pub const SUPPORTED_INSTANCE_CLASSES: &[&str] = &[
20 "db.t3.micro",
21 "db.t3.small",
22 "db.t3.medium",
23 "db.t3.large",
24 "db.t4g.micro",
25 "db.t4g.small",
26 "db.m5.large",
27];
28
29#[derive(Clone, serde::Serialize, serde::Deserialize)]
30pub struct DbInstance {
31 pub db_instance_identifier: String,
32 pub db_instance_arn: String,
33 pub db_instance_class: String,
34 pub engine: String,
35 pub engine_version: String,
36 pub db_instance_status: String,
37 pub master_username: String,
38 pub db_name: Option<String>,
39 pub endpoint_address: String,
40 pub port: i32,
41 pub allocated_storage: i32,
42 pub publicly_accessible: bool,
43 pub deletion_protection: bool,
44 pub created_at: DateTime<Utc>,
45 pub dbi_resource_id: String,
46 pub master_user_password: String,
47 pub container_id: String,
48 pub host_port: u16,
49 pub tags: Vec<RdsTag>,
50 pub read_replica_source_db_instance_identifier: Option<String>,
51 pub read_replica_db_instance_identifiers: Vec<String>,
52 pub vpc_security_group_ids: Vec<String>,
53 pub db_parameter_group_name: Option<String>,
54 pub backup_retention_period: i32,
55 pub preferred_backup_window: String,
56 pub latest_restorable_time: Option<DateTime<Utc>>,
57 pub option_group_name: Option<String>,
58 pub multi_az: bool,
59 pub pending_modified_values: Option<PendingModifiedValues>,
60}
61
62#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
63pub struct PendingModifiedValues {
64 pub db_instance_class: Option<String>,
65 pub allocated_storage: Option<i32>,
66 pub backup_retention_period: Option<i32>,
67 pub multi_az: Option<bool>,
68 pub engine_version: Option<String>,
69 pub master_user_password: Option<String>,
70}
71
72impl fmt::Debug for PendingModifiedValues {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.debug_struct("PendingModifiedValues")
75 .field("db_instance_class", &self.db_instance_class)
76 .field("allocated_storage", &self.allocated_storage)
77 .field("backup_retention_period", &self.backup_retention_period)
78 .field("multi_az", &self.multi_az)
79 .field("engine_version", &self.engine_version)
80 .field(
81 "master_user_password",
82 &self.master_user_password.as_ref().map(|_| "<redacted>"),
83 )
84 .finish()
85 }
86}
87
88impl fmt::Debug for DbInstance {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.debug_struct("DbInstance")
91 .field("db_instance_identifier", &self.db_instance_identifier)
92 .field("db_instance_arn", &self.db_instance_arn)
93 .field("db_instance_class", &self.db_instance_class)
94 .field("engine", &self.engine)
95 .field("engine_version", &self.engine_version)
96 .field("db_instance_status", &self.db_instance_status)
97 .field("master_username", &self.master_username)
98 .field("db_name", &self.db_name)
99 .field("endpoint_address", &self.endpoint_address)
100 .field("port", &self.port)
101 .field("allocated_storage", &self.allocated_storage)
102 .field("publicly_accessible", &self.publicly_accessible)
103 .field("deletion_protection", &self.deletion_protection)
104 .field("created_at", &self.created_at)
105 .field("dbi_resource_id", &self.dbi_resource_id)
106 .field("master_user_password", &"<redacted>")
107 .field("container_id", &self.container_id)
108 .field("host_port", &self.host_port)
109 .field("tags", &self.tags)
110 .field(
111 "read_replica_source_db_instance_identifier",
112 &self.read_replica_source_db_instance_identifier,
113 )
114 .field(
115 "read_replica_db_instance_identifiers",
116 &self.read_replica_db_instance_identifiers,
117 )
118 .field("vpc_security_group_ids", &self.vpc_security_group_ids)
119 .field("db_parameter_group_name", &self.db_parameter_group_name)
120 .field("backup_retention_period", &self.backup_retention_period)
121 .field("preferred_backup_window", &self.preferred_backup_window)
122 .field("latest_restorable_time", &self.latest_restorable_time)
123 .field("option_group_name", &self.option_group_name)
124 .field("multi_az", &self.multi_az)
125 .field("pending_modified_values", &self.pending_modified_values)
126 .finish()
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
131pub struct RdsTag {
132 pub key: String,
133 pub value: String,
134}
135
136#[derive(Clone, serde::Serialize, serde::Deserialize)]
137pub struct DbSnapshot {
138 pub db_snapshot_identifier: String,
139 pub db_snapshot_arn: String,
140 pub db_instance_identifier: String,
141 pub snapshot_create_time: DateTime<Utc>,
142 pub engine: String,
143 pub engine_version: String,
144 pub allocated_storage: i32,
145 pub status: String,
146 pub port: i32,
147 pub master_username: String,
148 pub db_name: Option<String>,
149 pub dbi_resource_id: String,
150 pub snapshot_type: String,
151 pub master_user_password: String,
152 pub tags: Vec<RdsTag>,
153 pub dump_data: Vec<u8>,
154}
155
156impl fmt::Debug for DbSnapshot {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.debug_struct("DbSnapshot")
159 .field("db_snapshot_identifier", &self.db_snapshot_identifier)
160 .field("db_snapshot_arn", &self.db_snapshot_arn)
161 .field("db_instance_identifier", &self.db_instance_identifier)
162 .field("snapshot_create_time", &self.snapshot_create_time)
163 .field("engine", &self.engine)
164 .field("engine_version", &self.engine_version)
165 .field("allocated_storage", &self.allocated_storage)
166 .field("status", &self.status)
167 .field("port", &self.port)
168 .field("master_username", &self.master_username)
169 .field("db_name", &self.db_name)
170 .field("dbi_resource_id", &self.dbi_resource_id)
171 .field("snapshot_type", &self.snapshot_type)
172 .field("master_user_password", &"<redacted>")
173 .field("tags", &self.tags)
174 .field("dump_data", &format!("<{} bytes>", self.dump_data.len()))
175 .finish()
176 }
177}
178
179#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
180pub struct RdsState {
181 pub account_id: String,
182 pub region: String,
183 pub instances: HashMap<String, DbInstance>,
184 pub in_progress_instance_ids: HashSet<String>,
185 pub snapshots: HashMap<String, DbSnapshot>,
186 pub subnet_groups: HashMap<String, DbSubnetGroup>,
187 pub parameter_groups: HashMap<String, DbParameterGroup>,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct EngineVersionInfo {
192 pub engine: String,
193 pub engine_version: String,
194 pub db_parameter_group_family: String,
195 pub db_engine_description: String,
196 pub db_engine_version_description: String,
197 pub status: String,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct OrderableDbInstanceOption {
202 pub engine: String,
203 pub engine_version: String,
204 pub db_instance_class: String,
205 pub license_model: String,
206 pub storage_type: String,
207 pub min_storage_size: i32,
208 pub max_storage_size: i32,
209}
210
211#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
212pub struct DbSubnetGroup {
213 pub db_subnet_group_name: String,
214 pub db_subnet_group_arn: String,
215 pub db_subnet_group_description: String,
216 pub vpc_id: String,
217 pub subnet_ids: Vec<String>,
218 pub subnet_availability_zones: Vec<String>,
219 pub tags: Vec<RdsTag>,
220}
221
222#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
223pub struct DbParameterGroup {
224 pub db_parameter_group_name: String,
225 pub db_parameter_group_arn: String,
226 pub db_parameter_group_family: String,
227 pub description: String,
228 pub parameters: HashMap<String, String>,
229 pub tags: Vec<RdsTag>,
230}
231
232impl RdsState {
233 pub fn new(account_id: &str, region: &str) -> Self {
234 Self {
235 account_id: account_id.to_string(),
236 region: region.to_string(),
237 instances: HashMap::new(),
238 in_progress_instance_ids: HashSet::new(),
239 snapshots: HashMap::new(),
240 subnet_groups: HashMap::new(),
241 parameter_groups: default_parameter_groups(account_id, region),
242 }
243 }
244
245 pub fn reset(&mut self) {
246 self.instances.clear();
247 self.in_progress_instance_ids.clear();
248 self.snapshots.clear();
249 self.subnet_groups.clear();
250 self.parameter_groups = default_parameter_groups(&self.account_id, &self.region);
251 }
252
253 pub fn db_instance_arn(&self, db_instance_identifier: &str) -> String {
254 Arn::new(
255 "rds",
256 &self.region,
257 &self.account_id,
258 &format!("db:{db_instance_identifier}"),
259 )
260 .to_string()
261 }
262
263 pub fn db_snapshot_arn(&self, db_snapshot_identifier: &str) -> String {
264 Arn::new(
265 "rds",
266 &self.region,
267 &self.account_id,
268 &format!("snapshot:{db_snapshot_identifier}"),
269 )
270 .to_string()
271 }
272
273 pub fn db_subnet_group_arn(&self, db_subnet_group_name: &str) -> String {
274 Arn::new(
275 "rds",
276 &self.region,
277 &self.account_id,
278 &format!("subgrp:{db_subnet_group_name}"),
279 )
280 .to_string()
281 }
282
283 pub fn db_parameter_group_arn(&self, db_parameter_group_name: &str) -> String {
284 Arn::new(
285 "rds",
286 &self.region,
287 &self.account_id,
288 &format!("pg:{db_parameter_group_name}"),
289 )
290 .to_string()
291 }
292
293 pub fn next_dbi_resource_id(&self) -> String {
294 format!("db-{}", Uuid::new_v4().simple())
295 }
296
297 pub fn begin_instance_creation(&mut self, db_instance_identifier: &str) -> bool {
298 if self.instances.contains_key(db_instance_identifier)
299 || self
300 .in_progress_instance_ids
301 .contains(db_instance_identifier)
302 {
303 return false;
304 }
305
306 self.in_progress_instance_ids
307 .insert(db_instance_identifier.to_string());
308 true
309 }
310
311 pub fn finish_instance_creation(&mut self, instance: DbInstance) {
312 self.in_progress_instance_ids
313 .remove(&instance.db_instance_identifier);
314 self.instances
315 .insert(instance.db_instance_identifier.clone(), instance);
316 }
317
318 pub fn cancel_instance_creation(&mut self, db_instance_identifier: &str) {
319 self.in_progress_instance_ids.remove(db_instance_identifier);
320 }
321}
322
323pub fn default_engine_versions() -> Vec<EngineVersionInfo> {
324 vec![
325 EngineVersionInfo {
327 engine: "postgres".to_string(),
328 engine_version: "16.3".to_string(),
329 db_parameter_group_family: "postgres16".to_string(),
330 db_engine_description: "PostgreSQL".to_string(),
331 db_engine_version_description: "PostgreSQL 16.3".to_string(),
332 status: "available".to_string(),
333 },
334 EngineVersionInfo {
335 engine: "postgres".to_string(),
336 engine_version: "15.5".to_string(),
337 db_parameter_group_family: "postgres15".to_string(),
338 db_engine_description: "PostgreSQL".to_string(),
339 db_engine_version_description: "PostgreSQL 15.5".to_string(),
340 status: "available".to_string(),
341 },
342 EngineVersionInfo {
343 engine: "postgres".to_string(),
344 engine_version: "14.10".to_string(),
345 db_parameter_group_family: "postgres14".to_string(),
346 db_engine_description: "PostgreSQL".to_string(),
347 db_engine_version_description: "PostgreSQL 14.10".to_string(),
348 status: "available".to_string(),
349 },
350 EngineVersionInfo {
351 engine: "postgres".to_string(),
352 engine_version: "13.13".to_string(),
353 db_parameter_group_family: "postgres13".to_string(),
354 db_engine_description: "PostgreSQL".to_string(),
355 db_engine_version_description: "PostgreSQL 13.13".to_string(),
356 status: "available".to_string(),
357 },
358 EngineVersionInfo {
360 engine: "mysql".to_string(),
361 engine_version: "8.0.35".to_string(),
362 db_parameter_group_family: "mysql8.0".to_string(),
363 db_engine_description: "MySQL Community Edition".to_string(),
364 db_engine_version_description: "MySQL 8.0.35".to_string(),
365 status: "available".to_string(),
366 },
367 EngineVersionInfo {
368 engine: "mysql".to_string(),
369 engine_version: "8.0.28".to_string(),
370 db_parameter_group_family: "mysql8.0".to_string(),
371 db_engine_description: "MySQL Community Edition".to_string(),
372 db_engine_version_description: "MySQL 8.0.28".to_string(),
373 status: "available".to_string(),
374 },
375 EngineVersionInfo {
376 engine: "mysql".to_string(),
377 engine_version: "5.7.44".to_string(),
378 db_parameter_group_family: "mysql5.7".to_string(),
379 db_engine_description: "MySQL Community Edition".to_string(),
380 db_engine_version_description: "MySQL 5.7.44".to_string(),
381 status: "available".to_string(),
382 },
383 EngineVersionInfo {
385 engine: "mariadb".to_string(),
386 engine_version: "10.11.6".to_string(),
387 db_parameter_group_family: "mariadb10.11".to_string(),
388 db_engine_description: "MariaDB Community Edition".to_string(),
389 db_engine_version_description: "MariaDB 10.11.6".to_string(),
390 status: "available".to_string(),
391 },
392 EngineVersionInfo {
393 engine: "mariadb".to_string(),
394 engine_version: "10.6.16".to_string(),
395 db_parameter_group_family: "mariadb10.6".to_string(),
396 db_engine_description: "MariaDB Community Edition".to_string(),
397 db_engine_version_description: "MariaDB 10.6.16".to_string(),
398 status: "available".to_string(),
399 },
400 ]
401}
402
403pub fn default_orderable_options() -> Vec<OrderableDbInstanceOption> {
404 let mut options = Vec::new();
405 let engines_and_versions = vec![
406 ("postgres", "16.3", "postgresql-license"),
407 ("postgres", "15.5", "postgresql-license"),
408 ("postgres", "14.10", "postgresql-license"),
409 ("postgres", "13.13", "postgresql-license"),
410 ("mysql", "8.0.35", "general-public-license"),
411 ("mysql", "8.0.28", "general-public-license"),
412 ("mysql", "5.7.44", "general-public-license"),
413 ("mariadb", "10.11.6", "general-public-license"),
414 ("mariadb", "10.6.16", "general-public-license"),
415 ];
416
417 for (engine, version, license) in engines_and_versions {
418 for class in SUPPORTED_INSTANCE_CLASSES {
419 options.push(OrderableDbInstanceOption {
420 engine: engine.to_string(),
421 engine_version: version.to_string(),
422 db_instance_class: class.to_string(),
423 license_model: license.to_string(),
424 storage_type: "gp2".to_string(),
425 min_storage_size: 20,
426 max_storage_size: 16384,
427 });
428 }
429 }
430
431 options
432}
433
434pub fn default_parameter_groups(
435 account_id: &str,
436 region: &str,
437) -> HashMap<String, DbParameterGroup> {
438 let mut groups = HashMap::new();
439
440 let families = vec![
441 ("postgres16", "Default parameter group for postgres16"),
442 ("postgres15", "Default parameter group for postgres15"),
443 ("postgres14", "Default parameter group for postgres14"),
444 ("postgres13", "Default parameter group for postgres13"),
445 ("mysql8.0", "Default parameter group for mysql8.0"),
446 ("mysql5.7", "Default parameter group for mysql5.7"),
447 ("mariadb10.11", "Default parameter group for mariadb10.11"),
448 ("mariadb10.6", "Default parameter group for mariadb10.6"),
449 ];
450
451 for (family, description) in families {
452 let group_name = format!("default.{}", family);
453 let group = DbParameterGroup {
454 db_parameter_group_name: group_name.clone(),
455 db_parameter_group_arn: Arn::new(
456 "rds",
457 region,
458 account_id,
459 &format!("pg:{group_name}"),
460 )
461 .to_string(),
462 db_parameter_group_family: family.to_string(),
463 description: description.to_string(),
464 parameters: HashMap::new(),
465 tags: Vec::new(),
466 };
467 groups.insert(group_name, group);
468 }
469
470 groups
471}
472
473pub const RDS_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
474
475#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
476pub struct RdsSnapshot {
477 pub schema_version: u32,
478 #[serde(default)]
479 pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<RdsState>>,
480 #[serde(default)]
481 pub state: Option<RdsState>,
482}
483
484#[cfg(test)]
485mod tests {
486 use chrono::Utc;
487
488 use super::{
489 default_engine_versions, default_orderable_options, default_parameter_groups, DbInstance,
490 RdsState,
491 };
492
493 #[test]
494 fn new_initializes_account_and_region() {
495 let state = RdsState::new("123456789012", "us-east-1");
496
497 assert_eq!(state.account_id, "123456789012");
498 assert_eq!(state.region, "us-east-1");
499 assert!(state.instances.is_empty());
500 assert!(state.in_progress_instance_ids.is_empty());
501 }
502
503 #[test]
504 fn reset_clears_instances() {
505 let mut state = RdsState::new("123456789012", "us-east-1");
506 let created_at = Utc::now();
507 state.instances.insert(
508 "db-1".to_string(),
509 DbInstance {
510 db_instance_identifier: "db-1".to_string(),
511 db_instance_arn: "arn:aws:rds:us-east-1:123456789012:db:db-1".to_string(),
512 db_instance_class: "db.t3.micro".to_string(),
513 engine: "postgres".to_string(),
514 engine_version: "16.3".to_string(),
515 db_instance_status: "available".to_string(),
516 master_username: "admin".to_string(),
517 db_name: Some("postgres".to_string()),
518 endpoint_address: "127.0.0.1".to_string(),
519 port: 5432,
520 allocated_storage: 20,
521 publicly_accessible: true,
522 deletion_protection: false,
523 created_at,
524 dbi_resource_id: "db-test".to_string(),
525 master_user_password: "secret123".to_string(),
526 container_id: "container-id".to_string(),
527 host_port: 15432,
528 tags: Vec::new(),
529 read_replica_source_db_instance_identifier: None,
530 read_replica_db_instance_identifiers: Vec::new(),
531 vpc_security_group_ids: Vec::new(),
532 db_parameter_group_name: None,
533 backup_retention_period: 1,
534 preferred_backup_window: "03:00-04:00".to_string(),
535 latest_restorable_time: Some(created_at),
536 option_group_name: None,
537 multi_az: false,
538 pending_modified_values: None,
539 },
540 );
541
542 state.reset();
543
544 assert!(state.instances.is_empty());
545 assert!(state.in_progress_instance_ids.is_empty());
546 }
547
548 #[test]
549 fn default_engine_versions_are_postgres_metadata() {
550 let versions = default_engine_versions();
551
552 assert_eq!(versions.len(), 9); assert_eq!(versions[0].engine, "postgres");
555 assert_eq!(versions[0].engine_version, "16.3");
556 assert_eq!(versions[0].db_parameter_group_family, "postgres16");
557 }
558
559 #[test]
560 fn default_orderable_options_match_engine_versions() {
561 let versions = default_engine_versions();
562 let options = default_orderable_options();
563
564 assert_eq!(options.len(), 63); for version in &versions {
567 assert!(options.iter().any(|opt| {
568 opt.engine == version.engine && opt.engine_version == version.engine_version
569 }));
570 }
571 }
572
573 #[test]
574 fn begin_instance_creation_rejects_duplicate_identifiers() {
575 let mut state = RdsState::new("123456789012", "us-east-1");
576
577 assert!(state.begin_instance_creation("db-1"));
578 assert!(!state.begin_instance_creation("db-1"));
579
580 state.cancel_instance_creation("db-1");
581 assert!(state.begin_instance_creation("db-1"));
582 }
583
584 #[test]
585 fn arn_helpers_format_correctly() {
586 let state = RdsState::new("123456789012", "eu-west-1");
587 assert!(state.db_instance_arn("mydb").contains(":db:mydb"));
588 assert!(state.db_snapshot_arn("snap1").contains(":snapshot:snap1"));
589 assert!(state.db_subnet_group_arn("sng").contains("sng"));
590 assert!(state.db_parameter_group_arn("pg").contains("pg"));
591 }
592
593 #[test]
594 fn next_dbi_resource_id_format() {
595 let state = RdsState::new("123456789012", "us-east-1");
596 let id = state.next_dbi_resource_id();
597 assert!(id.starts_with("db-"));
598 assert!(id.len() > 3);
599 }
600
601 #[test]
602 fn default_engine_versions_list_not_empty() {
603 let versions = default_engine_versions();
604 assert!(!versions.is_empty());
605 }
606
607 #[test]
608 fn default_orderable_options_list_not_empty() {
609 let opts = default_orderable_options();
610 assert!(!opts.is_empty());
611 }
612
613 #[test]
614 fn default_parameter_groups_returned_per_family() {
615 let groups = default_parameter_groups("123456789012", "us-east-1");
616 assert!(!groups.is_empty());
617 }
618
619 fn make_instance(id: &str) -> DbInstance {
620 let created_at = Utc::now();
621 DbInstance {
622 db_instance_identifier: id.to_string(),
623 db_instance_arn: format!("arn:aws:rds:us-east-1:123:db:{id}"),
624 db_instance_class: "db.t3.micro".to_string(),
625 engine: "postgres".to_string(),
626 engine_version: "16.3".to_string(),
627 db_instance_status: "available".to_string(),
628 master_username: "admin".to_string(),
629 db_name: None,
630 endpoint_address: "x".to_string(),
631 port: 5432,
632 allocated_storage: 20,
633 publicly_accessible: false,
634 deletion_protection: false,
635 created_at,
636 dbi_resource_id: "d".to_string(),
637 master_user_password: "p".to_string(),
638 container_id: "c".to_string(),
639 host_port: 0,
640 tags: Vec::new(),
641 read_replica_source_db_instance_identifier: None,
642 read_replica_db_instance_identifiers: Vec::new(),
643 vpc_security_group_ids: Vec::new(),
644 db_parameter_group_name: None,
645 backup_retention_period: 0,
646 preferred_backup_window: String::new(),
647 latest_restorable_time: None,
648 option_group_name: None,
649 multi_az: false,
650 pending_modified_values: None,
651 }
652 }
653
654 #[test]
655 fn finish_instance_creation_moves_from_pending_to_instances() {
656 let mut state = RdsState::new("123456789012", "us-east-1");
657 assert!(state.begin_instance_creation("db-x"));
658 assert!(state.in_progress_instance_ids.contains("db-x"));
659 state.finish_instance_creation(make_instance("db-x"));
660 assert!(!state.in_progress_instance_ids.contains("db-x"));
661 assert!(state.instances.contains_key("db-x"));
662 }
663
664 #[test]
665 fn cancel_instance_creation_drops_pending() {
666 let mut state = RdsState::new("123456789012", "us-east-1");
667 state.begin_instance_creation("db-y");
668 state.cancel_instance_creation("db-y");
669 assert!(!state.in_progress_instance_ids.contains("db-y"));
670 }
671
672 #[test]
673 fn begin_instance_creation_rejects_when_already_created() {
674 let mut state = RdsState::new("123456789012", "us-east-1");
675 state
676 .instances
677 .insert("db-z".to_string(), make_instance("db-z"));
678 assert!(!state.begin_instance_creation("db-z"));
679 }
680
681 #[test]
682 fn reset_restores_default_parameter_groups() {
683 let mut state = RdsState::new("123456789012", "us-east-1");
684 state.parameter_groups.clear();
685 state.reset();
686 assert!(!state.parameter_groups.is_empty());
687 }
688
689 #[test]
690 fn arn_helpers_include_region_and_account() {
691 let state = RdsState::new("111122223333", "ap-southeast-2");
692 let arn = state.db_instance_arn("my-db");
693 assert!(arn.contains("111122223333"));
694 assert!(arn.contains("ap-southeast-2"));
695 let snap = state.db_snapshot_arn("snap");
696 assert!(snap.contains("snapshot:snap"));
697 }
698
699 #[test]
700 fn next_dbi_resource_id_unique_across_calls() {
701 let state = RdsState::new("123", "us-east-1");
702 let a = state.next_dbi_resource_id();
703 let b = state.next_dbi_resource_id();
704 assert_ne!(a, b);
705 }
706}