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 #[serde(default)]
195 pub extras: HashMap<String, HashMap<String, serde_json::Value>>,
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub struct EngineVersionInfo {
200 pub engine: String,
201 pub engine_version: String,
202 pub db_parameter_group_family: String,
203 pub db_engine_description: String,
204 pub db_engine_version_description: String,
205 pub status: String,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct OrderableDbInstanceOption {
210 pub engine: String,
211 pub engine_version: String,
212 pub db_instance_class: String,
213 pub license_model: String,
214 pub storage_type: String,
215 pub min_storage_size: i32,
216 pub max_storage_size: i32,
217}
218
219#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
220pub struct DbSubnetGroup {
221 pub db_subnet_group_name: String,
222 pub db_subnet_group_arn: String,
223 pub db_subnet_group_description: String,
224 pub vpc_id: String,
225 pub subnet_ids: Vec<String>,
226 pub subnet_availability_zones: Vec<String>,
227 pub tags: Vec<RdsTag>,
228}
229
230#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
231pub struct DbParameterGroup {
232 pub db_parameter_group_name: String,
233 pub db_parameter_group_arn: String,
234 pub db_parameter_group_family: String,
235 pub description: String,
236 pub parameters: HashMap<String, String>,
237 pub tags: Vec<RdsTag>,
238}
239
240impl RdsState {
241 pub fn new(account_id: &str, region: &str) -> Self {
242 Self {
243 account_id: account_id.to_string(),
244 region: region.to_string(),
245 instances: HashMap::new(),
246 in_progress_instance_ids: HashSet::new(),
247 snapshots: HashMap::new(),
248 subnet_groups: HashMap::new(),
249 parameter_groups: default_parameter_groups(account_id, region),
250 extras: HashMap::new(),
251 }
252 }
253
254 pub fn reset(&mut self) {
255 self.instances.clear();
256 self.in_progress_instance_ids.clear();
257 self.snapshots.clear();
258 self.subnet_groups.clear();
259 self.parameter_groups = default_parameter_groups(&self.account_id, &self.region);
260 self.extras.clear();
261 }
262
263 pub fn db_instance_arn(&self, db_instance_identifier: &str) -> String {
264 Arn::new(
265 "rds",
266 &self.region,
267 &self.account_id,
268 &format!("db:{db_instance_identifier}"),
269 )
270 .to_string()
271 }
272
273 pub fn db_snapshot_arn(&self, db_snapshot_identifier: &str) -> String {
274 Arn::new(
275 "rds",
276 &self.region,
277 &self.account_id,
278 &format!("snapshot:{db_snapshot_identifier}"),
279 )
280 .to_string()
281 }
282
283 pub fn db_subnet_group_arn(&self, db_subnet_group_name: &str) -> String {
284 Arn::new(
285 "rds",
286 &self.region,
287 &self.account_id,
288 &format!("subgrp:{db_subnet_group_name}"),
289 )
290 .to_string()
291 }
292
293 pub fn db_parameter_group_arn(&self, db_parameter_group_name: &str) -> String {
294 Arn::new(
295 "rds",
296 &self.region,
297 &self.account_id,
298 &format!("pg:{db_parameter_group_name}"),
299 )
300 .to_string()
301 }
302
303 pub fn next_dbi_resource_id(&self) -> String {
304 format!("db-{}", Uuid::new_v4().simple())
305 }
306
307 pub fn begin_instance_creation(&mut self, db_instance_identifier: &str) -> bool {
308 if self.instances.contains_key(db_instance_identifier)
309 || self
310 .in_progress_instance_ids
311 .contains(db_instance_identifier)
312 {
313 return false;
314 }
315
316 self.in_progress_instance_ids
317 .insert(db_instance_identifier.to_string());
318 true
319 }
320
321 pub fn finish_instance_creation(&mut self, instance: DbInstance) {
322 self.in_progress_instance_ids
323 .remove(&instance.db_instance_identifier);
324 self.instances
325 .insert(instance.db_instance_identifier.clone(), instance);
326 }
327
328 pub fn cancel_instance_creation(&mut self, db_instance_identifier: &str) {
329 self.in_progress_instance_ids.remove(db_instance_identifier);
330 }
331}
332
333pub fn default_engine_versions() -> Vec<EngineVersionInfo> {
334 vec![
335 EngineVersionInfo {
337 engine: "postgres".to_string(),
338 engine_version: "16.3".to_string(),
339 db_parameter_group_family: "postgres16".to_string(),
340 db_engine_description: "PostgreSQL".to_string(),
341 db_engine_version_description: "PostgreSQL 16.3".to_string(),
342 status: "available".to_string(),
343 },
344 EngineVersionInfo {
345 engine: "postgres".to_string(),
346 engine_version: "15.5".to_string(),
347 db_parameter_group_family: "postgres15".to_string(),
348 db_engine_description: "PostgreSQL".to_string(),
349 db_engine_version_description: "PostgreSQL 15.5".to_string(),
350 status: "available".to_string(),
351 },
352 EngineVersionInfo {
353 engine: "postgres".to_string(),
354 engine_version: "14.10".to_string(),
355 db_parameter_group_family: "postgres14".to_string(),
356 db_engine_description: "PostgreSQL".to_string(),
357 db_engine_version_description: "PostgreSQL 14.10".to_string(),
358 status: "available".to_string(),
359 },
360 EngineVersionInfo {
361 engine: "postgres".to_string(),
362 engine_version: "13.13".to_string(),
363 db_parameter_group_family: "postgres13".to_string(),
364 db_engine_description: "PostgreSQL".to_string(),
365 db_engine_version_description: "PostgreSQL 13.13".to_string(),
366 status: "available".to_string(),
367 },
368 EngineVersionInfo {
370 engine: "mysql".to_string(),
371 engine_version: "8.0.35".to_string(),
372 db_parameter_group_family: "mysql8.0".to_string(),
373 db_engine_description: "MySQL Community Edition".to_string(),
374 db_engine_version_description: "MySQL 8.0.35".to_string(),
375 status: "available".to_string(),
376 },
377 EngineVersionInfo {
378 engine: "mysql".to_string(),
379 engine_version: "8.0.28".to_string(),
380 db_parameter_group_family: "mysql8.0".to_string(),
381 db_engine_description: "MySQL Community Edition".to_string(),
382 db_engine_version_description: "MySQL 8.0.28".to_string(),
383 status: "available".to_string(),
384 },
385 EngineVersionInfo {
386 engine: "mysql".to_string(),
387 engine_version: "5.7.44".to_string(),
388 db_parameter_group_family: "mysql5.7".to_string(),
389 db_engine_description: "MySQL Community Edition".to_string(),
390 db_engine_version_description: "MySQL 5.7.44".to_string(),
391 status: "available".to_string(),
392 },
393 EngineVersionInfo {
395 engine: "mariadb".to_string(),
396 engine_version: "10.11.6".to_string(),
397 db_parameter_group_family: "mariadb10.11".to_string(),
398 db_engine_description: "MariaDB Community Edition".to_string(),
399 db_engine_version_description: "MariaDB 10.11.6".to_string(),
400 status: "available".to_string(),
401 },
402 EngineVersionInfo {
403 engine: "mariadb".to_string(),
404 engine_version: "10.6.16".to_string(),
405 db_parameter_group_family: "mariadb10.6".to_string(),
406 db_engine_description: "MariaDB Community Edition".to_string(),
407 db_engine_version_description: "MariaDB 10.6.16".to_string(),
408 status: "available".to_string(),
409 },
410 ]
411}
412
413pub fn default_orderable_options() -> Vec<OrderableDbInstanceOption> {
414 let mut options = Vec::new();
415 let engines_and_versions = vec![
416 ("postgres", "16.3", "postgresql-license"),
417 ("postgres", "15.5", "postgresql-license"),
418 ("postgres", "14.10", "postgresql-license"),
419 ("postgres", "13.13", "postgresql-license"),
420 ("mysql", "8.0.35", "general-public-license"),
421 ("mysql", "8.0.28", "general-public-license"),
422 ("mysql", "5.7.44", "general-public-license"),
423 ("mariadb", "10.11.6", "general-public-license"),
424 ("mariadb", "10.6.16", "general-public-license"),
425 ];
426
427 for (engine, version, license) in engines_and_versions {
428 for class in SUPPORTED_INSTANCE_CLASSES {
429 options.push(OrderableDbInstanceOption {
430 engine: engine.to_string(),
431 engine_version: version.to_string(),
432 db_instance_class: class.to_string(),
433 license_model: license.to_string(),
434 storage_type: "gp2".to_string(),
435 min_storage_size: 20,
436 max_storage_size: 16384,
437 });
438 }
439 }
440
441 options
442}
443
444pub fn default_parameter_groups(
445 account_id: &str,
446 region: &str,
447) -> HashMap<String, DbParameterGroup> {
448 let mut groups = HashMap::new();
449
450 let families = vec![
451 ("postgres16", "Default parameter group for postgres16"),
452 ("postgres15", "Default parameter group for postgres15"),
453 ("postgres14", "Default parameter group for postgres14"),
454 ("postgres13", "Default parameter group for postgres13"),
455 ("mysql8.0", "Default parameter group for mysql8.0"),
456 ("mysql5.7", "Default parameter group for mysql5.7"),
457 ("mariadb10.11", "Default parameter group for mariadb10.11"),
458 ("mariadb10.6", "Default parameter group for mariadb10.6"),
459 ];
460
461 for (family, description) in families {
462 let group_name = format!("default.{}", family);
463 let group = DbParameterGroup {
464 db_parameter_group_name: group_name.clone(),
465 db_parameter_group_arn: Arn::new(
466 "rds",
467 region,
468 account_id,
469 &format!("pg:{group_name}"),
470 )
471 .to_string(),
472 db_parameter_group_family: family.to_string(),
473 description: description.to_string(),
474 parameters: HashMap::new(),
475 tags: Vec::new(),
476 };
477 groups.insert(group_name, group);
478 }
479
480 groups
481}
482
483pub const RDS_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
484
485#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
486pub struct RdsSnapshot {
487 pub schema_version: u32,
488 #[serde(default)]
489 pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<RdsState>>,
490 #[serde(default)]
491 pub state: Option<RdsState>,
492}
493
494#[cfg(test)]
495mod tests {
496 use chrono::Utc;
497
498 use super::{
499 default_engine_versions, default_orderable_options, default_parameter_groups, DbInstance,
500 RdsState,
501 };
502
503 #[test]
504 fn new_initializes_account_and_region() {
505 let state = RdsState::new("123456789012", "us-east-1");
506
507 assert_eq!(state.account_id, "123456789012");
508 assert_eq!(state.region, "us-east-1");
509 assert!(state.instances.is_empty());
510 assert!(state.in_progress_instance_ids.is_empty());
511 }
512
513 #[test]
514 fn reset_clears_instances() {
515 let mut state = RdsState::new("123456789012", "us-east-1");
516 let created_at = Utc::now();
517 state.instances.insert(
518 "db-1".to_string(),
519 DbInstance {
520 db_instance_identifier: "db-1".to_string(),
521 db_instance_arn: "arn:aws:rds:us-east-1:123456789012:db:db-1".to_string(),
522 db_instance_class: "db.t3.micro".to_string(),
523 engine: "postgres".to_string(),
524 engine_version: "16.3".to_string(),
525 db_instance_status: "available".to_string(),
526 master_username: "admin".to_string(),
527 db_name: Some("postgres".to_string()),
528 endpoint_address: "127.0.0.1".to_string(),
529 port: 5432,
530 allocated_storage: 20,
531 publicly_accessible: true,
532 deletion_protection: false,
533 created_at,
534 dbi_resource_id: "db-test".to_string(),
535 master_user_password: "secret123".to_string(),
536 container_id: "container-id".to_string(),
537 host_port: 15432,
538 tags: Vec::new(),
539 read_replica_source_db_instance_identifier: None,
540 read_replica_db_instance_identifiers: Vec::new(),
541 vpc_security_group_ids: Vec::new(),
542 db_parameter_group_name: None,
543 backup_retention_period: 1,
544 preferred_backup_window: "03:00-04:00".to_string(),
545 latest_restorable_time: Some(created_at),
546 option_group_name: None,
547 multi_az: false,
548 pending_modified_values: None,
549 },
550 );
551
552 state.reset();
553
554 assert!(state.instances.is_empty());
555 assert!(state.in_progress_instance_ids.is_empty());
556 }
557
558 #[test]
559 fn default_engine_versions_are_postgres_metadata() {
560 let versions = default_engine_versions();
561
562 assert_eq!(versions.len(), 9); assert_eq!(versions[0].engine, "postgres");
565 assert_eq!(versions[0].engine_version, "16.3");
566 assert_eq!(versions[0].db_parameter_group_family, "postgres16");
567 }
568
569 #[test]
570 fn default_orderable_options_match_engine_versions() {
571 let versions = default_engine_versions();
572 let options = default_orderable_options();
573
574 assert_eq!(options.len(), 63); for version in &versions {
577 assert!(options.iter().any(|opt| {
578 opt.engine == version.engine && opt.engine_version == version.engine_version
579 }));
580 }
581 }
582
583 #[test]
584 fn begin_instance_creation_rejects_duplicate_identifiers() {
585 let mut state = RdsState::new("123456789012", "us-east-1");
586
587 assert!(state.begin_instance_creation("db-1"));
588 assert!(!state.begin_instance_creation("db-1"));
589
590 state.cancel_instance_creation("db-1");
591 assert!(state.begin_instance_creation("db-1"));
592 }
593
594 #[test]
595 fn arn_helpers_format_correctly() {
596 let state = RdsState::new("123456789012", "eu-west-1");
597 assert!(state.db_instance_arn("mydb").contains(":db:mydb"));
598 assert!(state.db_snapshot_arn("snap1").contains(":snapshot:snap1"));
599 assert!(state.db_subnet_group_arn("sng").contains("sng"));
600 assert!(state.db_parameter_group_arn("pg").contains("pg"));
601 }
602
603 #[test]
604 fn next_dbi_resource_id_format() {
605 let state = RdsState::new("123456789012", "us-east-1");
606 let id = state.next_dbi_resource_id();
607 assert!(id.starts_with("db-"));
608 assert!(id.len() > 3);
609 }
610
611 #[test]
612 fn default_engine_versions_list_not_empty() {
613 let versions = default_engine_versions();
614 assert!(!versions.is_empty());
615 }
616
617 #[test]
618 fn default_orderable_options_list_not_empty() {
619 let opts = default_orderable_options();
620 assert!(!opts.is_empty());
621 }
622
623 #[test]
624 fn default_parameter_groups_returned_per_family() {
625 let groups = default_parameter_groups("123456789012", "us-east-1");
626 assert!(!groups.is_empty());
627 }
628
629 fn make_instance(id: &str) -> DbInstance {
630 let created_at = Utc::now();
631 DbInstance {
632 db_instance_identifier: id.to_string(),
633 db_instance_arn: format!("arn:aws:rds:us-east-1:123:db:{id}"),
634 db_instance_class: "db.t3.micro".to_string(),
635 engine: "postgres".to_string(),
636 engine_version: "16.3".to_string(),
637 db_instance_status: "available".to_string(),
638 master_username: "admin".to_string(),
639 db_name: None,
640 endpoint_address: "x".to_string(),
641 port: 5432,
642 allocated_storage: 20,
643 publicly_accessible: false,
644 deletion_protection: false,
645 created_at,
646 dbi_resource_id: "d".to_string(),
647 master_user_password: "p".to_string(),
648 container_id: "c".to_string(),
649 host_port: 0,
650 tags: Vec::new(),
651 read_replica_source_db_instance_identifier: None,
652 read_replica_db_instance_identifiers: Vec::new(),
653 vpc_security_group_ids: Vec::new(),
654 db_parameter_group_name: None,
655 backup_retention_period: 0,
656 preferred_backup_window: String::new(),
657 latest_restorable_time: None,
658 option_group_name: None,
659 multi_az: false,
660 pending_modified_values: None,
661 }
662 }
663
664 #[test]
665 fn finish_instance_creation_moves_from_pending_to_instances() {
666 let mut state = RdsState::new("123456789012", "us-east-1");
667 assert!(state.begin_instance_creation("db-x"));
668 assert!(state.in_progress_instance_ids.contains("db-x"));
669 state.finish_instance_creation(make_instance("db-x"));
670 assert!(!state.in_progress_instance_ids.contains("db-x"));
671 assert!(state.instances.contains_key("db-x"));
672 }
673
674 #[test]
675 fn cancel_instance_creation_drops_pending() {
676 let mut state = RdsState::new("123456789012", "us-east-1");
677 state.begin_instance_creation("db-y");
678 state.cancel_instance_creation("db-y");
679 assert!(!state.in_progress_instance_ids.contains("db-y"));
680 }
681
682 #[test]
683 fn begin_instance_creation_rejects_when_already_created() {
684 let mut state = RdsState::new("123456789012", "us-east-1");
685 state
686 .instances
687 .insert("db-z".to_string(), make_instance("db-z"));
688 assert!(!state.begin_instance_creation("db-z"));
689 }
690
691 #[test]
692 fn reset_restores_default_parameter_groups() {
693 let mut state = RdsState::new("123456789012", "us-east-1");
694 state.parameter_groups.clear();
695 state.reset();
696 assert!(!state.parameter_groups.is_empty());
697 }
698
699 #[test]
700 fn arn_helpers_include_region_and_account() {
701 let state = RdsState::new("111122223333", "ap-southeast-2");
702 let arn = state.db_instance_arn("my-db");
703 assert!(arn.contains("111122223333"));
704 assert!(arn.contains("ap-southeast-2"));
705 let snap = state.db_snapshot_arn("snap");
706 assert!(snap.contains("snapshot:snap"));
707 }
708
709 #[test]
710 fn next_dbi_resource_id_unique_across_calls() {
711 let state = RdsState::new("123", "us-east-1");
712 let a = state.next_dbi_resource_id();
713 let b = state.next_dbi_resource_id();
714 assert_ne!(a, b);
715 }
716}