From 89c32be18da00dc50c44472d83a5b3ab1152e2c4 Mon Sep 17 00:00:00 2001 From: Ranjeet Prabhat Date: Wed, 14 Oct 2026 07:36:19 +0545 Subject: [PATCH] feat(social): prevent users from following themselves (BA-053) --- BackendAcademy/src/social/dto/follow.dto.ts | 8 +++++ .../src/social/follow.service.spec.ts | 29 +++++++++++++++++++ BackendAcademy/src/social/follow.service.ts | 20 +++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/BackendAcademy/src/social/dto/follow.dto.ts b/BackendAcademy/src/social/dto/follow.dto.ts index 401d080256..74a3b4f3c9 100644 --- a/BackendAcademy/src/social/dto/follow.dto.ts +++ b/BackendAcademy/src/social/dto/follow.dto.ts @@ -1,11 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; export class FollowDto { + @ApiProperty({ example: 'learner-1', description: 'The user initiating the follow request' }) @IsString() @IsNotEmpty() followerId: string; + @ApiProperty({ example: 'tutor-1', description: 'The target user to follow (must not be self)' }) @IsString() @IsNotEmpty() followeeId: string; } + +export interface SelfFollowError { + code: 'CANNOT_FOLLOW_SELF'; + message: string; +} diff --git a/BackendAcademy/src/social/follow.service.spec.ts b/BackendAcademy/src/social/follow.service.spec.ts index 2a3357c857..7cfbd7d98a 100644 --- a/BackendAcademy/src/social/follow.service.spec.ts +++ b/BackendAcademy/src/social/follow.service.spec.ts @@ -1,4 +1,5 @@ import { BadRequestException } from '@nestjs/common'; +import { FollowDto } from './dto/follow.dto'; import { FollowService } from './follow.service'; import { DEFAULT_RANK_WEIGHT, FOLLOW_RANK_WEIGHT } from './social.types'; @@ -152,4 +153,32 @@ describe('FollowService', () => { expect(service.getFeed('nobody')).toEqual([]); }); }); + + // ── BA-053: Prevent users from following themselves ────────────────────── + + describe('BA-053 — Prevent users from following themselves', () => { + it('rejects self-follow with structured CANNOT_FOLLOW_SELF error code', () => { + let caught: BadRequestException | undefined; + try { + service.follow('learner-self', 'learner-self'); + } catch (err) { + caught = err as BadRequestException; + } + + expect(caught).toBeInstanceOf(BadRequestException); + expect(caught!.getStatus()).toBe(400); + const res = caught!.getResponse() as Record; + expect(res.code).toBe('CANNOT_FOLLOW_SELF'); + expect(res.message).toBe('An account cannot follow itself'); + }); + + it('rejects self-follow passed via DTO object', () => { + const dto: FollowDto = { followerId: 'user-99', followeeId: 'user-99' }; + expect(() => service.follow(dto)).toThrow(BadRequestException); + }); + + it('rejects self-follow with trailing or leading whitespace', () => { + expect(() => service.follow('user-trim ', ' user-trim')).toThrow(BadRequestException); + }); + }); }); diff --git a/BackendAcademy/src/social/follow.service.ts b/BackendAcademy/src/social/follow.service.ts index ed3f8332b3..898625b3cc 100644 --- a/BackendAcademy/src/social/follow.service.ts +++ b/BackendAcademy/src/social/follow.service.ts @@ -1,4 +1,5 @@ import { BadRequestException, Injectable } from '@nestjs/common'; +import { FollowDto, SelfFollowError } from './dto/follow.dto'; import { DEFAULT_RANK_WEIGHT, FeedItem, @@ -35,9 +36,22 @@ export class FollowService { return `${followerId}|${followeeId}`; } - follow(followerId: string, followeeId: string): FollowEdge { - if (followerId === followeeId) { - throw new BadRequestException('An account cannot follow itself'); + follow(followerId: string, followeeId: string): FollowEdge; + follow(dto: FollowDto): FollowEdge; + follow(followerOrDto: string | FollowDto, maybeFolloweeId?: string): FollowEdge { + const followerId = typeof followerOrDto === 'object' ? followerOrDto.followerId : followerOrDto; + const followeeId = typeof followerOrDto === 'object' ? followerOrDto.followeeId : maybeFolloweeId!; + + if (!followerId || !followeeId) { + throw new BadRequestException('Follower and followee IDs must be provided'); + } + + if (followerId === followeeId || followerId.trim() === followeeId.trim()) { + const errorPayload: SelfFollowError = { + code: 'CANNOT_FOLLOW_SELF', + message: 'An account cannot follow itself', + }; + throw new BadRequestException(errorPayload); } const key = FollowService.edgeKey(followerId, followeeId);