test: characterize RDS DB subnet group lifecycle before extraction

This commit is contained in:
joshsiv-mulga
2026-10-08 09:45:59 +10:00
parent a147edca53
commit 01276bf29d
+163
View File
@@ -503,3 +503,166 @@ func TestModifyDBSubnetGroup_RejectsAnUnknownName(t *testing.T) {
assert.Equal(t, awserrors.ErrorDBSubnetGroupNotFound, awserrors.ValidErrorCodeFromError(err),
"the code has to survive resolution or the client sees a 500")
}
// AWS's delete is not idempotent: a repeat, or a delete of a name never
// created, reports the group's own not-found fault rather than success.
func TestDeleteDBSubnetGroup_RepeatReportsNotFound(t *testing.T) {
t.Parallel()
h := newCreateHarness(t, testBaseDomain)
_, err := h.svc.CreateDBSubnetGroup(t.Context(), subnetGroupInput(testSubnetGroup, "subnet-alpha"), testAccountID)
require.NoError(t, err)
input := &rds.DeleteDBSubnetGroupInput{DBSubnetGroupName: aws.String(testSubnetGroup)}
_, err = h.svc.DeleteDBSubnetGroup(t.Context(), input, testAccountID)
require.NoError(t, err)
_, err = h.svc.DeleteDBSubnetGroup(t.Context(), input, testAccountID)
require.Error(t, err)
assert.Equal(t, awserrors.ErrorDBSubnetGroupNotFound, awserrors.ValidErrorCodeFromError(err),
"the code has to survive resolution or the client sees a 500")
}
// Identity is the name within one account: another account neither sees nor
// deletes the group, and may hold its own group of the same name.
func TestDBSubnetGroup_NameIsScopedToTheAccount(t *testing.T) {
t.Parallel()
const otherAccount = "210987654321"
h := newCreateHarness(t, testBaseDomain)
_, err := h.svc.CreateDBSubnetGroup(t.Context(), subnetGroupInput(testSubnetGroup, "subnet-alpha"), testAccountID)
require.NoError(t, err)
_, err = h.svc.DescribeDBSubnetGroups(t.Context(),
&rds.DescribeDBSubnetGroupsInput{DBSubnetGroupName: aws.String(testSubnetGroup)}, otherAccount)
assert.Equal(t, awserrors.ErrorDBSubnetGroupNotFound, awserrors.ValidErrorCodeFromError(err))
_, err = h.svc.DeleteDBSubnetGroup(t.Context(),
&rds.DeleteDBSubnetGroupInput{DBSubnetGroupName: aws.String(testSubnetGroup)}, otherAccount)
assert.Equal(t, awserrors.ErrorDBSubnetGroupNotFound, awserrors.ValidErrorCodeFromError(err))
_, err = h.svc.CreateDBSubnetGroup(t.Context(), subnetGroupInput(testSubnetGroup, "subnet-zebra"), otherAccount)
require.NoError(t, err)
out, err := h.svc.DescribeDBSubnetGroups(t.Context(), &rds.DescribeDBSubnetGroupsInput{}, testAccountID)
require.NoError(t, err)
require.Len(t, out.DBSubnetGroups, 1)
require.Len(t, out.DBSubnetGroups[0].Subnets, 1)
assert.Equal(t, "subnet-alpha", aws.StringValue(out.DBSubnetGroups[0].Subnets[0].SubnetIdentifier),
"the other account's create must not overwrite this one")
}
// The group is read once, at placement: changing its subnets afterwards
// shapes later placements only and never moves an instance already placed.
func TestModifyDBSubnetGroup_LeavesAPlacedInstanceWhereItIs(t *testing.T) {
t.Parallel()
h := newCreateHarness(t, testBaseDomain)
_, err := h.svc.CreateDBSubnetGroup(t.Context(), subnetGroupInput(testSubnetGroup, "subnet-zebra"), testAccountID)
require.NoError(t, err)
input := validCreateInput()
input.DBSubnetGroupName = aws.String(testSubnetGroup)
_, err = h.svc.CreateDBInstance(t.Context(), input, testAccountID)
require.NoError(t, err)
require.Equal(t, "subnet-zebra", h.record(t, testDBInstanceID).SubnetID)
_, err = h.svc.ModifyDBSubnetGroup(t.Context(),
modifySubnetGroupInput(testSubnetGroup, "subnet-alpha", "subnet-zebra"), testAccountID)
require.NoError(t, err)
rec := h.record(t, testDBInstanceID)
assert.Equal(t, "subnet-zebra", rec.SubnetID, "a fresh placement would now pick subnet-alpha")
assert.Equal(t, testSubnetGroup, rec.DBSubnetGroupName)
}
// Only instances hold a group in use: a snapshot records the group's name for
// a later restore, but does not block the group's delete.
func TestDeleteDBSubnetGroup_IsNotBlockedByASnapshotNamingIt(t *testing.T) {
t.Parallel()
h := newCreateHarness(t, testBaseDomain)
_, err := h.svc.CreateDBSubnetGroup(t.Context(), subnetGroupInput(testSubnetGroup, "subnet-alpha"), testAccountID)
require.NoError(t, err)
kv, err := h.svc.bucket(t.Context(), testAccountID)
require.NoError(t, err)
require.NoError(t, putJSON(t.Context(), kv, DBSnapshotKey("orders-snap"), &DBSnapshotRecord{
DBSnapshotIdentifier: "orders-snap",
AccountID: testAccountID,
Status: SnapshotStatusAvailable,
DBSubnetGroupName: testSubnetGroup,
}))
_, err = h.svc.DeleteDBSubnetGroup(t.Context(),
&rds.DeleteDBSubnetGroupInput{DBSubnetGroupName: aws.String(testSubnetGroup)}, testAccountID)
require.NoError(t, err)
}
// Restore is the second consumer of the group's placement: an unknown group
// fails it with the group's own fault before anything is reserved.
func TestRestoreDBInstanceFromDBSnapshot_RejectsAnUnknownSubnetGroup(t *testing.T) {
t.Parallel()
h := newSnapshotHarness(t, false)
h.seedSnapshot(t)
input := restoreInput()
input.DBSubnetGroupName = aws.String("absent")
_, err := h.svc.RestoreDBInstanceFromDBSnapshot(t.Context(), input, testAccountID)
require.Error(t, err)
assert.Equal(t, awserrors.ErrorDBSubnetGroupNotFound, awserrors.ValidErrorCodeFromError(err),
"the code has to survive resolution or the client sees a 500")
assert.False(t, h.instanceExists(t, testRestoredID), "a rejected restore must reserve nothing")
}
// Tags live on the group's own record, so they are reached through its ARN and
// go when the group does.
func TestDBSubnetGroup_TagsAreReachedThroughItsARN(t *testing.T) {
t.Parallel()
h := newCreateHarness(t, testBaseDomain)
input := subnetGroupInput(testSubnetGroup, "subnet-alpha")
input.Tags = awsTags("env", "prod")
_, err := h.svc.CreateDBSubnetGroup(t.Context(), input, testAccountID)
require.NoError(t, err)
arn := aws.String(FormatARN(ResourceKindDBSubnetGroup, testRegion, testAccountID, testSubnetGroup))
out, err := h.svc.ListTagsForResource(t.Context(), &rds.ListTagsForResourceInput{ResourceName: arn}, testAccountID)
require.NoError(t, err)
require.Len(t, out.TagList, 1)
assert.Equal(t, "env", aws.StringValue(out.TagList[0].Key))
assert.Equal(t, "prod", aws.StringValue(out.TagList[0].Value))
_, err = h.svc.DeleteDBSubnetGroup(t.Context(),
&rds.DeleteDBSubnetGroupInput{DBSubnetGroupName: aws.String(testSubnetGroup)}, testAccountID)
require.NoError(t, err)
_, err = h.svc.ListTagsForResource(t.Context(), &rds.ListTagsForResourceInput{ResourceName: arn}, testAccountID)
require.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorDBSubnetGroupNotFound)
}
// The stored shape outlives any move of the code that writes it, so a record
// written before a relocation must still decode after it.
func TestDBSubnetGroupRecord_PersistedFieldNames(t *testing.T) {
t.Parallel()
h := newCreateHarness(t, testBaseDomain)
input := subnetGroupInput(testSubnetGroup, "subnet-alpha")
input.Tags = awsTags("env", "prod")
_, err := h.svc.CreateDBSubnetGroup(t.Context(), input, testAccountID)
require.NoError(t, err)
kv, err := h.svc.bucket(t.Context(), testAccountID)
require.NoError(t, err)
js, err := kv.KV(t.Context())
require.NoError(t, err)
entry, err := js.Get(t.Context(), DBSubnetGroupKey(testSubnetGroup))
require.NoError(t, err)
var raw map[string]json.RawMessage
require.NoError(t, json.Unmarshal(entry.Value(), &raw))
keys := make([]string, 0, len(raw))
for k := range raw {
keys = append(keys, k)
}
assert.ElementsMatch(t, []string{
"name", "accountId", "description", "subnets", "vpcId", "tags", "createdAt", "updatedAt",
}, keys)
var subnets []map[string]string
require.NoError(t, json.Unmarshal(raw["subnets"], &subnets))
assert.Equal(t, []map[string]string{{"subnetId": "subnet-alpha", "availabilityZone": testZone}}, subnets)
}