diff --git a/pkg/handlers/handlers_test.go b/pkg/handlers/handlers_test.go index a336965be..d2e7b81a1 100644 --- a/pkg/handlers/handlers_test.go +++ b/pkg/handlers/handlers_test.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" stdctx "context" + "errors" "fmt" "io" "net/http" @@ -12,6 +13,7 @@ import ( "path/filepath" "strings" "testing" + "testing/iotest" "time" "github.com/stretchr/testify/assert" @@ -518,13 +520,10 @@ func TestHandleGitCatFile(t *testing.T) { } defer os.RemoveAll(gitDir) - cmd := exec.Command("git", "-C", gitDir, "rev-parse", "HEAD") - hashBytes, err := cmd.Output() - assert.NoError(t, err, "Failed to get commit hash") - commitHash := strings.TrimSpace(string(hashBytes)) + commitHash := getGitCommitHash(t, gitDir) // Create a pipe to simulate the git cat-file stdout. - cmd = exec.Command("git", "-C", gitDir, "cat-file", "blob", fmt.Sprintf("%s:%s", commitHash, tt.fileName)) + cmd := exec.Command("git", "-C", gitDir, "cat-file", "blob", fmt.Sprintf("%s:%s", commitHash, tt.fileName)) var stderr bytes.Buffer cmd.Stderr = &stderr @@ -686,6 +685,102 @@ func setupTempGitRepoCommon(t *testing.T, fileName string, fileSize int, isUnsup return tempDir } +func TestHandleFileNewFileReaderFailure(t *testing.T) { + customReader := iotest.ErrReader(errors.New("simulated newFileReader error")) + + chunkSkel := &sources.Chunk{} + chunkCh := make(chan *sources.Chunk) + reporter := sources.ChanReporter{Ch: chunkCh} + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := HandleFile(ctx, customReader, chunkSkel, reporter) + + assert.Error(t, err, "HandleFile should return an error when newFileReader fails") +} + +// errorInjectingReader is a custom io.Reader that injects an error after reading a certain number of bytes. +type errorInjectingReader struct { + reader io.Reader + injectAfter int64 // Number of bytes after which to inject the error + injected bool + bytesRead int64 + errorToInject error +} + +func (eir *errorInjectingReader) Read(p []byte) (int, error) { + if eir.injectAfter > 0 && eir.bytesRead >= eir.injectAfter && !eir.injected { + eir.injected = true + return 0, eir.errorToInject + } + + n, err := eir.reader.Read(p) + eir.bytesRead += int64(n) + return n, err +} + +// TestHandleGitCatFileWithPipeError tests that when an error is injected during the HandleFile processing, +// the error is reported and the git cat-file command completes successfully. +func TestHandleGitCatFileWithPipeError(t *testing.T) { + fileName := "largefile_with_error.bin" + fileSize := 100 * 1024 // 100 KB + injectErrorAfter := int64(50 * 1024) // Inject error after 50 KB + simulatedError := errors.New("simulated error during newFileReader") + + gitDir := setupTempGitRepo(t, fileName, fileSize) + defer os.RemoveAll(gitDir) + + commitHash := getGitCommitHash(t, gitDir) + + cmd := exec.Command("git", "-C", gitDir, "cat-file", "blob", fmt.Sprintf("%s:%s", commitHash, fileName)) + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + stdout, err := cmd.StdoutPipe() + assert.NoError(t, err, "Failed to create stdout pipe") + + err = cmd.Start() + assert.NoError(t, err, "Failed to start git cat-file command") + + // Wrap the stdout with errorInjectingReader to simulate an error after reading injectErrorAfter bytes. + wrappedReader := &errorInjectingReader{ + reader: stdout, + injectAfter: injectErrorAfter, + injected: false, + bytesRead: 0, + errorToInject: simulatedError, + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + chunkCh := make(chan *sources.Chunk, 1000) + + go func() { + defer close(chunkCh) + err = HandleFile(ctx, wrappedReader, &sources.Chunk{}, sources.ChanReporter{Ch: chunkCh}, WithSkipArchives(false)) + assert.NoError(t, err, "HandleFile should not return an error") + }() + + for range chunkCh { + } + + err = cmd.Wait() + assert.NoError(t, err, "git cat-file command should complete without error") +} + +// getGitCommitHash retrieves the current commit hash of the Git repository. +func getGitCommitHash(t *testing.T, gitDir string) string { + t.Helper() + cmd := exec.Command("git", "-C", gitDir, "rev-parse", "HEAD") + hashBytes, err := cmd.Output() + assert.NoError(t, err, "Failed to get commit hash") + commitHash := strings.TrimSpace(string(hashBytes)) + return commitHash +} + type mockReporter struct{ reportedChunks int } func (m *mockReporter) ChunkOk(logContext.Context, sources.Chunk) error {