Path: blob/main/test/tests/components/content-service/content-service_test.go
2499 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package contentservice56import (7"context"8"fmt"9"io"10"net/http"11"net/url"12"strings"13"testing"14"time"1516"google.golang.org/grpc/codes"17"google.golang.org/grpc/status"18"sigs.k8s.io/e2e-framework/pkg/envconf"19"sigs.k8s.io/e2e-framework/pkg/features"2021content_service_api "github.com/gitpod-io/gitpod/content-service/api"22"github.com/gitpod-io/gitpod/test/pkg/integration"23)2425var (26gitpodBuiltinUserID = "00000000-0000-0000-0000-000000000000"27)2829func hasErrorCode(err error, code codes.Code) bool {30st, ok := status.FromError(err)31return ok && st.Code() == code32}3334func TestUploadUrl(t *testing.T) {35tests := []struct {36Name string37InputOwnerID string38InputName string39ExpectedErrorCode codes.Code40}{41{42Name: "simple name",43InputOwnerID: gitpodBuiltinUserID,44InputName: "test-blob",45},46{47Name: "new user",48InputOwnerID: "new-user",49InputName: "test-blob",50},51{52Name: "name with whitespace",53InputOwnerID: gitpodBuiltinUserID,54InputName: "whitespaces are not allowed",55ExpectedErrorCode: codes.InvalidArgument,56},57{58Name: "name with invalid char",59InputOwnerID: gitpodBuiltinUserID,60InputName: "รค-is-not-allowed",61ExpectedErrorCode: codes.InvalidArgument,62},63}6465f := features.New("UploadUrlRequest").66WithLabel("component", "content-service").67Assess("it should run content-service tests", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context {68t.Parallel()6970ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute)71defer cancel()7273api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())74t.Cleanup(func() {75api.Done(t)76})7778bs, err := api.BlobService()79if err != nil {80t.Fatal(err)81}8283for _, test := range tests {84t.Run(test.Name, func(t *testing.T) {85resp, err := bs.UploadUrl(ctx, &content_service_api.UploadUrlRequest{OwnerId: test.InputOwnerID, Name: test.InputName})86if err != nil && test.ExpectedErrorCode == codes.OK {87t.Fatal(err)88}89if err == nil && test.ExpectedErrorCode != codes.OK {90t.Fatalf("expected error with error code '%v' but got no error at all", test.ExpectedErrorCode)91}92if !hasErrorCode(err, test.ExpectedErrorCode) {93t.Fatalf("expected error with error code '%v' but got error %v", test.ExpectedErrorCode, err)94}95if err != nil && test.ExpectedErrorCode == codes.OK {96url := resp.Url97if url == "" {98t.Fatal("upload url is empty")99}100t.Logf("Got URL repsonse: %s", url)101}102})103}104105return testCtx106}).107Feature()108109testEnv.Test(t, f)110}111112func TestDownloadUrl(t *testing.T) {113tests := []struct {114Name string115InputOwnerID string116InputName string117ExpectedErrorCode codes.Code118}{119{120Name: "not existing download",121InputOwnerID: gitpodBuiltinUserID,122InputName: "this-does-not-exist",123ExpectedErrorCode: codes.NotFound,124},125}126127f := features.New("DownloadUrl").128WithLabel("component", "server").129Assess("it should pass download URL tests", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context {130t.Parallel()131132ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute)133defer cancel()134135api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())136t.Cleanup(func() {137api.Done(t)138})139140bs, err := api.BlobService()141if err != nil {142t.Fatal(err)143}144145for _, test := range tests {146t.Run(test.Name, func(t *testing.T) {147resp, err := bs.DownloadUrl(ctx, &content_service_api.DownloadUrlRequest{OwnerId: test.InputOwnerID, Name: test.InputName})148if err != nil && test.ExpectedErrorCode == codes.OK {149t.Fatal(err)150}151if err == nil && test.ExpectedErrorCode != codes.OK {152t.Fatalf("expected error with error code '%v' but got no error at all", test.ExpectedErrorCode)153}154if !hasErrorCode(err, test.ExpectedErrorCode) {155t.Fatalf("expected error with error code '%v' but got error %v", test.ExpectedErrorCode, err)156}157if err != nil && test.ExpectedErrorCode == codes.OK {158url := resp.Url159if url == "" {160t.Fatal("download url is empty")161}162t.Logf("Got URL repsonse: %s", url)163}164})165}166167return testCtx168}).169Feature()170171testEnv.Test(t, f)172}173174func TestUploadDownloadBlob(t *testing.T) {175f := features.New("UploadDownloadBlob").176WithLabel("component", "server").177Assess("it should upload and download blob", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context {178t.Parallel()179180ctx, cancel := context.WithTimeout(testCtx, 5*time.Minute)181defer cancel()182183api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())184t.Cleanup(func() {185api.Done(t)186})187188blobContent := fmt.Sprintf("Hello Blobs! It's %s!", time.Now())189190bs, err := api.BlobService()191if err != nil {192t.Fatal(err)193}194195resp, err := bs.UploadUrl(ctx, &content_service_api.UploadUrlRequest{OwnerId: gitpodBuiltinUserID, Name: "test-blob"})196if err != nil {197t.Fatal(err)198}199originalUrl := resp.Url200updatedUrl, err := api.Storage(originalUrl)201if err != nil {202t.Fatalf("error resolving blob upload target url: %q", err)203}204t.Logf("upload URL: %s", updatedUrl)205206uploadBlob(t, originalUrl, updatedUrl, blobContent)207208resp2, err := bs.DownloadUrl(ctx, &content_service_api.DownloadUrlRequest{OwnerId: gitpodBuiltinUserID, Name: "test-blob"})209if err != nil {210t.Fatal(err)211}212originalUrl = resp2.Url213updatedUrl, err = api.Storage(originalUrl)214if err != nil {215t.Fatalf("error resolving blob download target url: %q", err)216}217t.Logf("download URL: %s", updatedUrl)218219body := downloadBlob(t, originalUrl, updatedUrl)220if string(body) != blobContent {221t.Fatalf("blob content mismatch: should '%s' but is '%s'", blobContent, body)222}223224return testCtx225}).226Feature()227228testEnv.Test(t, f)229}230231func uploadBlob(t *testing.T, originalUrl, updatedUrl, content string) {232// Always use original URL to extract the host information.233// This will avoid any Signature mismatch errors234u, err := url.Parse(originalUrl)235if err != nil {236t.Fatal(err)237}238var client = &http.Client{Timeout: time.Second * 10}239httpreq, err := http.NewRequest(http.MethodPut, updatedUrl, strings.NewReader(content))240if err != nil {241t.Fatalf("cannot create HTTP PUT request: %q", err)242}243// Add Host header244httpreq.Host = u.Host245httpresp, err := client.Do(httpreq)246if err != nil {247t.Fatalf("HTTP PUT request failed: %q", err)248}249body, err := io.ReadAll(httpresp.Body)250if err != nil {251t.Fatalf("cannot read response body of HTTP PUT: %q", err)252}253if string(body) != "" {254t.Fatalf("unexpected response body of HTTP PUT: '%q'", body)255}256}257258func downloadBlob(t *testing.T, originalUrl, updatedUrl string) string {259// Always use original URL to extract the host information.260// This will avoid any Signature mismatch errors261u, err := url.Parse(originalUrl)262if err != nil {263t.Fatal(err)264}265var client = &http.Client{Timeout: time.Second * 10}266httpreq, err := http.NewRequest(http.MethodGet, updatedUrl, nil)267if err != nil {268t.Fatalf("cannot create HTTP GET request: %q", err)269}270// Add Host header271httpreq.Host = u.Host272httpresp, err := client.Do(httpreq)273if err != nil {274t.Fatalf("HTTP GET request failed: %q", err)275}276body, err := io.ReadAll(httpresp.Body)277if err != nil {278t.Fatalf("cannot read response body of HTTP GET: %q", err)279}280return string(body)281}282283284