Compare commits

...

1 Commits

Author SHA1 Message Date
Jiang Bohan
2eee219f5c test(storage): lock S3 upload URL behavior across all env combos
Extract the URL assembly at the end of S3Storage.Upload into a helper
(uploadedURL) so the four env-var combinations can be covered by a
table-driven test without mocking s3.PutObject. This locks in the fix
from #1300 — cdn > endpoint > bucket — so future refactors can't
silently regress the CDN-wins-over-custom-endpoint case.

No behavior change.
2026-04-21 12:51:34 +08:00
2 changed files with 67 additions and 10 deletions

View File

@@ -157,19 +157,20 @@ func (s *S3Storage) Upload(ctx context.Context, key string, data []byte, content
if err != nil {
return "", fmt.Errorf("s3 PutObject: %w", err)
}
return s.uploadedURL(key), nil
}
// Prefer the configured CDN/public-read domain when set. This allows S3-compatible
// backends (MinIO, R2, B2, Wasabi, etc.) to be paired with a separate public domain
// for reads — the custom endpoint is still used for writes via the SDK, but the URL
// stored for client consumption points at the reader-facing domain.
// uploadedURL returns the URL stored for client consumption after an upload.
// Priority: CDN domain > custom endpoint > bucket. The CDN domain wins even when
// a custom endpoint is set so S3-compatible backends (MinIO, R2, B2, Wasabi, etc.)
// can be paired with a separate public-read domain — writes still go through the
// SDK with the custom endpoint; only the reader-facing URL changes.
func (s *S3Storage) uploadedURL(key string) string {
if s.cdnDomain != "" {
link := fmt.Sprintf("https://%s/%s", s.cdnDomain, key)
return link, nil
return fmt.Sprintf("https://%s/%s", s.cdnDomain, key)
}
if s.endpointURL != "" {
link := fmt.Sprintf("%s/%s/%s", strings.TrimRight(s.endpointURL, "/"), s.bucket, key)
return link, nil
return fmt.Sprintf("%s/%s/%s", strings.TrimRight(s.endpointURL, "/"), s.bucket, key)
}
link := fmt.Sprintf("https://%s/%s", s.bucket, key)
return link, nil
return fmt.Sprintf("https://%s/%s", s.bucket, key)
}

View File

@@ -27,3 +27,59 @@ func TestS3StorageKeyFromURL_CustomEndpointWithTrailingSlash(t *testing.T) {
t.Fatalf("KeyFromURL(%q) = %q, want %q", rawURL, got, "uploads/abc/file.png")
}
}
func TestS3StorageUploadedURL(t *testing.T) {
const key = "uploads/abc/file.png"
cases := []struct {
name string
bucket string
cdnDomain string
endpointURL string
want string
}{
{
name: "bucket only",
bucket: "test-bucket",
want: "https://test-bucket/uploads/abc/file.png",
},
{
name: "cdn only",
bucket: "test-bucket",
cdnDomain: "cdn.example.com",
want: "https://cdn.example.com/uploads/abc/file.png",
},
{
name: "endpoint only",
bucket: "test-bucket",
endpointURL: "http://localhost:9000",
want: "http://localhost:9000/test-bucket/uploads/abc/file.png",
},
{
name: "endpoint with trailing slash",
bucket: "test-bucket",
endpointURL: "http://localhost:9000/",
want: "http://localhost:9000/test-bucket/uploads/abc/file.png",
},
{
name: "endpoint and cdn both set prefers cdn",
bucket: "test-bucket",
cdnDomain: "cdn.example.com",
endpointURL: "http://localhost:9000",
want: "https://cdn.example.com/uploads/abc/file.png",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s := &S3Storage{
bucket: tc.bucket,
cdnDomain: tc.cdnDomain,
endpointURL: tc.endpointURL,
}
if got := s.uploadedURL(key); got != tc.want {
t.Fatalf("uploadedURL() = %q, want %q", got, tc.want)
}
})
}
}