Map s3 not-found to storage.ErrNotFound in Get and Stat (closes #129)
check / check (pull_request) Successful in 2m47s

S3Storer.Get and Stat returned the raw AWS SDK error for a missing
object, so errors.Is(err, storage.ErrNotFound) was false on the s3
backend while the file and rclone backends honored the Storer contract.
Callers that branch on ErrNotFound behaved differently per backend.

Both now wrap ErrNotFound when the SDK reports a missing object,
leaving every other error intact. The not-found detection is a small
exported s3.IsNotFound helper, also used by HeadObject so the two share
one definition. A test asserts a missing key maps to ErrNotFound on the
s3 backend; it fails without the mapping.

model: claude-opus-4-8
This commit is contained in:
2026-09-21 18:57:04 +00:00
parent 07ef3a1c78
commit 89862b9e32
4 changed files with 97 additions and 6 deletions
+59
View File
@@ -0,0 +1,59 @@
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)
}
}