195 lines
5.6 KiB
Go
195 lines
5.6 KiB
Go
package filesvc_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"portal-mini-go/internal/model"
|
|
"portal-mini-go/internal/pkg/config"
|
|
"portal-mini-go/internal/pkg/storage"
|
|
filesvc "portal-mini-go/internal/service/file"
|
|
)
|
|
|
|
// --- 测试替身 ---
|
|
|
|
type fakeStorage struct {
|
|
putErr error
|
|
gotKey string
|
|
gotSize int64
|
|
data map[string][]byte
|
|
}
|
|
|
|
func newFakeStorage() *fakeStorage { return &fakeStorage{data: map[string][]byte{}} }
|
|
|
|
func (s *fakeStorage) Put(_ context.Context, key string, r io.Reader, size int64) error {
|
|
if s.putErr != nil {
|
|
return s.putErr
|
|
}
|
|
b, _ := io.ReadAll(r)
|
|
s.data[key] = b
|
|
s.gotKey = key
|
|
s.gotSize = size
|
|
return nil
|
|
}
|
|
func (s *fakeStorage) Get(_ context.Context, key string) (io.ReadCloser, error) {
|
|
b, ok := s.data[key]
|
|
if !ok {
|
|
return nil, errors.New("not found")
|
|
}
|
|
return io.NopCloser(bytes.NewReader(b)), nil
|
|
}
|
|
func (s *fakeStorage) Delete(_ context.Context, key string) error {
|
|
delete(s.data, key)
|
|
return nil
|
|
}
|
|
func (s *fakeStorage) URL(key string) string { return "/files/static/" + key }
|
|
|
|
type fakeRepo struct {
|
|
created *model.File
|
|
byID *model.File
|
|
getErr error
|
|
createErr error
|
|
delErr error
|
|
deletedID uint64
|
|
}
|
|
|
|
func (r *fakeRepo) Create(_ context.Context, f *model.File) error { r.created = f; return r.createErr }
|
|
func (r *fakeRepo) GetByID(_ context.Context, id uint64) (*model.File, error) {
|
|
if r.getErr != nil {
|
|
return nil, r.getErr
|
|
}
|
|
return r.byID, nil
|
|
}
|
|
func (r *fakeRepo) Delete(_ context.Context, id uint64) error { r.deletedID = id; return r.delErr }
|
|
|
|
// 编译期接口断言。
|
|
var (
|
|
_ filesvc.FileRepository = (*fakeRepo)(nil)
|
|
_ storage.Storage = (*fakeStorage)(nil)
|
|
)
|
|
|
|
func newSvc(cfg config.UploadConfig, store *fakeStorage, repo *fakeRepo) *filesvc.Service {
|
|
return filesvc.New(repo, store, cfg)
|
|
}
|
|
|
|
// --- Upload ---
|
|
|
|
func TestUpload_Success(t *testing.T) {
|
|
store := newFakeStorage()
|
|
repo := &fakeRepo{}
|
|
svc := newSvc(config.UploadConfig{MaxSize: 1 << 20}, store, repo)
|
|
|
|
res, err := svc.Upload(context.Background(), filesvc.UploadInput{
|
|
Reader: bytes.NewReader([]byte("hello")),
|
|
Size: 5,
|
|
Filename: "我的图片.png",
|
|
UploaderType: "admin",
|
|
UploaderID: 1,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
require.Equal(t, uint64(1), repo.created.UploaderID)
|
|
require.Equal(t, "admin", repo.created.UploaderType)
|
|
require.Equal(t, "png", repo.created.Ext)
|
|
require.Equal(t, "image/png", repo.created.MimeType)
|
|
require.NotEmpty(t, repo.created.StorageKey)
|
|
require.Equal(t, store.URL(repo.created.StorageKey), res.URL)
|
|
require.Contains(t, store.gotKey, ".png")
|
|
}
|
|
|
|
func TestUpload_TooLarge(t *testing.T) {
|
|
svc := newSvc(config.UploadConfig{MaxSize: 10}, newFakeStorage(), &fakeRepo{})
|
|
_, err := svc.Upload(context.Background(), filesvc.UploadInput{
|
|
Reader: bytes.NewReader(make([]byte, 11)),
|
|
Size: 11,
|
|
Filename: "a.png",
|
|
})
|
|
require.ErrorIs(t, err, filesvc.ErrFileTooLarge)
|
|
}
|
|
|
|
func TestUpload_TypeForbidden(t *testing.T) {
|
|
svc := newSvc(config.UploadConfig{MaxSize: 1 << 20, AllowedExt: []string{"jpg"}}, newFakeStorage(), &fakeRepo{})
|
|
_, err := svc.Upload(context.Background(), filesvc.UploadInput{
|
|
Reader: bytes.NewReader([]byte("x")),
|
|
Size: 1,
|
|
Filename: "a.png",
|
|
})
|
|
require.ErrorIs(t, err, filesvc.ErrFileTypeForbid)
|
|
}
|
|
|
|
func TestUpload_EmptyWhitelistAllowsAll(t *testing.T) {
|
|
store := newFakeStorage()
|
|
repo := &fakeRepo{}
|
|
svc := newSvc(config.UploadConfig{MaxSize: 1 << 20}, store, repo)
|
|
_, err := svc.Upload(context.Background(), filesvc.UploadInput{
|
|
Reader: bytes.NewReader([]byte("x")),
|
|
Size: 1,
|
|
Filename: "a.unknown-ext",
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "unknown-ext", repo.created.Ext)
|
|
}
|
|
|
|
func TestUpload_StoreErrorNoCreate(t *testing.T) {
|
|
store := newFakeStorage()
|
|
store.putErr = errors.New("disk full")
|
|
repo := &fakeRepo{}
|
|
svc := newSvc(config.UploadConfig{}, store, repo)
|
|
_, err := svc.Upload(context.Background(), filesvc.UploadInput{
|
|
Reader: bytes.NewReader([]byte("x")), Size: 1, Filename: "a.png",
|
|
})
|
|
require.Error(t, err)
|
|
require.Nil(t, repo.created, "存储失败不应写入元数据")
|
|
}
|
|
|
|
func TestUpload_RollbackOnRepoError(t *testing.T) {
|
|
store := newFakeStorage()
|
|
repo := &fakeRepo{createErr: errors.New("db down")}
|
|
svc := newSvc(config.UploadConfig{}, store, repo)
|
|
_, err := svc.Upload(context.Background(), filesvc.UploadInput{
|
|
Reader: bytes.NewReader([]byte("x")), Size: 1, Filename: "a.png",
|
|
})
|
|
require.Error(t, err)
|
|
require.Empty(t, store.data, "元数据落库失败应回滚已落盘文件")
|
|
}
|
|
|
|
// --- Get / Download / Delete ---
|
|
|
|
func TestGet(t *testing.T) {
|
|
want := &model.File{ID: 7, OriginName: "doc.pdf"}
|
|
svc := newSvc(config.UploadConfig{}, newFakeStorage(), &fakeRepo{byID: want})
|
|
got, err := svc.Get(context.Background(), 7)
|
|
require.NoError(t, err)
|
|
require.Equal(t, want, got)
|
|
}
|
|
|
|
func TestDownload(t *testing.T) {
|
|
store := newFakeStorage()
|
|
store.data["2006/01/01/abc.png"] = []byte("binary")
|
|
repo := &fakeRepo{byID: &model.File{ID: 3, StorageKey: "2006/01/01/abc.png", MimeType: "image/png", Size: 6}}
|
|
svc := newSvc(config.UploadConfig{}, store, repo)
|
|
|
|
rc, f, err := svc.Download(context.Background(), 3)
|
|
require.NoError(t, err)
|
|
defer rc.Close()
|
|
b, _ := io.ReadAll(rc)
|
|
require.Equal(t, []byte("binary"), b)
|
|
require.Equal(t, "image/png", f.MimeType)
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
store := newFakeStorage()
|
|
store.data["k"] = []byte("x")
|
|
repo := &fakeRepo{byID: &model.File{ID: 9, StorageKey: "k"}}
|
|
svc := newSvc(config.UploadConfig{}, store, repo)
|
|
|
|
require.NoError(t, svc.Delete(context.Background(), 9))
|
|
require.Equal(t, uint64(9), repo.deletedID)
|
|
require.Empty(t, store.data)
|
|
}
|