package app import ( "context" "errors" "fmt" "strings" "testing" "time" "evening_detective_server/internal/modules/processor_jwt" "evening_detective_server/internal/services/task_service" proto "evening_detective_server/proto" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) func TestTaskEndpointsRejectUnauthorizedRoles(t *testing.T) { s := &server{} operations := []struct { name string call func(context.Context) error }{ {"list", func(ctx context.Context) error { _, err := s.GetTasks(ctx, &proto.GetTasksReq{}); return err }}, {"assignees", func(ctx context.Context) error { _, err := s.GetTaskAssignees(ctx, &proto.GetTaskAssigneesReq{}) return err }}, {"create", func(ctx context.Context) error { _, err := s.CreateTask(ctx, &proto.CreateTaskReq{}); return err }}, {"update", func(ctx context.Context) error { _, err := s.UpdateTask(ctx, &proto.UpdateTaskReq{}); return err }}, {"status", func(ctx context.Context) error { _, err := s.SetTaskStatus(ctx, &proto.SetTaskStatusReq{}); return err }}, {"delete", func(ctx context.Context) error { _, err := s.DeleteTask(ctx, &proto.DeleteTaskReq{}); return err }}, } for _, operation := range operations { for _, role := range []string{"", "user", "author", "organizer"} { t.Run(operation.name+"/"+role, func(t *testing.T) { ctx := context.Background() want := codes.Unauthenticated if role != "" { ctx = processor_jwt.ContextWithClaims(ctx, &processor_jwt.JWTClaims{UserID: 1, Roles: []string{role}}) want = codes.PermissionDenied } if err := operation.call(ctx); status.Code(err) != want { t.Fatalf("error=%v want=%v", err, want) } }) } } } func TestTaskActorAllowsDeveloperAndAdmin(t *testing.T) { for _, role := range []string{"developer", "admin"} { t.Run(role, func(t *testing.T) { ctx := processor_jwt.ContextWithClaims(context.Background(), &processor_jwt.JWTClaims{UserID: 42, Roles: []string{role}}) id, err := taskActor(ctx) if id != 42 || err != nil { t.Fatalf("id=%d error=%v", id, err) } }) } } func TestTaskErrorsUseSafeTransportCodes(t *testing.T) { for _, tc := range []struct { err error code codes.Code }{ {task_service.ErrInvalidTask, codes.InvalidArgument}, {task_service.ErrInvalidAssignee, codes.InvalidArgument}, {task_service.ErrNotFound, codes.NotFound}, {task_service.ErrConflict, codes.Aborted}, {errors.New("database-password-secret"), codes.Internal}, } { t.Run(tc.err.Error(), func(t *testing.T) { err := taskErrorStatus(fmt.Errorf("database-password-secret: %w", tc.err)) if status.Code(err) != tc.code || strings.Contains(err.Error(), "database-password-secret") { t.Fatalf("unsafe or incorrect error=%v", err) } }) } } func TestTaskMappingPreservesOptionalFields(t *testing.T) { id := 4 date := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC) for _, tc := range []struct { name string assignee *int date *time.Time wantDate string }{ {"unset", nil, nil, ""}, {"set", &id, &date, "2026-10-07"}, } { t.Run(tc.name, func(t *testing.T) { task := mapTask(&task_service.Task{ID: 1, AssigneeID: tc.assignee, DueDate: tc.date, CreatedAt: date, UpdatedAt: date, Version: 2}) if task.DueDate != tc.wantDate || (task.AssigneeID == nil) != (tc.assignee == nil) || task.Version != 2 { t.Fatalf("task=%+v", task) } input, err := taskInput("Task", "", "new", task.AssigneeID, task.DueDate) if err != nil || (input.AssigneeID == nil) != (tc.assignee == nil) { t.Fatalf("input=%+v error=%v", input, err) } }) } }