minio/cmd/object-lambda-handlers_test.go

175 lines
5.2 KiB
Go

// Copyright (c) 2015-2025 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"time"
"github.com/minio/minio-go/v7/pkg/signer"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/lambda"
levent "github.com/minio/minio/internal/config/lambda/event"
xhttp "github.com/minio/minio/internal/http"
)
func TestGetObjectLambdaHandler(t *testing.T) {
testCases := []struct {
name string
statusCode int
body string
contentType string
expectStatus int
}{
{
name: "Success 206 Partial Content",
statusCode: 206,
body: "partial-object-data",
contentType: "text/plain",
expectStatus: 206,
},
{
name: "Success 200 OK",
statusCode: 200,
body: "full-object-data",
contentType: "application/json",
expectStatus: 200,
},
{
name: "Client Error 400",
statusCode: 400,
body: "bad-request",
contentType: "application/xml",
expectStatus: 400,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
runObjectLambdaTest(t, tc.statusCode, tc.body, tc.contentType, tc.expectStatus)
})
}
}
func runObjectLambdaTest(t *testing.T, lambdaStatus int, lambdaBody, contentType string, expectStatus int) {
ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{
t: t,
objAPITest: func(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, credentials auth.Credentials, t *testing.T) {
objectName := "dummy-object"
functionID := "lambda1"
functionToken := "token123"
// Lambda mock server
lambdaServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(xhttp.AmzRequestRoute, functionID)
w.Header().Set(xhttp.AmzRequestToken, functionToken)
w.Header().Set(xhttp.AmzFwdHeaderContentType, contentType)
w.Header().Set(xhttp.AmzFwdStatus, strconv.Itoa(lambdaStatus))
w.WriteHeader(lambdaStatus)
w.Write([]byte(lambdaBody))
}))
defer lambdaServer.Close()
lambdaARN := "arn:minio:s3-object-lambda::lambda1:webhook"
cfg := config.New()
cfg[config.LambdaWebhookSubSys] = map[string]config.KVS{
functionID: {
{Key: "endpoint", Value: lambdaServer.URL},
{Key: "enable", Value: config.EnableOn},
},
}
cfg[config.APISubSys] = map[string]config.KVS{
"api": {
{Key: "gzip", Value: config.EnableOff},
},
}
var err error
globalLambdaTargetList, err = lambda.FetchEnabledTargets(context.Background(), cfg, http.DefaultTransport.(*http.Transport))
if err != nil {
t.Fatalf("failed to load lambda targets: %v", err)
}
getLambdaEventData = func(_, _ string, _ auth.Credentials, _ *http.Request) (levent.Event, error) {
return levent.Event{
GetObjectContext: &levent.GetObjectContext{
OutputRoute: functionID,
OutputToken: functionToken,
InputS3URL: "http://localhost/dummy",
},
UserRequest: levent.UserRequest{
Headers: map[string][]string{},
},
UserIdentity: levent.Identity{
PrincipalID: "test-user",
},
}, nil
}
body := []byte{}
req := httptest.NewRequest("GET", "/objectlambda/"+bucketName+"/"+objectName+"?lambdaArn="+url.QueryEscape(lambdaARN), bytes.NewReader(body))
req.Form = url.Values{"lambdaArn": []string{lambdaARN}}
req.Header.Set("Host", "localhost")
req.Header.Set("X-Amz-Date", time.Now().UTC().Format("20060102T150405Z"))
sum := sha256.Sum256(body)
req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(sum[:]))
req = signer.SignV4(*req, credentials.AccessKey, credentials.SecretKey, "", "us-east-1")
rec := httptest.NewRecorder()
api := objectAPIHandlers{
ObjectAPI: func() ObjectLayer {
return obj
},
}
api.GetObjectLambdaHandler(rec, req)
res := rec.Result()
defer res.Body.Close()
respBody, _ := io.ReadAll(res.Body)
if res.StatusCode != expectStatus {
t.Errorf("Expected status %d, got %d", expectStatus, res.StatusCode)
}
if contentType != "" {
if ct := res.Header.Get("Content-Type"); ct != contentType {
t.Errorf("Expected Content-Type %q, got %q", contentType, ct)
}
}
if res.StatusCode < 400 {
if string(respBody) != lambdaBody {
t.Errorf("Expected body %q, got %q", lambdaBody, string(respBody))
}
}
},
endpoints: []string{"GetObject"},
})
}