github.com/ashishbhate/mattermost-server@v5.11.1+incompatible/api4/file_test.go (about)

     1  // Copyright (c) 2017-present Mattermost, 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/mattermost/mattermost-server/app"
    22  	"github.com/mattermost/mattermost-server/model"
    23  	"github.com/mattermost/mattermost-server/store"
    24  	"github.com/mattermost/mattermost-server/utils/fileutils"
    25  	"github.com/mattermost/mattermost-server/utils/testutils"
    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  					var dbInfo *model.FileInfo
   684  					result := <-th.App.Srv.Store.FileInfo().Get(ri.Id)
   685  					if result.Err != nil {
   686  						t.Error(result.Err)
   687  					} else {
   688  						dbInfo = result.Data.(*model.FileInfo)
   689  					}
   690  					checkEq(t, dbInfo.Id, ri.Id, "File id from response should match one stored in database")
   691  					checkEq(t, dbInfo.CreatorId, tc.expectedCreatorId, "F ile should be assigned to user")
   692  					checkEq(t, dbInfo.PostId, "", "File shouldn't have a post")
   693  					checkNeq(t, dbInfo.Path, "", "File path should be set in database")
   694  					_, fname := filepath.Split(dbInfo.Path)
   695  					ext := filepath.Ext(fname)
   696  					name := fname[:len(fname)-len(ext)]
   697  					expectedDir := fmt.Sprintf("%v/teams/%v/channels/%v/users/%s/%s", date, FILE_TEAM_ID, channel.Id, ri.CreatorId, ri.Id)
   698  					expectedPath := fmt.Sprintf("%s/%s", expectedDir, fname)
   699  					checkEq(t, dbInfo.Path, expectedPath,
   700  						fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath))
   701  
   702  					if tc.expectImage {
   703  						expectedThumbnailPath := fmt.Sprintf("%s/%s_thumb.jpg", expectedDir, name)
   704  						expectedPreviewPath := fmt.Sprintf("%s/%s_preview.jpg", expectedDir, name)
   705  						checkEq(t, dbInfo.ThumbnailPath, expectedThumbnailPath,
   706  							fmt.Sprintf("Thumbnail for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.ThumbnailPath, expectedThumbnailPath))
   707  						checkEq(t, dbInfo.PreviewPath, expectedPreviewPath,
   708  							fmt.Sprintf("Preview for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.PreviewPath, expectedPreviewPath))
   709  
   710  						checkCond(t,
   711  							dbInfo.HasPreviewImage == tc.expectedImageHasPreview[i],
   712  							fmt.Sprintf("Image: HasPreviewImage should be set for %s", dbInfo.Name))
   713  						checkCond(t,
   714  							dbInfo.Width == tc.expectedImageWidths[i] && dbInfo.Height == tc.expectedImageHeights[i],
   715  							fmt.Sprintf("Image dimensions: expected %dwx%dh, got %vwx%dh",
   716  								tc.expectedImageWidths[i], tc.expectedImageHeights[i],
   717  								dbInfo.Width, dbInfo.Height))
   718  					}
   719  
   720  					if !tc.skipPayloadValidation {
   721  						compare := func(get func(string) ([]byte, *model.Response), name string) {
   722  							data, resp := get(ri.Id)
   723  							if resp.Error != nil {
   724  								t.Fatal(resp.Error)
   725  							}
   726  
   727  							expected, err := ioutil.ReadFile(filepath.Join(testDir, name))
   728  							if err != nil {
   729  								t.Fatal(err)
   730  							}
   731  
   732  							if bytes.Compare(data, expected) != 0 {
   733  								tf, err := ioutil.TempFile("", fmt.Sprintf("test_%v_*_%s", i, name))
   734  								if err != nil {
   735  									t.Fatal(err)
   736  								}
   737  								io.Copy(tf, bytes.NewReader(data))
   738  								tf.Close()
   739  								t.Errorf("Actual data mismatched %s, written to %q", name, tf.Name())
   740  							}
   741  						}
   742  						if len(tc.expectedPayloadNames) == 0 {
   743  							tc.expectedPayloadNames = tc.names
   744  						}
   745  
   746  						compare(client.GetFile, tc.expectedPayloadNames[i])
   747  						if len(tc.expectedImageThumbnailNames) > i {
   748  							compare(client.GetFileThumbnail, tc.expectedImageThumbnailNames[i])
   749  						}
   750  						if len(tc.expectedImageThumbnailNames) > i {
   751  							compare(client.GetFilePreview, tc.expectedImagePreviewNames[i])
   752  						}
   753  					}
   754  
   755  					th.cleanupTestFile(dbInfo)
   756  				}
   757  			})
   758  
   759  			if restoreConfig != nil {
   760  				restoreConfig(th.App)
   761  			}
   762  		}
   763  	}
   764  }
   765  
   766  func TestGetFile(t *testing.T) {
   767  	th := Setup().InitBasic()
   768  	defer th.TearDown()
   769  	Client := th.Client
   770  	channel := th.BasicChannel
   771  
   772  	if *th.App.Config().FileSettings.DriverName == "" {
   773  		t.Skip("skipping because no file driver is enabled")
   774  	}
   775  
   776  	fileId := ""
   777  	var sent []byte
   778  	var err error
   779  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
   780  		t.Fatal(err)
   781  	} else {
   782  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
   783  		CheckNoError(t, resp)
   784  
   785  		fileId = fileResp.FileInfos[0].Id
   786  	}
   787  
   788  	data, resp := Client.GetFile(fileId)
   789  	CheckNoError(t, resp)
   790  
   791  	if len(data) == 0 {
   792  		t.Fatal("should not be empty")
   793  	}
   794  
   795  	for i := range data {
   796  		if data[i] != sent[i] {
   797  			t.Fatal("received file didn't match sent one")
   798  		}
   799  	}
   800  
   801  	_, resp = Client.GetFile("junk")
   802  	CheckBadRequestStatus(t, resp)
   803  
   804  	_, resp = Client.GetFile(model.NewId())
   805  	CheckNotFoundStatus(t, resp)
   806  
   807  	Client.Logout()
   808  	_, resp = Client.GetFile(fileId)
   809  	CheckUnauthorizedStatus(t, resp)
   810  
   811  	_, resp = th.SystemAdminClient.GetFile(fileId)
   812  	CheckNoError(t, resp)
   813  }
   814  
   815  func TestGetFileHeaders(t *testing.T) {
   816  	th := Setup().InitBasic()
   817  	defer th.TearDown()
   818  
   819  	Client := th.Client
   820  	channel := th.BasicChannel
   821  
   822  	if *th.App.Config().FileSettings.DriverName == "" {
   823  		t.Skip("skipping because no file driver is enabled")
   824  	}
   825  
   826  	testHeaders := func(data []byte, filename string, expectedContentType string, getInline bool) func(*testing.T) {
   827  		return func(t *testing.T) {
   828  			fileResp, resp := Client.UploadFile(data, channel.Id, filename)
   829  			CheckNoError(t, resp)
   830  
   831  			fileId := fileResp.FileInfos[0].Id
   832  
   833  			_, resp = Client.GetFile(fileId)
   834  			CheckNoError(t, resp)
   835  
   836  			if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, expectedContentType) {
   837  				t.Fatal("returned incorrect Content-Type", contentType)
   838  			}
   839  
   840  			if getInline {
   841  				if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "inline") {
   842  					t.Fatal("returned incorrect Content-Disposition", contentDisposition)
   843  				}
   844  			} else {
   845  				if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") {
   846  					t.Fatal("returned incorrect Content-Disposition", contentDisposition)
   847  				}
   848  			}
   849  
   850  			_, resp = Client.DownloadFile(fileId, true)
   851  			CheckNoError(t, resp)
   852  
   853  			if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, expectedContentType) {
   854  				t.Fatal("returned incorrect Content-Type", contentType)
   855  			}
   856  
   857  			if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") {
   858  				t.Fatal("returned incorrect Content-Disposition", contentDisposition)
   859  			}
   860  		}
   861  	}
   862  
   863  	data := []byte("ABC")
   864  
   865  	t.Run("png", testHeaders(data, "test.png", "image/png", true))
   866  	t.Run("gif", testHeaders(data, "test.gif", "image/gif", true))
   867  	t.Run("mp4", testHeaders(data, "test.mp4", "video/mp4", true))
   868  	t.Run("mp3", testHeaders(data, "test.mp3", "audio/mpeg", true))
   869  	t.Run("pdf", testHeaders(data, "test.pdf", "application/pdf", false))
   870  	t.Run("txt", testHeaders(data, "test.txt", "text/plain", false))
   871  	t.Run("html", testHeaders(data, "test.html", "text/plain", false))
   872  	t.Run("js", testHeaders(data, "test.js", "text/plain", false))
   873  	t.Run("go", testHeaders(data, "test.go", "application/octet-stream", false))
   874  	t.Run("zip", testHeaders(data, "test.zip", "application/zip", false))
   875  	// Not every platform can recognize these
   876  	//t.Run("exe", testHeaders(data, "test.exe", "application/x-ms", false))
   877  	t.Run("no extension", testHeaders(data, "test", "application/octet-stream", false))
   878  	t.Run("no extension 2", testHeaders([]byte("<html></html>"), "test", "application/octet-stream", false))
   879  }
   880  
   881  func TestGetFileThumbnail(t *testing.T) {
   882  	th := Setup().InitBasic()
   883  	defer th.TearDown()
   884  	Client := th.Client
   885  	channel := th.BasicChannel
   886  
   887  	if *th.App.Config().FileSettings.DriverName == "" {
   888  		t.Skip("skipping because no file driver is enabled")
   889  	}
   890  
   891  	fileId := ""
   892  	var sent []byte
   893  	var err error
   894  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
   895  		t.Fatal(err)
   896  	} else {
   897  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
   898  		CheckNoError(t, resp)
   899  
   900  		fileId = fileResp.FileInfos[0].Id
   901  	}
   902  
   903  	// Wait a bit for files to ready
   904  	time.Sleep(2 * time.Second)
   905  
   906  	data, resp := Client.GetFileThumbnail(fileId)
   907  	CheckNoError(t, resp)
   908  
   909  	if len(data) == 0 {
   910  		t.Fatal("should not be empty")
   911  	}
   912  
   913  	_, resp = Client.GetFileThumbnail("junk")
   914  	CheckBadRequestStatus(t, resp)
   915  
   916  	_, resp = Client.GetFileThumbnail(model.NewId())
   917  	CheckNotFoundStatus(t, resp)
   918  
   919  	Client.Logout()
   920  	_, resp = Client.GetFileThumbnail(fileId)
   921  	CheckUnauthorizedStatus(t, resp)
   922  
   923  	otherUser := th.CreateUser()
   924  	Client.Login(otherUser.Email, otherUser.Password)
   925  	_, resp = Client.GetFileThumbnail(fileId)
   926  	CheckForbiddenStatus(t, resp)
   927  
   928  	Client.Logout()
   929  	_, resp = th.SystemAdminClient.GetFileThumbnail(fileId)
   930  	CheckNoError(t, resp)
   931  }
   932  
   933  func TestGetFileLink(t *testing.T) {
   934  	th := Setup().InitBasic()
   935  	defer th.TearDown()
   936  	Client := th.Client
   937  	channel := th.BasicChannel
   938  
   939  	if *th.App.Config().FileSettings.DriverName == "" {
   940  		t.Skip("skipping because no file driver is enabled")
   941  	}
   942  
   943  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
   944  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
   945  
   946  	fileId := ""
   947  	if data, err := testutils.ReadTestFile("test.png"); err != nil {
   948  		t.Fatal(err)
   949  	} else {
   950  		fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
   951  		CheckNoError(t, resp)
   952  
   953  		fileId = fileResp.FileInfos[0].Id
   954  	}
   955  
   956  	_, resp := Client.GetFileLink(fileId)
   957  	CheckBadRequestStatus(t, resp)
   958  
   959  	// Hacky way to assign file to a post (usually would be done by CreatePost call)
   960  	store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id))
   961  
   962  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false })
   963  	_, resp = Client.GetFileLink(fileId)
   964  	CheckNotImplementedStatus(t, resp)
   965  
   966  	// Wait a bit for files to ready
   967  	time.Sleep(2 * time.Second)
   968  
   969  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
   970  	link, resp := Client.GetFileLink(fileId)
   971  	CheckNoError(t, resp)
   972  
   973  	if link == "" {
   974  		t.Fatal("should've received public link")
   975  	}
   976  
   977  	_, resp = Client.GetFileLink("junk")
   978  	CheckBadRequestStatus(t, resp)
   979  
   980  	_, resp = Client.GetFileLink(model.NewId())
   981  	CheckNotFoundStatus(t, resp)
   982  
   983  	Client.Logout()
   984  	_, resp = Client.GetFileLink(fileId)
   985  	CheckUnauthorizedStatus(t, resp)
   986  
   987  	otherUser := th.CreateUser()
   988  	Client.Login(otherUser.Email, otherUser.Password)
   989  	_, resp = Client.GetFileLink(fileId)
   990  	CheckForbiddenStatus(t, resp)
   991  
   992  	Client.Logout()
   993  	_, resp = th.SystemAdminClient.GetFileLink(fileId)
   994  	CheckNoError(t, resp)
   995  
   996  	if result := <-th.App.Srv.Store.FileInfo().Get(fileId); result.Err != nil {
   997  		t.Fatal(result.Err)
   998  	} else {
   999  		th.cleanupTestFile(result.Data.(*model.FileInfo))
  1000  	}
  1001  }
  1002  
  1003  func TestGetFilePreview(t *testing.T) {
  1004  	th := Setup().InitBasic()
  1005  	defer th.TearDown()
  1006  	Client := th.Client
  1007  	channel := th.BasicChannel
  1008  
  1009  	if *th.App.Config().FileSettings.DriverName == "" {
  1010  		t.Skip("skipping because no file driver is enabled")
  1011  	}
  1012  
  1013  	fileId := ""
  1014  	var sent []byte
  1015  	var err error
  1016  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
  1017  		t.Fatal(err)
  1018  	} else {
  1019  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  1020  		CheckNoError(t, resp)
  1021  
  1022  		fileId = fileResp.FileInfos[0].Id
  1023  	}
  1024  
  1025  	// Wait a bit for files to ready
  1026  	time.Sleep(2 * time.Second)
  1027  
  1028  	data, resp := Client.GetFilePreview(fileId)
  1029  	CheckNoError(t, resp)
  1030  
  1031  	if len(data) == 0 {
  1032  		t.Fatal("should not be empty")
  1033  	}
  1034  
  1035  	_, resp = Client.GetFilePreview("junk")
  1036  	CheckBadRequestStatus(t, resp)
  1037  
  1038  	_, resp = Client.GetFilePreview(model.NewId())
  1039  	CheckNotFoundStatus(t, resp)
  1040  
  1041  	Client.Logout()
  1042  	_, resp = Client.GetFilePreview(fileId)
  1043  	CheckUnauthorizedStatus(t, resp)
  1044  
  1045  	otherUser := th.CreateUser()
  1046  	Client.Login(otherUser.Email, otherUser.Password)
  1047  	_, resp = Client.GetFilePreview(fileId)
  1048  	CheckForbiddenStatus(t, resp)
  1049  
  1050  	Client.Logout()
  1051  	_, resp = th.SystemAdminClient.GetFilePreview(fileId)
  1052  	CheckNoError(t, resp)
  1053  }
  1054  
  1055  func TestGetFileInfo(t *testing.T) {
  1056  	th := Setup().InitBasic()
  1057  	defer th.TearDown()
  1058  	Client := th.Client
  1059  	user := th.BasicUser
  1060  	channel := th.BasicChannel
  1061  
  1062  	if *th.App.Config().FileSettings.DriverName == "" {
  1063  		t.Skip("skipping because no file driver is enabled")
  1064  	}
  1065  
  1066  	fileId := ""
  1067  	var sent []byte
  1068  	var err error
  1069  	if sent, err = testutils.ReadTestFile("test.png"); err != nil {
  1070  		t.Fatal(err)
  1071  	} else {
  1072  		fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  1073  		CheckNoError(t, resp)
  1074  
  1075  		fileId = fileResp.FileInfos[0].Id
  1076  	}
  1077  
  1078  	// Wait a bit for files to ready
  1079  	time.Sleep(2 * time.Second)
  1080  
  1081  	info, resp := Client.GetFileInfo(fileId)
  1082  	CheckNoError(t, resp)
  1083  
  1084  	if err != nil {
  1085  		t.Fatal(err)
  1086  	} else if info.Id != fileId {
  1087  		t.Fatal("got incorrect file")
  1088  	} else if info.CreatorId != user.Id {
  1089  		t.Fatal("file should be assigned to user")
  1090  	} else if info.PostId != "" {
  1091  		t.Fatal("file shouldn't have a post")
  1092  	} else if info.Path != "" {
  1093  		t.Fatal("file path shouldn't have been returned to client")
  1094  	} else if info.ThumbnailPath != "" {
  1095  		t.Fatal("file thumbnail path shouldn't have been returned to client")
  1096  	} else if info.PreviewPath != "" {
  1097  		t.Fatal("file preview path shouldn't have been returned to client")
  1098  	} else if info.MimeType != "image/png" {
  1099  		t.Fatal("mime type should've been image/png")
  1100  	}
  1101  
  1102  	_, resp = Client.GetFileInfo("junk")
  1103  	CheckBadRequestStatus(t, resp)
  1104  
  1105  	_, resp = Client.GetFileInfo(model.NewId())
  1106  	CheckNotFoundStatus(t, resp)
  1107  
  1108  	Client.Logout()
  1109  	_, resp = Client.GetFileInfo(fileId)
  1110  	CheckUnauthorizedStatus(t, resp)
  1111  
  1112  	otherUser := th.CreateUser()
  1113  	Client.Login(otherUser.Email, otherUser.Password)
  1114  	_, resp = Client.GetFileInfo(fileId)
  1115  	CheckForbiddenStatus(t, resp)
  1116  
  1117  	Client.Logout()
  1118  	_, resp = th.SystemAdminClient.GetFileInfo(fileId)
  1119  	CheckNoError(t, resp)
  1120  }
  1121  
  1122  func TestGetPublicFile(t *testing.T) {
  1123  	th := Setup().InitBasic()
  1124  	defer th.TearDown()
  1125  	Client := th.Client
  1126  	channel := th.BasicChannel
  1127  
  1128  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  1129  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  1130  
  1131  	fileId := ""
  1132  	if data, err := testutils.ReadTestFile("test.png"); err != nil {
  1133  		t.Fatal(err)
  1134  	} else {
  1135  		fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
  1136  		CheckNoError(t, resp)
  1137  
  1138  		fileId = fileResp.FileInfos[0].Id
  1139  	}
  1140  
  1141  	// Hacky way to assign file to a post (usually would be done by CreatePost call)
  1142  	store.Must(th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id))
  1143  
  1144  	result := <-th.App.Srv.Store.FileInfo().Get(fileId)
  1145  	info := result.Data.(*model.FileInfo)
  1146  	link := th.App.GeneratePublicLink(Client.Url, info)
  1147  
  1148  	// Wait a bit for files to ready
  1149  	time.Sleep(2 * time.Second)
  1150  
  1151  	if resp, err := http.Get(link); err != nil || resp.StatusCode != http.StatusOK {
  1152  		t.Log(link)
  1153  		t.Fatal("failed to get image with public link", err)
  1154  	}
  1155  
  1156  	if resp, err := http.Get(link[:strings.LastIndex(link, "?")]); err == nil && resp.StatusCode != http.StatusBadRequest {
  1157  		t.Fatal("should've failed to get image with public link without hash", resp.Status)
  1158  	}
  1159  
  1160  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false })
  1161  	if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusNotImplemented {
  1162  		t.Fatal("should've failed to get image with disabled public link")
  1163  	}
  1164  
  1165  	// test after the salt has changed
  1166  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  1167  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  1168  
  1169  	if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest {
  1170  		t.Fatal("should've failed to get image with public link after salt changed")
  1171  	}
  1172  
  1173  	if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest {
  1174  		t.Fatal("should've failed to get image with public link after salt changed")
  1175  	}
  1176  
  1177  	if err := th.cleanupTestFile(store.Must(th.App.Srv.Store.FileInfo().Get(fileId)).(*model.FileInfo)); err != nil {
  1178  		t.Fatal(err)
  1179  	}
  1180  
  1181  	th.cleanupTestFile(info)
  1182  }