1package lfs
2
3import (
4	"bytes"
5	"crypto/sha256"
6	"encoding/hex"
7	"fmt"
8	"io"
9	"math/rand"
10	"testing"
11
12	"github.com/git-lfs/git-lfs/v3/git"
13	"github.com/git-lfs/gitobj/v2"
14	"github.com/stretchr/testify/assert"
15	"github.com/stretchr/testify/require"
16)
17
18func TestPointerScannerWithValidOutput(t *testing.T) {
19	blobs := []*Pointer{
20		&Pointer{
21			Version: "https://git-lfs.github.com/spec/v1",
22			Oid:     "e71eefd918ea175b8f362611f981f648dbf9888ff74865077cb4c9077728f350",
23			Size:    123,
24			OidType: "sha256",
25		},
26		&Pointer{
27			Version: "https://git-lfs.github.com/spec/v1",
28			Oid:     "0eb69b651be65d5a61d6bebf2c53c811a5bf8031951111000e2077f4d7fe43b1",
29			Size:    132,
30			OidType: "sha256",
31		},
32	}
33
34	be, _ := gitobj.NewMemoryBackend(nil)
35	db, _ := gitobj.FromBackend(be)
36	shas := fakeObjectsWithRandoData(t, db, blobs)
37
38	scanner := &PointerScanner{
39		scanner: git.NewObjectScannerFrom(db),
40	}
41	iter := 0
42
43	for i := 0; i < 5; i++ {
44		assertNextEmptyPointer(t, scanner, shas[iter])
45		iter++
46	}
47
48	assertNextPointer(t, scanner, shas[iter], "e71eefd918ea175b8f362611f981f648dbf9888ff74865077cb4c9077728f350")
49	iter++
50
51	for i := 0; i < 5; i++ {
52		assertNextEmptyPointer(t, scanner, shas[iter])
53		iter++
54	}
55
56	assertNextPointer(t, scanner, shas[iter], "0eb69b651be65d5a61d6bebf2c53c811a5bf8031951111000e2077f4d7fe43b1")
57	iter++
58
59	for i := 0; i < 5; i++ {
60		assertNextEmptyPointer(t, scanner, shas[iter])
61		iter++
62	}
63}
64
65func TestPointerScannerWithLargeBlobs(t *testing.T) {
66	buf := bytes.NewBuffer(make([]byte, 0, 1025))
67	sha := sha256.New()
68	rng := rand.New(rand.NewSource(0))
69
70	_, err := io.CopyN(io.MultiWriter(sha, buf), rng, 1025)
71	require.Nil(t, err)
72
73	be, _ := gitobj.NewMemoryBackend(nil)
74	db, _ := gitobj.FromBackend(be)
75
76	fake := bytes.NewBuffer(nil)
77	oid := writeFakeBuffer(t, db, fake, buf.Bytes(), buf.Len())
78
79	scanner := &PointerScanner{
80		scanner: git.NewObjectScannerFrom(db),
81	}
82
83	require.True(t, scanner.Scan(oid))
84	assert.Nil(t, scanner.Pointer())
85	assert.Equal(t, fmt.Sprintf("%x", sha.Sum(nil)), scanner.ContentsSha())
86}
87
88func assertNextPointer(t *testing.T, scanner *PointerScanner, sha string, oid string) {
89	assert.True(t, scanner.Scan(sha))
90	assert.Nil(t, scanner.Err())
91
92	p := scanner.Pointer()
93
94	assert.NotNil(t, p)
95	assert.Equal(t, oid, p.Oid)
96}
97
98func assertNextEmptyPointer(t *testing.T, scanner *PointerScanner, sha string) {
99	assert.True(t, scanner.Scan(sha))
100	assert.Nil(t, scanner.Err())
101
102	assert.Nil(t, scanner.Pointer())
103}
104
105func fakeObjectsWithRandoData(t *testing.T, db *gitobj.ObjectDatabase, blobs []*Pointer) []string {
106	buf := &bytes.Buffer{}
107	rngbuf := make([]byte, 1000) // just under blob size cutoff
108	rng := rand.New(rand.NewSource(0))
109	oids := make([]string, 0)
110
111	for i := 0; i < 5; i++ {
112		n, err := io.ReadFull(rng, rngbuf)
113		if err != nil {
114			t.Fatalf("error reading from rng: %+v", err)
115		}
116		oids = append(oids, writeFakeBuffer(t, db, buf, rngbuf, n))
117	}
118
119	for _, b := range blobs {
120		ptrtext := b.Encoded()
121		oids = append(oids, writeFakeBuffer(t, db, buf, []byte(ptrtext), len(ptrtext)))
122		for i := 0; i < 5; i++ {
123			n, err := io.ReadFull(rng, rngbuf)
124			if err != nil {
125				t.Fatalf("error reading from rng: %+v", err)
126			}
127			oids = append(oids, writeFakeBuffer(t, db, buf, rngbuf, n))
128		}
129	}
130
131	return oids
132}
133
134func writeFakeBuffer(t *testing.T, db *gitobj.ObjectDatabase, buf *bytes.Buffer, by []byte, size int) string {
135	oid, _ := db.WriteBlob(gitobj.NewBlobFromBytes(by))
136	return hex.EncodeToString(oid)
137}
138