fix: return InvalidParameterValue for a missing RDS master password

ValidateMasterUserPassword built the empty-password error with a bare
errors.New carrying the code only in its text, which awserrors does not
resolve, so CreateDBInstance without MasterUserPassword returned HTTP 500
ServerInternal. Build it with awserrors.Errorf like the other branches so
the gateway returns 400 InvalidParameterValue, and cover the full gateway
to RDS service path with a regression test.
This commit is contained in:
joshsiv-mulga
2026-10-08 14:43:25 +10:00
parent 955486421b
commit f1f194b16a
3 changed files with 36 additions and 11 deletions
+31
View File
@@ -9,10 +9,13 @@ import (
"strings"
"testing"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/mulgadc/spinifex/internal/testkit"
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
natsmsg "github.com/mulgadc/spinifex/spinifex/foundation/messaging/nats"
handlers_iam "github.com/mulgadc/spinifex/spinifex/handlers/iam"
handlers_rds "github.com/mulgadc/spinifex/spinifex/handlers/rds"
"github.com/nats-io/nats.go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -361,3 +364,31 @@ func TestRDSErrorHandler_UsesRDSUnsupportedActionWording(t *testing.T) {
}
var errNotImplementedForTest = errors.New(awserrors.ErrorNotImplemented)
// TestRDSRequest_CreateDBInstance_EmptyMasterUserPasswordReturns400 runs the real public path:
// a CreateDBInstance query request through RDS_Request, over NATS, into the production
// handlers_rds.Service, and the resulting error through ErrorHandler's HTTP/XML rendering. AWS
// returns 400 InvalidParameterValue for a create with no MasterUserPassword.
func TestRDSRequest_CreateDBInstance_EmptyMasterUserPasswordReturns400(t *testing.T) {
_, nc, _ := testutil.StartTestJetStream(t)
svc := handlers_rds.NewService(nc, "ap-southeast-2")
sub, err := nc.Subscribe(handlers_rds.SubjectCreateDBInstance, func(msg *nats.Msg) {
natsmsg.ServeNATSRequestCtx(msg, func(ctx context.Context, in *rds.CreateDBInstanceInput) (*rds.CreateDBInstanceOutput, error) {
return svc.CreateDBInstance(ctx, in, natsmsg.AccountIDFromMsg(msg))
})
})
require.NoError(t, err)
t.Cleanup(func() { _ = sub.Unsubscribe() })
gw := &GatewayConfig{DisableLogging: true, NATSConn: nc, IAMService: allowAllIAMService()}
req := setupRDSRequest("Action=CreateDBInstance&DBInstanceIdentifier=orders-db&Engine=postgres&" +
"DBInstanceClass=db.t3.medium&AllocatedStorage=20&MasterUsername=appuser&DBName=orders")
reqErr := gw.RDS_Request(httptest.NewRecorder(), req)
require.Error(t, reqErr)
w := httptest.NewRecorder()
gw.ErrorHandler(w, req, reqErr)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "<Code>"+awserrors.ErrorInvalidParameterValue+"</Code>")
}
+1 -1
View File
@@ -255,7 +255,7 @@ func validateIdentifier(field, value string, maxLen int, allowEmpty bool) error
func ValidateMasterUserPassword(password string) error {
switch {
case password == "":
return errors.New(awserrors.ErrorInvalidParameterValue + ": MasterUserPassword is required")
return awserrors.Errorf(awserrors.ErrorInvalidParameterValue, "MasterUserPassword is required")
case len(password) < minMasterPasswordLen || len(password) > maxMasterPasswordLen:
return awserrors.Errorf(awserrors.ErrorInvalidParameterValue,
"MasterUserPassword must be between %d and %d characters", minMasterPasswordLen, maxMasterPasswordLen)
@@ -56,21 +56,15 @@ func TestEngineValidateVersion_UnsupportedVersionCarriesExactCode(t *testing.T)
assert.Equal(t, awserrors.ErrorInvalidParameterValue, awserrors.ValidErrorCodeFromError(err))
}
// ValidateMasterUserPassword's three rejections are built two different ways
// today: the length and character checks go through awserrors.Errorf, whose
// wrapped code resolves through ValidErrorCodeFromError; the empty-password
// check is a bare errors.New with the code only prefixed into the message
// text, which ValidErrorCodeFromError's exact-match lookup does not resolve.
// This pins that divergence rather than papering over it.
// ValidateMasterUserPassword's three rejections all go through awserrors.Errorf,
// so each one resolves through ValidErrorCodeFromError to the same code.
func TestValidateMasterUserPassword_ExactCodesAndMessages(t *testing.T) {
t.Parallel()
empty := ValidateMasterUserPassword("")
require.Error(t, empty)
assert.Equal(t, "InvalidParameterValue: MasterUserPassword is required", empty.Error())
assert.Equal(t, awserrors.ErrorServerInternal, awserrors.ValidErrorCodeFromError(empty),
"today this does NOT resolve to InvalidParameterValue: the code is only prefixed into the "+
"message text, not carried as a wrapped codedError, so the helper's exact-match lookup misses it")
assert.Equal(t, "MasterUserPassword is required: InvalidParameterValue", empty.Error())
assert.Equal(t, awserrors.ErrorInvalidParameterValue, awserrors.ValidErrorCodeFromError(empty))
tooShort := ValidateMasterUserPassword("short1")
require.Error(t, tooShort)