diff --git a/internal/imclient/client.go b/internal/imclient/client.go index 9617ac9..0366a64 100644 --- a/internal/imclient/client.go +++ b/internal/imclient/client.go @@ -388,6 +388,12 @@ func IsConflict(err error) bool { return errors.As(err, &target) && target.Status == http.StatusConflict } +func IsFriendRequestConflict(err error) bool { + var target *Error + return errors.As(err, &target) && target.Status == http.StatusConflict && + strings.EqualFold(strings.TrimSpace(target.Reason), "FRIEND_REQUEST_CONFLICT") +} + func IsPermissionDenied(err error) bool { var target *Error return errors.As(err, &target) && target.Status == http.StatusForbidden diff --git a/internal/support/service.go b/internal/support/service.go index 97081ae..bdaf1f9 100644 --- a/internal/support/service.go +++ b/internal/support/service.go @@ -125,42 +125,42 @@ func (s *Service) OpenSession(ctx context.Context, tenantID, memberID, serviceID if !strings.EqualFold(strings.TrimSpace(imSession.UserID), strings.TrimSpace(identity.IMUserID)) { return nil, ErrIMUnavailable } - var conversation *imclient.Conversation profile, err := s.im.UserProfile(ctx, imSession.Token, agent.IMUserID) + relationship := "" if err != nil { if !imclient.IsPermissionDenied(err) { return nil, fmt.Errorf("query customer service relationship: %w", err) } - conversation, err = s.im.EnsureP2P(ctx, imSession.Token, agent.IMUserID, - fmt.Sprintf("p2p:%s:%s", identity.IMUserID, agent.IMUserID)) - if err != nil { - return nil, fmt.Errorf("query customer service relationship: %w", err) + key := fmt.Sprintf("customer-service:%d:%d:%d", tenantID, memberID, serviceID) + request, requestErr := s.im.CreateFriendRequest(ctx, imSession.Token, agent.IMUserID, key) + if imclient.IsFriendRequestConflict(requestErr) { + return nil, ErrRelationPending } - } - relationship := "" - if profile != nil { - relationship = strings.TrimSpace(profile.Relationship) + if requestErr != nil { + if imclient.IsConflict(requestErr) { + return nil, ErrRelationDenied + } + return nil, fmt.Errorf("create customer service friend request: %w", requestErr) + } + relationship = strings.TrimSpace(request.Status) } else { - relationship = strings.TrimSpace(conversation.Relationship) + relationship = strings.TrimSpace(profile.Relationship) } switch relationship { case "friend": case "none", "": key := fmt.Sprintf("customer-service:%d:%d:%d", tenantID, memberID, serviceID) request, requestErr := s.im.CreateFriendRequest(ctx, imSession.Token, agent.IMUserID, key) + if imclient.IsFriendRequestConflict(requestErr) { + return nil, ErrRelationPending + } if requestErr != nil && !imclient.IsConflict(requestErr) { return nil, fmt.Errorf("create customer service friend request: %w", requestErr) } if requestErr != nil { - conversation, err = s.im.EnsureP2P(ctx, imSession.Token, agent.IMUserID, - fmt.Sprintf("p2p:%s:%s", identity.IMUserID, agent.IMUserID)) - if err != nil { - return nil, fmt.Errorf("refresh customer service relationship: %w", err) - } - relationship = strings.TrimSpace(conversation.Relationship) + return nil, ErrRelationDenied } else { relationship = strings.TrimSpace(request.Status) - conversation = nil } if relationship != "accepted" && relationship != "friend" { return nil, ErrRelationPending @@ -170,12 +170,10 @@ func (s *Service) OpenSession(ctx context.Context, tenantID, memberID, serviceID default: return nil, ErrRelationDenied } - if conversation == nil { - conversation, err = s.im.EnsureP2P(ctx, imSession.Token, agent.IMUserID, - fmt.Sprintf("p2p:%s:%s", identity.IMUserID, agent.IMUserID)) - if err != nil { - return nil, fmt.Errorf("ensure customer service conversation: %w", err) - } + conversation, err := s.im.EnsureP2P(ctx, imSession.Token, agent.IMUserID, + fmt.Sprintf("p2p:%s:%s", identity.IMUserID, agent.IMUserID)) + if err != nil { + return nil, fmt.Errorf("ensure customer service conversation: %w", err) } if strings.TrimSpace(conversation.Topic) == "" || (conversation.Relationship != "" && conversation.Relationship != "friend") || !conversation.CanSend { return nil, ErrRelationDenied diff --git a/internal/support/service_test.go b/internal/support/service_test.go index 9c36dee..78831af 100644 --- a/internal/support/service_test.go +++ b/internal/support/service_test.go @@ -37,13 +37,15 @@ func (r *repositoryStub) SaveMemberIMIdentity(_ context.Context, identity *store } type imStub struct { - friendRequests int - loginAttempts int - resetPasswords int - loginError error - profileError error - conversation *imclient.Conversation - ensureP2PCalls int + friendRequests int + loginAttempts int + resetPasswords int + loginError error + profileError error + friendError error + conversation *imclient.Conversation + conversationErr error + ensureP2PCalls int } func (i *imStub) Ready() bool { return true } @@ -78,10 +80,16 @@ func (i *imStub) UserProfile(context.Context, string, string) (*imclient.UserPro } func (i *imStub) CreateFriendRequest(context.Context, string, string, string) (*imclient.FriendRequest, error) { i.friendRequests++ + if i.friendError != nil { + return nil, i.friendError + } return &imclient.FriendRequest{RequestID: "frq_1", Status: "accepted"}, nil } func (i *imStub) EnsureP2P(context.Context, string, string, string) (*imclient.Conversation, error) { i.ensureP2PCalls++ + if i.conversationErr != nil { + return nil, i.conversationErr + } if i.conversation != nil { return i.conversation, nil } @@ -132,8 +140,9 @@ func TestOpenSessionReturnsPendingWhenPrivateProfileHidesPendingRelationship(t * identity: &store.MemberIMIdentity{TenantID: 0, MemberID: 9, IMTenantCode: "allrich", IMUserID: "usr_member", IMUsername: "member01"}, } im := &imStub{ - profileError: &imclient.Error{Status: 403, Reason: "PERMISSION_DENIED"}, - conversation: &imclient.Conversation{Topic: "p2p-topic", Relationship: "pending_outbound", CanSend: false}, + profileError: &imclient.Error{Status: 403, Reason: "PERMISSION_DENIED"}, + friendError: &imclient.Error{Status: 409, Reason: "FRIEND_REQUEST_CONFLICT"}, + conversationErr: &imclient.Error{Status: 403, Reason: "PERMISSION_DENIED"}, } service := New(repository, im, config.AuthConfig{FinanceMasterKey: strings.Repeat("k", 32)}) repository.identity.IMPasswordCipher, _ = service.encryptCredential("password") @@ -142,8 +151,8 @@ func TestOpenSessionReturnsPendingWhenPrivateProfileHidesPendingRelationship(t * if !errors.Is(err, ErrRelationPending) { t.Fatalf("error = %v, want ErrRelationPending", err) } - if im.ensureP2PCalls != 1 || im.friendRequests != 0 { - t.Fatalf("ensure p2p calls=%d friend requests=%d, want 1 and 0", im.ensureP2PCalls, im.friendRequests) + if im.ensureP2PCalls != 0 || im.friendRequests != 1 { + t.Fatalf("ensure p2p calls=%d friend requests=%d, want 0 and 1", im.ensureP2PCalls, im.friendRequests) } }