Files
probo/pkg/filemanager/s3_test.go
Bryan Frimin 43ce3a7c53 Harden compliance portal auth and TLS
Align console references and OAuth branding with the
compliance-page model, and fix certificate cache eviction,
portal OAuth handlers, and magic-link edge cases left after
the trust-center rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:22 +02:00

350 lines
8.4 KiB
Go

// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package filemanager_test
import (
"context"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/filemanager"
)
func newTestS3Service(t *testing.T, handler http.HandlerFunc) *filemanager.Service {
t.Helper()
srv := httptest.NewServer(handler)
t.Cleanup(srv.Close)
s3Client := awss3.NewFromConfig(
aws.Config{
Region: "us-east-1",
Credentials: credentials.NewStaticCredentialsProvider("access-key", "secret-key", ""),
},
func(o *awss3.Options) {
o.BaseEndpoint = aws.String(srv.URL)
o.UsePathStyle = true
},
)
return filemanager.NewService(nil, nil, s3Client, log.NewLogger(log.WithOutput(io.Discard)))
}
func TestOpenFile_StreamsBody(t *testing.T) {
t.Parallel()
const (
etag = `"abc123"`
content = "hello world"
)
svc := newTestS3Service(
t,
func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("ETag", etag)
w.Header().Set("Content-Type", "application/octet-stream")
_, _ = io.WriteString(w, content)
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
FileSize: int64(len(content)),
}
obj, err := svc.OpenFile(context.Background(), file, filemanager.FileConditions{})
require.NoError(t, err)
require.NotNil(t, obj)
require.False(t, obj.NotModified)
defer func() { _ = obj.Body.Close() }()
assert.Equal(t, etag, obj.ETag)
assert.Equal(t, "text/plain", obj.ContentType)
assert.Equal(t, int64(len(content)), obj.ContentLength)
body, err := io.ReadAll(obj.Body)
require.NoError(t, err)
assert.Equal(t, content, string(body))
}
func TestOpenFile_RangeRequestReturnsPartialContent(t *testing.T) {
t.Parallel()
const (
etag = `"abc123"`
content = "hello world"
)
svc := newTestS3Service(
t,
func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "bytes=0-4", r.Header.Get("Range"))
w.Header().Set("ETag", etag)
w.Header().Set("Content-Range", "bytes 0-4/11")
w.Header().Set("Content-Length", "5")
w.WriteHeader(http.StatusPartialContent)
_, _ = io.WriteString(w, content[:5])
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
FileSize: int64(len(content)),
}
obj, err := svc.OpenFile(
context.Background(),
file,
filemanager.FileConditions{Range: "bytes=0-4"},
)
require.NoError(t, err)
require.NotNil(t, obj)
defer func() { _ = obj.Body.Close() }()
assert.True(t, obj.PartialContent)
assert.False(t, obj.NotModified)
assert.Equal(t, "bytes 0-4/11", obj.ContentRange)
assert.Equal(t, int64(5), obj.ContentLength)
body, err := io.ReadAll(obj.Body)
require.NoError(t, err)
assert.Equal(t, content[:5], string(body))
}
func TestOpenFile_IfRangeMatchHonorsRange(t *testing.T) {
t.Parallel()
const (
etag = `"abc123"`
content = "hello world"
)
svc := newTestS3Service(
t,
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.Header().Set("ETag", etag)
w.WriteHeader(http.StatusOK)
return
}
assert.Equal(t, "bytes=0-4", r.Header.Get("Range"))
w.Header().Set("ETag", etag)
w.Header().Set("Content-Range", "bytes 0-4/11")
w.Header().Set("Content-Length", "5")
w.WriteHeader(http.StatusPartialContent)
_, _ = io.WriteString(w, content[:5])
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
FileSize: int64(len(content)),
}
obj, err := svc.OpenFile(
context.Background(),
file,
filemanager.FileConditions{Range: "bytes=0-4", IfRange: etag},
)
require.NoError(t, err)
require.NotNil(t, obj)
defer func() { _ = obj.Body.Close() }()
assert.True(t, obj.PartialContent)
assert.Equal(t, "bytes 0-4/11", obj.ContentRange)
body, err := io.ReadAll(obj.Body)
require.NoError(t, err)
assert.Equal(t, content[:5], string(body))
}
func TestOpenFile_IfRangeMismatchServesFullContent(t *testing.T) {
t.Parallel()
const (
staleETag = `"old"`
currentETag = `"new"`
content = "hello world"
)
svc := newTestS3Service(
t,
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.Header().Set("ETag", currentETag)
w.WriteHeader(http.StatusOK)
return
}
// The stale If-Range guard must have dropped the Range so S3
// returns the full object rather than a 206 of the fresh bytes.
assert.Empty(t, r.Header.Get("Range"))
w.Header().Set("ETag", currentETag)
_, _ = io.WriteString(w, content)
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
FileSize: int64(len(content)),
}
obj, err := svc.OpenFile(
context.Background(),
file,
filemanager.FileConditions{Range: "bytes=0-4", IfRange: staleETag},
)
require.NoError(t, err)
require.NotNil(t, obj)
defer func() { _ = obj.Body.Close() }()
assert.False(t, obj.PartialContent)
assert.Empty(t, obj.ContentRange)
body, err := io.ReadAll(obj.Body)
require.NoError(t, err)
assert.Equal(t, content, string(body))
}
func TestOpenFile_RangeNotSatisfiable(t *testing.T) {
t.Parallel()
const content = "hello world"
svc := newTestS3Service(
t,
func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Range", "bytes */11")
w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
FileSize: int64(len(content)),
}
obj, err := svc.OpenFile(
context.Background(),
file,
filemanager.FileConditions{Range: "bytes=999-1000"},
)
require.NoError(t, err)
require.NotNil(t, obj)
assert.True(t, obj.RangeNotSatisfiable)
assert.False(t, obj.PartialContent)
assert.Nil(t, obj.Body)
assert.Equal(t, int64(len(content)), obj.ContentLength)
}
func TestOpenFile_NotModifiedByETag(t *testing.T) {
t.Parallel()
const etag = `"abc123"`
svc := newTestS3Service(
t,
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
w.Header().Set("ETag", etag)
_, _ = io.WriteString(w, "content")
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
}
obj, err := svc.OpenFile(context.Background(), file, filemanager.FileConditions{IfNoneMatch: etag})
require.NoError(t, err)
require.NotNil(t, obj)
assert.True(t, obj.NotModified)
assert.Nil(t, obj.Body)
}
func TestOpenFile_NotModifiedByModifiedSince(t *testing.T) {
t.Parallel()
svc := newTestS3Service(
t,
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("If-Modified-Since") != "" {
w.WriteHeader(http.StatusNotModified)
return
}
_, _ = io.WriteString(w, "content")
},
)
file := &coredata.File{
BucketName: "uploads",
FileKey: "tenant/file",
MimeType: "text/plain",
}
obj, err := svc.OpenFile(
context.Background(),
file,
filemanager.FileConditions{IfModifiedSince: time.Now()},
)
require.NoError(t, err)
require.NotNil(t, obj)
assert.True(t, obj.NotModified)
assert.Nil(t, obj.Body)
}