Files
evening_detective_server/internal/app/file_service_test.go
T
2026-08-30 21:17:32 +07:00

192 lines
5.5 KiB
Go

package app
import (
"context"
"errors"
"testing"
"evening_detective_server/internal/modules/file_storage"
"evening_detective_server/internal/modules/processor_jwt"
"evening_detective_server/internal/modules/string_tools"
"evening_detective_server/internal/services/file_service"
proto "evening_detective_server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// fakeStorage — in-memory реализация IFileStorage для app-слоя.
type fakeStorage struct {
files map[string]*file_storage.File
putErr error
getErr error
}
func newFakeStorage() *fakeStorage {
return &fakeStorage{files: map[string]*file_storage.File{}}
}
func (s *fakeStorage) Put(_ context.Context, f *file_storage.File) error {
if s.putErr != nil {
return s.putErr
}
cp := *f
cp.Data = append([]byte(nil), f.Data...)
s.files[f.Name] = &cp
return nil
}
func (s *fakeStorage) Get(_ context.Context, name string) (*file_storage.File, error) {
if s.getErr != nil {
return nil, s.getErr
}
f, ok := s.files[name]
if !ok {
return nil, file_storage.ErrFileNotFound
}
return f, nil
}
func (s *fakeStorage) Delete(_ context.Context, name string) error {
delete(s.files, name)
return nil
}
func (s *fakeStorage) MimeType(filename string) string {
return file_storage.FileType(filename)
}
func newTestFileServer(storage *fakeStorage, maxSize int) *server {
if storage == nil {
storage = newFakeStorage()
}
return &server{
fileService: file_service.NewFileService(storage, string_tools.NewStringTools(), maxSize),
}
}
func ctxWithClaims(roles ...string) context.Context {
return context.WithValue(context.Background(), "claims", &processor_jwt.JWTClaims{UserID: 1, Roles: roles})
}
func pdfData(n int) []byte {
b := make([]byte, n)
copy(b, "%PDF-1.7 ")
for i := 8; i < n; i++ {
b[i] = 'x'
}
return b
}
func TestUploadFilePermissionDenied(t *testing.T) {
s := newTestFileServer(nil, 1<<20)
_, err := s.UploadFile(ctxWithClaims(), &proto.UploadFileReq{Filename: "clue.pdf", Data: pdfData(16)})
if status.Code(err) != codes.PermissionDenied {
t.Errorf("code = %v, want PermissionDenied", status.Code(err))
}
}
func TestUploadFileValidationInvalidArgument(t *testing.T) {
s := newTestFileServer(nil, 1<<20)
// Имя без расширения.
_, err := s.UploadFile(ctxWithClaims("author"), &proto.UploadFileReq{Filename: "clue", Data: pdfData(16)})
if status.Code(err) != codes.InvalidArgument {
t.Errorf("code = %v, want InvalidArgument", status.Code(err))
}
}
func TestUploadFileTooLargeResourceExhausted(t *testing.T) {
s := newTestFileServer(nil, 1024)
_, err := s.UploadFile(ctxWithClaims("author"), &proto.UploadFileReq{Filename: "clue.pdf", Data: pdfData(1025)})
if status.Code(err) != codes.ResourceExhausted {
t.Errorf("code = %v, want ResourceExhausted", status.Code(err))
}
}
func TestUploadFilePutErrorInternal(t *testing.T) {
storage := newFakeStorage()
storage.putErr = errors.New("s3 недоступен")
s := newTestFileServer(storage, 1<<20)
_, err := s.UploadFile(ctxWithClaims("author"), &proto.UploadFileReq{Filename: "clue.pdf", Data: pdfData(16)})
if status.Code(err) != codes.Internal {
t.Errorf("code = %v, want Internal", status.Code(err))
}
}
func TestUploadFileSuccess(t *testing.T) {
s := newTestFileServer(nil, 1<<20)
rsp, err := s.UploadFile(ctxWithClaims("author"), &proto.UploadFileReq{Filename: "clue.pdf", Data: pdfData(16)})
if err != nil {
t.Fatalf("UploadFile: %v", err)
}
if rsp.Filename == "" {
t.Errorf("Filename пустой")
}
if rsp.FileType != "pdf" {
t.Errorf("FileType = %q, want pdf", rsp.FileType)
}
if rsp.Error != "" {
t.Errorf("Error = %q, want пусто", rsp.Error)
}
}
// mockServerTransportStream — заглушка для grpc.SetHeader в юнит-тестах.
type mockServerTransportStream struct {
grpc.ServerTransportStream
header metadata.MD
}
func (m *mockServerTransportStream) SetHeader(md metadata.MD) error {
m.header = md
return nil
}
func TestDownloadFileNotFound(t *testing.T) {
s := newTestFileServer(nil, 1<<20)
_, err := s.DownloadFile(context.Background(), &proto.DownloadFileReq{Filename: "missing.pdf"})
if status.Code(err) != codes.NotFound {
t.Errorf("code = %v, want NotFound", status.Code(err))
}
}
func TestDownloadFileInternal(t *testing.T) {
storage := newFakeStorage()
storage.getErr = errors.New("s3 недоступен")
s := newTestFileServer(storage, 1<<20)
_, err := s.DownloadFile(context.Background(), &proto.DownloadFileReq{Filename: "clue.pdf"})
if status.Code(err) != codes.Internal {
t.Errorf("code = %v, want Internal", status.Code(err))
}
}
func TestDownloadFileSuccess(t *testing.T) {
storage := newFakeStorage()
storage.files["clue.pdf"] = &file_storage.File{
Name: "clue.pdf",
Data: []byte("%PDF-1.7 content"),
Mime: "application/pdf",
}
s := newTestFileServer(storage, 1<<20)
stream := &mockServerTransportStream{}
ctx := grpc.NewContextWithServerTransportStream(context.Background(), stream)
body, err := s.DownloadFile(ctx, &proto.DownloadFileReq{Filename: "clue.pdf"})
if err != nil {
t.Fatalf("DownloadFile: %v", err)
}
if string(body.Data) != "%PDF-1.7 content" {
t.Errorf("Data = %q", body.Data)
}
if body.ContentType != "application/pdf" {
t.Errorf("ContentType = %q, want application/pdf", body.ContentType)
}
if got := stream.header.Get("X-Content-Type-Options"); len(got) != 1 || got[0] != "nosniff" {
t.Errorf("X-Content-Type-Options = %v, want nosniff", got)
}
}