rusticity_term/
ec2.rs

1use crate::common::{translate_column, ColumnId, UTC_TIMESTAMP_WIDTH};
2use crate::ui::table::Column as TableColumn;
3use ratatui::prelude::*;
4use ratatui::style::Color;
5use std::collections::HashMap;
6
7pub fn init(i18n: &mut HashMap<String, String>) {
8    for col in Column::all() {
9        i18n.entry(col.id().to_string())
10            .or_insert_with(|| col.default_name().to_string());
11    }
12}
13
14#[derive(Debug, Clone)]
15pub struct Instance {
16    pub instance_id: String,
17    pub name: String,
18    pub state: String,
19    pub instance_type: String,
20    pub availability_zone: String,
21    pub public_ipv4_dns: String,
22    pub public_ipv4_address: String,
23    pub elastic_ip: String,
24    pub ipv6_ips: String,
25    pub monitoring: String,
26    pub security_groups: String,
27    pub key_name: String,
28    pub launch_time: String,
29    pub platform_details: String,
30    pub status_checks: String,
31    pub alarm_status: String,
32    pub private_dns_name: String,
33    pub private_ip_address: String,
34    pub security_group_ids: String,
35    pub owner_id: String,
36    pub volume_id: String,
37    pub root_device_name: String,
38    pub root_device_type: String,
39    pub ebs_optimized: String,
40    pub image_id: String,
41    pub kernel_id: String,
42    pub ramdisk_id: String,
43    pub ami_launch_index: String,
44    pub reservation_id: String,
45    pub vpc_id: String,
46    pub subnet_ids: String,
47    pub instance_lifecycle: String,
48    pub architecture: String,
49    pub virtualization_type: String,
50    pub platform: String,
51    pub iam_instance_profile_arn: String,
52    pub tenancy: String,
53    pub affinity: String,
54    pub host_id: String,
55    pub placement_group: String,
56    pub partition_number: String,
57    pub capacity_reservation_id: String,
58    pub state_transition_reason_code: String,
59    pub state_transition_reason_message: String,
60    pub stop_hibernation_behavior: String,
61    pub outpost_arn: String,
62    pub product_codes: String,
63    pub availability_zone_id: String,
64    pub imdsv2: String,
65    pub usage_operation: String,
66    pub managed: String,
67    pub operator: String,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq)]
71pub enum Column {
72    InstanceId,
73    Name,
74    InstanceState,
75    InstanceType,
76    StatusCheck,
77    AlarmStatus,
78    AvailabilityZone,
79    PublicIpv4Dns,
80    PublicIpv4Address,
81    ElasticIp,
82    Ipv6Ips,
83    Monitoring,
84    SecurityGroupName,
85    KeyName,
86    LaunchTime,
87    PlatformDetails,
88    PrivateDnsName,
89    PrivateIpAddress,
90    SecurityGroupIds,
91    OwnerId,
92    VolumeId,
93    RootDeviceName,
94    RootDeviceType,
95    EbsOptimized,
96    ImageId,
97    KernelId,
98    RamdiskId,
99    AmiLaunchIndex,
100    ReservationId,
101    VpcId,
102    SubnetIds,
103    InstanceLifecycle,
104    Architecture,
105    VirtualizationType,
106    Platform,
107    IamInstanceProfileArn,
108    Tenancy,
109    Affinity,
110    HostId,
111    PlacementGroup,
112    PartitionNumber,
113    CapacityReservationId,
114    StateTransitionReasonCode,
115    StateTransitionReasonMessage,
116    StopHibernationBehavior,
117    OutpostArn,
118    ProductCodes,
119    AvailabilityZoneId,
120    Imdsv2,
121    UsageOperation,
122    Managed,
123    Operator,
124}
125
126impl Column {
127    pub fn id(&self) -> &'static str {
128        match self {
129            Column::InstanceId => "column.ec2.instance.instance_id",
130            Column::Name => "column.ec2.instance.name",
131            Column::InstanceState => "column.ec2.instance.state",
132            Column::InstanceType => "column.ec2.instance.instance_type",
133            Column::StatusCheck => "column.ec2.instance.status_check",
134            Column::AlarmStatus => "column.ec2.instance.alarm_status",
135            Column::AvailabilityZone => "column.ec2.instance.availability_zone",
136            Column::PublicIpv4Dns => "column.ec2.instance.public_ipv4_dns",
137            Column::PublicIpv4Address => "column.ec2.instance.public_ipv4_address",
138            Column::ElasticIp => "column.ec2.instance.elastic_ip",
139            Column::Ipv6Ips => "column.ec2.instance.ipv6_ips",
140            Column::Monitoring => "column.ec2.instance.monitoring",
141            Column::SecurityGroupName => "column.ec2.instance.security_group_name",
142            Column::KeyName => "column.ec2.instance.key_name",
143            Column::LaunchTime => "column.ec2.instance.launch_time",
144            Column::PlatformDetails => "column.ec2.instance.platform_details",
145            Column::PrivateDnsName => "column.ec2.instance.private_dns_name",
146            Column::PrivateIpAddress => "column.ec2.instance.private_ip_address",
147            Column::SecurityGroupIds => "column.ec2.instance.security_group_ids",
148            Column::OwnerId => "column.ec2.instance.owner_id",
149            Column::VolumeId => "column.ec2.instance.volume_id",
150            Column::RootDeviceName => "column.ec2.instance.root_device_name",
151            Column::RootDeviceType => "column.ec2.instance.root_device_type",
152            Column::EbsOptimized => "column.ec2.instance.ebs_optimized",
153            Column::ImageId => "column.ec2.instance.image_id",
154            Column::KernelId => "column.ec2.instance.kernel_id",
155            Column::RamdiskId => "column.ec2.instance.ramdisk_id",
156            Column::AmiLaunchIndex => "column.ec2.instance.ami_launch_index",
157            Column::ReservationId => "column.ec2.instance.reservation_id",
158            Column::VpcId => "column.ec2.instance.vpc_id",
159            Column::SubnetIds => "column.ec2.instance.subnet_ids",
160            Column::InstanceLifecycle => "column.ec2.instance.instance_lifecycle",
161            Column::Architecture => "column.ec2.instance.architecture",
162            Column::VirtualizationType => "column.ec2.instance.virtualization_type",
163            Column::Platform => "column.ec2.instance.platform",
164            Column::IamInstanceProfileArn => "column.ec2.instance.iam_instance_profile_arn",
165            Column::Tenancy => "column.ec2.instance.tenancy",
166            Column::Affinity => "column.ec2.instance.affinity",
167            Column::HostId => "column.ec2.instance.host_id",
168            Column::PlacementGroup => "column.ec2.instance.placement_group",
169            Column::PartitionNumber => "column.ec2.instance.partition_number",
170            Column::CapacityReservationId => "column.ec2.instance.capacity_reservation_id",
171            Column::StateTransitionReasonCode => "column.ec2.instance.state_transition_reason_code",
172            Column::StateTransitionReasonMessage => {
173                "column.ec2.instance.state_transition_reason_message"
174            }
175            Column::StopHibernationBehavior => "column.ec2.instance.stop_hibernation_behavior",
176            Column::OutpostArn => "column.ec2.instance.outpost_arn",
177            Column::ProductCodes => "column.ec2.instance.product_codes",
178            Column::AvailabilityZoneId => "column.ec2.instance.availability_zone_id",
179            Column::Imdsv2 => "column.ec2.instance.imdsv2",
180            Column::UsageOperation => "column.ec2.instance.usage_operation",
181            Column::Managed => "column.ec2.instance.managed",
182            Column::Operator => "column.ec2.instance.operator",
183        }
184    }
185
186    pub fn default_name(&self) -> &'static str {
187        match self {
188            Column::InstanceId => "Instance ID",
189            Column::Name => "Name",
190            Column::InstanceState => "Instance state",
191            Column::InstanceType => "Instance type",
192            Column::StatusCheck => "Status check",
193            Column::AlarmStatus => "Alarm status",
194            Column::AvailabilityZone => "Availability Zone",
195            Column::PublicIpv4Dns => "Public IPv4 DNS",
196            Column::PublicIpv4Address => "Public IPv4 address",
197            Column::ElasticIp => "Elastic IP",
198            Column::Ipv6Ips => "IPv6 IPs",
199            Column::Monitoring => "Monitoring",
200            Column::SecurityGroupName => "Security group name",
201            Column::KeyName => "Key name",
202            Column::LaunchTime => "Launch time",
203            Column::PlatformDetails => "Platform details",
204            Column::PrivateDnsName => "Private DNS name",
205            Column::PrivateIpAddress => "Private IP address",
206            Column::SecurityGroupIds => "Security group IDs",
207            Column::OwnerId => "Owner ID",
208            Column::VolumeId => "Volume ID",
209            Column::RootDeviceName => "Root device name",
210            Column::RootDeviceType => "Root device type",
211            Column::EbsOptimized => "EBS optimized",
212            Column::ImageId => "Image ID",
213            Column::KernelId => "Kernel ID",
214            Column::RamdiskId => "RAM disk ID",
215            Column::AmiLaunchIndex => "AMI launch index",
216            Column::ReservationId => "Reservation ID",
217            Column::VpcId => "VPC ID",
218            Column::SubnetIds => "Subnet IDs",
219            Column::InstanceLifecycle => "Instance lifecycle",
220            Column::Architecture => "Architecture",
221            Column::VirtualizationType => "Virtualization type",
222            Column::Platform => "Platform",
223            Column::IamInstanceProfileArn => "IAM instance profile ARN",
224            Column::Tenancy => "Tenancy",
225            Column::Affinity => "Affinity",
226            Column::HostId => "Host ID",
227            Column::PlacementGroup => "Placement group",
228            Column::PartitionNumber => "Partition number",
229            Column::CapacityReservationId => "Capacity Reservation ID",
230            Column::StateTransitionReasonCode => "State transition reason code",
231            Column::StateTransitionReasonMessage => "State transition reason message",
232            Column::StopHibernationBehavior => "Stop-hibernation behavior",
233            Column::OutpostArn => "Outpost ARN",
234            Column::ProductCodes => "Product codes",
235            Column::AvailabilityZoneId => "Availability Zone ID",
236            Column::Imdsv2 => "IMDSv2",
237            Column::UsageOperation => "Usage operation",
238            Column::Managed => "Managed",
239            Column::Operator => "Operator",
240        }
241    }
242
243    pub fn name(&self) -> String {
244        translate_column(self.id(), self.default_name())
245    }
246
247    pub fn from_id(id: &str) -> Option<Self> {
248        match id {
249            "column.ec2.instance.instance_id" => Some(Column::InstanceId),
250            "column.ec2.instance.name" => Some(Column::Name),
251            "column.ec2.instance.state" => Some(Column::InstanceState),
252            "column.ec2.instance.instance_type" => Some(Column::InstanceType),
253            "column.ec2.instance.status_check" => Some(Column::StatusCheck),
254            "column.ec2.instance.alarm_status" => Some(Column::AlarmStatus),
255            "column.ec2.instance.availability_zone" => Some(Column::AvailabilityZone),
256            "column.ec2.instance.public_ipv4_dns" => Some(Column::PublicIpv4Dns),
257            "column.ec2.instance.public_ipv4_address" => Some(Column::PublicIpv4Address),
258            "column.ec2.instance.elastic_ip" => Some(Column::ElasticIp),
259            "column.ec2.instance.ipv6_ips" => Some(Column::Ipv6Ips),
260            "column.ec2.instance.monitoring" => Some(Column::Monitoring),
261            "column.ec2.instance.security_group_name" => Some(Column::SecurityGroupName),
262            "column.ec2.instance.key_name" => Some(Column::KeyName),
263            "column.ec2.instance.launch_time" => Some(Column::LaunchTime),
264            "column.ec2.instance.platform_details" => Some(Column::PlatformDetails),
265            "column.ec2.instance.private_dns_name" => Some(Column::PrivateDnsName),
266            "column.ec2.instance.private_ip_address" => Some(Column::PrivateIpAddress),
267            "column.ec2.instance.security_group_ids" => Some(Column::SecurityGroupIds),
268            "column.ec2.instance.owner_id" => Some(Column::OwnerId),
269            "column.ec2.instance.volume_id" => Some(Column::VolumeId),
270            "column.ec2.instance.root_device_name" => Some(Column::RootDeviceName),
271            "column.ec2.instance.root_device_type" => Some(Column::RootDeviceType),
272            "column.ec2.instance.ebs_optimized" => Some(Column::EbsOptimized),
273            "column.ec2.instance.image_id" => Some(Column::ImageId),
274            "column.ec2.instance.kernel_id" => Some(Column::KernelId),
275            "column.ec2.instance.ramdisk_id" => Some(Column::RamdiskId),
276            "column.ec2.instance.ami_launch_index" => Some(Column::AmiLaunchIndex),
277            "column.ec2.instance.reservation_id" => Some(Column::ReservationId),
278            "column.ec2.instance.vpc_id" => Some(Column::VpcId),
279            "column.ec2.instance.subnet_ids" => Some(Column::SubnetIds),
280            "column.ec2.instance.instance_lifecycle" => Some(Column::InstanceLifecycle),
281            "column.ec2.instance.architecture" => Some(Column::Architecture),
282            "column.ec2.instance.virtualization_type" => Some(Column::VirtualizationType),
283            "column.ec2.instance.platform" => Some(Column::Platform),
284            "column.ec2.instance.iam_instance_profile_arn" => Some(Column::IamInstanceProfileArn),
285            "column.ec2.instance.tenancy" => Some(Column::Tenancy),
286            "column.ec2.instance.affinity" => Some(Column::Affinity),
287            "column.ec2.instance.host_id" => Some(Column::HostId),
288            "column.ec2.instance.placement_group" => Some(Column::PlacementGroup),
289            "column.ec2.instance.partition_number" => Some(Column::PartitionNumber),
290            "column.ec2.instance.capacity_reservation_id" => Some(Column::CapacityReservationId),
291            "column.ec2.instance.state_transition_reason_code" => {
292                Some(Column::StateTransitionReasonCode)
293            }
294            "column.ec2.instance.state_transition_reason_message" => {
295                Some(Column::StateTransitionReasonMessage)
296            }
297            "column.ec2.instance.stop_hibernation_behavior" => {
298                Some(Column::StopHibernationBehavior)
299            }
300            "column.ec2.instance.outpost_arn" => Some(Column::OutpostArn),
301            "column.ec2.instance.product_codes" => Some(Column::ProductCodes),
302            "column.ec2.instance.availability_zone_id" => Some(Column::AvailabilityZoneId),
303            "column.ec2.instance.imdsv2" => Some(Column::Imdsv2),
304            "column.ec2.instance.usage_operation" => Some(Column::UsageOperation),
305            "column.ec2.instance.managed" => Some(Column::Managed),
306            "column.ec2.instance.operator" => Some(Column::Operator),
307            _ => None,
308        }
309    }
310
311    pub fn all() -> [Column; 52] {
312        [
313            Column::InstanceId,
314            Column::Name,
315            Column::InstanceState,
316            Column::InstanceType,
317            Column::StatusCheck,
318            Column::AlarmStatus,
319            Column::AvailabilityZone,
320            Column::PublicIpv4Dns,
321            Column::PublicIpv4Address,
322            Column::ElasticIp,
323            Column::Ipv6Ips,
324            Column::Monitoring,
325            Column::SecurityGroupName,
326            Column::KeyName,
327            Column::LaunchTime,
328            Column::PlatformDetails,
329            Column::PrivateDnsName,
330            Column::PrivateIpAddress,
331            Column::SecurityGroupIds,
332            Column::OwnerId,
333            Column::VolumeId,
334            Column::RootDeviceName,
335            Column::RootDeviceType,
336            Column::EbsOptimized,
337            Column::ImageId,
338            Column::KernelId,
339            Column::RamdiskId,
340            Column::AmiLaunchIndex,
341            Column::ReservationId,
342            Column::VpcId,
343            Column::SubnetIds,
344            Column::InstanceLifecycle,
345            Column::Architecture,
346            Column::VirtualizationType,
347            Column::Platform,
348            Column::IamInstanceProfileArn,
349            Column::Tenancy,
350            Column::Affinity,
351            Column::HostId,
352            Column::PlacementGroup,
353            Column::PartitionNumber,
354            Column::CapacityReservationId,
355            Column::StateTransitionReasonCode,
356            Column::StateTransitionReasonMessage,
357            Column::StopHibernationBehavior,
358            Column::OutpostArn,
359            Column::ProductCodes,
360            Column::AvailabilityZoneId,
361            Column::Imdsv2,
362            Column::UsageOperation,
363            Column::Managed,
364            Column::Operator,
365        ]
366    }
367
368    pub fn ids() -> Vec<ColumnId> {
369        Self::all().iter().map(|c| c.id()).collect()
370    }
371
372    pub fn to_column(&self) -> Box<dyn TableColumn<Instance>> {
373        struct InstanceColumn {
374            variant: Column,
375        }
376
377        impl TableColumn<Instance> for InstanceColumn {
378            fn name(&self) -> &str {
379                Box::leak(self.variant.name().into_boxed_str())
380            }
381
382            fn width(&self) -> u16 {
383                let translated = translate_column(self.variant.id(), self.variant.default_name());
384                translated.len().max(match self.variant {
385                    Column::InstanceId => 20,
386                    Column::Name => 30,
387                    Column::InstanceState => 18,
388                    Column::InstanceType => 15,
389                    Column::StatusCheck => 15,
390                    Column::AlarmStatus => 15,
391                    Column::AvailabilityZone => 20,
392                    Column::PublicIpv4Dns => 40,
393                    Column::PublicIpv4Address => 20,
394                    Column::ElasticIp => 20,
395                    Column::Ipv6Ips => 30,
396                    Column::Monitoring => 15,
397                    Column::SecurityGroupName => 30,
398                    Column::KeyName => 20,
399                    Column::LaunchTime => UTC_TIMESTAMP_WIDTH as usize,
400                    Column::PlatformDetails => 30,
401                    Column::PrivateDnsName => 40,
402                    Column::PrivateIpAddress => 20,
403                    Column::SecurityGroupIds => 30,
404                    Column::OwnerId => 15,
405                    Column::VolumeId => 25,
406                    Column::RootDeviceName => 20,
407                    Column::RootDeviceType => 18,
408                    Column::EbsOptimized => 15,
409                    Column::ImageId => 25,
410                    Column::KernelId => 25,
411                    Column::RamdiskId => 25,
412                    Column::AmiLaunchIndex => 18,
413                    Column::ReservationId => 20,
414                    Column::VpcId => 25,
415                    Column::SubnetIds => 30,
416                    Column::InstanceLifecycle => 20,
417                    Column::Architecture => 15,
418                    Column::VirtualizationType => 20,
419                    Column::Platform => 15,
420                    Column::IamInstanceProfileArn => 50,
421                    Column::Tenancy => 15,
422                    Column::Affinity => 15,
423                    Column::HostId => 25,
424                    Column::PlacementGroup => 25,
425                    Column::PartitionNumber => 18,
426                    Column::CapacityReservationId => 30,
427                    Column::StateTransitionReasonCode => 30,
428                    Column::StateTransitionReasonMessage => 50,
429                    Column::StopHibernationBehavior => 28,
430                    Column::OutpostArn => 40,
431                    Column::ProductCodes => 30,
432                    Column::AvailabilityZoneId => 20,
433                    Column::Imdsv2 => 15,
434                    Column::UsageOperation => 25,
435                    Column::Managed => 15,
436                    Column::Operator => 15,
437                }) as u16
438            }
439
440            fn render(&self, item: &Instance) -> (String, Style) {
441                match self.variant {
442                    Column::InstanceId => (item.instance_id.clone(), Style::default()),
443                    Column::Name => (item.name.clone(), Style::default()),
444                    Column::InstanceState => {
445                        let (formatted, color) = format_state(&item.state);
446                        (formatted, Style::default().fg(color))
447                    }
448                    Column::InstanceType => (item.instance_type.clone(), Style::default()),
449                    Column::StatusCheck => (item.status_checks.clone(), Style::default()),
450                    Column::AlarmStatus => (item.alarm_status.clone(), Style::default()),
451                    Column::AvailabilityZone => (item.availability_zone.clone(), Style::default()),
452                    Column::PublicIpv4Dns => (item.public_ipv4_dns.clone(), Style::default()),
453                    Column::PublicIpv4Address => {
454                        (item.public_ipv4_address.clone(), Style::default())
455                    }
456                    Column::ElasticIp => (item.elastic_ip.clone(), Style::default()),
457                    Column::Ipv6Ips => (item.ipv6_ips.clone(), Style::default()),
458                    Column::Monitoring => (item.monitoring.clone(), Style::default()),
459                    Column::SecurityGroupName => (item.security_groups.clone(), Style::default()),
460                    Column::KeyName => (item.key_name.clone(), Style::default()),
461                    Column::LaunchTime => (item.launch_time.clone(), Style::default()),
462                    Column::PlatformDetails => (item.platform_details.clone(), Style::default()),
463                    Column::PrivateDnsName => (item.private_dns_name.clone(), Style::default()),
464                    Column::PrivateIpAddress => (item.private_ip_address.clone(), Style::default()),
465                    Column::SecurityGroupIds => (item.security_group_ids.clone(), Style::default()),
466                    Column::OwnerId => (item.owner_id.clone(), Style::default()),
467                    Column::VolumeId => (item.volume_id.clone(), Style::default()),
468                    Column::RootDeviceName => (item.root_device_name.clone(), Style::default()),
469                    Column::RootDeviceType => (item.root_device_type.clone(), Style::default()),
470                    Column::EbsOptimized => (item.ebs_optimized.clone(), Style::default()),
471                    Column::ImageId => (item.image_id.clone(), Style::default()),
472                    Column::KernelId => (item.kernel_id.clone(), Style::default()),
473                    Column::RamdiskId => (item.ramdisk_id.clone(), Style::default()),
474                    Column::AmiLaunchIndex => (item.ami_launch_index.clone(), Style::default()),
475                    Column::ReservationId => (item.reservation_id.clone(), Style::default()),
476                    Column::VpcId => (item.vpc_id.clone(), Style::default()),
477                    Column::SubnetIds => (item.subnet_ids.clone(), Style::default()),
478                    Column::InstanceLifecycle => {
479                        (item.instance_lifecycle.clone(), Style::default())
480                    }
481                    Column::Architecture => (item.architecture.clone(), Style::default()),
482                    Column::VirtualizationType => {
483                        (item.virtualization_type.clone(), Style::default())
484                    }
485                    Column::Platform => (item.platform.clone(), Style::default()),
486                    Column::IamInstanceProfileArn => {
487                        (item.iam_instance_profile_arn.clone(), Style::default())
488                    }
489                    Column::Tenancy => (item.tenancy.clone(), Style::default()),
490                    Column::Affinity => (item.affinity.clone(), Style::default()),
491                    Column::HostId => (item.host_id.clone(), Style::default()),
492                    Column::PlacementGroup => (item.placement_group.clone(), Style::default()),
493                    Column::PartitionNumber => (item.partition_number.clone(), Style::default()),
494                    Column::CapacityReservationId => {
495                        (item.capacity_reservation_id.clone(), Style::default())
496                    }
497                    Column::StateTransitionReasonCode => {
498                        (item.state_transition_reason_code.clone(), Style::default())
499                    }
500                    Column::StateTransitionReasonMessage => (
501                        item.state_transition_reason_message.clone(),
502                        Style::default(),
503                    ),
504                    Column::StopHibernationBehavior => {
505                        (item.stop_hibernation_behavior.clone(), Style::default())
506                    }
507                    Column::OutpostArn => (item.outpost_arn.clone(), Style::default()),
508                    Column::ProductCodes => (item.product_codes.clone(), Style::default()),
509                    Column::AvailabilityZoneId => {
510                        (item.availability_zone_id.clone(), Style::default())
511                    }
512                    Column::Imdsv2 => (item.imdsv2.clone(), Style::default()),
513                    Column::UsageOperation => (item.usage_operation.clone(), Style::default()),
514                    Column::Managed => (item.managed.clone(), Style::default()),
515                    Column::Operator => (item.operator.clone(), Style::default()),
516                }
517            }
518        }
519
520        Box::new(InstanceColumn { variant: *self })
521    }
522}
523
524pub fn format_state(state: &str) -> (String, Color) {
525    match state {
526        "running" => ("✅ Running".to_string(), Color::Green),
527        "stopped" => ("🛑 Stopped".to_string(), Color::Red),
528        "terminated" => ("❌ Terminated".to_string(), Color::DarkGray),
529        "pending" => ("❎ Pending".to_string(), Color::Yellow),
530        "shutting-down" => ("🔴 Shutting-down".to_string(), Color::Yellow),
531        "stopping" => ("🚫 Stopping".to_string(), Color::Yellow),
532        _ => (state.to_string(), Color::White),
533    }
534}
535
536#[cfg(test)]
537mod tests {
538    use super::*;
539
540    #[test]
541    fn test_column_names() {
542        assert_eq!(Column::Name.name(), "Name");
543        assert_eq!(Column::InstanceId.name(), "Instance ID");
544        assert_eq!(Column::InstanceState.name(), "Instance state");
545        assert_eq!(Column::InstanceType.name(), "Instance type");
546    }
547
548    #[test]
549    fn test_column_all() {
550        let columns = Column::ids();
551        assert_eq!(columns.len(), 52);
552        assert_eq!(columns[0], Column::InstanceId.id());
553    }
554
555    #[test]
556    fn test_format_state() {
557        let (formatted, color) = format_state("running");
558        assert_eq!(formatted, "✅ Running");
559        assert_eq!(color, Color::Green);
560
561        let (formatted, color) = format_state("stopped");
562        assert_eq!(formatted, "🛑 Stopped");
563        assert_eq!(color, Color::Red);
564
565        let (formatted, color) = format_state("terminated");
566        assert_eq!(formatted, "❌ Terminated");
567        assert_eq!(color, Color::DarkGray);
568
569        let (formatted, color) = format_state("pending");
570        assert_eq!(formatted, "❎ Pending");
571        assert_eq!(color, Color::Yellow);
572
573        let (formatted, color) = format_state("shutting-down");
574        assert_eq!(formatted, "🔴 Shutting-down");
575        assert_eq!(color, Color::Yellow);
576
577        let (formatted, color) = format_state("stopping");
578        assert_eq!(formatted, "🚫 Stopping");
579        assert_eq!(color, Color::Yellow);
580    }
581
582    #[test]
583    fn test_column_from_id() {
584        assert_eq!(
585            Column::from_id("column.ec2.instance.name"),
586            Some(Column::Name)
587        );
588        assert_eq!(
589            Column::from_id("column.ec2.instance.instance_id"),
590            Some(Column::InstanceId)
591        );
592        assert_eq!(
593            Column::from_id("column.ec2.instance.state"),
594            Some(Column::InstanceState)
595        );
596        assert_eq!(Column::from_id("invalid"), None);
597    }
598
599    #[test]
600    fn test_column_ids_unique() {
601        let ids = Column::ids();
602        let mut seen = std::collections::HashSet::new();
603        for id in ids {
604            assert!(seen.insert(id), "Duplicate column ID: {}", id);
605        }
606    }
607
608    #[test]
609    fn test_column_ids_have_correct_prefix() {
610        for col in Column::all() {
611            assert!(
612                col.id().starts_with("column.ec2.instance."),
613                "Column ID '{}' should start with 'column.ec2.instance.'",
614                col.id()
615            );
616        }
617    }
618
619    #[test]
620    fn test_launch_time_uses_utc_timestamp_width() {
621        let col = Column::LaunchTime.to_column();
622        assert_eq!(col.width(), UTC_TIMESTAMP_WIDTH);
623    }
624
625    #[test]
626    fn test_utc_timestamp_width_constant_is_27() {
627        assert_eq!(UTC_TIMESTAMP_WIDTH, 27);
628    }
629}