package loose_test
import (
"bytes"
"errors"
"io"
"strings"
"testing"
"lindenii.org/go/furgit/internal/testgit"
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/furgit/object/store"
"lindenii.org/go/furgit/object/typ"
)
func TestRead(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
repo, err := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: objectFormat})
if err != nil {
t.Fatalf("NewRepo: %v", err)
}
objects := gitOracleObjects(t, repo)
looseStore := openLooseStore(t, repo)
t.Run("BytesFull", func(t *testing.T) {
t.Parallel()
for _, o := range objects {
got, err := looseStore.ReadBytesFull(o.id)
if err != nil {
t.Fatalf("%s: ReadBytesFull: %v", o.name, err)
}
if !bytes.Equal(got, o.raw) {
t.Fatalf("%s: ReadBytesFull mismatch", o.name)
}
}
})
t.Run("BytesContent", func(t *testing.T) {
t.Parallel()
for _, o := range objects {
gotType, gotBody, err := looseStore.ReadBytesContent(o.id)
if err != nil {
t.Fatalf("%s: ReadBytesContent: %v", o.name, err)
}
if gotType != o.ty {
t.Fatalf("%s: ReadBytesContent type = %v, want %v", o.name, gotType, o.ty)
}
if !bytes.Equal(gotBody, o.body) {
t.Fatalf("%s: ReadBytesContent body mismatch", o.name)
}
}
})
t.Run("Header", func(t *testing.T) {
t.Parallel()
for _, o := range objects {
gotType, gotSize, err := looseStore.ReadHeader(o.id)
if err != nil {
t.Fatalf("%s: ReadHeader: %v", o.name, err)
}
if gotType != o.ty {
t.Fatalf("%s: ReadHeader type = %v, want %v", o.name, gotType, o.ty)
}
if gotSize != uint64(len(o.body)) {
t.Fatalf("%s: ReadHeader size = %d, want %d", o.name, gotSize, len(o.body))
}
}
})
t.Run("ReaderFull", func(t *testing.T) {
t.Parallel()
for _, o := range objects {
reader, err := looseStore.ReadReaderFull(o.id)
if err != nil {
t.Fatalf("%s: ReadReaderFull: %v", o.name, err)
}
got, err := io.ReadAll(reader)
if err != nil {
_ = reader.Close()
t.Fatalf("%s: ReadReaderFull ReadAll: %v", o.name, err)
}
err = reader.Close()
if err != nil {
t.Fatalf("%s: ReadReaderFull Close: %v", o.name, err)
}
if !bytes.Equal(got, o.raw) {
t.Fatalf("%s: ReadReaderFull mismatch", o.name)
}
}
})
t.Run("ReaderContent", func(t *testing.T) {
t.Parallel()
for _, o := range objects {
gotType, gotSize, reader, err := looseStore.ReadReaderContent(o.id)
if err != nil {
t.Fatalf("%s: ReadReaderContent: %v", o.name, err)
}
got, err := io.ReadAll(reader)
if err != nil {
_ = reader.Close()
t.Fatalf("%s: ReadReaderContent ReadAll: %v", o.name, err)
}
err = reader.Close()
if err != nil {
t.Fatalf("%s: ReadReaderContent Close: %v", o.name, err)
}
if gotType != o.ty {
t.Fatalf("%s: ReadReaderContent type = %v, want %v", o.name, gotType, o.ty)
}
if gotSize != uint64(len(o.body)) {
t.Fatalf("%s: ReadReaderContent size = %d, want %d", o.name, gotSize, len(o.body))
}
if !bytes.Equal(got, o.body) {
t.Fatalf("%s: ReadReaderContent mismatch", o.name)
}
}
})
})
}
}
func TestReadNotFound(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
repo, err := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: objectFormat})
if err != nil {
t.Fatalf("NewRepo: %v", err)
}
looseStore := openLooseStore(t, repo)
missingID, err := objectFormat.FromString(strings.Repeat("0", objectFormat.HexLen()))
if err != nil {
t.Fatalf("FromString(missing): %v", err)
}
_, err = looseStore.ReadBytesFull(missingID)
if !errors.Is(err, store.ErrObjectNotFound) {
t.Fatalf("ReadBytesFull not-found = %v", err)
}
_, _, err = looseStore.ReadBytesContent(missingID)
if !errors.Is(err, store.ErrObjectNotFound) {
t.Fatalf("ReadBytesContent not-found = %v", err)
}
_, _, err = looseStore.ReadHeader(missingID)
if !errors.Is(err, store.ErrObjectNotFound) {
t.Fatalf("ReadHeader not-found = %v", err)
}
_, err = looseStore.ReadReaderFull(missingID)
if !errors.Is(err, store.ErrObjectNotFound) {
t.Fatalf("ReadReaderFull not-found = %v", err)
}
_, _, _, err = looseStore.ReadReaderContent(missingID)
if !errors.Is(err, store.ErrObjectNotFound) {
t.Fatalf("ReadReaderContent not-found = %v", err)
}
otherFormat := objectFormat
for _, candidate := range id.SupportedObjectFormats() {
if candidate != objectFormat {
otherFormat = candidate
break
}
}
if otherFormat == objectFormat {
return
}
mismatchID, err := otherFormat.FromString(strings.Repeat("1", otherFormat.HexLen()))
if err != nil {
t.Fatalf("FromString(mismatch): %v", err)
}
_, err = looseStore.ReadBytesFull(mismatchID)
if !errors.Is(err, id.ErrInvalidObjectFormat) {
t.Fatalf("ReadBytesFull format mismatch = %v, want ErrInvalidObjectFormat", err)
}
})
}
}
func TestReadCorruptTrailer(t *testing.T) {
t.Parallel()
for _, objectFormat := range id.SupportedObjectFormats() {
t.Run(objectFormat.String(), func(t *testing.T) {
t.Parallel()
repo, err := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: objectFormat})
if err != nil {
t.Fatalf("NewRepo: %v", err)
}
looseStore := openLooseStore(t, repo)
content := []byte("corrupt-trailer-check\n")
objectID, err := looseStore.WriteBytesContent(typ.TypeBlob, content)
if err != nil {
t.Fatalf("WriteBytesContent: %v", err)
}
corruptLooseObjectTrailer(t, repo, objectID)
// Stops before the trailer.
ty, size, err := looseStore.ReadHeader(objectID)
if err != nil {
t.Fatalf("ReadHeader: %v", err)
}
if ty != typ.TypeBlob {
t.Fatalf("ReadHeader type = %v, want %v", ty, typ.TypeBlob)
}
if size != uint64(len(content)) {
t.Fatalf("ReadHeader size = %d, want %d", size, len(content))
}
// Consumes the whole stream.
_, err = looseStore.ReadBytesFull(objectID)
if err == nil {
t.Fatalf("ReadBytesFull on corrupt trailer succeeded")
}
})
}
}