Skip to content

Commit f6f33ed

Browse files
committed
test(block/gs): run pre-signed URL test with fake-gcs-server
Add `nowFactory` injection and explicit signing credentials to enable proper testing of GetPreSignedURL with `fake-gcs-server`. This follows the same pattern used in the Azure adapter. - Add WithNowFactory option to mock time.Now() for deterministic tests - Add WithPresignedCredentials option for fake-gcs-server signing - Remove "no credentials found" test skip workaround - Include test RSA private key for URL signing
1 parent c0c1e42 commit f6f33ed

3 files changed

Lines changed: 68 additions & 8 deletions

File tree

pkg/block/blocktest/adapter.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,6 @@ func getPresignedURLBasicTest(t *testing.T, adapter block.Adapter, storageNamesp
230230
if errors.Is(err, block.ErrOperationNotSupported) {
231231
t.Skip("GetPreSignedURL not supported")
232232
}
233-
// Google storage returns an error if no credentials are found, and we can't sign the URL
234-
if err != nil && strings.Contains(err.Error(), "no credentials found") {
235-
t.Skip("GetPreSignedURL no credentials found")
236-
}
237233
require.NoError(t, err)
238234
return preSignedURL, &exp
239235
}

pkg/block/gs/adapter.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ type Adapter struct {
4545
disablePreSignedUI bool
4646
ServerSideEncryptionCustomerSupplied []byte
4747
ServerSideEncryptionKmsKeyID string
48+
nowFactory func() time.Time
49+
// presignedGoogleAccessID and presignedPrivateKey are used for testing with fake-gcs-server
50+
// which requires explicit signing credentials since the test client has no credentials
51+
presignedGoogleAccessID string
52+
presignedPrivateKey []byte
4853
}
4954

5055
func WithPreSignedExpiry(v time.Duration) func(a *Adapter) {
@@ -73,12 +78,28 @@ func WithDisablePreSignedUI(b bool) func(a *Adapter) {
7378
}
7479
}
7580

81+
func WithNowFactory(f func() time.Time) func(a *Adapter) {
82+
return func(a *Adapter) {
83+
a.nowFactory = f
84+
}
85+
}
86+
87+
// WithPresignedCredentials sets the Google Access ID and private key for signing URLs.
88+
// This is primarily used for testing with fake-gcs-server where the client has no credentials.
89+
func WithPresignedCredentials(googleAccessID string, privateKey []byte) func(a *Adapter) {
90+
return func(a *Adapter) {
91+
a.presignedGoogleAccessID = googleAccessID
92+
a.presignedPrivateKey = privateKey
93+
}
94+
}
95+
7696
type AdapterOption func(a *Adapter)
7797

