@@ -20,6 +20,7 @@ import (
2020 "github.com/stretchr/testify/require"
2121
2222 "instant.dev/internal/config"
23+ "instant.dev/internal/crypto"
2324 "instant.dev/internal/handlers"
2425 "instant.dev/internal/middleware"
2526 "instant.dev/internal/plans"
@@ -93,6 +94,17 @@ func rfErr(t *testing.T, resp *http.Response) string {
9394 return ""
9495}
9596
97+ // rfEncryptURL encrypts a connection URL with the test AES key so a seeded
98+ // resource's stored URL decrypts cleanly in the handler.
99+ func rfEncryptURL (t * testing.T , plain string ) string {
100+ t .Helper ()
101+ key , err := crypto .ParseAESKey (testhelpers .TestAESKeyHex )
102+ require .NoError (t , err )
103+ enc , err := crypto .Encrypt (key , plain )
104+ require .NoError (t , err )
105+ return enc
106+ }
107+
96108// Pause: GetResourceByToken errors → fetch_failed (resource.go:567). failAfter=0.
97109func TestResourceFinal_Pause_LookupError_503 (t * testing.T ) {
98110 seedDB , clean := testhelpers .SetupTestDB (t )
@@ -180,3 +192,28 @@ func TestResourceFinal_Rotate_LookupError_503(t *testing.T) {
180192 require .Equal (t , http .StatusServiceUnavailable , resp .StatusCode )
181193 assert .Equal (t , "fetch_failed" , rfErr (t , resp ))
182194}
195+
196+ // RotateCredentials: UpdateConnectionURL errors → update_failed
197+ // (resource.go:499). The resource must have a DECRYPTABLE connection_url so the
198+ // rotate reaches the persist step; seed it with a real encrypted URL. resource
199+ // lookup(1) succeeds, the postgres ALTER ROLE is a no-op (nil customer DB), the
200+ // UpdateConnectionURL UPDATE(2) errors. failAfter=1.
201+ func TestResourceFinal_Rotate_UpdateFailed_503 (t * testing.T ) {
202+ seedDB , clean := testhelpers .SetupTestDB (t )
203+ defer clean ()
204+ teamID := testhelpers .MustCreateTeamDB (t , seedDB , "pro" )
205+ // Encrypt a real postgres URL with the test AES key so decrypt + url-parse
206+ // succeed and the handler reaches UpdateConnectionURL.
207+ enc := rfEncryptURL (t , "postgres://usr:pw@host:5432/db_x" )
208+ var token string
209+ require .NoError (t , seedDB .QueryRowContext (context .Background (),
210+ `INSERT INTO resources (team_id, resource_type, tier, status, connection_url)
211+ VALUES ($1::uuid, 'postgres', 'pro', 'active', $2) RETURNING token::text` ,
212+ teamID , enc ).Scan (& token ))
213+
214+ app := resourceFaultApp (t , openFaultDB (t , 1 ), teamID )
215+ resp := rfPost (t , app , "/r/" + token + "/rotate" )
216+ defer resp .Body .Close ()
217+ require .Equal (t , http .StatusServiceUnavailable , resp .StatusCode )
218+ assert .Equal (t , "update_failed" , rfErr (t , resp ))
219+ }
0 commit comments