Files
photoprism/internal/api/users_upload_multipart_test.go
2025-09-22 10:42:53 +02:00

678 lines
21 KiB
Go

package api
import (
"archive/zip"
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/service/http/header"
)
// buildMultipart builds a multipart form with one field name "files" and provided files.
func buildMultipart(files map[string][]byte) (body *bytes.Buffer, contentType string, err error) {
body = &bytes.Buffer{}
mw := multipart.NewWriter(body)
for name, data := range files {
fw, cerr := mw.CreateFormFile("files", name)
if cerr != nil {
return nil, "", cerr
}
if _, werr := fw.Write(data); werr != nil {
return nil, "", werr
}
}
cerr := mw.Close()
return body, mw.FormDataContentType(), cerr
}
// buildMultipartTwo builds a multipart form with exactly two files (same field name: "files").
func buildMultipartTwo(name1 string, data1 []byte, name2 string, data2 []byte) (body *bytes.Buffer, contentType string, err error) {
body = &bytes.Buffer{}
mw := multipart.NewWriter(body)
for _, it := range [][2]interface{}{{name1, data1}, {name2, data2}} {
fw, cerr := mw.CreateFormFile("files", it[0].(string))
if cerr != nil {
return nil, "", cerr
}
if _, werr := fw.Write(it[1].([]byte)); werr != nil {
return nil, "", werr
}
}
cerr := mw.Close()
return body, mw.FormDataContentType(), cerr
}
// buildZipWithDirsAndFiles creates a zip archive bytes with explicit directory entries and files.
func buildZipWithDirsAndFiles(dirs []string, files map[string][]byte) []byte {
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
// Directories (ensure trailing slash)
for _, d := range dirs {
name := d
if !strings.HasSuffix(name, "/") {
name += "/"
}
_, _ = zw.Create(name)
}
// Files
for name, data := range files {
f, _ := zw.Create(name)
_, _ = f.Write(data)
}
_ = zw.Close()
return zbuf.Bytes()
}
func findUploadedFiles(t *testing.T, base string) []string {
t.Helper()
var out []string
_ = filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
out = append(out, path)
}
return nil
})
return out
}
// findUploadedFilesForToken lists files only under upload subfolders whose name ends with token suffix.
func findUploadedFilesForToken(t *testing.T, base string, tokenSuffix string) []string {
t.Helper()
var out []string
entries, _ := os.ReadDir(base)
for _, e := range entries {
if !e.IsDir() {
continue
}
name := e.Name()
if !strings.HasSuffix(name, tokenSuffix) {
continue
}
dir := filepath.Join(base, name)
_ = filepath.Walk(dir, func(p string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() {
out = append(out, p)
}
return nil
})
}
return out
}
// removeUploadDirsForToken removes upload subdirectories whose name ends with tokenSuffix.
func removeUploadDirsForToken(t *testing.T, base string, tokenSuffix string) {
t.Helper()
entries, _ := os.ReadDir(base)
for _, e := range entries {
if !e.IsDir() {
continue
}
name := e.Name()
if strings.HasSuffix(name, tokenSuffix) {
_ = os.RemoveAll(filepath.Join(base, name))
}
}
}
func TestUploadUserFiles_Multipart_SingleJPEG(t *testing.T) {
app, router, conf := NewApiTest()
// Limit allowed upload extensions to ensure text files get rejected in tests
conf.Options().UploadAllow = "jpg"
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
// Cleanup: remove token-specific upload dir after test
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "abc123")
// Load a real tiny JPEG from testdata
jpgPath := filepath.Clean("../../pkg/fs/testdata/directory/example.jpg")
data, err := os.ReadFile(jpgPath)
if err != nil {
t.Skipf("missing example.jpg: %v", err)
}
body, ctype, err := buildMultipart(map[string][]byte{"example.jpg": data})
if err != nil {
t.Fatal(err)
}
reqUrl := "/api/v1/users/" + adminUid + "/upload/abc123"
req := httptest.NewRequest(http.MethodPost, reqUrl, body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
// Verify file written somewhere under users/<uid>/upload/*
uploadBase := filepath.Join(conf.UserStoragePath(adminUid), "upload")
files := findUploadedFilesForToken(t, uploadBase, "abc123")
// At least one file written
assert.NotEmpty(t, files)
// Expect the filename to appear somewhere
var found bool
for _, f := range files {
if strings.HasSuffix(f, "example.jpg") {
found = true
break
}
}
assert.True(t, found, "uploaded JPEG not found")
}
func TestUploadUserFiles_Multipart_ZipExtract(t *testing.T) {
app, router, conf := NewApiTest()
// Allow archives and restrict allowed extensions to images
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,png,zip"
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
// Cleanup after test
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "ziptok")
// Create an in-memory zip with one JPEG (valid) and one TXT (rejected)
jpgPath := filepath.Clean("../../pkg/fs/testdata/directory/example.jpg")
jpg, err := os.ReadFile(jpgPath)
if err != nil {
t.Skip("missing example.jpg")
}
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
// add jpeg
jf, _ := zw.Create("a.jpg")
_, _ = jf.Write(jpg)
// add txt
tf, _ := zw.Create("note.txt")
_, _ = io.WriteString(tf, "hello")
_ = zw.Close()
body, ctype, err := buildMultipart(map[string][]byte{"upload.zip": zbuf.Bytes()})
if err != nil {
t.Fatal(err)
}
reqUrl := "/api/v1/users/" + adminUid + "/upload/zipoff"
req := httptest.NewRequest(http.MethodPost, reqUrl, body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
uploadBase := filepath.Join(conf.UserStoragePath(adminUid), "upload")
files := findUploadedFilesForToken(t, uploadBase, "zipoff")
// Expect extracted jpeg present and txt absent
var jpgFound, txtFound bool
for _, f := range files {
if strings.HasSuffix(f, "a.jpg") {
jpgFound = true
}
if strings.HasSuffix(f, "note.txt") {
txtFound = true
}
}
assert.True(t, jpgFound, "extracted jpeg not found")
assert.False(t, txtFound, "text file should be rejected")
}
func TestUploadUserFiles_Multipart_ArchivesDisabled(t *testing.T) {
app, router, conf := NewApiTest()
// disallow archives while allowing the .zip extension in filter
conf.Options().UploadArchives = false
conf.Options().UploadAllow = "jpg,zip"
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
// Cleanup after test
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "zipoff")
// zip with one jpeg inside
jpgPath := filepath.Clean("../../pkg/fs/testdata/directory/example.jpg")
jpg, err := os.ReadFile(jpgPath)
if err != nil {
t.Skip("missing example.jpg")
}
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
jf, _ := zw.Create("a.jpg")
_, _ = jf.Write(jpg)
_ = zw.Close()
body, ctype, err := buildMultipart(map[string][]byte{"upload.zip": zbuf.Bytes()})
if err != nil {
t.Fatal(err)
}
reqUrl := "/api/v1/users/" + adminUid + "/upload/ziptok"
req := httptest.NewRequest(http.MethodPost, reqUrl, body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
// server returns 200 even if rejected internally; nothing extracted/saved
assert.Equal(t, http.StatusOK, w.Code, w.Body.String())
uploadBase := filepath.Join(conf.UserStoragePath(adminUid), "upload")
files := findUploadedFilesForToken(t, uploadBase, "ziptok")
assert.Empty(t, files, "no files should remain when archives disabled")
}
func TestUploadUserFiles_Multipart_PerFileLimitExceeded(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadAllow = "jpg"
conf.Options().OriginalsLimit = 1 // 1 MiB per-file
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "size1")
// Build a 2MiB dummy payload (not a real JPEG; that's fine for pre-save size check)
big := bytes.Repeat([]byte("A"), 2*1024*1024)
body, ctype, err := buildMultipart(map[string][]byte{"big.jpg": big})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/size1", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
// Ensure nothing saved
files := findUploadedFilesForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "size1")
assert.Empty(t, files)
}
func TestUploadUserFiles_Multipart_TotalLimitExceeded(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadAllow = "jpg"
conf.Options().UploadLimit = 1 // 1 MiB total
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "total")
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
// build multipart with two images so sum > 1 MiB (2*~63KiB = ~126KiB) -> still <1MiB, so use 16 copies
// build two bigger bodies by concatenation
times := 9
big1 := bytes.Repeat(data, times)
big2 := bytes.Repeat(data, times)
body, ctype, err := buildMultipartTwo("a.jpg", big1, "b.jpg", big2)
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/total", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
// Expect at most one file saved (second should be rejected by total limit)
files := findUploadedFilesForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "total")
assert.LessOrEqual(t, len(files), 1)
}
func TestUploadUserFiles_Multipart_ZipPartialExtraction(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,zip"
conf.Options().UploadLimit = 1 // 1 MiB total
conf.Options().OriginalsLimit = 50 // 50 MiB per file
conf.Options().UploadNSFW = true // skip nsfw scanning to speed up test
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "partial")
// Build a zip containing multiple JPEG entries so that total extracted size > 1 MiB
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
for i := 0; i < 20; i++ { // ~20 * 63 KiB ≈ 1.2 MiB
f, _ := zw.Create(fmt.Sprintf("pic%02d.jpg", i+1))
_, _ = f.Write(data)
}
_ = zw.Close()
body, ctype, err := buildMultipart(map[string][]byte{"multi.zip": zbuf.Bytes()})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/partial", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
files := findUploadedFilesForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "partial")
// At least one extracted, but not all 20 due to total limit
var countJPG int
for _, f := range files {
if strings.HasSuffix(f, ".jpg") {
countJPG++
}
}
assert.GreaterOrEqual(t, countJPG, 1)
assert.Less(t, countJPG, 20)
}
func TestUploadUserFiles_Multipart_ZipDeepNestingStress(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,zip"
conf.Options().UploadNSFW = true
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "zipdeep")
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
// Build a deeply nested path (20 levels)
deep := ""
for i := 0; i < 20; i++ {
if i == 0 {
deep = "deep"
} else {
deep = filepath.Join(deep, fmt.Sprintf("lvl%02d", i))
}
}
name := filepath.Join(deep, "deep.jpg")
zbytes := buildZipWithDirsAndFiles(nil, map[string][]byte{name: data})
body, ctype, err := buildMultipart(map[string][]byte{"deepnest.zip": zbytes})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/zipdeep", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
base := filepath.Join(conf.UserStoragePath(adminUid), "upload")
files := findUploadedFilesForToken(t, base, "zipdeep")
// Only one file expected, deep path created
assert.Equal(t, 1, len(files))
assert.True(t, strings.Contains(files[0], filepath.Join("deep", "lvl01")))
}
func TestUploadUserFiles_Multipart_ZipRejectsHiddenAndTraversal(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,zip"
conf.Options().UploadNSFW = true // skip scanning
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "rejects")
// Prepare a valid jpg payload
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
// Hidden file
f1, _ := zw.Create(".hidden.jpg")
_, _ = f1.Write(data)
// @ file
f2, _ := zw.Create("@meta.jpg")
_, _ = f2.Write(data)
// Traversal path (will be skipped by safe join in unzip)
f3, _ := zw.Create("dir/../traverse.jpg")
_, _ = f3.Write(data)
// Valid file
f4, _ := zw.Create("ok.jpg")
_, _ = f4.Write(data)
_ = zw.Close()
body, ctype, err := buildMultipart(map[string][]byte{"test.zip": zbuf.Bytes()})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/rejects", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
files := findUploadedFilesForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "rejects")
var hasOk, hasHidden, hasAt, hasTraverse bool
for _, f := range files {
if strings.HasSuffix(f, "ok.jpg") {
hasOk = true
}
if strings.HasSuffix(f, ".hidden.jpg") {
hasHidden = true
}
if strings.HasSuffix(f, "@meta.jpg") {
hasAt = true
}
if strings.HasSuffix(f, "traverse.jpg") {
hasTraverse = true
}
}
assert.True(t, hasOk)
assert.False(t, hasHidden)
assert.False(t, hasAt)
assert.False(t, hasTraverse)
}
func TestUploadUserFiles_Multipart_ZipNestedDirectories(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,zip"
conf.Options().UploadNSFW = true
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "zipnest")
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
// Create nested dirs and files
dirs := []string{"nested", "nested/sub"}
files := map[string][]byte{
"nested/a.jpg": data,
"nested/sub/b.jpg": data,
}
zbytes := buildZipWithDirsAndFiles(dirs, files)
body, ctype, err := buildMultipart(map[string][]byte{"nested.zip": zbytes})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/zipnest", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
base := filepath.Join(conf.UserStoragePath(adminUid), "upload")
filesOut := findUploadedFilesForToken(t, base, "zipnest")
var haveA, haveB bool
for _, f := range filesOut {
if strings.HasSuffix(f, filepath.Join("nested", "a.jpg")) {
haveA = true
}
if strings.HasSuffix(f, filepath.Join("nested", "sub", "b.jpg")) {
haveB = true
}
}
assert.True(t, haveA)
assert.True(t, haveB)
// Directories exist
// Locate token dir
entries, _ := os.ReadDir(base)
var tokenDir string
for _, e := range entries {
if e.IsDir() && strings.HasSuffix(e.Name(), "zipnest") {
tokenDir = filepath.Join(base, e.Name())
break
}
}
if tokenDir != "" {
_, errA := os.Stat(filepath.Join(tokenDir, "nested"))
_, errB := os.Stat(filepath.Join(tokenDir, "nested", "sub"))
assert.NoError(t, errA)
assert.NoError(t, errB)
} else {
t.Fatalf("token dir not found under %s", base)
}
}
func TestUploadUserFiles_Multipart_ZipImplicitDirectories(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,zip"
conf.Options().UploadNSFW = true
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "zipimpl")
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
// Create zip containing only files with nested paths (no explicit directory entries)
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
f1, _ := zw.Create(filepath.Join("nested", "a.jpg"))
_, _ = f1.Write(data)
f2, _ := zw.Create(filepath.Join("nested", "sub", "b.jpg"))
_, _ = f2.Write(data)
_ = zw.Close()
body, ctype, err := buildMultipart(map[string][]byte{"nested-files-only.zip": zbuf.Bytes()})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/zipimpl", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
base := filepath.Join(conf.UserStoragePath(adminUid), "upload")
files := findUploadedFilesForToken(t, base, "zipimpl")
var haveA, haveB bool
for _, f := range files {
if strings.HasSuffix(f, filepath.Join("nested", "a.jpg")) {
haveA = true
}
if strings.HasSuffix(f, filepath.Join("nested", "sub", "b.jpg")) {
haveB = true
}
}
assert.True(t, haveA)
assert.True(t, haveB)
// Confirm directories were implicitly created
entries, _ := os.ReadDir(base)
var tokenDir string
for _, e := range entries {
if e.IsDir() && strings.HasSuffix(e.Name(), "zipimpl") {
tokenDir = filepath.Join(base, e.Name())
break
}
}
if tokenDir == "" {
t.Fatalf("token dir not found under %s", base)
}
_, errA := os.Stat(filepath.Join(tokenDir, "nested"))
_, errB := os.Stat(filepath.Join(tokenDir, "nested", "sub"))
assert.NoError(t, errA)
assert.NoError(t, errB)
}
func TestUploadUserFiles_Multipart_ZipAbsolutePathRejected(t *testing.T) {
app, router, conf := NewApiTest()
conf.Options().UploadArchives = true
conf.Options().UploadAllow = "jpg,zip"
conf.Options().UploadNSFW = true
UploadUserFiles(router)
token := AuthenticateAdmin(app, router)
adminUid := entity.Admin.UserUID
defer removeUploadDirsForToken(t, filepath.Join(conf.UserStoragePath(adminUid), "upload"), "zipabs")
data, err := os.ReadFile(filepath.Clean("../../pkg/fs/testdata/directory/example.jpg"))
if err != nil {
t.Skip("missing example.jpg")
}
// Zip with an absolute path entry
var zbuf bytes.Buffer
zw := zip.NewWriter(&zbuf)
f, _ := zw.Create("/abs.jpg")
_, _ = f.Write(data)
_ = zw.Close()
body, ctype, err := buildMultipart(map[string][]byte{"abs.zip": zbuf.Bytes()})
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/users/"+adminUid+"/upload/zipabs", body)
req.Header.Set("Content-Type", ctype)
header.SetAuthorization(req, token)
w := httptest.NewRecorder()
app.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
// No files should be extracted/saved for this token
base := filepath.Join(conf.UserStoragePath(adminUid), "upload")
files := findUploadedFilesForToken(t, base, "zipabs")
assert.Empty(t, files)
}