From 7011f836c3094fbb5a3787504bb276388e5eb8ac Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Sat, 23 Aug 2025 20:53:28 +0800 Subject: [PATCH] fix: account for changed verify jwt config --- pkg/function/batch.go | 15 ++-- pkg/function/batch_test.go | 150 ++++++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 67 deletions(-) diff --git a/pkg/function/batch.go b/pkg/function/batch.go index f651110fc8..a98e0d5ed6 100644 --- a/pkg/function/batch.go +++ b/pkg/function/batch.go @@ -16,7 +16,6 @@ import ( "github.com/docker/go-units" "github.com/go-errors/errors" "github.com/supabase/cli/pkg/api" - "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/config" ) @@ -44,9 +43,9 @@ func (s *EdgeRuntimeAPI) UpsertFunctions(ctx context.Context, functionConfig con return err } policy.Reset() - checksum := make(map[string]string, len(result)) - for _, f := range result { - checksum[f.Slug] = cast.Val(f.EzbrSha256, "") + slugToIndex := make(map[string]int, len(result)) + for i, f := range result { + slugToIndex[f.Slug] = i } var toUpdate api.BulkUpdateFunctionBody OUTER: @@ -69,13 +68,15 @@ OUTER: bodyHash := sha256.Sum256(body.Bytes()) meta.SHA256 = hex.EncodeToString(bodyHash[:]) // Skip if function has not changed - if checksum[slug] == meta.SHA256 { + if i, exists := slugToIndex[slug]; exists && i >= 0 && + result[i].EzbrSha256 != nil && *result[i].EzbrSha256 == meta.SHA256 && + result[i].VerifyJwt != nil && *result[i].VerifyJwt == function.VerifyJWT { fmt.Fprintln(os.Stderr, "No change found in Function:", slug) continue } // Update if function already exists upsert := func() (api.BulkUpdateFunctionBody, error) { - if _, ok := checksum[slug]; ok { + if _, exists := slugToIndex[slug]; exists { return s.updateFunction(ctx, slug, meta, bytes.NewReader(body.Bytes())) } return s.createFunction(ctx, slug, meta, bytes.NewReader(body.Bytes())) @@ -84,7 +85,7 @@ OUTER: fmt.Fprintf(os.Stderr, "Deploying Function: %s (script size: %s)\n", slug, functionSize) result, err := backoff.RetryNotifyWithData(upsert, policy, func(err error, d time.Duration) { if strings.Contains(err.Error(), "Duplicated function slug") { - checksum[slug] = "" + slugToIndex[slug] = -1 } }) if err != nil { diff --git a/pkg/function/batch_test.go b/pkg/function/batch_test.go index cc6437ec36..d47b28be4a 100644 --- a/pkg/function/batch_test.go +++ b/pkg/function/batch_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/supabase/cli/pkg/api" + "github.com/supabase/cli/pkg/cast" "github.com/supabase/cli/pkg/config" ) @@ -29,19 +30,24 @@ func (b *MockBundler) Bundle(ctx context.Context, slug, entrypoint, importMap st }, nil } +func mockClient(t *testing.T) EdgeRuntimeAPI { + apiClient, err := api.NewClientWithResponses(mockApiHost) + require.NoError(t, err) + return NewEdgeRuntimeAPI(mockProject, *apiClient, func(era *EdgeRuntimeAPI) { + era.eszip = &MockBundler{} + }) +} + const ( mockApiHost = "https://api.supabase.com" mockProject = "test-project" ) func TestUpsertFunctions(t *testing.T) { - apiClient, err := api.NewClientWithResponses(mockApiHost) - require.NoError(t, err) - client := NewEdgeRuntimeAPI(mockProject, *apiClient, func(era *EdgeRuntimeAPI) { - era.eszip = &MockBundler{} - }) + client := mockClient(t) t.Run("deploys with bulk update", func(t *testing.T) { + // t.Cleanup(mockClock(100 * time.Millisecond)) // Setup mock api defer gock.OffAll() gock.New(mockApiHost). @@ -53,8 +59,8 @@ func TestUpsertFunctions(t *testing.T) { Reply(http.StatusOK). JSON(api.FunctionResponse{Slug: "test-a"}) gock.New(mockApiHost). - Post("/v1/projects/" + mockProject + "/functions/test-b"). - Reply(http.StatusOK). + Post("/v1/projects/" + mockProject + "/functions"). + Reply(http.StatusCreated). JSON(api.FunctionResponse{Slug: "test-b"}) gock.New(mockApiHost). Put("/v1/projects/" + mockProject + "/functions"). @@ -68,8 +74,30 @@ func TestUpsertFunctions(t *testing.T) { JSON(api.V1BulkUpdateFunctionsResponse{}) // Run test err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ - "test-a": {}, - "test-b": {}, + "test-a": {Enabled: true}, + "test-b": {Enabled: true}, + }) + // Check error + assert.NoError(t, err) + assert.Empty(t, gock.Pending()) + assert.Empty(t, gock.GetUnmatchedRequests()) + }) + + t.Run("skips disabled and unchanged", func(t *testing.T) { + // Setup mock api + defer gock.OffAll() + gock.New(mockApiHost). + Get("/v1/projects/" + mockProject + "/functions"). + Reply(http.StatusOK). + JSON([]api.FunctionResponse{{ + Slug: "test-a", + VerifyJwt: cast.Ptr(true), + EzbrSha256: cast.Ptr("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), + }}) + // Run test + err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ + "test-a": {Enabled: true, VerifyJWT: true}, + "test-b": {Enabled: false}, }) // Check error assert.NoError(t, err) @@ -94,7 +122,7 @@ func TestUpsertFunctions(t *testing.T) { JSON(api.FunctionResponse{Slug: "test"}) // Run test err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ - "test": {}, + "test": {Enabled: true}, }) // Check error assert.NoError(t, err) @@ -121,58 +149,60 @@ func TestUpsertFunctions(t *testing.T) { assert.Empty(t, gock.Pending()) assert.Empty(t, gock.GetUnmatchedRequests()) }) +} - t.Run("retries on create failure", func(t *testing.T) { - // Setup mock api - defer gock.OffAll() - gock.New(mockApiHost). - Get("/v1/projects/" + mockProject + "/functions"). - Reply(http.StatusOK). - JSON([]api.FunctionResponse{}) - gock.New(mockApiHost). - Post("/v1/projects/" + mockProject + "/functions"). - ReplyError(errors.New("network error")) - gock.New(mockApiHost). - Post("/v1/projects/" + mockProject + "/functions"). - Reply(http.StatusServiceUnavailable) - gock.New(mockApiHost). - Post("/v1/projects/" + mockProject + "/functions"). - Reply(http.StatusCreated). - JSON(api.FunctionResponse{Slug: "test"}) - // Run test - err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ - "test": {}, - }) - // Check error - assert.NoError(t, err) - assert.Empty(t, gock.Pending()) - assert.Empty(t, gock.GetUnmatchedRequests()) +func TestCreateFunction(t *testing.T) { + client := mockClient(t) + // Setup mock api + defer gock.OffAll() + gock.New(mockApiHost). + Get("/v1/projects/" + mockProject + "/functions"). + Reply(http.StatusOK). + JSON([]api.FunctionResponse{}) + gock.New(mockApiHost). + Post("/v1/projects/" + mockProject + "/functions"). + ReplyError(errors.New("network error")) + gock.New(mockApiHost). + Post("/v1/projects/" + mockProject + "/functions"). + Reply(http.StatusServiceUnavailable) + gock.New(mockApiHost). + Post("/v1/projects/" + mockProject + "/functions"). + Reply(http.StatusCreated). + JSON(api.FunctionResponse{Slug: "test"}) + // Run test + err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ + "test": {Enabled: true}, }) + // Check error + assert.NoError(t, err) + assert.Empty(t, gock.Pending()) + assert.Empty(t, gock.GetUnmatchedRequests()) +} - t.Run("retries on update failure", func(t *testing.T) { - // Setup mock api - defer gock.OffAll() - gock.New(mockApiHost). - Get("/v1/projects/" + mockProject + "/functions"). - Reply(http.StatusOK). - JSON([]api.FunctionResponse{{Slug: "test"}}) - gock.New(mockApiHost). - Patch("/v1/projects/" + mockProject + "/functions/test"). - ReplyError(errors.New("network error")) - gock.New(mockApiHost). - Patch("/v1/projects/" + mockProject + "/functions/test"). - Reply(http.StatusServiceUnavailable) - gock.New(mockApiHost). - Patch("/v1/projects/" + mockProject + "/functions/test"). - Reply(http.StatusOK). - JSON(api.FunctionResponse{Slug: "test"}) - // Run test - err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ - "test": {}, - }) - // Check error - assert.NoError(t, err) - assert.Empty(t, gock.Pending()) - assert.Empty(t, gock.GetUnmatchedRequests()) +func TestUpdateFunction(t *testing.T) { + client := mockClient(t) + // Setup mock api + defer gock.OffAll() + gock.New(mockApiHost). + Get("/v1/projects/" + mockProject + "/functions"). + Reply(http.StatusOK). + JSON([]api.FunctionResponse{{Slug: "test"}}) + gock.New(mockApiHost). + Patch("/v1/projects/" + mockProject + "/functions/test"). + ReplyError(errors.New("network error")) + gock.New(mockApiHost). + Patch("/v1/projects/" + mockProject + "/functions/test"). + Reply(http.StatusServiceUnavailable) + gock.New(mockApiHost). + Patch("/v1/projects/" + mockProject + "/functions/test"). + Reply(http.StatusOK). + JSON(api.FunctionResponse{Slug: "test"}) + // Run test + err := client.UpsertFunctions(context.Background(), config.FunctionConfig{ + "test": {Enabled: true}, }) + // Check error + assert.NoError(t, err) + assert.Empty(t, gock.Pending()) + assert.Empty(t, gock.GetUnmatchedRequests()) }