@@ -11,6 +11,7 @@ import (
11
11
12
12
"github.com/h2non/gock"
13
13
"github.com/spf13/afero"
14
+ "github.com/spf13/viper"
14
15
"github.com/stretchr/testify/assert"
15
16
"github.com/stretchr/testify/require"
16
17
"github.com/supabase/cli/internal/testing/apitest"
@@ -74,7 +75,7 @@ func TestDeployCommand(t *testing.T) {
74
75
}
75
76
// Run test
76
77
noVerifyJWT := true
77
- err = Run (context .Background (), functions , true , & noVerifyJWT , "" , 1 , fsys )
78
+ err = Run (context .Background (), functions , true , & noVerifyJWT , "" , 1 , false , fsys )
78
79
// Check error
79
80
assert .NoError (t , err )
80
81
assert .Empty (t , apitest .ListUnmatchedRequests ())
@@ -129,7 +130,7 @@ import_map = "./import_map.json"
129
130
outputDir := filepath .Join (utils .TempDir , fmt .Sprintf (".output_%s" , slug ))
130
131
require .NoError (t , afero .WriteFile (fsys , filepath .Join (outputDir , "output.eszip" ), []byte ("" ), 0644 ))
131
132
// Run test
132
- err = Run (context .Background (), nil , true , nil , "" , 1 , fsys )
133
+ err = Run (context .Background (), nil , true , nil , "" , 1 , false , fsys )
133
134
// Check error
134
135
assert .NoError (t , err )
135
136
assert .Empty (t , apitest .ListUnmatchedRequests ())
@@ -182,7 +183,7 @@ import_map = "./import_map.json"
182
183
outputDir := filepath .Join (utils .TempDir , ".output_enabled-func" )
183
184
require .NoError (t , afero .WriteFile (fsys , filepath .Join (outputDir , "output.eszip" ), []byte ("" ), 0644 ))
184
185
// Run test
185
- err = Run (context .Background (), nil , true , nil , "" , 1 , fsys )
186
+ err = Run (context .Background (), nil , true , nil , "" , 1 , false , fsys )
186
187
// Check error
187
188
assert .NoError (t , err )
188
189
assert .Empty (t , apitest .ListUnmatchedRequests ())
@@ -193,7 +194,7 @@ import_map = "./import_map.json"
193
194
fsys := afero .NewMemMapFs ()
194
195
require .NoError (t , utils .WriteConfig (fsys , false ))
195
196
// Run test
196
- err := Run (context .Background (), []string {"_invalid" }, true , nil , "" , 1 , fsys )
197
+ err := Run (context .Background (), []string {"_invalid" }, true , nil , "" , 1 , false , fsys )
197
198
// Check error
198
199
assert .ErrorContains (t , err , "Invalid Function name." )
199
200
})
@@ -203,7 +204,7 @@ import_map = "./import_map.json"
203
204
fsys := afero .NewMemMapFs ()
204
205
require .NoError (t , utils .WriteConfig (fsys , false ))
205
206
// Run test
206
- err := Run (context .Background (), nil , true , nil , "" , 1 , fsys )
207
+ err := Run (context .Background (), nil , true , nil , "" , 1 , false , fsys )
207
208
// Check error
208
209
assert .ErrorContains (t , err , "No Functions specified or found in supabase/functions" )
209
210
})
@@ -249,7 +250,7 @@ verify_jwt = false
249
250
outputDir := filepath .Join (utils .TempDir , fmt .Sprintf (".output_%s" , slug ))
250
251
require .NoError (t , afero .WriteFile (fsys , filepath .Join (outputDir , "output.eszip" ), []byte ("" ), 0644 ))
251
252
// Run test
252
- assert .NoError (t , Run (context .Background (), []string {slug }, true , nil , "" , 1 , fsys ))
253
+ assert .NoError (t , Run (context .Background (), []string {slug }, true , nil , "" , 1 , false , fsys ))
253
254
// Validate api
254
255
assert .Empty (t , apitest .ListUnmatchedRequests ())
255
256
})
@@ -295,8 +296,8 @@ verify_jwt = false
295
296
outputDir := filepath .Join (utils .TempDir , fmt .Sprintf (".output_%s" , slug ))
296
297
require .NoError (t , afero .WriteFile (fsys , filepath .Join (outputDir , "output.eszip" ), []byte ("" ), 0644 ))
297
298
// Run test
298
- noVerifyJwt := false
299
- assert .NoError (t , Run (context .Background (), []string {slug }, true , & noVerifyJwt , "" , 1 , fsys ))
299
+ noVerifyJWT := false
300
+ assert .NoError (t , Run (context .Background (), []string {slug }, true , & noVerifyJWT , "" , 1 , false , fsys ))
300
301
// Validate api
301
302
assert .Empty (t , apitest .ListUnmatchedRequests ())
302
303
})
@@ -372,3 +373,90 @@ func TestImportMapPath(t *testing.T) {
372
373
assert .Equal (t , path , fc ["test" ].ImportMap )
373
374
})
374
375
}
376
+
377
+ func TestPruneFunctions (t * testing.T ) {
378
+ flags .ProjectRef = apitest .RandomProjectRef ()
379
+ token := apitest .RandomAccessToken (t )
380
+ t .Setenv ("SUPABASE_ACCESS_TOKEN" , string (token ))
381
+ viper .Set ("YES" , true )
382
+
383
+ t .Run ("prunes functions not in local directory" , func (t * testing.T ) {
384
+ // Setup function entrypoints
385
+ localFunctions := config.FunctionConfig {
386
+ "local-func-1" : {Enabled : true },
387
+ "local-func-2" : {Enabled : true },
388
+ }
389
+ // Setup mock api - remote functions include local ones plus orphaned ones
390
+ defer gock .OffAll ()
391
+ remoteFunctions := []api.FunctionResponse {
392
+ {Slug : "local-func-1" },
393
+ {Slug : "local-func-2" },
394
+ {Slug : "orphaned-func-1" },
395
+ {Slug : "orphaned-func-2" },
396
+ }
397
+ gock .New (utils .DefaultApiHost ).
398
+ Get ("/v1/projects/" + flags .ProjectRef + "/functions" ).
399
+ Reply (http .StatusOK ).
400
+ JSON (remoteFunctions )
401
+ gock .New (utils .DefaultApiHost ).
402
+ Delete ("/v1/projects/" + flags .ProjectRef + "/functions/orphaned-func-1" ).
403
+ Reply (http .StatusOK )
404
+ gock .New (utils .DefaultApiHost ).
405
+ Delete ("/v1/projects/" + flags .ProjectRef + "/functions/orphaned-func-2" ).
406
+ Reply (http .StatusOK )
407
+ // Run test with prune and force (to skip confirmation)
408
+ err := pruneFunctions (context .Background (), localFunctions )
409
+ // Check error
410
+ assert .NoError (t , err )
411
+ assert .Empty (t , apitest .ListUnmatchedRequests ())
412
+ })
413
+
414
+ t .Run ("skips pruning when no orphaned functions" , func (t * testing.T ) {
415
+ // Setup function entrypoints
416
+ localFunctions := config.FunctionConfig {
417
+ "local-func-1" : {},
418
+ "local-func-2" : {},
419
+ }
420
+ // Setup mock api - remote functions match local ones exactly
421
+ defer gock .OffAll ()
422
+ remoteFunctions := []api.FunctionResponse {
423
+ {Slug : "local-func-1" },
424
+ {Slug : "local-func-2" },
425
+ {Slug : "orphaned-func-1" , Status : api .FunctionResponseStatusREMOVED },
426
+ {Slug : "orphaned-func-2" , Status : api .FunctionResponseStatusREMOVED },
427
+ }
428
+ gock .New (utils .DefaultApiHost ).
429
+ Get ("/v1/projects/" + flags .ProjectRef + "/functions" ).
430
+ Reply (http .StatusOK ).
431
+ JSON (remoteFunctions )
432
+ // Run test with prune and force
433
+ err := pruneFunctions (context .Background (), localFunctions )
434
+ // Check error
435
+ assert .NoError (t , err )
436
+ assert .Empty (t , apitest .ListUnmatchedRequests ())
437
+ })
438
+
439
+ t .Run ("handles 404 on delete gracefully" , func (t * testing.T ) {
440
+ // Setup function entrypoints
441
+ localFunctions := config.FunctionConfig {"local-func" : {}}
442
+ // Setup mock api
443
+ defer gock .OffAll ()
444
+ remoteFunctions := []api.FunctionResponse {
445
+ {Slug : "local-func" },
446
+ {Slug : "orphaned-func" },
447
+ }
448
+ gock .New (utils .DefaultApiHost ).
449
+ Get ("/v1/projects/" + flags .ProjectRef + "/functions" ).
450
+ Reply (http .StatusOK ).
451
+ JSON (remoteFunctions )
452
+ // Mock delete endpoint with 404 (function already deleted)
453
+ gock .New (utils .DefaultApiHost ).
454
+ Delete ("/v1/projects/" + flags .ProjectRef + "/functions/orphaned-func" ).
455
+ Reply (http .StatusNotFound )
456
+ // Run test with prune and force
457
+ err := pruneFunctions (context .Background (), localFunctions )
458
+ // Check error
459
+ assert .NoError (t , err )
460
+ assert .Empty (t , apitest .ListUnmatchedRequests ())
461
+ })
462
+ }
0 commit comments