github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/api4/file_test.go (about)

     1  // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"mime/multipart"
    12  	"net/http"
    13  	"net/textproto"
    14  	"net/url"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/xzl8028/xenia-server/app"
    22  	"github.com/xzl8028/xenia-server/model"
    23  	"github.com/xzl8028/xenia-server/utils/fileutils"
    24  	"github.com/xzl8028/xenia-server/utils/testutils"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  var testDir = ""
    29  
    30  func init() {
    31  	testDir, _ = fileutils.FindDir("tests")
    32  }
    33  
    34  func checkCond(tb testing.TB, cond bool, text string) {
    35  	if !cond {
    36  		tb.Error(text)
    37  	}
    38  }
    39  
    40  func checkEq(tb testing.TB, v1, v2 interface{}, text string) {
    41  	checkCond(tb, fmt.Sprintf("%+v", v1) == fmt.Sprintf("%+v", v2), text)
    42  }
    43  
    44  func checkNeq(tb testing.TB, v1, v2 interface{}, text string) {
    45  	checkCond(tb, fmt.Sprintf("%+v", v1) != fmt.Sprintf("%+v", v2), text)
    46  }
    47  
    48  type zeroReader struct {
    49  	limit, read int
    50  }
    51  
    52  func (z *zeroReader) Read(b []byte) (int, error) {
    53  	for i := range b {
    54  		if z.read == z.limit {
    55  			return i, io.EOF
    56  		}
    57  		b[i] = 0
    58  		z.read++
    59  	}
    60  
    61  	return len(b), nil
    62  }
    63  
    64  // File Section
    65  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
    66  
    67  func escapeQuotes(s string) string {
    68  	return quoteEscaper.Replace(s)
    69  }
    70  
    71  type UploadOpener func() (io.ReadCloser, int64, error)
    72  
    73  func NewUploadOpenerReader(in io.Reader) UploadOpener {
    74  	return func() (io.ReadCloser, int64, error) {
    75  		rc, ok := in.(io.ReadCloser)
    76  		if ok {
    77  			return rc, -1, nil
    78  		} else {
    79  			return ioutil.NopCloser(in), -1, nil
    80  		}
    81  	}
    82  }
    83  
    84  func NewUploadOpenerFile(path string) UploadOpener {
    85  	return func() (io.ReadCloser, int64, error) {
    86  		fi, err := os.Stat(path)
    87  		if err != nil {
    88  			return nil, 0, err
    89  		}
    90  		f, err := os.Open(path)
    91  		if err != nil {
    92  			return nil, 0, err
    93  		}
    94  		return f, fi.Size(), nil
    95  	}
    96  }
    97  
    98  // testUploadFile and testUploadFiles have been "staged" here, eventually they
    99  // should move back to being model.Client4 methods, once the specifics of the
   100  // public API are sorted out.
   101  func testUploadFile(c *model.Client4, url string, body io.Reader, contentType string,
   102  	contentLength int64) (*model.FileUploadResponse, *model.Response) {
   103  	rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetFilesRoute()+url, body)
   104  	if contentLength != 0 {
   105  		rq.ContentLength = contentLength
   106  	}
   107  	rq.Header.Set("Content-Type", contentType)
   108  
   109  	if len(c.AuthToken) > 0 {
   110  		rq.Header.Set(model.HEADER_AUTH, c.AuthType+" "+c.AuthToken)
   111  	}
   112  
   113  	rp, err := c.HttpClient.Do(rq)
   114  	if err != nil || rp == nil {
   115  		return nil, model.BuildErrorResponse(rp, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0))
   116  	}
   117  	defer closeBody(rp)
   118  
   119  	if rp.StatusCode >= 300 {
   120  		return nil, model.BuildErrorResponse(rp, model.AppErrorFromJson(rp.Body))
   121  	}
   122  
   123  	return model.FileUploadResponseFromJson(rp.Body), model.BuildResponse(rp)
   124  }
   125  
   126  func testUploadFiles(
   127  	c *model.Client4,
   128  	channelId string,
   129  	names []string,
   130  	openers []UploadOpener,
   131  	contentLengths []int64,
   132  	clientIds []string,
   133  	useMultipart,
   134  	useChunkedInSimplePost bool,
   135  ) (
   136  	fileUploadResponse *model.FileUploadResponse,
   137  	response *model.Response,
   138  ) {
   139  	// Do not check len(clientIds), leave it entirely to the user to
   140  	// provide. The server will error out if it does not match the number
   141  	// of files, but it's not critical here.
   142  	if len(names) == 0 || len(openers) == 0 || len(names) != len(openers) {
   143  		return nil, &model.Response{
   144  			Error: model.NewAppError("testUploadFiles",
   145  				"model.client.upload_post_attachment.file.app_error",
   146  				nil, "Empty or mismatched file data", http.StatusBadRequest),
   147  		}
   148  	}
   149  
   150  	// emergencyResponse is a convenience wrapper to return an error response
   151  	emergencyResponse := func(err error, errCode string) *model.Response {
   152  		return &model.Response{
   153  			Error: model.NewAppError("testUploadFiles",
   154  				"model.client."+errCode+".app_error",
   155  				nil, err.Error(), http.StatusBadRequest),
   156  		}
   157  	}
   158  
   159  	// For multipart, start writing the request as a goroutine, and pipe
   160  	// multipart.Writer into it, otherwise generate a new request each
   161  	// time.
   162  	pipeReader, pipeWriter := io.Pipe()
   163  	mw := multipart.NewWriter(pipeWriter)
   164  
   165  	if useMultipart {
   166  		fileUploadResponseChannel := make(chan *model.FileUploadResponse)
   167  		responseChannel := make(chan *model.Response)
   168  		closedMultipart := false
   169  
   170  		go func() {
   171  			fur, resp := testUploadFile(c, "", pipeReader, mw.FormDataContentType(), -1)
   172  			responseChannel <- resp
   173  			fileUploadResponseChannel <- fur
   174  		}()
   175  
   176  		defer func() {
   177  			for {
   178  				select {
   179  				// Premature response, before the entire
   180  				// multipart was sent
   181  				case response = <-responseChannel:
   182  					// Guaranteed to be there
   183  					fileUploadResponse = <-fileUploadResponseChannel
   184  					if !closedMultipart {
   185  						_ = mw.Close()
   186  						_ = pipeWriter.Close()
   187  						closedMultipart = true
   188  					}
   189  					return
   190  
   191  				// Normal response, after the multipart was sent.
   192  				default:
   193  					if !closedMultipart {
   194  						err := mw.Close()
   195  						if err != nil {
   196  							fileUploadResponse = nil
   197  							response = emergencyResponse(err, "upload_post_attachment.writer")
   198  							return
   199  						}
   200  						err = pipeWriter.Close()
   201  						if err != nil {
   202  							fileUploadResponse = nil
   203  							response = emergencyResponse(err, "upload_post_attachment.writer")
   204  							return
   205  						}
   206  						closedMultipart = true
   207  					}
   208  				}
   209  			}
   210  		}()
   211  
   212  		err := mw.WriteField("channel_id", channelId)
   213  		if err != nil {
   214  			return nil, emergencyResponse(err, "upload_post_attachment.channel_id")
   215  		}
   216  	} else {
   217  		fileUploadResponse = &model.FileUploadResponse{}
   218  	}
   219  
   220  	data := make([]byte, 512)
   221  
   222  	upload := func(i int, f io.ReadCloser) *model.Response {
   223  		var cl int64
   224  		defer f.Close()
   225  
   226  		if len(contentLengths) > i {
   227  			cl = contentLengths[i]
   228  		}
   229  
   230  		n, err := f.Read(data)
   231  		if err != nil && err != io.EOF {
   232  			return emergencyResponse(err, "upload_post_attachment")
   233  		}
   234  		ct := http.DetectContentType(data[:n])
   235  		reader := io.MultiReader(bytes.NewReader(data[:n]), f)
   236  
   237  		if useMultipart {
   238  			if len(clientIds) > i {
   239  				err := mw.WriteField("client_ids", clientIds[i])
   240  				if err != nil {
   241  					return emergencyResponse(err, "upload_post_attachment.file")
   242  				}
   243  			}
   244  
   245  			h := make(textproto.MIMEHeader)
   246  			h.Set("Content-Disposition",
   247  				fmt.Sprintf(`form-data; name="files"; filename="%s"`, escapeQuotes(names[i])))
   248  			h.Set("Content-Type", ct)
   249  
   250  			// If we error here, writing to mw, the deferred handler
   251  			part, err := mw.CreatePart(h)
   252  			if err != nil {
   253  				return emergencyResponse(err, "upload_post_attachment.writer")
   254  			}
   255  
   256  			_, err = io.Copy(part, reader)
   257  			if err != nil {
   258  				return emergencyResponse(err, "upload_post_attachment.writer")
   259  			}
   260  		} else {
   261  			postURL := fmt.Sprintf("?channel_id=%v", url.QueryEscape(channelId)) +
   262  				fmt.Sprintf("&filename=%v", url.QueryEscape(names[i]))
   263  			if len(clientIds) > i {
   264  				postURL += fmt.Sprintf("&client_id=%v", url.QueryEscape(clientIds[i]))
   265  			}
   266  			if useChunkedInSimplePost {
   267  				cl = -1
   268  			}
   269  			fur, resp := testUploadFile(c, postURL, reader, ct, cl)
   270  			if resp.Error != nil {
   271  				return resp
   272  			}
   273  			fileUploadResponse.FileInfos = append(fileUploadResponse.FileInfos, fur.FileInfos[0])
   274  			if len(clientIds) > 0 {
   275  				if len(fur.ClientIds) > 0 {
   276  					fileUploadResponse.ClientIds = append(fileUploadResponse.ClientIds, fur.ClientIds[0])
   277  				} else {
   278  					fileUploadResponse.ClientIds = append(fileUploadResponse.ClientIds, "")
   279  				}
   280  			}
   281  			response = resp
   282  		}
   283  
   284  		return nil
   285  	}
   286  
   287  	for i, open := range openers {
   288  		f, _, err := open()
   289  		if err != nil {
   290  			return nil, emergencyResponse(err, "upload_post_attachment")
   291  		}
   292  
   293  		resp := upload(i, f)
   294  		if resp != nil && resp.Error != nil {
   295  			return nil, resp
   296  		}
   297  	}
   298  
   299  	// In case of a simple POST, the return values have been set by upload(),
   300  	// otherwise we finished writing the multipart, and the return values will
   301  	// be set in defer
   302  	return fileUploadResponse, response
   303  }
   304  
   305  func TestUploadFiles(t *testing.T) {
   306  	th := Setup().InitBasic()
   307  	defer th.TearDown()
   308  	if *th.App.Config().FileSettings.DriverName == "" {
   309  		t.Skip("skipping because no file driver is enabled")
   310  	}
   311  
   312  	channel := th.BasicChannel
   313  	date := time.Now().Format("20060102")
   314  
   315  	// Get better error messages
   316  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true })
   317  
   318  	op := func(name string) UploadOpener {
   319  		return NewUploadOpenerFile(filepath.Join(testDir, name))
   320  	}
   321  
   322  	tests := []struct {
   323  		title     string
   324  		client    *model.Client4
   325  		openers   []UploadOpener
   326  		names     []string
   327  		clientIds []string
   328  
   329  		skipSuccessValidation       bool
   330  		skipPayloadValidation       bool
   331  		skipSimplePost              bool
   332  		skipMultipart               bool
   333  		channelId                   string
   334  		useChunkedInSimplePost      bool
   335  		expectedCreatorId           string
   336  		expectedPayloadNames        []string
   337  		expectImage                 bool
   338  		expectedImageWidths         []int
   339  		expectedImageHeights        []int
   340  		expectedImageThumbnailNames []string
   341  		expectedImagePreviewNames   []string
   342  		expectedImageHasPreview     []bool
   343  		setupConfig                 func(a *app.App) func(a *app.App)
   344  		checkResponse               func(t *testing.T, resp *model.Response)
   345  	}{
   346  		// Upload a bunch of files, mixed images and non-images
   347  		{
   348  			title:             "Happy",
   349  			names:             []string{"test.png", "testgif.gif", "testplugin.tar.gz", "test-search.md", "test.tiff"},
   350  			expectedCreatorId: th.BasicUser.Id,
   351  		},
   352  		// Upload a bunch of files, with clientIds
   353  		{
   354  			title:             "Happy client_ids",
   355  			names:             []string{"test.png", "testgif.gif", "testplugin.tar.gz", "test-search.md", "test.tiff"},
   356  			clientIds:         []string{"1", "2", "3", "4", "5"},
   357  			expectedCreatorId: th.BasicUser.Id,
   358  		},
   359  		// Upload a bunch of images. testgif.gif is an animated GIF,
   360  		// so it does not have HasPreviewImage set.
   361  		{
   362  			title:                   "Happy images",
   363  			names:                   []string{"test.png", "testgif.gif"},
   364  			expectImage:             true,
   365  			expectedCreatorId:       th.BasicUser.Id,
   366  			expectedImageWidths:     []int{408, 118},
   367  			expectedImageHeights:    []int{336, 118},
   368  			expectedImageHasPreview: []bool{true, false},
   369  		},
   370  		{
   371  			title:                 "Happy invalid image",
   372  			names:                 []string{"testgif.gif"},
   373  			openers:               []UploadOpener{NewUploadOpenerFile(filepath.Join(testDir, "test-search.md"))},
   374  			skipPayloadValidation: true,
   375  			expectedCreatorId:     th.BasicUser.Id,
   376  		},
   377  		// Simple POST, chunked encoding
   378  		{
   379  			title:                   "Happy image chunked post",
   380  			skipMultipart:           true,
   381  			useChunkedInSimplePost:  true,
   382  			names:                   []string{"test.png"},
   383  			expectImage:             true,
   384  			expectedImageWidths:     []int{408},
   385  			expectedImageHeights:    []int{336},
   386  			expectedImageHasPreview: []bool{true},
   387  			expectedCreatorId:       th.BasicUser.Id,
   388  		},
   389  		// Image thumbnail and preview: size and orientation. Note that
   390  		// the expected image dimensions remain the same regardless of the
   391  		// orientation - what we save in FileInfo is used by the
   392  		// clients to size UI elements, so the dimensions are "actual".
   393  		{
   394  			title:                       "Happy image thumbnail/preview 1",
   395  			names:                       []string{"orientation_test_1.jpeg"},
   396  			expectedImageThumbnailNames: []string{"orientation_test_1_expected_thumb.jpeg"},
   397  			expectedImagePreviewNames:   []string{"orientation_test_1_expected_preview.jpeg"},
   398  			expectImage:                 true,
   399  			expectedImageWidths:         []int{2860},
   400  			expectedImageHeights:        []int{1578},
   401  			expectedImageHasPreview:     []bool{true},
   402  			expectedCreatorId:           th.BasicUser.Id,
   403  		},
   404  		{
   405  			title:                       "Happy image thumbnail/preview 2",
   406  			names:                       []string{"orientation_test_2.jpeg"},
   407  			expectedImageThumbnailNames: []string{"orientation_test_2_expected_thumb.jpeg"},
   408  			expectedImagePreviewNames:   []string{"orientation_test_2_expected_preview.jpeg"},
   409  			expectImage:                 true,
   410  			expectedImageWidths:         []int{2860},
   411  			expectedImageHeights:        []int{1578},
   412  			expectedImageHasPreview:     []bool{true},
   413  			expectedCreatorId:           th.BasicUser.Id,
   414  		},
   415  		{
   416  			title:                       "Happy image thumbnail/preview 3",
   417  			names:                       []string{"orientation_test_3.jpeg"},
   418  			expectedImageThumbnailNames: []string{"orientation_test_3_expected_thumb.jpeg"},
   419  			expectedImagePreviewNames:   []string{"orientation_test_3_expected_preview.jpeg"},
   420  			expectImage:                 true,
   421  			expectedImageWidths:         []int{2860},
   422  			expectedImageHeights:        []int{1578},
   423  			expectedImageHasPreview:     []bool{true},
   424  			expectedCreatorId:           th.BasicUser.Id,
   425  		},
   426  		{
   427  			title:                       "Happy image thumbnail/preview 4",
   428  			names:                       []string{"orientation_test_4.jpeg"},
   429  			expectedImageThumbnailNames: []string{"orientation_test_4_expected_thumb.jpeg"},
   430  			expectedImagePreviewNames:   []string{"orientation_test_4_expected_preview.jpeg"},
   431  			expectImage:                 true,
   432  			expectedImageWidths:         []int{2860},
   433  			expectedImageHeights:        []int{1578},
   434  			expectedImageHasPreview:     []bool{true},
   435  			expectedCreatorId:           th.BasicUser.Id,
   436  		},
   437  		{
   438  			title:                       "Happy image thumbnail/preview 5",
   439  			names:                       []string{"orientation_test_5.jpeg"},
   440  			expectedImageThumbnailNames: []string{"orientation_test_5_expected_thumb.jpeg"},
   441  			expectedImagePreviewNames:   []string{"orientation_test_5_expected_preview.jpeg"},
   442  			expectImage:                 true,
   443  			expectedImageWidths:         []int{2860},
   444  			expectedImageHeights:        []int{1578},
   445  			expectedImageHasPreview:     []bool{true},
   446  			expectedCreatorId:           th.BasicUser.Id,
   447  		},
   448  		{
   449  			title:                       "Happy image thumbnail/preview 6",
   450  			names:                       []string{"orientation_test_6.jpeg"},
   451  			expectedImageThumbnailNames: []string{"orientation_test_6_expected_thumb.jpeg"},
   452  			expectedImagePreviewNames:   []string{"orientation_test_6_expected_preview.jpeg"},
   453  			expectImage:                 true,
   454  			expectedImageWidths:         []int{2860},
   455  			expectedImageHeights:        []int{1578},
   456  			expectedImageHasPreview:     []bool{true},
   457  			expectedCreatorId:           th.BasicUser.Id,
   458  		},
   459  		{
   460  			title:                       "Happy image thumbnail/preview 7",
   461  			names:                       []string{"orientation_test_7.jpeg"},
   462  			expectedImageThumbnailNames: []string{"orientation_test_7_expected_thumb.jpeg"},
   463  			expectedImagePreviewNames:   []string{"orientation_test_7_expected_preview.jpeg"},
   464  			expectImage:                 true,
   465  			expectedImageWidths:         []int{2860},
   466  			expectedImageHeights:        []int{1578},
   467  			expectedImageHasPreview:     []bool{true},
   468  			expectedCreatorId:           th.BasicUser.Id,
   469  		},
   470  		{
   471  			title:                       "Happy image thumbnail/preview 8",
   472  			names:                       []string{"orientation_test_8.jpeg"},
   473  			expectedImageThumbnailNames: []string{"orientation_test_8_expected_thumb.jpeg"},
   474  			expectedImagePreviewNames:   []string{"orientation_test_8_expected_preview.jpeg"},
   475  			expectImage:                 true,
   476  			expectedImageWidths:         []int{2860},
   477  			expectedImageHeights:        []int{1578},
   478  			expectedImageHasPreview:     []bool{true},
   479  			expectedCreatorId:           th.BasicUser.Id,
   480  		},
   481  		// TIFF preview test
   482  		{
   483  			title:                       "Happy image thumbnail/preview 9",
   484  			names:                       []string{"test.tiff"},
   485  			expectedImageThumbnailNames: []string{"test_expected_thumb.tiff"},
   486  			expectedImagePreviewNames:   []string{"test_expected_preview.tiff"},
   487  			expectImage:                 true,
   488  			expectedImageWidths:         []int{701},
   489  			expectedImageHeights:        []int{701},
   490  			expectedImageHasPreview:     []bool{true},
   491  			expectedCreatorId:           th.BasicUser.Id,
   492  		},
   493  		{
   494  			title:             "Happy admin",
   495  			client:            th.SystemAdminClient,
   496  			names:             []string{"test.png"},
   497  			expectedCreatorId: th.SystemAdminUser.Id,
   498  		},
   499  		{
   500  			title:                  "Happy stream",
   501  			useChunkedInSimplePost: true,
   502  			skipPayloadValidation:  true,
   503  			names:                  []string{"50Mb-stream"},
   504  			openers:                []UploadOpener{NewUploadOpenerReader(&zeroReader{limit: 50 * 1024 * 1024})},
   505  			setupConfig: func(a *app.App) func(a *app.App) {
   506  				maxFileSize := *a.Config().FileSettings.MaxFileSize
   507  				a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 50 * 1024 * 1024 })
   508  				return func(a *app.App) {
   509  					a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
   510  				}
   511  			},
   512  			expectedCreatorId: th.BasicUser.Id,
   513  		},
   514  		// Error cases
   515  		{
   516  			title:                 "Error channel_id does not exist",
   517  			channelId:             model.NewId(),
   518  			names:                 []string{"test.png"},
   519  			skipSuccessValidation: true,
   520  			checkResponse:         CheckForbiddenStatus,
   521  		},
   522  		{
   523  			// on simple post this uploads the last file
   524  			// successfully, without a ClientId
   525  			title:                 "Error too few client_ids",
   526  			skipSimplePost:        true,
   527  			names:                 []string{"test.png", "testplugin.tar.gz", "test-search.md"},
   528  			clientIds:             []string{"1", "4"},
   529  			skipSuccessValidation: true,
   530  			checkResponse:         CheckBadRequestStatus,
   531  		},
   532  		{
   533  			title:                 "Error invalid channel_id",
   534  			channelId:             "../../junk",
   535  			names:                 []string{"test.png"},
   536  			skipSuccessValidation: true,
   537  			checkResponse:         CheckBadRequestStatus,
   538  		},
   539  		{
   540  			title:                 "Error admin channel_id does not exist",
   541  			client:                th.SystemAdminClient,
   542  			channelId:             model.NewId(),
   543  			names:                 []string{"test.png"},
   544  			skipSuccessValidation: true,
   545  			checkResponse:         CheckForbiddenStatus,
   546  		},
   547  		{
   548  			title:                 "Error admin invalid channel_id",
   549  			client:                th.SystemAdminClient,
   550  			channelId:             "../../junk",
   551  			names:                 []string{"test.png"},
   552  			skipSuccessValidation: true,
   553  			checkResponse:         CheckBadRequestStatus,
   554  		},
   555  		{
   556  			title:                 "Error admin disabled uploads",
   557  			client:                th.SystemAdminClient,
   558  			names:                 []string{"test.png"},
   559  			skipSuccessValidation: true,
   560  			checkResponse:         CheckNotImplementedStatus,
   561  			setupConfig: func(a *app.App) func(a *app.App) {
   562  				enableFileAttachments := *a.Config().FileSettings.EnableFileAttachments
   563  				a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = false })
   564  				return func(a *app.App) {
   565  					a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = enableFileAttachments })
   566  				}
   567  			},
   568  		},
   569  		{
   570  			title:                 "Error file too large",
   571  			names:                 []string{"test.png"},
   572  			skipSuccessValidation: true,
   573  			checkResponse:         CheckRequestEntityTooLargeStatus,
   574  			setupConfig: func(a *app.App) func(a *app.App) {
   575  				maxFileSize := *a.Config().FileSettings.MaxFileSize
   576  				a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 279590 })
   577  				return func(a *app.App) {
   578  					a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
   579  				}
   580  			},
   581  		},
   582  		// File too large (chunked, simple POST only, multipart would've been redundant with above)
   583  		{
   584  			title:                  "File too large chunked",
   585  			useChunkedInSimplePost: true,
   586  			skipMultipart:          true,
   587  			names:                  []string{"test.png"},
   588  			skipSuccessValidation:  true,
   589  			checkResponse:          CheckRequestEntityTooLargeStatus,
   590  			setupConfig: func(a *app.App) func(a *app.App) {
   591  				maxFileSize := *a.Config().FileSettings.MaxFileSize
   592  				a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 279590 })
   593  				return func(a *app.App) {
   594  					a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
   595  				}
   596  			},
   597  		},
   598  		{
   599  			title:                 "Error stream too large",
   600  			skipPayloadValidation: true,
   601  			names:                 []string{"50Mb-stream"},
   602  			openers:               []UploadOpener{NewUploadOpenerReader(&zeroReader{limit: 50 * 1024 * 1024})},
   603  			skipSuccessValidation: true,
   604  			checkResponse:         CheckRequestEntityTooLargeStatus,
   605  			setupConfig: func(a *app.App) func(a *app.App) {
   606  				maxFileSize := *a.Config().FileSettings.MaxFileSize
   607  				a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 100 * 1024 })
   608  				return func(a *app.App) {
   609  					a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
   610  				}
   611  			},
   612  		},
   613  	}
   614  
   615  	for _, useMultipart := range []bool{true, false} {
   616  		for _, tc := range tests {
   617  			if tc.skipMultipart && useMultipart || tc.skipSimplePost && !useMultipart {
   618  				continue
   619  			}
   620  
   621  			// Set the default values and title
   622  			client := th.Client
   623  			if tc.client != nil {
   624  				client = tc.client
   625  			}
   626  			channelId := channel.Id
   627  			if tc.channelId != "" {
   628  				channelId = tc.channelId
   629  			}
   630  			if tc.checkResponse == nil {
   631  				tc.checkResponse = CheckNoError
   632  			}
   633  
   634  			title := ""
   635  			if useMultipart {
   636  				title = "multipart "
   637  			} else {
   638  				title = "simple "
   639  			}
   640  			if tc.title != "" {
   641  				title += tc.title + " "
   642  			}
   643  			title += fmt.Sprintf("%v", tc.names)
   644  
   645  			// Apply any necessary config changes
   646  			var restoreConfig func(a *app.App)
   647  			if tc.setupConfig != nil {
   648  				restoreConfig = tc.setupConfig(th.App)
   649  			}
   650  
   651  			t.Run(title, func(t *testing.T) {
   652  				if len(tc.openers) == 0 {
   653  					for _, name := range tc.names {
   654  						tc.openers = append(tc.openers, op(name))
   655  					}
   656  				}
   657  				fileResp, resp := testUploadFiles(client, channelId, tc.names,
   658  					tc.openers, nil, tc.clientIds, useMultipart,
   659  					tc.useChunkedInSimplePost)
   660  				tc.checkResponse(t, resp)
   661  				if tc.skipSuccessValidation {
   662  					return
   663  				}
   664  
   665  				if fileResp == nil || len(fileResp.FileInfos) == 0 || len(fileResp.FileInfos) != len(tc.names) {
   666  					t.Fatal("Empty or mismatched actual or expected FileInfos")
   667  				}
   668  
   669  				for i, ri := range fileResp.FileInfos {
   670  					// The returned file info from the upload call will be missing some fields that will be stored in the database
   671  					checkEq(t, ri.CreatorId, tc.expectedCreatorId, "File should be assigned to user")
   672  					checkEq(t, ri.PostId, "", "File shouldn't have a post Id")
   673  					checkEq(t, ri.Path, "", "File path should not be set on returned info")
   674  					checkEq(t, ri.ThumbnailPath, "", "File thumbnail path should not be set on returned info")
   675  					checkEq(t, ri.PreviewPath, "", "File preview path should not be set on returned info")
   676  					if len(tc.clientIds) > i {
   677  						checkCond(t, len(fileResp.ClientIds) == len(tc.clientIds),
   678  							fmt.Sprintf("Wrong number of clientIds returned, expected %v, got %v", len(tc.clientIds), len(fileResp.ClientIds)))
   679  						checkEq(t, fileResp.ClientIds[i], tc.clientIds[i],
   680  							fmt.Sprintf("Wrong clientId returned, expected %v, got %v", tc.clientIds[i], fileResp.ClientIds[i]))
   681  					}
   682  
   683  					dbInfo, err := th.App.Srv.Store.FileInfo().Get(ri.Id)
   684  					require.Nil(t, err)
   685  					checkEq(t, dbInfo.Id, ri.Id, "File id from response should match one stored in database")
   686  					checkEq(t, dbInfo.CreatorId, tc.expectedCreatorId, "F ile should be assigned to user")
   687  					checkEq(t, dbInfo.PostId, "", "File shouldn't have a post")
   688  					checkNeq(t, dbInfo.Path, "", "File path should be set in database")
   689  					_, fname := filepath.Split(dbInfo.Path)
   690  					ext := filepath.Ext(fname)
   691  					name := fname[:len(fname)-len(ext)]
   692  					expectedDir := fmt.Sprintf("%v/teams/%v/channels/%v/users/%s/%s", date, FILE_TEAM_ID, channel.Id, ri.CreatorId, ri.Id)
   693  					expectedPath := fmt.Sprintf("%s/%s", expectedDir, fname)
   694  					checkEq(t, dbInfo.Path, expectedPath,
   695  						fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath))
   696  
   697  					if tc.expectImage {
   698  						expectedThumbnailPath := fmt.Sprintf("%s/%s_thumb.jpg", expectedDir, name)
   699  						expectedPreviewPath := fmt.Sprintf("%s/%s_preview.jpg", expectedDir, name)
   700  						checkEq(t, dbInfo.ThumbnailPath, expectedThumbnailPath,
   701  							fmt.Sprintf("Thumbnail for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.ThumbnailPath, expectedThumbnailPath))
   702  						checkEq(t, dbInfo.PreviewPath, expectedPreviewPath,
   703  							fmt.Sprintf("Preview for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.PreviewPath, expectedPreviewPath))
   704  
   705  						checkCond(t,
   706  							dbInfo.HasPreviewImage == tc.expectedImageHasPreview[i],
   707  							fmt.Sprintf("Image: HasPreviewImage should be set for %s", dbInfo.Name))
   708  						checkCond(t,
   709  							dbInfo.Width == tc.expectedImageWidths[i] && dbInfo.Height == tc.expectedImageHeights[i],
   710  							fmt.Sprintf("Image dimensions: expected %dwx%dh, got %vwx%dh",
   711  								tc.expectedImageWidths[i], tc.expectedImageHeights[i],
   712  								dbInfo.Width, dbInfo.Height))
   713  					}
   714  
   715  					/*if !tc.skipPayloadValidation {
   716  						compare := func(get func(string) ([]byte, *model.Response), name string) {
   717  							data, resp := get(ri.Id)
   718  							if resp.Error != nil {
   719  								t.Fatal(resp.Error)
   720  							}
   721  
   722  							expected, err := ioutil.ReadFile(filepath.Join(testDir, name))
   723  							if err != nil {
   724  								t.Fatal(err)
   725  							}
   726  
   727  							if bytes.Compare(data, expected) != 0 {
   728  								tf, err := ioutil.TempFile("", fmt.Sprintf("test_%v_*_%s", i, name))
   729  								if err != nil {
   730  									t.Fatal(err)
   731  								}
   732  								io.Copy(tf, bytes.NewReader(data))
   733  								tf.Close()
   734  								t.Errorf("Actual data mismatched %s, written to %q", name, tf.Name())
   735  							}
   736  						}
   737  						if len(tc.expectedPayloadNames) == 0 {
   738  							tc.expectedPayloadNames = tc.names
   739  						}
   740  
   741  						compare(client.GetFile, tc.expectedPayloadNames[i])
   742  						if len(tc.expectedImageThumbnailNames) > i {
   743  							compare(client.GetFileThumbnail, tc.expectedImageThumbnailNames[i])
   744  						}
   745  						if len(tc.expectedImageThumbnailNames) > i {
   746  							compare(client.GetFilePreview, tc.expectedImagePreviewNames[i])
   747  						}
   748  					}*/
   749  
   750  					th.cleanupTestFile(dbInfo)
   751  				}
   752  			})
   753  
   754  			if restoreConfig != nil {
   755  				restoreConfig(th.App)
   756  			}
   757  		}
   758  	}
   759  }
   760  
   761  func TestGetFile(t *testing.T) {
   762  	th := Setup().InitBasic()
   763  	defer th.TearDown()
   764  	Client := th.Client
   765  	channel := th.BasicChannel
   766  
   767  	if *th.App.Config().FileSettings.DriverName == "" {
   768  		t.Skip("skipping because no file driver is enabled")
   769  	}
   770  
   771  	fileId := ""
   772  	var sent []byte
   773  	var err error
   774  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
   775  		t.Fatal(err)
   776  	} else {
   777  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
   778  		CheckNoError(t, resp)
   779  
   780  		fileId = fileResp.FileInfos[0].Id
   781  	}
   782  
   783  	data, resp := Client.GetFile(fileId)
   784  	CheckNoError(t, resp)
   785  
   786  	if len(data) == 0 {
   787  		t.Fatal("should not be empty")
   788  	}
   789  
   790  	for i := range data {
   791  		if data[i] != sent[i] {
   792  			t.Fatal("received file didn't match sent one")
   793  		}
   794  	}
   795  
   796  	_, resp = Client.GetFile("junk")
   797  	CheckBadRequestStatus(t, resp)
   798  
   799  	_, resp = Client.GetFile(model.NewId())
   800  	CheckNotFoundStatus(t, resp)
   801  
   802  	Client.Logout()
   803  	_, resp = Client.GetFile(fileId)
   804  	CheckUnauthorizedStatus(t, resp)
   805  
   806  	_, resp = th.SystemAdminClient.GetFile(fileId)
   807  	CheckNoError(t, resp)
   808  }
   809  
   810  func TestGetFileHeaders(t *testing.T) {
   811  	th := Setup().InitBasic()
   812  	defer th.TearDown()
   813  
   814  	Client := th.Client
   815  	channel := th.BasicChannel
   816  
   817  	if *th.App.Config().FileSettings.DriverName == "" {
   818  		t.Skip("skipping because no file driver is enabled")
   819  	}
   820  
   821  	testHeaders := func(data []byte, filename string, expectedContentType string, getInline bool) func(*testing.T) {
   822  		return func(t *testing.T) {
   823  			fileResp, resp := Client.UploadFile(data, channel.Id, filename)
   824  			CheckNoError(t, resp)
   825  
   826  			fileId := fileResp.FileInfos[0].Id
   827  
   828  			_, resp = Client.GetFile(fileId)
   829  			CheckNoError(t, resp)
   830  
   831  			if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, expectedContentType) {
   832  				t.Fatal("returned incorrect Content-Type", contentType)
   833  			}
   834  
   835  			if getInline {
   836  				if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "inline") {
   837  					t.Fatal("returned incorrect Content-Disposition", contentDisposition)
   838  				}
   839  			} else {
   840  				if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") {
   841  					t.Fatal("returned incorrect Content-Disposition", contentDisposition)
   842  				}
   843  			}
   844  
   845  			_, resp = Client.DownloadFile(fileId, true)
   846  			CheckNoError(t, resp)
   847  
   848  			if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, expectedContentType) {
   849  				t.Fatal("returned incorrect Content-Type", contentType)
   850  			}
   851  
   852  			if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") {
   853  				t.Fatal("returned incorrect Content-Disposition", contentDisposition)
   854  			}
   855  		}
   856  	}
   857  
   858  	data := []byte("ABC")
   859  
   860  	t.Run("png", testHeaders(data, "test.png", "image/png", true))
   861  	t.Run("gif", testHeaders(data, "test.gif", "image/gif", true))
   862  	t.Run("mp4", testHeaders(data, "test.mp4", "video/mp4", true))
   863  	t.Run("mp3", testHeaders(data, "test.mp3", "audio/mpeg", true))
   864  	t.Run("pdf", testHeaders(data, "test.pdf", "application/pdf", false))
   865  	t.Run("txt", testHeaders(data, "test.txt", "text/plain", false))
   866  	t.Run("html", testHeaders(data, "test.html", "text/plain", false))
   867  	t.Run("js", testHeaders(data, "test.js", "text/plain", false))
   868  	t.Run("go", testHeaders(data, "test.go", "application/octet-stream", false))
   869  	t.Run("zip", testHeaders(data, "test.zip", "application/zip", false))
   870  	// Not every platform can recognize these
   871  	//t.Run("exe", testHeaders(data, "test.exe", "application/x-ms", false))
   872  	t.Run("no extension", testHeaders(data, "test", "application/octet-stream", false))
   873  	t.Run("no extension 2", testHeaders([]byte("<html></html>"), "test", "application/octet-stream", false))
   874  }
   875  
   876  func TestGetFileThumbnail(t *testing.T) {
   877  	th := Setup().InitBasic()
   878  	defer th.TearDown()
   879  	Client := th.Client
   880  	channel := th.BasicChannel
   881  
   882  	if *th.App.Config().FileSettings.DriverName == "" {
   883  		t.Skip("skipping because no file driver is enabled")
   884  	}
   885  
   886  	fileId := ""
   887  	var sent []byte
   888  	var err error
   889  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
   890  		t.Fatal(err)
   891  	} else {
   892  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
   893  		CheckNoError(t, resp)
   894  
   895  		fileId = fileResp.FileInfos[0].Id
   896  	}
   897  
   898  	// Wait a bit for files to ready
   899  	time.Sleep(2 * time.Second)
   900  
   901  	data, resp := Client.GetFileThumbnail(fileId)
   902  	CheckNoError(t, resp)
   903  
   904  	if len(data) == 0 {
   905  		t.Fatal("should not be empty")
   906  	}
   907  
   908  	_, resp = Client.GetFileThumbnail("junk")
   909  	CheckBadRequestStatus(t, resp)
   910  
   911  	_, resp = Client.GetFileThumbnail(model.NewId())
   912  	CheckNotFoundStatus(t, resp)
   913  
   914  	Client.Logout()
   915  	_, resp = Client.GetFileThumbnail(fileId)
   916  	CheckUnauthorizedStatus(t, resp)
   917  
   918  	otherUser := th.CreateUser()
   919  	Client.Login(otherUser.Email, otherUser.Password)
   920  	_, resp = Client.GetFileThumbnail(fileId)
   921  	CheckForbiddenStatus(t, resp)
   922  
   923  	Client.Logout()
   924  	_, resp = th.SystemAdminClient.GetFileThumbnail(fileId)
   925  	CheckNoError(t, resp)
   926  }
   927  
   928  func TestGetFileLink(t *testing.T) {
   929  	th := Setup().InitBasic()
   930  	defer th.TearDown()
   931  	Client := th.Client
   932  	channel := th.BasicChannel
   933  
   934  	if *th.App.Config().FileSettings.DriverName == "" {
   935  		t.Skip("skipping because no file driver is enabled")
   936  	}
   937  
   938  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
   939  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
   940  
   941  	fileId := ""
   942  	if data, err := testutils.ReadTestFile("test.png"); err != nil {
   943  		t.Fatal(err)
   944  	} else {
   945  		fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
   946  		CheckNoError(t, resp)
   947  
   948  		fileId = fileResp.FileInfos[0].Id
   949  	}
   950  
   951  	_, resp := Client.GetFileLink(fileId)
   952  	CheckBadRequestStatus(t, resp)
   953  
   954  	// Hacky way to assign file to a post (usually would be done by CreatePost call)
   955  	err := th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id)
   956  	require.Nil(t, err)
   957  
   958  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false })
   959  	_, resp = Client.GetFileLink(fileId)
   960  	CheckNotImplementedStatus(t, resp)
   961  
   962  	// Wait a bit for files to ready
   963  	time.Sleep(2 * time.Second)
   964  
   965  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
   966  	link, resp := Client.GetFileLink(fileId)
   967  	CheckNoError(t, resp)
   968  
   969  	if link == "" {
   970  		t.Fatal("should've received public link")
   971  	}
   972  
   973  	_, resp = Client.GetFileLink("junk")
   974  	CheckBadRequestStatus(t, resp)
   975  
   976  	_, resp = Client.GetFileLink(model.NewId())
   977  	CheckNotFoundStatus(t, resp)
   978  
   979  	Client.Logout()
   980  	_, resp = Client.GetFileLink(fileId)
   981  	CheckUnauthorizedStatus(t, resp)
   982  
   983  	otherUser := th.CreateUser()
   984  	Client.Login(otherUser.Email, otherUser.Password)
   985  	_, resp = Client.GetFileLink(fileId)
   986  	CheckForbiddenStatus(t, resp)
   987  
   988  	Client.Logout()
   989  	_, resp = th.SystemAdminClient.GetFileLink(fileId)
   990  	CheckNoError(t, resp)
   991  
   992  	fileInfo, err := th.App.Srv.Store.FileInfo().Get(fileId)
   993  	require.Nil(t, err)
   994  	th.cleanupTestFile(fileInfo)
   995  }
   996  
   997  func TestGetFilePreview(t *testing.T) {
   998  	th := Setup().InitBasic()
   999  	defer th.TearDown()
  1000  	Client := th.Client
  1001  	channel := th.BasicChannel
  1002  
  1003  	if *th.App.Config().FileSettings.DriverName == "" {
  1004  		t.Skip("skipping because no file driver is enabled")
  1005  	}
  1006  
  1007  	fileId := ""
  1008  	var sent []byte
  1009  	var err error
  1010  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
  1011  		t.Fatal(err)
  1012  	} else {
  1013  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  1014  		CheckNoError(t, resp)
  1015  
  1016  		fileId = fileResp.FileInfos[0].Id
  1017  	}
  1018  
  1019  	// Wait a bit for files to ready
  1020  	time.Sleep(2 * time.Second)
  1021  
  1022  	data, resp := Client.GetFilePreview(fileId)
  1023  	CheckNoError(t, resp)
  1024  
  1025  	if len(data) == 0 {
  1026  		t.Fatal("should not be empty")
  1027  	}
  1028  
  1029  	_, resp = Client.GetFilePreview("junk")
  1030  	CheckBadRequestStatus(t, resp)
  1031  
  1032  	_, resp = Client.GetFilePreview(model.NewId())
  1033  	CheckNotFoundStatus(t, resp)
  1034  
  1035  	Client.Logout()
  1036  	_, resp = Client.GetFilePreview(fileId)
  1037  	CheckUnauthorizedStatus(t, resp)
  1038  
  1039  	otherUser := th.CreateUser()
  1040  	Client.Login(otherUser.Email, otherUser.Password)
  1041  	_, resp = Client.GetFilePreview(fileId)
  1042  	CheckForbiddenStatus(t, resp)
  1043  
  1044  	Client.Logout()
  1045  	_, resp = th.SystemAdminClient.GetFilePreview(fileId)
  1046  	CheckNoError(t, resp)
  1047  }
  1048  
  1049  func TestGetFileInfo(t *testing.T) {
  1050  	th := Setup().InitBasic()
  1051  	defer th.TearDown()
  1052  	Client := th.Client
  1053  	user := th.BasicUser
  1054  	channel := th.BasicChannel
  1055  
  1056  	if *th.App.Config().FileSettings.DriverName == "" {
  1057  		t.Skip("skipping because no file driver is enabled")
  1058  	}
  1059  
  1060  	fileId := ""
  1061  	var sent []byte
  1062  	var err error
  1063  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
  1064  		t.Fatal(err)
  1065  	} else {
  1066  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  1067  		CheckNoError(t, resp)
  1068  
  1069  		fileId = fileResp.FileInfos[0].Id
  1070  	}
  1071  
  1072  	// Wait a bit for files to ready
  1073  	time.Sleep(2 * time.Second)
  1074  
  1075  	info, resp := Client.GetFileInfo(fileId)
  1076  	CheckNoError(t, resp)
  1077  
  1078  	if err != nil {
  1079  		t.Fatal(err)
  1080  	} else if info.Id != fileId {
  1081  		t.Fatal("got incorrect file")
  1082  	} else if info.CreatorId != user.Id {
  1083  		t.Fatal("file should be assigned to user")
  1084  	} else if info.PostId != "" {
  1085  		t.Fatal("file shouldn't have a post")
  1086  	} else if info.Path != "" {
  1087  		t.Fatal("file path shouldn't have been returned to client")
  1088  	} else if info.ThumbnailPath != "" {
  1089  		t.Fatal("file thumbnail path shouldn't have been returned to client")
  1090  	} else if info.PreviewPath != "" {
  1091  		t.Fatal("file preview path shouldn't have been returned to client")
  1092  	} else if info.MimeType != "image/png" {
  1093  		t.Fatal("mime type should've been image/png")
  1094  	}
  1095  
  1096  	_, resp = Client.GetFileInfo("junk")
  1097  	CheckBadRequestStatus(t, resp)
  1098  
  1099  	_, resp = Client.GetFileInfo(model.NewId())
  1100  	CheckNotFoundStatus(t, resp)
  1101  
  1102  	Client.Logout()
  1103  	_, resp = Client.GetFileInfo(fileId)
  1104  	CheckUnauthorizedStatus(t, resp)
  1105  
  1106  	otherUser := th.CreateUser()
  1107  	Client.Login(otherUser.Email, otherUser.Password)
  1108  	_, resp = Client.GetFileInfo(fileId)
  1109  	CheckForbiddenStatus(t, resp)
  1110  
  1111  	Client.Logout()
  1112  	_, resp = th.SystemAdminClient.GetFileInfo(fileId)
  1113  	CheckNoError(t, resp)
  1114  }
  1115  
  1116  func TestGetPublicFile(t *testing.T) {
  1117  	th := Setup().InitBasic()
  1118  	defer th.TearDown()
  1119  	Client := th.Client
  1120  	channel := th.BasicChannel
  1121  
  1122  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  1123  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  1124  
  1125  	fileId := ""
  1126  	if data, err := testutils.ReadTestFile("test.png"); err != nil {
  1127  		t.Fatal(err)
  1128  	} else {
  1129  		fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
  1130  		CheckNoError(t, resp)
  1131  
  1132  		fileId = fileResp.FileInfos[0].Id
  1133  	}
  1134  
  1135  	// Hacky way to assign file to a post (usually would be done by CreatePost call)
  1136  	err := th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id)
  1137  	require.Nil(t, err)
  1138  
  1139  	info, err := th.App.Srv.Store.FileInfo().Get(fileId)
  1140  	require.Nil(t, err)
  1141  	link := th.App.GeneratePublicLink(Client.Url, info)
  1142  
  1143  	// Wait a bit for files to ready
  1144  	time.Sleep(2 * time.Second)
  1145  
  1146  	if resp, err := http.Get(link); err != nil || resp.StatusCode != http.StatusOK {
  1147  		t.Log(link)
  1148  		t.Fatal("failed to get image with public link", err)
  1149  	}
  1150  
  1151  	if resp, err := http.Get(link[:strings.LastIndex(link, "?")]); err == nil && resp.StatusCode != http.StatusBadRequest {
  1152  		t.Fatal("should've failed to get image with public link without hash", resp.Status)
  1153  	}
  1154  
  1155  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false })
  1156  	if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusNotImplemented {
  1157  		t.Fatal("should've failed to get image with disabled public link")
  1158  	}
  1159  
  1160  	// test after the salt has changed
  1161  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  1162  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  1163  
  1164  	if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest {
  1165  		t.Fatal("should've failed to get image with public link after salt changed")
  1166  	}
  1167  
  1168  	if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest {
  1169  		t.Fatal("should've failed to get image with public link after salt changed")
  1170  	}
  1171  
  1172  	fileInfo, err := th.App.Srv.Store.FileInfo().Get(fileId)
  1173  	require.Nil(t, err)
  1174  	require.Nil(t, th.cleanupTestFile(fileInfo))
  1175  	th.cleanupTestFile(info)
  1176  }