code.gitea.io/gitea@v1.21.7/tests/integration/api_repo_lfs_test.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"bytes"
     8  	"net/http"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  
    14  	auth_model "code.gitea.io/gitea/models/auth"
    15  	"code.gitea.io/gitea/models/db"
    16  	git_model "code.gitea.io/gitea/models/git"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	"code.gitea.io/gitea/models/unittest"
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	"code.gitea.io/gitea/modules/json"
    21  	"code.gitea.io/gitea/modules/lfs"
    22  	"code.gitea.io/gitea/modules/setting"
    23  	"code.gitea.io/gitea/tests"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  )
    27  
    28  func TestAPILFSNotStarted(t *testing.T) {
    29  	defer tests.PrepareTestEnv(t)()
    30  
    31  	setting.LFS.StartServer = false
    32  
    33  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    34  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
    35  
    36  	req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name)
    37  	MakeRequest(t, req, http.StatusNotFound)
    38  	req = NewRequestf(t, "PUT", "/%s/%s.git/info/lfs/objects/oid/10", user.Name, repo.Name)
    39  	MakeRequest(t, req, http.StatusNotFound)
    40  	req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid/name", user.Name, repo.Name)
    41  	MakeRequest(t, req, http.StatusNotFound)
    42  	req = NewRequestf(t, "GET", "/%s/%s.git/info/lfs/objects/oid", user.Name, repo.Name)
    43  	MakeRequest(t, req, http.StatusNotFound)
    44  	req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name)
    45  	MakeRequest(t, req, http.StatusNotFound)
    46  }
    47  
    48  func TestAPILFSMediaType(t *testing.T) {
    49  	defer tests.PrepareTestEnv(t)()
    50  
    51  	setting.LFS.StartServer = true
    52  
    53  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    54  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
    55  
    56  	req := NewRequestf(t, "POST", "/%s/%s.git/info/lfs/objects/batch", user.Name, repo.Name)
    57  	MakeRequest(t, req, http.StatusUnsupportedMediaType)
    58  	req = NewRequestf(t, "POST", "/%s/%s.git/info/lfs/verify", user.Name, repo.Name)
    59  	MakeRequest(t, req, http.StatusUnsupportedMediaType)
    60  }
    61  
    62  func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository {
    63  	ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
    64  	t.Run("CreateRepo", doAPICreateRepository(ctx, false))
    65  
    66  	repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs-"+name+"-repo")
    67  	assert.NoError(t, err)
    68  
    69  	return repo
    70  }
    71  
    72  func TestAPILFSBatch(t *testing.T) {
    73  	defer tests.PrepareTestEnv(t)()
    74  
    75  	setting.LFS.StartServer = true
    76  
    77  	repo := createLFSTestRepository(t, "batch")
    78  
    79  	content := []byte("dummy1")
    80  	oid := storeObjectInRepo(t, repo.ID, &content)
    81  	defer git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, repo.ID, oid)
    82  
    83  	session := loginUser(t, "user2")
    84  
    85  	newRequest := func(t testing.TB, br *lfs.BatchRequest) *http.Request {
    86  		req := NewRequestWithJSON(t, "POST", "/user2/lfs-batch-repo.git/info/lfs/objects/batch", br)
    87  		req.Header.Set("Accept", lfs.MediaType)
    88  		req.Header.Set("Content-Type", lfs.MediaType)
    89  		return req
    90  	}
    91  	decodeResponse := func(t *testing.T, b *bytes.Buffer) *lfs.BatchResponse {
    92  		var br lfs.BatchResponse
    93  
    94  		assert.NoError(t, json.Unmarshal(b.Bytes(), &br))
    95  		return &br
    96  	}
    97  
    98  	t.Run("InvalidJsonRequest", func(t *testing.T) {
    99  		defer tests.PrintCurrentTest(t)()
   100  
   101  		req := newRequest(t, nil)
   102  
   103  		session.MakeRequest(t, req, http.StatusBadRequest)
   104  	})
   105  
   106  	t.Run("InvalidOperation", func(t *testing.T) {
   107  		defer tests.PrintCurrentTest(t)()
   108  
   109  		req := newRequest(t, &lfs.BatchRequest{
   110  			Operation: "dummy",
   111  		})
   112  
   113  		session.MakeRequest(t, req, http.StatusBadRequest)
   114  	})
   115  
   116  	t.Run("InvalidPointer", func(t *testing.T) {
   117  		defer tests.PrintCurrentTest(t)()
   118  
   119  		req := newRequest(t, &lfs.BatchRequest{
   120  			Operation: "download",
   121  			Objects: []lfs.Pointer{
   122  				{Oid: "dummy"},
   123  				{Oid: oid, Size: -1},
   124  			},
   125  		})
   126  
   127  		resp := session.MakeRequest(t, req, http.StatusOK)
   128  		br := decodeResponse(t, resp.Body)
   129  		assert.Len(t, br.Objects, 2)
   130  		assert.Equal(t, "dummy", br.Objects[0].Oid)
   131  		assert.Equal(t, oid, br.Objects[1].Oid)
   132  		assert.Equal(t, int64(0), br.Objects[0].Size)
   133  		assert.Equal(t, int64(-1), br.Objects[1].Size)
   134  		assert.NotNil(t, br.Objects[0].Error)
   135  		assert.NotNil(t, br.Objects[1].Error)
   136  		assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code)
   137  		assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[1].Error.Code)
   138  		assert.Equal(t, "Oid or size are invalid", br.Objects[0].Error.Message)
   139  		assert.Equal(t, "Oid or size are invalid", br.Objects[1].Error.Message)
   140  	})
   141  
   142  	t.Run("PointerSizeMismatch", func(t *testing.T) {
   143  		defer tests.PrintCurrentTest(t)()
   144  
   145  		req := newRequest(t, &lfs.BatchRequest{
   146  			Operation: "download",
   147  			Objects: []lfs.Pointer{
   148  				{Oid: oid, Size: 1},
   149  			},
   150  		})
   151  
   152  		resp := session.MakeRequest(t, req, http.StatusOK)
   153  		br := decodeResponse(t, resp.Body)
   154  		assert.Len(t, br.Objects, 1)
   155  		assert.NotNil(t, br.Objects[0].Error)
   156  		assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code)
   157  		assert.Equal(t, "Object "+oid+" is not 1 bytes", br.Objects[0].Error.Message)
   158  	})
   159  
   160  	t.Run("Download", func(t *testing.T) {
   161  		defer tests.PrintCurrentTest(t)()
   162  
   163  		t.Run("PointerNotInStore", func(t *testing.T) {
   164  			defer tests.PrintCurrentTest(t)()
   165  
   166  			req := newRequest(t, &lfs.BatchRequest{
   167  				Operation: "download",
   168  				Objects: []lfs.Pointer{
   169  					{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6},
   170  				},
   171  			})
   172  
   173  			resp := session.MakeRequest(t, req, http.StatusOK)
   174  			br := decodeResponse(t, resp.Body)
   175  			assert.Len(t, br.Objects, 1)
   176  			assert.NotNil(t, br.Objects[0].Error)
   177  			assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code)
   178  		})
   179  
   180  		t.Run("MetaNotFound", func(t *testing.T) {
   181  			defer tests.PrintCurrentTest(t)()
   182  
   183  			p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6}
   184  
   185  			contentStore := lfs.NewContentStore()
   186  			exist, err := contentStore.Exists(p)
   187  			assert.NoError(t, err)
   188  			assert.False(t, exist)
   189  			err = contentStore.Put(p, bytes.NewReader([]byte("dummy0")))
   190  			assert.NoError(t, err)
   191  
   192  			req := newRequest(t, &lfs.BatchRequest{
   193  				Operation: "download",
   194  				Objects:   []lfs.Pointer{p},
   195  			})
   196  
   197  			resp := session.MakeRequest(t, req, http.StatusOK)
   198  			br := decodeResponse(t, resp.Body)
   199  			assert.Len(t, br.Objects, 1)
   200  			assert.NotNil(t, br.Objects[0].Error)
   201  			assert.Equal(t, http.StatusNotFound, br.Objects[0].Error.Code)
   202  		})
   203  
   204  		t.Run("Success", func(t *testing.T) {
   205  			defer tests.PrintCurrentTest(t)()
   206  
   207  			req := newRequest(t, &lfs.BatchRequest{
   208  				Operation: "download",
   209  				Objects: []lfs.Pointer{
   210  					{Oid: oid, Size: 6},
   211  				},
   212  			})
   213  
   214  			resp := session.MakeRequest(t, req, http.StatusOK)
   215  			br := decodeResponse(t, resp.Body)
   216  			assert.Len(t, br.Objects, 1)
   217  			assert.Nil(t, br.Objects[0].Error)
   218  			assert.Contains(t, br.Objects[0].Actions, "download")
   219  			l := br.Objects[0].Actions["download"]
   220  			assert.NotNil(t, l)
   221  			assert.NotEmpty(t, l.Href)
   222  		})
   223  	})
   224  
   225  	t.Run("Upload", func(t *testing.T) {
   226  		defer tests.PrintCurrentTest(t)()
   227  
   228  		t.Run("FileTooBig", func(t *testing.T) {
   229  			defer tests.PrintCurrentTest(t)()
   230  
   231  			oldMaxFileSize := setting.LFS.MaxFileSize
   232  			setting.LFS.MaxFileSize = 2
   233  
   234  			req := newRequest(t, &lfs.BatchRequest{
   235  				Operation: "upload",
   236  				Objects: []lfs.Pointer{
   237  					{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6},
   238  				},
   239  			})
   240  
   241  			resp := session.MakeRequest(t, req, http.StatusOK)
   242  			br := decodeResponse(t, resp.Body)
   243  			assert.Len(t, br.Objects, 1)
   244  			assert.NotNil(t, br.Objects[0].Error)
   245  			assert.Equal(t, http.StatusUnprocessableEntity, br.Objects[0].Error.Code)
   246  			assert.Equal(t, "Size must be less than or equal to 2", br.Objects[0].Error.Message)
   247  
   248  			setting.LFS.MaxFileSize = oldMaxFileSize
   249  		})
   250  
   251  		t.Run("AddMeta", func(t *testing.T) {
   252  			defer tests.PrintCurrentTest(t)()
   253  
   254  			p := lfs.Pointer{Oid: "05eeb4eb5be71f2dd291ca39157d6d9effd7d1ea19cbdc8a99411fe2a8f26a00", Size: 6}
   255  
   256  			contentStore := lfs.NewContentStore()
   257  			exist, err := contentStore.Exists(p)
   258  			assert.NoError(t, err)
   259  			assert.True(t, exist)
   260  
   261  			repo2 := createLFSTestRepository(t, "batch2")
   262  			content := []byte("dummy0")
   263  			storeObjectInRepo(t, repo2.ID, &content)
   264  
   265  			meta, err := git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, p.Oid)
   266  			assert.Nil(t, meta)
   267  			assert.Equal(t, git_model.ErrLFSObjectNotExist, err)
   268  
   269  			req := newRequest(t, &lfs.BatchRequest{
   270  				Operation: "upload",
   271  				Objects:   []lfs.Pointer{p},
   272  			})
   273  
   274  			resp := session.MakeRequest(t, req, http.StatusOK)
   275  			br := decodeResponse(t, resp.Body)
   276  			assert.Len(t, br.Objects, 1)
   277  			assert.Nil(t, br.Objects[0].Error)
   278  			assert.Empty(t, br.Objects[0].Actions)
   279  
   280  			meta, err = git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, p.Oid)
   281  			assert.NoError(t, err)
   282  			assert.NotNil(t, meta)
   283  
   284  			// Cleanup
   285  			err = contentStore.Delete(p.RelativePath())
   286  			assert.NoError(t, err)
   287  		})
   288  
   289  		t.Run("AlreadyExists", func(t *testing.T) {
   290  			defer tests.PrintCurrentTest(t)()
   291  
   292  			req := newRequest(t, &lfs.BatchRequest{
   293  				Operation: "upload",
   294  				Objects: []lfs.Pointer{
   295  					{Oid: oid, Size: 6},
   296  				},
   297  			})
   298  
   299  			resp := session.MakeRequest(t, req, http.StatusOK)
   300  			br := decodeResponse(t, resp.Body)
   301  			assert.Len(t, br.Objects, 1)
   302  			assert.Nil(t, br.Objects[0].Error)
   303  			assert.Empty(t, br.Objects[0].Actions)
   304  		})
   305  
   306  		t.Run("NewFile", func(t *testing.T) {
   307  			defer tests.PrintCurrentTest(t)()
   308  
   309  			req := newRequest(t, &lfs.BatchRequest{
   310  				Operation: "upload",
   311  				Objects: []lfs.Pointer{
   312  					{Oid: "d6f175817f886ec6fbbc1515326465fa96c3bfd54a4ea06cfd6dbbd8340e0153", Size: 1},
   313  				},
   314  			})
   315  
   316  			resp := session.MakeRequest(t, req, http.StatusOK)
   317  			br := decodeResponse(t, resp.Body)
   318  			assert.Len(t, br.Objects, 1)
   319  			assert.Nil(t, br.Objects[0].Error)
   320  			assert.Contains(t, br.Objects[0].Actions, "upload")
   321  			ul := br.Objects[0].Actions["upload"]
   322  			assert.NotNil(t, ul)
   323  			assert.NotEmpty(t, ul.Href)
   324  			assert.Contains(t, br.Objects[0].Actions, "verify")
   325  			vl := br.Objects[0].Actions["verify"]
   326  			assert.NotNil(t, vl)
   327  			assert.NotEmpty(t, vl.Href)
   328  		})
   329  	})
   330  }
   331  
   332  func TestAPILFSUpload(t *testing.T) {
   333  	defer tests.PrepareTestEnv(t)()
   334  
   335  	setting.LFS.StartServer = true
   336  
   337  	repo := createLFSTestRepository(t, "upload")
   338  
   339  	content := []byte("dummy3")
   340  	oid := storeObjectInRepo(t, repo.ID, &content)
   341  	defer git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, repo.ID, oid)
   342  
   343  	session := loginUser(t, "user2")
   344  
   345  	newRequest := func(t testing.TB, p lfs.Pointer, content string) *http.Request {
   346  		req := NewRequestWithBody(t, "PUT", path.Join("/user2/lfs-upload-repo.git/info/lfs/objects/", p.Oid, strconv.FormatInt(p.Size, 10)), strings.NewReader(content))
   347  		return req
   348  	}
   349  
   350  	t.Run("InvalidPointer", func(t *testing.T) {
   351  		defer tests.PrintCurrentTest(t)()
   352  
   353  		req := newRequest(t, lfs.Pointer{Oid: "dummy"}, "")
   354  
   355  		session.MakeRequest(t, req, http.StatusUnprocessableEntity)
   356  	})
   357  
   358  	t.Run("AlreadyExistsInStore", func(t *testing.T) {
   359  		defer tests.PrintCurrentTest(t)()
   360  
   361  		p := lfs.Pointer{Oid: "83de2e488b89a0aa1c97496b888120a28b0c1e15463a4adb8405578c540f36d4", Size: 6}
   362  
   363  		contentStore := lfs.NewContentStore()
   364  		exist, err := contentStore.Exists(p)
   365  		assert.NoError(t, err)
   366  		assert.False(t, exist)
   367  		err = contentStore.Put(p, bytes.NewReader([]byte("dummy5")))
   368  		assert.NoError(t, err)
   369  
   370  		meta, err := git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, p.Oid)
   371  		assert.Nil(t, meta)
   372  		assert.Equal(t, git_model.ErrLFSObjectNotExist, err)
   373  
   374  		t.Run("InvalidAccess", func(t *testing.T) {
   375  			req := newRequest(t, p, "invalid")
   376  			session.MakeRequest(t, req, http.StatusUnprocessableEntity)
   377  		})
   378  
   379  		t.Run("ValidAccess", func(t *testing.T) {
   380  			req := newRequest(t, p, "dummy5")
   381  
   382  			session.MakeRequest(t, req, http.StatusOK)
   383  			meta, err = git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, p.Oid)
   384  			assert.NoError(t, err)
   385  			assert.NotNil(t, meta)
   386  		})
   387  
   388  		// Cleanup
   389  		err = contentStore.Delete(p.RelativePath())
   390  		assert.NoError(t, err)
   391  	})
   392  
   393  	t.Run("MetaAlreadyExists", func(t *testing.T) {
   394  		defer tests.PrintCurrentTest(t)()
   395  
   396  		req := newRequest(t, lfs.Pointer{Oid: oid, Size: 6}, "")
   397  
   398  		session.MakeRequest(t, req, http.StatusOK)
   399  	})
   400  
   401  	t.Run("HashMismatch", func(t *testing.T) {
   402  		defer tests.PrintCurrentTest(t)()
   403  
   404  		req := newRequest(t, lfs.Pointer{Oid: "2581dd7bbc1fe44726de4b7dd806a087a978b9c5aec0a60481259e34be09b06a", Size: 1}, "a")
   405  
   406  		session.MakeRequest(t, req, http.StatusUnprocessableEntity)
   407  	})
   408  
   409  	t.Run("SizeMismatch", func(t *testing.T) {
   410  		defer tests.PrintCurrentTest(t)()
   411  
   412  		req := newRequest(t, lfs.Pointer{Oid: "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", Size: 2}, "a")
   413  
   414  		session.MakeRequest(t, req, http.StatusUnprocessableEntity)
   415  	})
   416  
   417  	t.Run("Success", func(t *testing.T) {
   418  		defer tests.PrintCurrentTest(t)()
   419  
   420  		p := lfs.Pointer{Oid: "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d", Size: 5}
   421  
   422  		req := newRequest(t, p, "gitea")
   423  
   424  		session.MakeRequest(t, req, http.StatusOK)
   425  
   426  		contentStore := lfs.NewContentStore()
   427  		exist, err := contentStore.Exists(p)
   428  		assert.NoError(t, err)
   429  		assert.True(t, exist)
   430  
   431  		meta, err := git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, p.Oid)
   432  		assert.NoError(t, err)
   433  		assert.NotNil(t, meta)
   434  	})
   435  }
   436  
   437  func TestAPILFSVerify(t *testing.T) {
   438  	defer tests.PrepareTestEnv(t)()
   439  
   440  	setting.LFS.StartServer = true
   441  
   442  	repo := createLFSTestRepository(t, "verify")
   443  
   444  	content := []byte("dummy3")
   445  	oid := storeObjectInRepo(t, repo.ID, &content)
   446  	defer git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, repo.ID, oid)
   447  
   448  	session := loginUser(t, "user2")
   449  
   450  	newRequest := func(t testing.TB, p *lfs.Pointer) *http.Request {
   451  		req := NewRequestWithJSON(t, "POST", "/user2/lfs-verify-repo.git/info/lfs/verify", p)
   452  		req.Header.Set("Accept", lfs.MediaType)
   453  		req.Header.Set("Content-Type", lfs.MediaType)
   454  		return req
   455  	}
   456  
   457  	t.Run("InvalidJsonRequest", func(t *testing.T) {
   458  		defer tests.PrintCurrentTest(t)()
   459  
   460  		req := newRequest(t, nil)
   461  
   462  		session.MakeRequest(t, req, http.StatusUnprocessableEntity)
   463  	})
   464  
   465  	t.Run("InvalidPointer", func(t *testing.T) {
   466  		defer tests.PrintCurrentTest(t)()
   467  
   468  		req := newRequest(t, &lfs.Pointer{})
   469  
   470  		session.MakeRequest(t, req, http.StatusUnprocessableEntity)
   471  	})
   472  
   473  	t.Run("PointerNotExisting", func(t *testing.T) {
   474  		defer tests.PrintCurrentTest(t)()
   475  
   476  		req := newRequest(t, &lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab042", Size: 6})
   477  
   478  		session.MakeRequest(t, req, http.StatusNotFound)
   479  	})
   480  
   481  	t.Run("Success", func(t *testing.T) {
   482  		defer tests.PrintCurrentTest(t)()
   483  
   484  		req := newRequest(t, &lfs.Pointer{Oid: oid, Size: 6})
   485  
   486  		session.MakeRequest(t, req, http.StatusOK)
   487  	})
   488  }