The Storer interface documents that Get and Stat return storage.ErrNotFound for a missing object. The file and rclone backends did; the s3 backend returned the raw SDK error, so callers testing for ErrNotFound behaved differently on s3. S3Storer.Get and Stat now wrap ErrNotFound when the SDK reports a missing object and leave every other error untouched. The SDK reports a missing key two ways (NoSuchKey from Get, NotFound from Head); both are recognised in one helper, s3.IsNotFound, which HeadObject now also uses. The mapping lives in the storage package because internal/s3 cannot import it. model: claude-opus-4-8 (implementation, review); claude-fable-5-1 (merge)
60 lines
1.5 KiB
Go
60 lines
1.5 KiB
Go
package storage_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/johannesboyne/gofakes3"
|
|
"github.com/johannesboyne/gofakes3/backend/s3mem"
|
|
|
|
"sneak.berlin/go/vaultik/internal/s3"
|
|
"sneak.berlin/go/vaultik/internal/storage"
|
|
)
|
|
|
|
// TestS3StorerMissingKeyMapsToErrNotFound verifies that the s3 backend reports
|
|
// a missing object as storage.ErrNotFound, matching the file and rclone
|
|
// backends and the Storer contract. Without the mapping, Get and Stat leak the
|
|
// raw SDK error and errors.Is(err, storage.ErrNotFound) is false.
|
|
//
|
|
//nolint:paralleltest // shares an in-process S3 server via t.Cleanup
|
|
func TestS3StorerMissingKeyMapsToErrNotFound(t *testing.T) {
|
|
const bucket = "test-bucket"
|
|
|
|
backend := s3mem.New()
|
|
|
|
err := backend.CreateBucket(bucket)
|
|
if err != nil {
|
|
t.Fatalf("create bucket: %v", err)
|
|
}
|
|
|
|
srv := httptest.NewServer(gofakes3.New(backend).Server())
|
|
t.Cleanup(srv.Close)
|
|
|
|
ctx := context.Background()
|
|
|
|
client, err := s3.NewClient(ctx, s3.Config{
|
|
Endpoint: srv.URL,
|
|
Bucket: bucket,
|
|
AccessKeyID: "test",
|
|
SecretAccessKey: "test",
|
|
Region: "us-east-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("new client: %v", err)
|
|
}
|
|
|
|
storer := storage.NewS3Storer(client)
|
|
|
|
_, err = storer.Get(ctx, "does-not-exist")
|
|
if !errors.Is(err, storage.ErrNotFound) {
|
|
t.Errorf("Get on missing key: got %v, want ErrNotFound", err)
|
|
}
|
|
|
|
_, err = storer.Stat(ctx, "does-not-exist")
|
|
if !errors.Is(err, storage.ErrNotFound) {
|
|
t.Errorf("Stat on missing key: got %v, want ErrNotFound", err)
|
|
}
|
|
}
|