generated from VLADIMIR/template
192 lines
5.5 KiB
Go
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)
|
|
}
|
|
}
|