Files
evening_detective_server/internal/app/game_print_test.go
T
2026-10-12 15:58:24 +07:00

112 lines
3.8 KiB
Go

package app
import (
"bytes"
"context"
"errors"
"fmt"
"testing"
"evening_detective_server/internal/modules/file_storage"
"evening_detective_server/internal/repos"
"evening_detective_server/internal/services/game_print_service"
"evening_detective_server/internal/services/scenarios_service"
"evening_detective_server/internal/services/team_service"
"evening_detective_server/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type printGameStub struct{}
func (printGameStub) GetGameByID(context.Context, int) (*repos.Game, error) {
return &repos.Game{ID: 1, Name: "Дело №1", Format: "offline"}, nil
}
func (printGameStub) GetFullScenarioByID(context.Context, int) (*scenarios_service.Scenario, error) {
return &scenarios_service.Scenario{}, nil
}
func (printGameStub) GetActiveTeamsByGameID(context.Context, int) ([]*repos.Team, error) {
return nil, nil
}
func TestDownloadGamePrintMaterialsHeaders(t *testing.T) {
for _, role := range []string{"organizer", "author", "admin"} {
t.Run(role, func(t *testing.T) {
stub := printGameStub{}
s := &server{gamePrintService: game_print_service.NewGamePrintService(stub, stub, stub, nil, nil, "")}
stream := &mockServerTransportStream{}
ctx := grpc.NewContextWithServerTransportStream(ctxWithClaims(role), stream)
body, err := s.DownloadGamePrintMaterials(ctx, &proto.GetGameReq{ID: 1})
if err != nil {
t.Fatal(err)
}
if body.ContentType != "application/zip" || !bytes.HasPrefix(body.Data, []byte("PK")) {
t.Fatal("invalid ZIP response")
}
for key, want := range map[string]string{
"content-disposition": "attachment; filename=game-1-print-materials.zip",
"cache-control": "no-store",
"x-content-type-options": "nosniff",
} {
values := stream.header.Get(key)
if len(values) != 1 || values[0] != want {
t.Errorf("%s = %v, want %s", key, values, want)
}
}
})
}
}
func TestDownloadGamePrintMaterialsAccess(t *testing.T) {
for _, tc := range []struct {
name string
ctx context.Context
request *proto.GetGameReq
want codes.Code
}{
{"anonymous", context.Background(), &proto.GetGameReq{ID: 1}, codes.Unauthenticated},
{"player", ctxWithClaims("user"), &proto.GetGameReq{ID: 1}, codes.PermissionDenied},
{"developer", ctxWithClaims("developer"), &proto.GetGameReq{ID: 1}, codes.PermissionDenied},
{"organizer invalid ID", ctxWithClaims("organizer"), &proto.GetGameReq{}, codes.InvalidArgument},
{"author invalid ID", ctxWithClaims("author"), &proto.GetGameReq{ID: -1}, codes.InvalidArgument},
{"admin nil request", ctxWithClaims("admin"), nil, codes.InvalidArgument},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := (&server{}).DownloadGamePrintMaterials(tc.ctx, tc.request)
if status.Code(err) != tc.want {
t.Fatalf("code = %v, want %v", status.Code(err), tc.want)
}
})
}
}
func TestGamePrintErrorMapping(t *testing.T) {
for _, tc := range []struct {
err error
want codes.Code
}{
{team_service.ErrGameNotFound, codes.NotFound},
{scenarios_service.ErrScenarioNotFound, codes.NotFound},
{game_print_service.ErrPackageTooLarge, codes.ResourceExhausted},
{game_print_service.ErrInvalidPrintText, codes.FailedPrecondition},
{file_storage.ErrFileNotFound, codes.FailedPrecondition},
{context.Canceled, codes.Canceled},
{context.DeadlineExceeded, codes.DeadlineExceeded},
{errors.New("private storage detail"), codes.Internal},
} {
t.Run(tc.err.Error(), func(t *testing.T) {
err := gamePrintError(fmt.Errorf("wrapped: %w", tc.err))
if status.Code(err) != tc.want {
t.Fatalf("code = %v, want %v", status.Code(err), tc.want)
}
if tc.want == codes.Internal && status.Convert(err).Message() != "не удалось подготовить материалы для печати" {
t.Fatal("private error exposed")
}
})
}
}