Go · 3624 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package storage
4
5 import (
6 "bytes"
7 "context"
8 "errors"
9 "io"
10 "os"
11 "strings"
12 "testing"
13 "time"
14 )
15
16 // s3FromEnv returns an S3Store configured from SHITHUB_TEST_S3_* env vars,
17 // or skips the test when those aren't set. Tests that exercise the s3
18 // backend end-to-end run only when CI or a developer has wired up MinIO.
19 //
20 // SHITHUB_TEST_S3_ENDPOINT (e.g. 127.0.0.1:9000)
21 // SHITHUB_TEST_S3_ACCESS_KEY_ID
22 // SHITHUB_TEST_S3_SECRET_ACCESS_KEY
23 // SHITHUB_TEST_S3_BUCKET (e.g. shithub-dev)
24 func s3FromEnv(t *testing.T) *S3Store {
25 t.Helper()
26 endpoint := os.Getenv("SHITHUB_TEST_S3_ENDPOINT")
27 if endpoint == "" {
28 t.Skip("SHITHUB_TEST_S3_ENDPOINT not set; skipping s3 integration test")
29 }
30 store, err := NewS3Store(S3Config{
31 Endpoint: endpoint,
32 Region: envOr("SHITHUB_TEST_S3_REGION", "us-east-1"),
33 AccessKeyID: os.Getenv("SHITHUB_TEST_S3_ACCESS_KEY_ID"),
34 SecretAccessKey: os.Getenv("SHITHUB_TEST_S3_SECRET_ACCESS_KEY"),
35 Bucket: os.Getenv("SHITHUB_TEST_S3_BUCKET"),
36 UseSSL: false,
37 ForcePathStyle: true,
38 })
39 if err != nil {
40 t.Fatalf("NewS3Store: %v", err)
41 }
42 return store
43 }
44
45 func envOr(key, def string) string {
46 if v := os.Getenv(key); v != "" {
47 return v
48 }
49 return def
50 }
51
52 func TestS3Store_RoundTrip(t *testing.T) {
53 t.Parallel()
54 s := s3FromEnv(t)
55 ctx := context.Background()
56 key := "test/round-trip-" + time.Now().UTC().Format("20060102-150405.000000000")
57
58 body := strings.NewReader("integration body")
59 res, err := s.Put(ctx, key, body, PutOpts{ContentType: "text/plain"})
60 if err != nil {
61 t.Fatalf("Put: %v", err)
62 }
63 t.Cleanup(func() { _ = s.Delete(ctx, key) })
64
65 if res.Size != int64(len("integration body")) {
66 t.Fatalf("size = %d, want %d", res.Size, len("integration body"))
67 }
68
69 rc, meta, err := s.Get(ctx, key)
70 if err != nil {
71 t.Fatalf("Get: %v", err)
72 }
73 defer func() { _ = rc.Close() }()
74 got, _ := io.ReadAll(rc)
75 if string(got) != "integration body" {
76 t.Fatalf("body mismatch: got %q", got)
77 }
78 if meta.Size != res.Size {
79 t.Fatalf("meta size mismatch")
80 }
81 }
82
83 func TestS3Store_PutIfNoneMatch(t *testing.T) {
84 t.Parallel()
85 s := s3FromEnv(t)
86 ctx := context.Background()
87 key := "test/inm-" + time.Now().UTC().Format("20060102-150405.000000000")
88
89 if _, err := s.Put(ctx, key, strings.NewReader("first"), PutOpts{}); err != nil {
90 t.Fatalf("first put: %v", err)
91 }
92 t.Cleanup(func() { _ = s.Delete(ctx, key) })
93
94 _, err := s.Put(ctx, key, strings.NewReader("second"), PutOpts{IfNoneMatch: "*"})
95 if !errors.Is(err, ErrPreconditionFailed) {
96 t.Fatalf("expected ErrPreconditionFailed, got %v", err)
97 }
98 }
99
100 func TestS3Store_LargeRoundTrip(t *testing.T) {
101 t.Parallel()
102 s := s3FromEnv(t)
103 ctx := context.Background()
104 key := "test/large-" + time.Now().UTC().Format("20060102-150405.000000000")
105
106 body := bytes.Repeat([]byte{0xab}, 5*1024*1024) // 5 MiB
107 if _, err := s.Put(ctx, key, bytes.NewReader(body), PutOpts{ContentLength: int64(len(body))}); err != nil {
108 t.Fatalf("Put large: %v", err)
109 }
110 t.Cleanup(func() { _ = s.Delete(ctx, key) })
111
112 rc, _, err := s.Get(ctx, key)
113 if err != nil {
114 t.Fatalf("Get large: %v", err)
115 }
116 defer func() { _ = rc.Close() }()
117 got, _ := io.ReadAll(rc)
118 if !bytes.Equal(got, body) {
119 t.Fatalf("body mismatch (len got=%d want=%d)", len(got), len(body))
120 }
121 }
122
123 func TestS3Store_GetMissing(t *testing.T) {
124 t.Parallel()
125 s := s3FromEnv(t)
126 _, _, err := s.Get(context.Background(), "test/should-not-exist-xyz-"+time.Now().UTC().Format("150405.000"))
127 if !errors.Is(err, ErrNotFound) {
128 t.Fatalf("expected ErrNotFound, got %v", err)
129 }
130 }
131