diff --git a/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs b/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs index 34925ec..db2c297 100644 --- a/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs +++ b/src/MicroStack/Services/Ec2/Ec2ServiceHandler.cs @@ -185,8 +185,8 @@ private ServiceResponse DispatchAction(string action, Dictionary DescribeInstanceAttribute(p), "DescribeInstanceTypes" => DescribeInstanceTypes(p), "DescribeInstanceCreditSpecifications" => DescribeInstanceCreditSpecifications(p), - "DescribeSpotInstanceRequests" => Xml(200, "DescribeSpotInstanceRequestsResponse", ""), - "DescribeCapacityReservations" => Xml(200, "DescribeCapacityReservationsResponse", ""), + "DescribeSpotInstanceRequests" => Xml(200, "DescribeSpotInstanceRequestsResponse", ""), + "DescribeCapacityReservations" => Xml(200, "DescribeCapacityReservationsResponse", ""), "DescribeImages" => DescribeImages(p), "CreateSecurityGroup" => CreateSecurityGroup(p), "DeleteSecurityGroup" => DeleteSecurityGroup(p), @@ -305,9 +305,9 @@ private ServiceResponse DispatchAction(string action, Dictionary DescribeLaunchTemplateVersions(p), "ModifyLaunchTemplate" => ModifyLaunchTemplate(p), "DeleteLaunchTemplate" => DeleteLaunchTemplate(p), - "DescribeAccountAttributes" => Xml(200, "DescribeAccountAttributesResponse", ""), - "DescribeInstanceMaintenanceOptions" => Xml(200, "DescribeInstanceMaintenanceOptionsResponse", ""), - "DescribeInstanceTopology" => Xml(200, "DescribeInstanceTopologyResponse", ""), + "DescribeAccountAttributes" => Xml(200, "DescribeAccountAttributesResponse", ""), + "DescribeInstanceMaintenanceOptions" => Xml(200, "DescribeInstanceMaintenanceOptionsResponse", ""), + "DescribeInstanceTopology" => Xml(200, "DescribeInstanceTopologyResponse", ""), _ => Error("InvalidAction", $"Unknown EC2 action: {action}", 400), }; } @@ -371,7 +371,7 @@ private ServiceResponse RunInstances(Dictionary p) } var reservationId = "r-" + Guid.NewGuid().ToString("N")[..17]; - var inner = $"{sb}{reservationId}{AccountContext.GetAccountId()}"; + var inner = $"{sb}{reservationId}{AccountContext.GetAccountId()}"; return Xml(200, "RunInstancesResponse", inner); } @@ -387,7 +387,7 @@ private ServiceResponse DescribeInstances(Dictionary p) if (!MatchesInstanceFilters(inst, filters)) continue; var iid = (string)inst["InstanceId"]; - sb.Append($"r-{iid[2..]}{AccountContext.GetAccountId()}{InstanceXml(inst)}"); + sb.Append($"r-{iid[2..]}{AccountContext.GetAccountId()}{InstanceXml(inst)}"); } return Xml(200, "DescribeInstancesResponse", $"{sb}"); @@ -465,11 +465,11 @@ private ServiceResponse DescribeInstanceAttribute(Dictionary p "instanceType" => $"{inst.GetValueOrDefault("InstanceType", "t2.micro")}", "instanceInitiatedShutdownBehavior" => "stop", "disableApiTermination" => "false", - "userData" => "", + "userData" => "", "rootDeviceName" => "/dev/xvda", "sourceDestCheck" => "true", "ebsOptimized" => "false", - _ => $"<{attribute}/>", + _ => $"<{attribute}>", }; return Xml(200, "DescribeInstanceAttributeResponse", $"{instanceId}{valueXml}"); @@ -1345,7 +1345,7 @@ private ServiceResponse DeleteVpcEndpoints(Dictionary p) { var ids = ParseMemberList(p, "VpcEndpointId"); foreach (var id in ids) _vpcEndpoints.TryRemove(id, out _); - return Xml(200, "DeleteVpcEndpointsResponse", ""); + return Xml(200, "DeleteVpcEndpointsResponse", ""); } private ServiceResponse DescribeVpcEndpoints(Dictionary p) @@ -1468,7 +1468,7 @@ private ServiceResponse DescribeVolumeStatus(Dictionary p) foreach (var vol in _volumes.Values.ToList()) { if (filterIds.Count > 0 && !filterIds.Contains((string)vol["VolumeId"])) continue; - sb.Append($"{vol["VolumeId"]}{vol["AvailabilityZone"]}ok
io-enabledpassed
"); + sb.Append($"{vol["VolumeId"]}{vol["AvailabilityZone"]}ok
io-enabledpassed
"); } return Xml(200, "DescribeVolumeStatusResponse", $"{sb}"); } @@ -1656,7 +1656,7 @@ private ServiceResponse CreateNatGateway(Dictionary p) ["State"] = "available", ["CreateTime"] = now, ["Tags"] = tags, }; if (tags.Count > 0) _tags[natId] = tags; - return Xml(200, "CreateNatGatewayResponse", $"{natId}{subnetId}{vpcId}available{connectivity}{now}"); + return Xml(200, "CreateNatGatewayResponse", $"{natId}{subnetId}{vpcId}available{connectivity}{now}"); } private ServiceResponse DescribeNatGateways(Dictionary p) @@ -1670,7 +1670,7 @@ private ServiceResponse DescribeNatGateways(Dictionary p) if (filters.TryGetValue("state", out var stateF) && !stateF.Contains((string)nat["State"])) continue; if (filters.TryGetValue("vpc-id", out var vpcF) && !vpcF.Contains((string)nat["VpcId"])) continue; if (filters.TryGetValue("subnet-id", out var subF) && !subF.Contains((string)nat["SubnetId"])) continue; - sb.Append($"{nat["NatGatewayId"]}{nat["SubnetId"]}{nat["VpcId"]}{nat["State"]}{nat["ConnectivityType"]}{nat["CreateTime"]}"); + sb.Append($"{nat["NatGatewayId"]}{nat["SubnetId"]}{nat["VpcId"]}{nat["State"]}{nat["ConnectivityType"]}{nat["CreateTime"]}"); } return Xml(200, "DescribeNatGatewaysResponse", $"{sb}"); } @@ -1702,7 +1702,7 @@ private ServiceResponse CreateNetworkAcl(Dictionary p) ["Tags"] = tags, ["OwnerId"] = accountId, }; if (tags.Count > 0) _tags[aclId] = tags; - return Xml(200, "CreateNetworkAclResponse", $"{aclId}{vpcId}false{accountId}"); + return Xml(200, "CreateNetworkAclResponse", $"{aclId}{vpcId}false{accountId}"); } private ServiceResponse DescribeNetworkAcls(Dictionary p) @@ -1728,7 +1728,7 @@ private ServiceResponse DescribeNetworkAcls(Dictionary p) foreach (var a in assocs) assocsSb.Append($"{a["NetworkAclAssociationId"]}{acl["NetworkAclId"]}{a["SubnetId"]}"); var isDefault = (bool)acl["IsDefault"] ? "true" : "false"; - sb.Append($"{acl["NetworkAclId"]}{acl["VpcId"]}{isDefault}{entriesSb}{assocsSb}{acl["OwnerId"]}"); + sb.Append($"{acl["NetworkAclId"]}{acl["VpcId"]}{isDefault}{entriesSb}{assocsSb}{acl["OwnerId"]}"); } return Xml(200, "DescribeNetworkAclsResponse", $"{sb}"); } @@ -1833,7 +1833,7 @@ private ServiceResponse CreateFlowLogs(Dictionary p) } var idsSb = new StringBuilder(); foreach (var fid in created) idsSb.Append($"{fid}"); - return Xml(200, "CreateFlowLogsResponse", $"{idsSb}"); + return Xml(200, "CreateFlowLogsResponse", $"{idsSb}"); } private ServiceResponse DescribeFlowLogs(Dictionary p) @@ -1854,7 +1854,7 @@ private ServiceResponse DeleteFlowLogs(Dictionary p) { var ids = ParseMemberList(p, "FlowLogId"); foreach (var fid in ids) _flowLogs.TryRemove(fid, out _); - return Xml(200, "DeleteFlowLogsResponse", ""); + return Xml(200, "DeleteFlowLogsResponse", ""); } // ── VPC Peering ─────────────────────────────────────────────────────────── @@ -1879,7 +1879,7 @@ private ServiceResponse CreateVpcPeeringConnection(Dictionary ["Status"] = new Dictionary { ["Code"] = "pending-acceptance", ["Message"] = "Pending Acceptance by " + peerOwnerId }, ["ExpirationTime"] = NowTs(), ["Tags"] = new List>(), }; - return Xml(200, "CreateVpcPeeringConnectionResponse", $"{pcxId}{vpcId}{accountId}{Region}{peerVpcId}{peerOwnerId}{peerRegion}pending-acceptance"); + return Xml(200, "CreateVpcPeeringConnectionResponse", $"{pcxId}{vpcId}{accountId}{Region}{peerVpcId}{peerOwnerId}{peerRegion}pending-acceptance"); } private ServiceResponse AcceptVpcPeeringConnection(Dictionary p) @@ -1890,7 +1890,7 @@ private ServiceResponse AcceptVpcPeeringConnection(Dictionary pcx["Status"] = new Dictionary { ["Code"] = "active", ["Message"] = "Active" }; var req = (Dictionary)pcx["RequesterVpcInfo"]; var acc = (Dictionary)pcx["AccepterVpcInfo"]; - return Xml(200, "AcceptVpcPeeringConnectionResponse", $"{pcxId}{req["VpcId"]}{req["OwnerId"]}{req["Region"]}{acc["VpcId"]}{acc["OwnerId"]}{acc["Region"]}active"); + return Xml(200, "AcceptVpcPeeringConnectionResponse", $"{pcxId}{req["VpcId"]}{req["OwnerId"]}{req["Region"]}{acc["VpcId"]}{acc["OwnerId"]}{acc["Region"]}active"); } private ServiceResponse DescribeVpcPeeringConnections(Dictionary p) @@ -1905,7 +1905,7 @@ private ServiceResponse DescribeVpcPeeringConnections(Dictionary)pcx["RequesterVpcInfo"]; var acc = (Dictionary)pcx["AccepterVpcInfo"]; - sb.Append($"{pcx["VpcPeeringConnectionId"]}{req["VpcId"]}{req["OwnerId"]}{req["Region"]}{acc["VpcId"]}{acc["OwnerId"]}{acc["Region"]}{status["Code"]}{status["Message"]}"); + sb.Append($"{pcx["VpcPeeringConnectionId"]}{req["VpcId"]}{req["OwnerId"]}{req["Region"]}{acc["VpcId"]}{acc["OwnerId"]}{acc["Region"]}{status["Code"]}{status["Message"]}"); } return Xml(200, "DescribeVpcPeeringConnectionsResponse", $"{sb}"); } @@ -1954,7 +1954,7 @@ private ServiceResponse CreateDhcpOptions(Dictionary p) valsSb.Append($"{v}"); configsXml.Append($"{c["Key"]}{valsSb}"); } - return Xml(200, "CreateDhcpOptionsResponse", $"{doptId}{configsXml}{accountId}"); + return Xml(200, "CreateDhcpOptionsResponse", $"{doptId}{configsXml}{accountId}"); } private ServiceResponse AssociateDhcpOptions(Dictionary p) @@ -1985,7 +1985,7 @@ private ServiceResponse DescribeDhcpOptions(Dictionary p) valsSb.Append($"{v}"); configsXml.Append($"{c["Key"]}{valsSb}"); } - sb.Append($"{dopt["DhcpOptionsId"]}{configsXml}{dopt["OwnerId"]}"); + sb.Append($"{dopt["DhcpOptionsId"]}{configsXml}{dopt["OwnerId"]}"); } return Xml(200, "DescribeDhcpOptionsResponse", $"{sb}"); } @@ -2014,7 +2014,7 @@ private ServiceResponse CreateEgressOnlyInternetGateway(Dictionary 0) _tags[eigwId] = tags; - return Xml(200, "CreateEgressOnlyInternetGatewayResponse", $"{eigwId}{vpcId}attached"); + return Xml(200, "CreateEgressOnlyInternetGatewayResponse", $"{eigwId}{vpcId}attached"); } private ServiceResponse DescribeEgressOnlyInternetGateways(Dictionary p) @@ -2024,7 +2024,7 @@ private ServiceResponse DescribeEgressOnlyInternetGateways(Dictionary 0 && !ids.Contains((string)eigw["EgressOnlyInternetGatewayId"])) continue; - sb.Append($"{eigw["EgressOnlyInternetGatewayId"]}{eigw["VpcId"]}{eigw["State"]}"); + sb.Append($"{eigw["EgressOnlyInternetGatewayId"]}{eigw["VpcId"]}{eigw["State"]}"); } return Xml(200, "DescribeEgressOnlyInternetGatewaysResponse", $"{sb}"); } @@ -2506,7 +2506,7 @@ private static string SgXml(Dictionary sg) var egress = new StringBuilder(); foreach (var r in (List>)sg["IpPermissionsEgress"]) egress.Append(PermXml(r)); - return $"{sg["OwnerId"]}{sg["GroupId"]}{sg["GroupName"]}{sg["Description"]}{sg["VpcId"]}{ingress}{egress}"; + return $"{sg["OwnerId"]}{sg["GroupId"]}{sg["GroupName"]}{sg["Description"]}{sg["VpcId"]}{ingress}{egress}"; } private static string PermXml(Dictionary r) @@ -2516,7 +2516,7 @@ private static string PermXml(Dictionary r) ranges.Append($"{ip["CidrIp"]}"); var fromPort = r.ContainsKey("FromPort") ? $"{r["FromPort"]}" : ""; var toPort = r.ContainsKey("ToPort") ? $"{r["ToPort"]}" : ""; - return $"{r.GetValueOrDefault("IpProtocol", "-1")}{fromPort}{toPort}{ranges}"; + return $"{r.GetValueOrDefault("IpProtocol", "-1")}{fromPort}{toPort}{ranges}"; } private static string VpcXml(Dictionary vpc) @@ -2534,7 +2534,7 @@ private static string VpcXml(Dictionary vpc, string tag) extra.Append($"{dsg}"); if (vpc.TryGetValue("MainRouteTableId", out var mrt) && !string.IsNullOrEmpty((string)mrt)) extra.Append($"{mrt}"); - return $"<{tag}>{vpc["VpcId"]}{vpc["State"]}{vpc["CidrBlock"]}{vpc["DhcpOptionsId"]}{vpc["InstanceTenancy"]}{isDefault}{vpc["OwnerId"]}{extra}"; + return $"<{tag}>{vpc["VpcId"]}{vpc["State"]}{vpc["CidrBlock"]}{vpc["DhcpOptionsId"]}{vpc["InstanceTenancy"]}{isDefault}{vpc["OwnerId"]}{extra}"; } private static string SubnetXml(Dictionary subnet) @@ -2548,7 +2548,7 @@ private static string SubnetXml(Dictionary subnet, string tag) var accountId = AccountContext.GetAccountId(); var defaultForAz = (bool)subnet["DefaultForAz"] ? "true" : "false"; var mapPublic = (bool)subnet["MapPublicIpOnLaunch"] ? "true" : "false"; - return $"<{tag}>{subnet["SubnetId"]}arn:aws:ec2:{region}:{accountId}:subnet/{subnet["SubnetId"]}{subnet["State"]}{subnet["VpcId"]}{subnet["CidrBlock"]}{subnet["AvailableIpAddressCount"]}{subnet["AvailabilityZone"]}{defaultForAz}{mapPublic}{subnet["OwnerId"]}"; + return $"<{tag}>{subnet["SubnetId"]}arn:aws:ec2:{region}:{accountId}:subnet/{subnet["SubnetId"]}{subnet["State"]}{subnet["VpcId"]}{subnet["CidrBlock"]}{subnet["AvailableIpAddressCount"]}{subnet["AvailabilityZone"]}{defaultForAz}{mapPublic}{subnet["OwnerId"]}"; } private static string IgwXml(Dictionary igw) @@ -2561,7 +2561,7 @@ private static string IgwXml(Dictionary igw, string tag) var attachments = new StringBuilder(); foreach (var a in (List>)igw["Attachments"]) attachments.Append($"{a["VpcId"]}{a["State"]}"); - return $"<{tag}>{igw["InternetGatewayId"]}{igw["OwnerId"]}{attachments}"; + return $"<{tag}>{igw["InternetGatewayId"]}{igw["OwnerId"]}{attachments}"; } private static string RtbXml(Dictionary rtb) @@ -2596,7 +2596,7 @@ private static string RtbXml(Dictionary rtb, string tag) var isMain = a.ContainsKey("Main") && (bool)a["Main"] ? "true" : "false"; assocsSb.Append($"{a["RouteTableAssociationId"]}{a["RouteTableId"]}
{isMain}
{subnetPart}associated
"); } - return $"<{tag}>{rtb["RouteTableId"]}{rtb["VpcId"]}{rtb["OwnerId"]}{routesSb}{assocsSb}"; + return $"<{tag}>{rtb["RouteTableId"]}{rtb["VpcId"]}{rtb["OwnerId"]}{routesSb}{assocsSb}"; } private static string EniXml(Dictionary eni) @@ -2618,7 +2618,7 @@ private static string EniXml(Dictionary eni, string tag) var privateIp = (string)eni["PrivateIpAddress"]; var region = MicroStackOptions.Instance.Region; var sourceDestCheck = eni.TryGetValue("SourceDestCheck", out var sdc) && sdc is bool sdcBool ? (sdcBool ? "true" : "false") : "true"; - return $"<{tag}>{eni["NetworkInterfaceId"]}{eni["SubnetId"]}{eni["VpcId"]}{eni.GetValueOrDefault("AvailabilityZone", region + "a")}{eni["Description"]}{eni["OwnerId"]}{eni["Status"]}{privateIp}{sourceDestCheck}{eni.GetValueOrDefault("InterfaceType", "interface")}{eni["MacAddress"]}{groups}{privateIp}true{attachment}"; + return $"<{tag}>{eni["NetworkInterfaceId"]}{eni["SubnetId"]}{eni["VpcId"]}{eni.GetValueOrDefault("AvailabilityZone", region + "a")}{eni["Description"]}{eni["OwnerId"]}{eni["Status"]}{privateIp}{sourceDestCheck}{eni.GetValueOrDefault("InterfaceType", "interface")}{eni["MacAddress"]}{groups}{privateIp}true{attachment}"; } private static string VpceXml(Dictionary ep) @@ -2634,7 +2634,7 @@ private static string VpceXml(Dictionary ep, string tag) var subnetIds = new StringBuilder(); foreach (var s in (List)ep.GetValueOrDefault("SubnetIds", new List())!) subnetIds.Append($"{s}"); - return $"<{tag}>{ep["VpcEndpointId"]}{ep["VpcEndpointType"]}{ep["VpcId"]}{ep["ServiceName"]}{ep["State"]}{ep["OwnerId"]}{rtbIds}{subnetIds}"; + return $"<{tag}>{ep["VpcEndpointId"]}{ep["VpcEndpointType"]}{ep["VpcId"]}{ep["ServiceName"]}{ep["State"]}{ep["OwnerId"]}{rtbIds}{subnetIds}"; } private static string VolumeInnerXml(Dictionary vol) @@ -2645,23 +2645,23 @@ private static string VolumeInnerXml(Dictionary vol) var dot = (bool)a["DeleteOnTermination"] ? "true" : "false"; attachments.Append($"{a["VolumeId"]}{a["InstanceId"]}{a["Device"]}{a["State"]}{a["AttachTime"]}{dot}"); } - var snap = !string.IsNullOrEmpty((string)vol["SnapshotId"]) ? $"{vol["SnapshotId"]}" : ""; + var snap = !string.IsNullOrEmpty((string)vol["SnapshotId"]) ? $"{vol["SnapshotId"]}" : ""; var iops = (int)vol["Iops"] != 0 ? $"{vol["Iops"]}" : ""; var encrypted = (bool)vol["Encrypted"] ? "true" : "false"; var multiAttach = (bool)vol["MultiAttachEnabled"] ? "true" : "false"; - return $"{vol["VolumeId"]}{vol["Size"]}{vol["AvailabilityZone"]}{vol["State"]}{vol["CreateTime"]}{vol["VolumeType"]}{snap}{iops}{encrypted}{multiAttach}{attachments}"; + return $"{vol["VolumeId"]}{vol["Size"]}{vol["AvailabilityZone"]}{vol["State"]}{vol["CreateTime"]}{vol["VolumeType"]}{snap}{iops}{encrypted}{multiAttach}{attachments}"; } private static string SnapshotInnerXml(Dictionary snap) { var encrypted = (bool)snap["Encrypted"] ? "true" : "false"; - return $"{snap["SnapshotId"]}{snap["VolumeId"]}{snap["State"]}{snap["StartTime"]}{snap["Progress"]}{snap["OwnerId"]}{snap["VolumeSize"]}{Esc((string)snap["Description"])}{encrypted}{snap["StorageTier"]}"; + return $"{snap["SnapshotId"]}{snap["VolumeId"]}{snap["State"]}{snap["StartTime"]}{snap["Progress"]}{snap["OwnerId"]}{snap["VolumeSize"]}{Esc((string)snap["Description"])}{encrypted}{snap["StorageTier"]}"; } private static string PrefixListXml(Dictionary pl, string tag) { var accountId = AccountContext.GetAccountId(); - return $"<{tag}>{pl["PrefixListId"]}{pl.GetValueOrDefault("PrefixListName", "")}{pl.GetValueOrDefault("State", "create-complete")}{pl.GetValueOrDefault("AddressFamily", "IPv4")}{pl.GetValueOrDefault("MaxEntries", 10)}{pl.GetValueOrDefault("Version", 1)}{pl.GetValueOrDefault("PrefixListArn", "")}{pl.GetValueOrDefault("OwnerId", accountId)}"; + return $"<{tag}>{pl["PrefixListId"]}{pl.GetValueOrDefault("PrefixListName", "")}{pl.GetValueOrDefault("State", "create-complete")}{pl.GetValueOrDefault("AddressFamily", "IPv4")}{pl.GetValueOrDefault("MaxEntries", 10)}{pl.GetValueOrDefault("Version", 1)}{pl.GetValueOrDefault("PrefixListArn", "")}{pl.GetValueOrDefault("OwnerId", accountId)}"; } private static string VgwXml(Dictionary vgw, string tag) @@ -2669,12 +2669,12 @@ private static string VgwXml(Dictionary vgw, string tag) var attachments = new StringBuilder(); foreach (var a in (List>)vgw["Attachments"]) attachments.Append($"{a["VpcId"]}{a["State"]}"); - return $"<{tag}>{vgw["VpnGatewayId"]}{vgw["State"]}{vgw["Type"]}{vgw.GetValueOrDefault("AvailabilityZone", "")}{vgw.GetValueOrDefault("AmazonSideAsn", "64512")}{attachments}"; + return $"<{tag}>{vgw["VpnGatewayId"]}{vgw["State"]}{vgw["Type"]}{vgw.GetValueOrDefault("AvailabilityZone", "")}{vgw.GetValueOrDefault("AmazonSideAsn", "64512")}{attachments}"; } private static string CgwXml(Dictionary cgw, string tag) { - return $"<{tag}>{cgw["CustomerGatewayId"]}{cgw["BgpAsn"]}{cgw["IpAddress"]}{cgw["Type"]}{cgw["State"]}"; + return $"<{tag}>{cgw["CustomerGatewayId"]}{cgw["BgpAsn"]}{cgw["IpAddress"]}{cgw["Type"]}{cgw["State"]}"; } // ── Launch Template helpers ─────────────────────────────────────────────── diff --git a/tests/MicroStack.Tests/AlbTests.cs b/tests/MicroStack.Tests/AlbTests.cs index 430ec1d..fd90950 100644 --- a/tests/MicroStack.Tests/AlbTests.cs +++ b/tests/MicroStack.Tests/AlbTests.cs @@ -78,7 +78,7 @@ await _elb.DeleteLoadBalancerAsync(new DeleteLoadBalancerRequest }); var descAfter = await _elb.DescribeLoadBalancersAsync(new DescribeLoadBalancersRequest()); - descAfter.LoadBalancers.ShouldNotContain(l => l.LoadBalancerArn == lbArn); + (descAfter.LoadBalancers ?? []).ShouldNotContain(l => l.LoadBalancerArn == lbArn); } [Fact] @@ -211,7 +211,7 @@ await _elb.DeleteTargetGroupAsync(new DeleteTargetGroupRequest }); var descAfter = await _elb.DescribeTargetGroupsAsync(new DescribeTargetGroupsRequest()); - descAfter.TargetGroups.ShouldNotContain(t => t.TargetGroupArn == tgArn); + (descAfter.TargetGroups ?? []).ShouldNotContain(t => t.TargetGroupArn == tgArn); } [Fact] @@ -404,7 +404,7 @@ await _elb.DeleteListenerAsync(new DeleteListenerRequest { LoadBalancerArn = lbArn, }); - descAfter.Listeners.ShouldNotContain(l => l.ListenerArn == lArn); + (descAfter.Listeners ?? []).ShouldNotContain(l => l.ListenerArn == lArn); await _elb.DeleteTargetGroupAsync(new DeleteTargetGroupRequest { TargetGroupArn = tgArn }); await _elb.DeleteLoadBalancerAsync(new DeleteLoadBalancerRequest { LoadBalancerArn = lbArn }); @@ -1288,7 +1288,7 @@ public async Task DescribeTargetGroupsByLoadBalancerArn() { LoadBalancerArn = lbArn, }); - before.TargetGroups.ShouldNotContain(tg => tg.TargetGroupArn == tgArn); + (before.TargetGroups ?? []).ShouldNotContain(tg => tg.TargetGroupArn == tgArn); // Create listener to link TG to LB var lArn = (await _elb.CreateListenerAsync(new CreateListenerRequest diff --git a/tests/MicroStack.Tests/Ec2Tests.cs b/tests/MicroStack.Tests/Ec2Tests.cs index 8776e00..668fac6 100644 --- a/tests/MicroStack.Tests/Ec2Tests.cs +++ b/tests/MicroStack.Tests/Ec2Tests.cs @@ -70,7 +70,7 @@ public async Task CreateAndDeleteVpc() await _ec2.DeleteVpcAsync(new DeleteVpcRequest { VpcId = vpcId }); var descAfter = await _ec2.DescribeVpcsAsync(new DescribeVpcsRequest()); - descAfter.Vpcs.ShouldNotContain(v => v.VpcId == vpcId); + (descAfter.Vpcs ?? []).ShouldNotContain(v => v.VpcId == vpcId); } [Fact] @@ -276,7 +276,7 @@ public async Task SecurityGroupCrud() await _ec2.DeleteSecurityGroupAsync(new DeleteSecurityGroupRequest { GroupId = sgId }); var descAfter = await _ec2.DescribeSecurityGroupsAsync(new DescribeSecurityGroupsRequest()); - descAfter.SecurityGroups.ShouldNotContain(sg => sg.GroupId == sgId); + (descAfter.SecurityGroups ?? []).ShouldNotContain(sg => sg.GroupId == sgId); } [Fact] @@ -347,7 +347,7 @@ await _ec2.RevokeSecurityGroupIngressAsync(new RevokeSecurityGroupIngressRequest { GroupIds = [sgId], }); - descAfter.SecurityGroups[0].IpPermissions.ShouldNotContain(p => p.FromPort == 80); + (descAfter.SecurityGroups[0].IpPermissions ?? []).ShouldNotContain(p => p.FromPort == 80); await _ec2.DeleteSecurityGroupAsync(new DeleteSecurityGroupRequest { GroupId = sgId }); } @@ -373,7 +373,7 @@ public async Task KeyPairCrud() await _ec2.DeleteKeyPairAsync(new DeleteKeyPairRequest { KeyName = "qa-ec2-key" }); var descAfter = await _ec2.DescribeKeyPairsAsync(new DescribeKeyPairsRequest()); - descAfter.KeyPairs.ShouldNotContain(kp => kp.KeyName == "qa-ec2-key"); + (descAfter.KeyPairs ?? []).ShouldNotContain(kp => kp.KeyName == "qa-ec2-key"); } [Fact] @@ -504,7 +504,7 @@ await _ec2.DisassociateRouteTableAsync(new DisassociateRouteTableRequest { RouteTableIds = [rtbId], }); - descAfter.RouteTables[0].Associations.ShouldNotContain(a => a.RouteTableAssociationId == assocId); + (descAfter.RouteTables[0].Associations ?? []).ShouldNotContain(a => a.RouteTableAssociationId == assocId); await _ec2.DeleteRouteTableAsync(new DeleteRouteTableRequest { RouteTableId = rtbId }); await _ec2.DeleteSubnetAsync(new DeleteSubnetRequest { SubnetId = subnetId }); @@ -551,7 +551,7 @@ await _ec2.DeleteRouteAsync(new DeleteRouteRequest { RouteTableIds = [rtbId], }); - descAfter.RouteTables[0].Routes.ShouldNotContain(r => r.DestinationCidrBlock == "0.0.0.0/0"); + (descAfter.RouteTables[0].Routes ?? []).ShouldNotContain(r => r.DestinationCidrBlock == "0.0.0.0/0"); await _ec2.DeleteInternetGatewayAsync(new DeleteInternetGatewayRequest { InternetGatewayId = igwId }); await _ec2.DeleteRouteTableAsync(new DeleteRouteTableRequest { RouteTableId = rtbId }); @@ -916,7 +916,7 @@ await _ec2.DeleteTagsAsync(new DeleteTagsRequest InstanceIds = [iid], }); var tags2 = desc2.Reservations[0].Instances[0].Tags; - tags2.ShouldNotContain(t => t.Key == "Name"); + (tags2 ?? []).ShouldNotContain(t => t.Key == "Name"); await _ec2.TerminateInstancesAsync(new TerminateInstancesRequest { InstanceIds = [iid] }); } @@ -987,7 +987,7 @@ await _ec2.CreateTagsAsync(new CreateTagsRequest { Filters = [new Filter("resource-id", ["i-doesnotexist"])], }); - resp4.Tags.ShouldBeEmpty(); + (resp4.Tags ?? []).ShouldBeEmpty(); // All tags have correct resource type var resp5 = await _ec2.DescribeTagsAsync(new DescribeTagsRequest @@ -1032,7 +1032,7 @@ await _ec2.DeleteNetworkInterfaceAsync(new DeleteNetworkInterfaceRequest }); var descAfter = await _ec2.DescribeNetworkInterfacesAsync(new DescribeNetworkInterfacesRequest()); - descAfter.NetworkInterfaces.ShouldNotContain(e => e.NetworkInterfaceId == eniId); + (descAfter.NetworkInterfaces ?? []).ShouldNotContain(e => e.NetworkInterfaceId == eniId); await _ec2.DeleteSubnetAsync(new DeleteSubnetRequest { SubnetId = subnetId }); await _ec2.DeleteVpcAsync(new DeleteVpcRequest { VpcId = vpcId }); @@ -1124,7 +1124,7 @@ await _ec2.DeleteVpcEndpointsAsync(new DeleteVpcEndpointsRequest }); var descAfter = await _ec2.DescribeVpcEndpointsAsync(new DescribeVpcEndpointsRequest()); - descAfter.VpcEndpoints.ShouldNotContain(e => e.VpcEndpointId == vpceId); + (descAfter.VpcEndpoints ?? []).ShouldNotContain(e => e.VpcEndpointId == vpceId); await _ec2.DeleteVpcAsync(new DeleteVpcRequest { VpcId = vpcId }); } @@ -1167,7 +1167,7 @@ await _ec2.ModifyVpcEndpointAsync(new ModifyVpcEndpointRequest { VpcEndpointIds = [vpceId], }); - desc.VpcEndpoints[0].RouteTableIds.ShouldNotContain(rtbId); + (desc.VpcEndpoints[0].RouteTableIds ?? []).ShouldNotContain(rtbId); await _ec2.DeleteVpcEndpointsAsync(new DeleteVpcEndpointsRequest { VpcEndpointIds = [vpceId] }); await _ec2.DeleteRouteTableAsync(new DeleteRouteTableRequest { RouteTableId = rtbId }); @@ -1252,7 +1252,7 @@ await _ec2.AttachVolumeAsync(new AttachVolumeRequest VolumeIds = [volId], }); desc2.Volumes[0].State.ShouldBe(VolumeState.Available); - desc2.Volumes[0].Attachments.ShouldBeEmpty(); + (desc2.Volumes[0].Attachments ?? []).ShouldBeEmpty(); } [Fact] @@ -1272,7 +1272,7 @@ public async Task DeleteVolume() { VolumeIds = [volId], }); - desc.Volumes.ShouldBeEmpty(); + (desc.Volumes ?? []).ShouldBeEmpty(); } [Fact] @@ -1416,7 +1416,7 @@ await _ec2.DeleteSnapshotAsync(new DeleteSnapshotRequest { SnapshotIds = [snap.Snapshot.SnapshotId], }); - desc.Snapshots.ShouldBeEmpty(); + (desc.Snapshots ?? []).ShouldBeEmpty(); } [Fact] @@ -1536,14 +1536,14 @@ public async Task DescribeInstanceCreditSpecifications() public async Task DescribeSpotInstanceRequests() { var resp = await _ec2.DescribeSpotInstanceRequestsAsync(new DescribeSpotInstanceRequestsRequest()); - resp.SpotInstanceRequests.ShouldBeEmpty(); + (resp.SpotInstanceRequests ?? []).ShouldBeEmpty(); } [Fact] public async Task DescribeCapacityReservations() { var resp = await _ec2.DescribeCapacityReservationsAsync(new DescribeCapacityReservationsRequest()); - resp.CapacityReservations.ShouldBeEmpty(); + (resp.CapacityReservations ?? []).ShouldBeEmpty(); } // ── Describe Prefix Lists ─────────────────────────────────────────────────── @@ -1818,14 +1818,14 @@ await _ec2.DeleteNetworkAclEntryAsync(new DeleteNetworkAclEntryRequest { NetworkAclIds = [aclId], }); - desc3.NetworkAcls[0].Entries.ShouldBeEmpty(); + (desc3.NetworkAcls[0].Entries ?? []).ShouldBeEmpty(); await _ec2.DeleteNetworkAclAsync(new DeleteNetworkAclRequest { NetworkAclId = aclId }); var desc4 = await _ec2.DescribeNetworkAclsAsync(new DescribeNetworkAclsRequest { NetworkAclIds = [aclId], }); - desc4.NetworkAcls.ShouldBeEmpty(); + (desc4.NetworkAcls ?? []).ShouldBeEmpty(); } [Fact] @@ -1881,7 +1881,7 @@ public async Task FlowLogsCrud() LogDestinationType = LogDestinationType.CloudWatchLogs, LogGroupName = "/aws/vpc/flowlogs", }); - resp.Unsuccessful.ShouldBeEmpty(); + (resp.Unsuccessful ?? []).ShouldBeEmpty(); var flIds = resp.FlowLogIds; flIds.ShouldHaveSingleItem(); flIds[0].ShouldStartWith("fl-"); @@ -1899,7 +1899,7 @@ public async Task FlowLogsCrud() { FlowLogIds = flIds, }); - desc2.FlowLogs.ShouldBeEmpty(); + (desc2.FlowLogs ?? []).ShouldBeEmpty(); } // ── VPC Peering Tests ─────────────────────────────────────────────────────── @@ -2003,7 +2003,7 @@ await _ec2.AssociateDhcpOptionsAsync(new AssociateDhcpOptionsRequest { DhcpOptionsIds = [doptId], }); - desc2.DhcpOptions.ShouldBeEmpty(); + (desc2.DhcpOptions ?? []).ShouldBeEmpty(); } [Fact] @@ -2051,7 +2051,7 @@ await _ec2.DeleteEgressOnlyInternetGatewayAsync(new DeleteEgressOnlyInternetGate { EgressOnlyInternetGatewayIds = [eigwId], }); - desc2.EgressOnlyInternetGateways.ShouldBeEmpty(); + (desc2.EgressOnlyInternetGateways ?? []).ShouldBeEmpty(); } [Fact] @@ -2113,14 +2113,14 @@ await _ec2.DetachVpnGatewayAsync(new DetachVpnGatewayRequest { VpnGatewayIds = [vgwId], }); - desc2.VpnGateways[0].VpcAttachments.ShouldBeEmpty(); + (desc2.VpnGateways[0].VpcAttachments ?? []).ShouldBeEmpty(); await _ec2.DeleteVpnGatewayAsync(new DeleteVpnGatewayRequest { VpnGatewayId = vgwId }); var desc3 = await _ec2.DescribeVpnGatewaysAsync(new DescribeVpnGatewaysRequest { VpnGatewayIds = [vgwId], }); - desc3.VpnGateways.ShouldBeEmpty(); + (desc3.VpnGateways ?? []).ShouldBeEmpty(); } finally { @@ -2195,7 +2195,7 @@ await _ec2.DeleteCustomerGatewayAsync(new DeleteCustomerGatewayRequest { CustomerGatewayIds = [cgwId], }); - desc2.CustomerGateways.ShouldBeEmpty(); + (desc2.CustomerGateways ?? []).ShouldBeEmpty(); } // ── Create Route with NAT Gateway ─────────────────────────────────────────── @@ -2322,8 +2322,8 @@ await _ec2.ModifyManagedPrefixListAsync(new ModifyManagedPrefixListRequest PrefixListId = plId, }); cidrs = entries.Entries.Select(e => e.Cidr).ToList(); - cidrs.ShouldNotContain("10.0.0.0/8"); - cidrs.ShouldContain("172.16.0.0/12"); + (cidrs ?? []).ShouldNotContain("10.0.0.0/8"); + (cidrs ?? []).ShouldContain("172.16.0.0/12"); // Delete await _ec2.DeleteManagedPrefixListAsync(new DeleteManagedPrefixListRequest @@ -2334,7 +2334,7 @@ await _ec2.DeleteManagedPrefixListAsync(new DeleteManagedPrefixListRequest { PrefixListIds = [plId], }); - desc.PrefixLists.ShouldBeEmpty(); + (desc.PrefixLists ?? []).ShouldBeEmpty(); } // ── Launch Template Tests ─────────────────────────────────────────────────── @@ -2395,7 +2395,7 @@ await _ec2.DeleteLaunchTemplateAsync(new DeleteLaunchTemplateRequest { LaunchTemplateIds = [ltId], }); - desc3.LaunchTemplates.ShouldBeEmpty(); + (desc3.LaunchTemplates ?? []).ShouldBeEmpty(); } [Fact] diff --git a/tests/MicroStack.Tests/IamTests.cs b/tests/MicroStack.Tests/IamTests.cs index 23348a0..8d81f60 100644 --- a/tests/MicroStack.Tests/IamTests.cs +++ b/tests/MicroStack.Tests/IamTests.cs @@ -636,7 +636,7 @@ await _iam.UntagPolicyAsync(new UntagPolicyRequest var tags2 = await _iam.ListPolicyTagsAsync( new ListPolicyTagsRequest { PolicyArn = policyArn }); - tags2.Tags.ShouldNotContain(t => t.Key == "env"); + (tags2.Tags ?? []).ShouldNotContain(t => t.Key == "env"); } // ── Update role ───────────────────────────────────────────────────────────── @@ -902,6 +902,6 @@ await _iam.DetachUserPolicyAsync(new DetachUserPolicyRequest EntityFilter = EntityType.Role, }); (((resp3.PolicyRoles ?? []).Count >= 1)).ShouldBe(true); - resp3.PolicyUsers.ShouldBeEmpty(); + (resp3.PolicyUsers ?? []).ShouldBeEmpty(); } } diff --git a/tests/MicroStack.Tests/PersistenceTests.cs b/tests/MicroStack.Tests/PersistenceTests.cs index 863d335..0c7c2ca 100644 --- a/tests/MicroStack.Tests/PersistenceTests.cs +++ b/tests/MicroStack.Tests/PersistenceTests.cs @@ -179,7 +179,7 @@ public async Task SnsPersistRoundtrip() // Reset and verify topic is gone handler.Reset(); var topics = await _sns.ListTopicsAsync(); - topics.Topics.ShouldNotContain(t => t.TopicArn == created.TopicArn); + (topics.Topics ?? []).ShouldNotContain(t => t.TopicArn == created.TopicArn); // Restore and verify topic is back handler.RestoreState(state.Value); @@ -304,7 +304,7 @@ await Should.ThrowAsync( () => _sqs.GetQueueUrlAsync("persist-reset-q")); var topics = await _sns.ListTopicsAsync(); - topics.Topics.ShouldBeEmpty(); + (topics.Topics ?? []).ShouldBeEmpty(); await Should.ThrowAsync( () => _ssm.GetParameterAsync(new GetParameterRequest { Name = "/persist/reset-key" })); diff --git a/tests/MicroStack.Tests/RdsTests.cs b/tests/MicroStack.Tests/RdsTests.cs index aa0dd82..820b0ae 100644 --- a/tests/MicroStack.Tests/RdsTests.cs +++ b/tests/MicroStack.Tests/RdsTests.cs @@ -663,7 +663,7 @@ await _rds.DeleteDBSnapshotAsync(new DeleteDBSnapshotRequest }); var snaps2 = await _rds.DescribeDBSnapshotsAsync(new DescribeDBSnapshotsRequest()); - snaps2.DBSnapshots.ShouldNotContain(s => s.DBSnapshotIdentifier == "qa-rds-snap-1"); + (snaps2.DBSnapshots ?? []).ShouldNotContain(s => s.DBSnapshotIdentifier == "qa-rds-snap-1"); await _rds.DeleteDBInstanceAsync(new DeleteDBInstanceRequest { @@ -1030,7 +1030,7 @@ await _rds.RemoveFromGlobalClusterAsync(new RemoveFromGlobalClusterRequest { GlobalClusterIdentifier = "test-global-src", }); - resp2.GlobalClusters[0].GlobalClusterMembers.ShouldBeEmpty(); + (resp2.GlobalClusters[0].GlobalClusterMembers ?? []).ShouldBeEmpty(); await _rds.DeleteGlobalClusterAsync(new DeleteGlobalClusterRequest { diff --git a/tests/MicroStack.Tests/S3Tests.cs b/tests/MicroStack.Tests/S3Tests.cs index a5e0776..ef2c5fd 100644 --- a/tests/MicroStack.Tests/S3Tests.cs +++ b/tests/MicroStack.Tests/S3Tests.cs @@ -91,7 +91,7 @@ public async Task DeleteBucket() await _s3.DeleteBucketAsync("del-bucket-test"); var list = await _s3.ListBucketsAsync(); - list.Buckets.ShouldNotContain(b => b.BucketName == "del-bucket-test"); + (list.Buckets ?? []).ShouldNotContain(b => b.BucketName == "del-bucket-test"); } [Fact] @@ -285,7 +285,7 @@ public async Task ListObjectsV1() ((resp.CommonPrefixes ?? []).Count).ShouldBe(2); resp.CommonPrefixes!.ShouldContain("photos/2024/"); resp.CommonPrefixes!.ShouldContain("photos/2025/"); - resp.S3Objects.ShouldBeEmpty(); + (resp.S3Objects ?? []).ShouldBeEmpty(); } [Fact] diff --git a/tests/MicroStack.Tests/SesTests.cs b/tests/MicroStack.Tests/SesTests.cs index 200a717..3b9ecac 100644 --- a/tests/MicroStack.Tests/SesTests.cs +++ b/tests/MicroStack.Tests/SesTests.cs @@ -201,7 +201,7 @@ await _ses.DeleteIdentityAsync(new SesV1.Model.DeleteIdentityRequest }); var resp = await _ses.ListIdentitiesAsync(new SesV1.Model.ListIdentitiesRequest()); - resp.Identities.ShouldNotContain("delete@example.com"); + (resp.Identities ?? []).ShouldNotContain("delete@example.com"); } [Fact] @@ -269,7 +269,7 @@ await _ses.DeleteConfigurationSetAsync(new SesV1.Model.DeleteConfigurationSetReq }); var sets2 = await _ses.ListConfigurationSetsAsync(new SesV1.Model.ListConfigurationSetsRequest()); - sets2.ConfigurationSets.ShouldNotContain(s => s.Name == "test-config"); + (sets2.ConfigurationSets ?? []).ShouldNotContain(s => s.Name == "test-config"); } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/tests/MicroStack.Tests/SnsTests.cs b/tests/MicroStack.Tests/SnsTests.cs index efac157..82396e6 100644 --- a/tests/MicroStack.Tests/SnsTests.cs +++ b/tests/MicroStack.Tests/SnsTests.cs @@ -454,7 +454,7 @@ public async Task PublishBatch() }); (resp.Successful?.Count ?? 0).ShouldBe(3); - resp.Failed.ShouldBeEmpty(); + (resp.Failed ?? []).ShouldBeEmpty(); } [Fact] @@ -512,7 +512,7 @@ await _sns.PublishAsync(new PublishRequest MaxNumberOfMessages = 1, WaitTimeSeconds = 0, }); - msgs.Messages.ShouldBeEmpty(); + (msgs.Messages ?? []).ShouldBeEmpty(); // Publish matching message (blue) await _sns.PublishAsync(new PublishRequest