7898
func NewAdapter(client *storage.Client, opts ...AdapterOption) *Adapter {
7999
a := &Adapter{
80100
client: client,
81101
preSignedExpiry: block.DefaultPreSignExpiryDuration,
102+
nowFactory: time.Now, // current time function can be mocked out via injection for testing purposes
82103
}
83104
for _, opt := range opts {
84105
opt(a)
@@ -103,7 +124,7 @@ func (a *Adapter) log(ctx context.Context) logging.Logger {
103124
}
104125

105126
func (a *Adapter) newPreSignedTime() time.Time {
106-
return time.Now().UTC().Add(a.preSignedExpiry)
127+
return a.nowFactory().UTC().Add(a.preSignedExpiry)
107128
}
108129

109130
// withReadHandle returns a corresponding handle for reading object based on the encryption settings.
@@ -243,6 +264,12 @@ func (a *Adapter) GetPreSignedURL(ctx context.Context, obj block.ObjectPointer,
243264
Expires: a.newPreSignedTime(),
244265
}
245266

267+
// Use explicit signing credentials if provided (for testing with fake-gcs-server)
268+
if a.presignedGoogleAccessID != "" && len(a.presignedPrivateKey) > 0 {
269+
opts.GoogleAccessID = a.presignedGoogleAccessID
270+
opts.PrivateKey = a.presignedPrivateKey
271+
}
272+
246273
// Add content-disposition if filename provided
247274
if mode == block.PreSignModeRead && filename != "" {
248275
contentDisposition := mime.FormatMediaType("attachment", map[string]string{

pkg/block/gs/adapter_test.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,42 @@ import (
1616
"github.com/treeverse/lakefs/pkg/config"
1717
)
1818

19-
func newAdapter() *gs.Adapter {
20-
return gs.NewAdapter(client)
19+
// testGoogleAccessID is a fake Google Access ID used for testing signed URLs
20+
const testGoogleAccessID = "fake@test-project.iam.gserviceaccount.com"
21+
22+
// testPrivateKey is a PEM-encoded RSA private key for testing signed URLs with fake-gcs-server.
23+
// This is a test-only key generated with: `openssl genrsa 2048`
24+
var testPrivateKey = []byte(`-----BEGIN PRIVATE KEY-----
25+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0LBKd6cf913PB
26+
GIsh9qfrBT2limGijI8ctSQCH7CrbEjCGI4gtyUhgxKaFMV9hKeAAca9Kilck+xH
27+
rjBM2kjzTHlkVgWa3TNy3VM2v26DsK9Q8v6p2enMLE6ofqWTyyNaaSDXuXVfNKGg
28+
vzahyIUp7kZG1f8HKuBIybZk74gTCubYF4wNZQ/asJuq+o7QGTQpK6v4SfdQtwxM
29+
6w0bRDc737M7WJLNn0r2dF4hOytQW7cO9vX02GrW94P3j+N5tT2ktrULo4XQxTZO
30+
uA6DawWGRg2jf1hsZ2aiOJdUU71FBx9iU7Z9tL4QyB29TOPxi4OugK3NMWlMhv/G
31+
93/feUHRAgMBAAECggEAB3KWcEC2ilhJJ0YEKKVf49EZxHbjyg1gyY/1zaDWeQGP
32+
AOH1DGZnAnt+7c+rvwsNmvbyf9rcg+sz7khwTpmhKsMiS0rbLLTV1dAkX/waCFd0
33+
fxu/pJEBecsqPbYNfV5dZzUhsnUkDvP3oJPNi/K5tBs5ZwpybNm21MoXcBTu2qhB
34+
RvQ3IvhcQj2PUfPw9S7kx5bCtW9aLcn5u7ySv6WNRBPbaEiMQhCEpFNQuG9w3keR
35+
tJiQUzgvUZlutnyczMU+EZchmSHMUQMyE6ah+QStiyyUtOC1vtV95ZvLboEc6NJk
36+
sPmx8ESugdj3Tx8PSciRD5T4C2RmktVJpxYaGEHauQKBgQDysPnIHEPsV7OjviZu
37+
UfblYWGiWol/MRHj8/TKIGYU/Ss1qXMsICLudq/sghBmYA0h6vN1i44RR1w1HtOU
38+
8urv7u90CCFigfu28UzPl2ajTkLbbg9wQwQ6qundcMSScSVEu9xRUrVkJToy5zuM
39+
itl5yOSavusdOvWwZlFaqiTQeQKBgQC+DWvaE4z+reVPghVyD4Ob76DW81eGoq0Y
40+
6JIMKnZqETVNEzWjWcsPF5dE747gmxtapHRvQrjrz0hfNttQgp7VcPbyLVx6MXrK
41+
qL/wqLpZpCx8yJ5MQUHe6a+DJGSJeFH1nEZ2Bw6aOjoOD2GvdPp6flDytwrDMXZM
42+
9cVbIwqWGQKBgQDn/jVIDX0AmHWouUSTgNa7PvPN9y4o4AdyGOqPrZjnx3teuLTY
43+
IYBC5EIXm92Bf6AOJELGwrjz23tRbD5lzDC5W3abPIptWEP/BXufleMPiOhwSi2H
44+
6whH7MnSXNIMCwzNP6fENYQgT1XrAw/xsWli+Z9OLeMi9hGWprhuKuc2QQKBgQCM
45+
RnO4fn2u7MM4MBeMHI9TZUcd4HZV1XRV0jMZ761/FDx3KxqH+xq5hPwN0ZNvjIxg
46+
Fsop5OGAi3orbN3rSr3ZZIugrIJ5XlP3iR5CjwccauS7JYhRWEk6MtlsvkvGe5xi
47+
4HnRW9wXUarP/eJoErtd9iXhP+EduUBMBYspfW+u4QKBgF/kF/fucq2QkMCpDf+H
48+
ITIxEup3Tg81lZAbtnZpfTU7TcPvgBRLWtg3gEKJfTIGL79hl7lPhdqUG9w5egl8
49+
KOGyY30wiwFQ4Lu1MX+BZTKgQG9FVDtOQ43JQOLCbYdcYeKU9tJi7FUgvZmdZRaL
50+
sGXLHjxoneUJGohAIXVzlIGW
51+
-----END PRIVATE KEY-----`)
52+
53+
func newAdapter(opts ...gs.AdapterOption) *gs.Adapter {
54+
return gs.NewAdapter(client, opts...)
2155
}
2256

2357
func TestAdapter(t *testing.T) {
@@ -28,7 +62,10 @@ func TestAdapter(t *testing.T) {
2862
externalPath, err := url.JoinPath(basePath, "external")
2963
require.NoError(t, err)
3064

31-
adapter := newAdapter()
65+
adapter := newAdapter(
66+
gs.WithNowFactory(blocktest.NowMockDefault),
67+
gs.WithPresignedCredentials(testGoogleAccessID, testPrivateKey),
68+
)
3269
defer func() {
3370
require.NoError(t, adapter.Close())
3471
}()

0 commit comments

Comments
 (0)