mirror of
https://github.com/minio/minio.git
synced 2025-07-21 06:21:15 -04:00
175 lines
5.2 KiB
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"},
|
|
})
|
|
}
|