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) } }