package packrev_test import ( "bytes" "encoding/binary" "errors" "os" "testing" "lindenii.org/go/furgit/internal/format/packidx" "lindenii.org/go/furgit/internal/format/packrev" "lindenii.org/go/furgit/object/id" ) func TestParseGitReverseIndex(t *testing.T) { t.Parallel() for _, objectFormat := range id.SupportedObjectFormats() { t.Run(objectFormat.String(), func(t *testing.T) { t.Parallel() prefix := makeGitPack(t, objectFormat) revData, err := os.ReadFile(prefix + ".rev") //nolint:gosec if err != nil { t.Fatalf("ReadFile: %v", err) } rev, err := packrev.Parse(revData, objectFormat) if err != nil { t.Fatalf("Parse: %v", err) } idxData, err := os.ReadFile(prefix + ".idx") //nolint:gosec if err != nil { t.Fatalf("ReadFile: %v", err) } idx, err := packidx.Parse(idxData, objectFormat.Size()) if err != nil { t.Fatalf("packidx.Parse: %v", err) } if rev.NumObjects() != idx.NumObjects() { t.Fatalf("NumObjects = %d, want %d", rev.NumObjects(), idx.NumObjects()) } packData, err := os.ReadFile(prefix + ".pack") //nolint:gosec if err != nil { t.Fatalf("ReadFile: %v", err) } packTrailer := packData[len(packData)-objectFormat.Size():] if !bytes.Equal(rev.PackHash(), packTrailer) { t.Fatalf("PackHash does not match pack trailer") } want := packOrderPositions(t, &idx) for packOrder, wantPosition := range want { position, err := rev.PositionAt(packOrder) if err != nil { t.Fatalf("PositionAt(%d): %v", packOrder, err) } if position != int(wantPosition) { t.Fatalf("PositionAt(%d) = %d, want %d", packOrder, position, wantPosition) } } }) } } func TestParseMalformed(t *testing.T) { t.Parallel() for _, objectFormat := range id.SupportedObjectFormats() { t.Run(objectFormat.String(), func(t *testing.T) { t.Parallel() valid := writeSyntheticRev(t, objectFormat, []uint32{2, 0, 1, 3}) corrupt := func(mutate func(data []byte) []byte) []byte { return mutate(bytes.Clone(valid)) } cases := []struct { name string data []byte }{ {name: "empty", data: []byte{}}, {name: "truncated", data: corrupt(func(d []byte) []byte { return d[:10] })}, { name: "bad signature", data: corrupt(func(d []byte) []byte { d[0] ^= 0xff return d }), }, { name: "bad version", data: corrupt(func(d []byte) []byte { d[7] = 2 return d }), }, { name: "hash function mismatch", data: corrupt(func(d []byte) []byte { d[11] ^= 0xff return d }), }, { name: "position table size not 32-bit multiple", data: corrupt(func(d []byte) []byte { return append(d, 0xde, 0xad) }), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() _, err := packrev.Parse(tc.data, objectFormat) if !errors.Is(err, packrev.ErrMalformedReverseIndex) { t.Fatalf("Parse error = %v, want ErrMalformedReverseIndex", err) } }) } }) } } func TestPositionAtMalformed(t *testing.T) { t.Parallel() for _, objectFormat := range id.SupportedObjectFormats() { t.Run(objectFormat.String(), func(t *testing.T) { t.Parallel() data := writeSyntheticRev(t, objectFormat, []uint32{2, 0, 1, 3}) // Corrupt the first stored position to one past the object count. binary.BigEndian.PutUint32(data[12:], 4) rev, err := packrev.Parse(data, objectFormat) if err != nil { t.Fatalf("Parse: %v", err) } _, err = rev.PositionAt(0) if !errors.Is(err, packrev.ErrMalformedReverseIndex) { t.Fatalf("PositionAt error = %v, want ErrMalformedReverseIndex", err) } }) } }