diff --git a/internal/gitaly/hook/prereceive_test.go b/internal/gitaly/hook/prereceive_test.go index 710b7d2526ca3c4a51bace1b3e67409c38f21f2b..5e106d3b1df31b6a51f752ce3c16cb53df3dbdd0 100644 --- a/internal/gitaly/hook/prereceive_test.go +++ b/internal/gitaly/hook/prereceive_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/gitaly/v18/internal/featureflag" "gitlab.com/gitlab-org/gitaly/v18/internal/git/gitcmd" @@ -303,6 +304,114 @@ func (m *prereceiveAPIMock) PostReceive(context.Context, string, string, string, return true, nil, errors.New("unexpected call") } +func TestPrereceive_notAllowedErrorLogging(t *testing.T) { + t.Parallel() + + ctx := testhelper.Context(t) + cfg := testcfg.Build(t) + + repo, _ := gittest.CreateRepository(t, ctx, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) + + logger := testhelper.NewLogger(t) + hook := testhelper.AddLoggerHook(logger) + + gitlabAPI := prereceiveAPIMock{ + allowed: func(ctx context.Context, params gitlab.AllowedParams) (bool, string, error) { + return false, "", errors.New("access denied") + }, + prereceive: func(ctx context.Context, glRepo string) (bool, error) { + return true, nil + }, + } + + hookManager := NewManager( + cfg, + config.NewLocator(cfg), + logger, + gittest.NewCommandFactory(t, cfg), + transaction.NewManager(cfg, logger, backchannel.NewRegistry()), + &gitlabAPI, + NewTransactionRegistry(storagemgr.NewTransactionRegistry()), + NewProcReceiveRegistry(), + nil, + ) + + payload, err := gitcmd.NewHooksPayload( + ctx, + cfg, + repo, + gittest.DefaultObjectHash, + &txinfo.Transaction{ + ID: 1234, Node: "primary", Primary: true, + }, + &gitcmd.UserDetails{ + UserID: "1234", + Username: "user", + Protocol: "web", + }, + gitcmd.PreReceiveHook, + featureflag.FromContext(ctx), + storage.ExtractTransactionID(ctx), + ).Env() + require.NoError(t, err) + + changes := "old-sha new-sha refs/heads/main\n" + var stdout, stderr bytes.Buffer + err = hookManager.PreReceiveHook(ctx, repo, nil, []string{payload}, strings.NewReader(changes), &stdout, &stderr) + + // Verify the error is returned with all fields populated + require.Error(t, err) + var notAllowedErr NotAllowedError + require.ErrorAs(t, err, ¬AllowedErr) + require.Equal(t, "access denied", notAllowedErr.Message) + require.Equal(t, "web", notAllowedErr.Protocol) + require.Equal(t, "1234", notAllowedErr.UserID) + require.Equal(t, []byte(changes), notAllowedErr.Changes) + + // Check what was logged + entries := hook.AllEntries() + require.NotEmpty(t, entries, "expected at least one log entry") + + // Find the log entry about the pre-receive hook failure + var foundEntry *logrus.Entry + for _, entry := range entries { + if strings.Contains(entry.Message, "stopping transaction because pre-receive hook failed") { + foundEntry = entry + break + } + } + require.NotNil(t, foundEntry, "expected to find log entry about pre-receive hook failure") + + // Check if the error field contains the error message + errorField, hasError := foundEntry.Data["error"] + require.True(t, hasError, "expected 'error' field in log entry") + errorStr := fmt.Sprintf("%v", errorField) + + // WithError only calls Error() method, so we should only see the formatted message + require.Contains(t, errorStr, "GitLab: access denied", "error field should contain the Error() output") + + // The Changes field should NOT be in the error field because WithError only calls Error() + // This proves that logrus.WithError does NOT serialize the struct fields + require.NotContains(t, errorStr, "old-sha new-sha refs/heads/main", + "Changes field should NOT be serialized in error field when using WithError") + + // Verify that the error field only contains the Error() method output, not struct fields + require.NotContains(t, errorStr, "Changes:", "Changes field name should not appear in error field") + require.NotContains(t, errorStr, "Protocol:", "Protocol field name should not appear in error field") + require.NotContains(t, errorStr, "UserID:", "UserID field name should not appear in error field") + + // Also verify the struct fields are not in any other log fields + for key, value := range foundEntry.Data { + valueStr := fmt.Sprintf("%v", value) + if key != "error" { + require.NotContains(t, valueStr, "old-sha new-sha refs/heads/main", + "Changes should not appear in log field %q", key) + } + } +} + func TestPrereceive_gitlab(t *testing.T) { t.Parallel()