mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
	
	
		
			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"}, | ||
|  | 	}) | ||
|  | } |