Files
pos-system/services/social-service-net/SERVICE_DOCS.md
Ho Ngoc Hai f3779c4ebe docs: add SERVICE_DOCS.md for all 24 microservices from per-service code audit
Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries,
Domain Model, Database Schema, Integration Events, Dependencies, Configuration.
Generated by 23 parallel audit agents reading actual source code.

Key corrections from audit:
- inventory-service: 12 commands/6 queries (was listed as scaffold)
- promotion-service: 12 commands/10 queries (was listed as 0)
- mission-service: 4 commands/7 queries (was listed as 0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:54:53 +07:00

26 KiB

SocialService - Service Documentation

1. Overview

The Social Service manages user-to-user social relationships within the GoodGo platform. It handles friendships (bidirectional, requires acceptance), following (unidirectional, auto-accepted), user blocking, and graph-based queries such as mutual friends and friend suggestions.

  • Service Name: social-service-net
  • Port: 5009 (Development, HTTP)
  • Docker Port: 8080
  • Database: PostgreSQL - social_service (Neon cloud)
  • Database (local): configurable via DATABASE_URL env var
  • Framework: .NET 10.0, C# 14
  • Architecture: Clean Architecture + CQRS (MediatR)
  • Solution File: SocialService.slnx
  • Migration: 20260112164356_InitialCreate (auto-applied on startup)

2. API Endpoints

2.1 RelationshipsController

Base Route: api/v1/relationships

Method Route Description Request Body Response
GET /users/{userId}/friends?skip=0&take=20 Get friends of a user (paginated) - GetFriendsResult
POST /friend-requests Send a friend request SendFriendRequestRequest SendFriendRequestResult (201)
PUT /friend-requests/{relationshipId} Accept or reject a friend request RespondToFriendRequestRequest { success: bool }
GET /users/{userId1}/mutual-friends/{userId2} Get mutual friends between two users - GetMutualFriendsResult
GET /users/{userId}/suggestions?limit=10 Get friend suggestions for a user - GetFriendSuggestionsResult
POST /follow Follow a user FollowUserRequest FollowUserResult (201)
DELETE /follow Unfollow a user UnfollowUserRequest { success: bool }

Request DTOs (defined in RelationshipsController.cs):

  • SendFriendRequestRequest(Guid RequesterId, Guid AddresseeId)
  • RespondToFriendRequestRequest(Guid UserId, bool Accept)
  • FollowUserRequest(Guid FollowerId, Guid FolloweeId)
  • UnfollowUserRequest(Guid FollowerId, Guid FolloweeId)

2.2 BlocksController

Base Route: api/v1/blocks

Method Route Description Request Body Response
POST / Block a user BlockUserRequest BlockUserResult (201)
DELETE / Unblock a user UnblockUserRequest { success: bool }
GET /users/{userId}?skip=0&take=20 Get list of blocked users (paginated) - GetBlockedUsersResult

Request DTOs (defined in BlocksController.cs):

  • BlockUserRequest(Guid BlockerId, Guid BlockedId, string? Reason = null)
  • UnblockUserRequest(Guid BlockerId, Guid BlockedId)

2.3 AdminController

Base Route: api/v1/admin/social

Method Route Description Query Params Response
GET /relationships?skip=0&take=20&status=&type= Get all relationships (filtered, paginated) skip, take, status (pending/accepted/rejected/cancelled), type (friendship/following) GetAllRelationshipsResult
GET /relationships/{id:guid} Get relationship by ID - RelationshipDetailDto or 404
DELETE /relationships/{id:guid} Admin delete a relationship - { success, message } or 404
GET /blocks?skip=0&take=20 Get all blocks (paginated) skip, take GetAllBlocksResult
DELETE /blocks/{id:guid} Admin delete a block - { success, message } or 404
GET /statistics Get social statistics overview - SocialStatisticsDto

2.4 Health Check Endpoints

Route Description
/health Full health check (includes PostgreSQL)
/health/live Liveness probe (app is running, no dependency checks)
/health/ready Readiness probe (includes PostgreSQL)

3. Commands

All commands are MediatR IRequest<T> records. The MediatR pipeline wraps commands in transactions automatically (via TransactionBehavior, which skips requests ending in "Query").

3.1 SendFriendRequestCommand

  • File: Application/Commands/SendFriendRequestCommand.cs
  • Parameters: Guid RequesterId, Guid AddresseeId
  • Returns: SendFriendRequestResult(Guid RelationshipId, string Status)
  • Behavior:
    • Checks for blocks between users (throws if blocked)
    • Checks for existing friendship (throws if already friends or pending)
    • If reverse pending request exists, auto-accepts it (mutual request)
    • Otherwise creates new Relationship with Pending status
  • Dependencies: IRelationshipRepository, IUserBlockRepository

3.2 RespondToFriendRequestCommand

  • File: Application/Commands/RespondToFriendRequestCommand.cs
  • Parameters: Guid RelationshipId, Guid UserId, bool Accept
  • Returns: bool
  • Behavior:
    • Verifies relationship exists (throws if not found)
    • Verifies user is the addressee (throws if not)
    • Calls Accept() or Reject() on the Relationship entity
  • Dependencies: IRelationshipRepository

3.3 FollowUserCommand

  • File: Application/Commands/FollowUserCommand.cs
  • Parameters: Guid FollowerId, Guid FolloweeId
  • Returns: FollowUserResult(Guid RelationshipId, bool Success)
  • Behavior:
    • Prevents self-follow
    • Checks for blocks between users
    • Checks for existing follow (throws if already following)
    • Creates Relationship with Following type, calls Accept() (auto-accepted)
  • Dependencies: IRelationshipRepository, IUserBlockRepository

3.4 UnfollowUserCommand

  • File: Application/Commands/UnfollowUserCommand.cs
  • Parameters: Guid FollowerId, Guid FolloweeId
  • Returns: bool
  • Behavior:
    • Finds existing Following relationship (throws if not following)
    • Calls Remove() on the Relationship entity (sets status to Cancelled)
  • Dependencies: IRelationshipRepository

3.5 BlockUserCommand

  • File: Application/Commands/BlockUserCommand.cs
  • Parameters: Guid BlockerId, Guid BlockedId, string? Reason = null
  • Returns: BlockUserResult(Guid BlockId, bool Success)
  • Behavior:
    • Prevents self-block
    • Checks for existing block (throws if already blocked)
    • Creates UserBlock entity
    • Removes all existing relationships between the two users (friendships and followings in both directions) by calling Remove() on each
  • Dependencies: IUserBlockRepository, IRelationshipRepository

3.6 UnblockUserCommand

  • File: Application/Commands/UnblockUserCommand.cs
  • Parameters: Guid BlockerId, Guid BlockedId
  • Returns: bool
  • Behavior:
    • Finds existing block (throws if not blocked)
    • Removes the UserBlock entity
  • Dependencies: IUserBlockRepository

3.7 AdminDeleteRelationshipCommand

  • File: Application/Commands/AdminDeleteRelationshipCommand.cs
  • Parameters: Guid RelationshipId
  • Returns: bool
  • Behavior: Hard-deletes a relationship from the database. Returns false if not found.
  • Dependencies: IRelationshipRepository

3.8 AdminDeleteBlockCommand

  • File: Application/Commands/AdminDeleteBlockCommand.cs
  • Parameters: Guid BlockId
  • Returns: bool
  • Behavior: Hard-deletes a block from the database. Returns false if not found.
  • Dependencies: IUserBlockRepository

4. Queries

4.1 GetFriendsQuery

  • File: Application/Queries/GetFriendsQuery.cs
  • Parameters: Guid UserId, int Skip = 0, int Take = 20
  • Returns: GetFriendsResult(IEnumerable<FriendDto> Friends, int TotalCount)
  • FriendDto: (Guid UserId, string DisplayName, string? AvatarUrl, DateTime FriendsSince)
  • Behavior: Gets accepted friendships where user is requester or addressee, enriches with UserProfile data.

4.2 GetMutualFriendsQuery

  • File: Application/Queries/GetMutualFriendsQuery.cs
  • Parameters: Guid UserId1, Guid UserId2
  • Returns: GetMutualFriendsResult(IEnumerable<FriendDto> MutualFriends, int Count)
  • Behavior: Uses IGraphQueryService with raw SQL (CTE) to find users who are friends with both users. Enriches with UserProfile data.

4.3 GetFriendSuggestionsQuery

  • File: Application/Queries/GetFriendSuggestionsQuery.cs
  • Parameters: Guid UserId, int Limit = 10
  • Returns: GetFriendSuggestionsResult(IEnumerable<FriendSuggestionDto> Suggestions)
  • FriendSuggestionDto: (Guid UserId, string DisplayName, string? AvatarUrl, int MutualFriendsCount)
  • Behavior: Uses IGraphQueryService with raw SQL (friends-of-friends CTE). Excludes already-friends and blocked users. Orders by mutual friend count descending.

4.4 GetBlockedUsersQuery

  • File: Defined in Controllers/BlocksController.cs
  • Parameters: Guid UserId, int Skip = 0, int Take = 20
  • Returns: GetBlockedUsersResult(IEnumerable<BlockedUserDto> BlockedUsers, int TotalCount)
  • BlockedUserDto: (Guid UserId, string DisplayName, string? AvatarUrl, DateTime BlockedAt, string? Reason)
  • Behavior: Gets blocks by blocker user ID, enriches with UserProfile data.

4.5 GetAllRelationshipsQuery (Admin)

  • File: Application/Queries/GetAllRelationshipsQuery.cs
  • Parameters: int Skip, int Take, string? StatusFilter, string? TypeFilter
  • Returns: GetAllRelationshipsResult(IEnumerable<RelationshipAdminDto> Relationships, int TotalCount)
  • RelationshipAdminDto: (Guid Id, Guid RequesterId, Guid AddresseeId, string Type, string Status, DateTime CreatedAt, DateTime? UpdatedAt)

4.6 GetRelationshipByIdQuery (Admin)

  • File: Application/Queries/GetRelationshipByIdQuery.cs
  • Parameters: Guid Id
  • Returns: RelationshipDetailDto? (nullable)
  • RelationshipDetailDto: (Guid Id, Guid RequesterId, Guid AddresseeId, string Type, string Status, DateTime CreatedAt, DateTime? UpdatedAt)

4.7 GetAllBlocksQuery (Admin)

  • File: Application/Queries/GetAllBlocksQuery.cs
  • Parameters: int Skip, int Take
  • Returns: GetAllBlocksResult(IEnumerable<BlockAdminDto> Blocks, int TotalCount)
  • BlockAdminDto: (Guid Id, Guid BlockerId, Guid BlockedId, string? Reason, DateTime CreatedAt)

4.8 GetSocialStatisticsQuery (Admin)

  • File: Application/Queries/GetSocialStatisticsQuery.cs
  • Parameters: none
  • Returns: SocialStatisticsDto(int TotalFriendships, int TotalFollowings, int TotalBlocks, int PendingFriendRequests, int AcceptedFriendships, int TotalRelationships)

5. Domain Model

5.1 Aggregates

Relationship (Aggregate Root)

  • File: Domain/AggregatesModel/RelationshipAggregate/Relationship.cs
  • Extends: Entity, implements IAggregateRoot
  • Private Fields: _requesterId, _addresseeId, _type, _status, _createdAt, _updatedAt
  • Public Properties: RequesterId, AddresseeId, Type, TypeId, Status, StatusId, CreatedAt, UpdatedAt
  • Constructor: Relationship(Guid requesterId, Guid addresseeId, RelationshipType type)
    • Validates: no empty GUIDs, no self-relationship
    • Following type: auto-sets status to Accepted, raises UserFollowedDomainEvent
    • Friendship type: sets status to Pending, raises FriendRequestSentDomainEvent
  • Behavior Methods:
    • Accept() - Only for pending friendships. Sets status to Accepted. Raises FriendshipCreatedDomainEvent and RelationshipStatusChangedDomainEvent.
    • Reject() - Only for pending friendships. Sets status to Rejected. Raises RelationshipStatusChangedDomainEvent.
    • Cancel() - Only for pending requests. Sets status to Cancelled. Raises RelationshipStatusChangedDomainEvent.
    • Remove() - Only for accepted relationships. Sets status to Cancelled. Raises RelationshipRemovedDomainEvent and RelationshipStatusChangedDomainEvent.

UserBlock (Aggregate Root)

  • File: Domain/AggregatesModel/UserBlockAggregate/UserBlock.cs
  • Extends: Entity, implements IAggregateRoot
  • Private Fields: _blockerId, _blockedId, _reason, _createdAt
  • Public Properties: BlockerId, BlockedId, Reason, CreatedAt
  • Constructor: UserBlock(Guid blockerId, Guid blockedId, string? reason = null)
    • Validates: no empty GUIDs, no self-block
    • Raises UserBlockedDomainEvent
  • No behavior methods (immutable once created; removal is done via repository Remove())

UserProfile (Aggregate Root)

  • File: Domain/AggregatesModel/UserProfileAggregate/UserProfile.cs
  • Extends: Entity, implements IAggregateRoot
  • Private Fields: _userId, _displayName, _avatarUrl, _bio, _lastSyncedAt, _createdAt
  • Public Properties: UserId, DisplayName, AvatarUrl, Bio, LastSyncedAt, CreatedAt
  • Constructor: UserProfile(Guid userId, string displayName, string? avatarUrl = null, string? bio = null)
  • Behavior Methods:
    • UpdateFromEvent(string displayName, string? avatarUrl, string? bio) - Updates profile data from IAM integration event

5.2 Enumerations (Type-Safe Enum Pattern)

RelationshipType

  • File: Domain/AggregatesModel/RelationshipAggregate/RelationshipType.cs
  • Values:
    • Friendship (Id: 1) - Bidirectional, requires acceptance
    • Following (Id: 2) - Unidirectional, auto-accepted

RelationshipStatus

  • File: Domain/AggregatesModel/RelationshipAggregate/RelationshipStatus.cs
  • Values:
    • Pending (Id: 1) - Waiting for acceptance
    • Accepted (Id: 2) - Active relationship
    • Rejected (Id: 3) - Request was rejected
    • Cancelled (Id: 4) - Cancelled by requester or removed

5.3 Domain Services

IGraphQueryService

  • File: Domain/Services/IGraphQueryService.cs
  • Implementation: Infrastructure/Services/GraphQueryService.cs (PostgreSQL raw SQL with CTEs)
  • Methods:
    • GetMutualFriendsAsync(Guid userId1, Guid userId2) - Returns mutual friend IDs using SQL CTE joins
    • GetFriendSuggestionsAsync(Guid userId, int limit) - Friends-of-friends who aren't already friends, excludes blocked users, ordered by mutual count
    • GetConnectionDegreeAsync(Guid userId1, Guid userId2) - Recursive CTE BFS, max depth 6, returns degrees of separation (-1 if not connected)
    • GetMutualFriendsCountAsync(Guid userId1, Guid userId2) - Count of mutual friends
  • Helper DTO: FriendSuggestion(Guid UserId, int MutualFriendsCount)

5.4 Domain Events

Relationship Events (Domain/Events/RelationshipDomainEvents.cs):

  • FriendRequestSentDomainEvent - Raised when a friendship request is created
  • FriendshipCreatedDomainEvent - Raised when a friendship request is accepted
  • UserFollowedDomainEvent - Raised when a follow relationship is created
  • RelationshipStatusChangedDomainEvent - Raised on any status transition (includes PreviousStatus and NewStatus)
  • RelationshipRemovedDomainEvent - Raised when an accepted relationship is removed

Block Events (Domain/Events/UserBlockDomainEvents.cs):

  • UserBlockedDomainEvent - Raised when a user block is created
  • UserUnblockedDomainEvent - Defined but not currently raised in code (the UnblockUserCommandHandler does not add this event)

5.5 Exceptions

  • DomainException - Base exception class
  • SocialDomainException - Extends DomainException, used for all social domain rule violations
  • SampleDomainException - Leftover from template, extends DomainException (unused)

5.6 SeedWork

Base classes shared across the domain:

  • Entity - Base entity with Id (Guid), DomainEvents collection, equality by ID
  • IAggregateRoot - Marker interface
  • IRepository<T> - Generic repository with IUnitOfWork UnitOfWork
  • IUnitOfWork - SaveChangesAsync(), SaveEntitiesAsync() (dispatches domain events)
  • Enumeration - Type-safe enum base class with Id, Name, GetAll<T>(), FromValue<T>(), FromDisplayName<T>()
  • ValueObject - Immutable value comparison base class (not currently used by any entity)

6. Database Schema

Database: social_service (PostgreSQL) Migration: 20260112164356_InitialCreate

6.1 Table: relationships

Column Type Nullable Constraints
id uuid NO PK, ValueGeneratedNever
requester_id uuid NO
addressee_id uuid NO
type_id integer NO FK -> relationship_types.id (RESTRICT)
status_id integer NO FK -> relationship_statuses.id (RESTRICT)
created_at timestamp with time zone NO
updated_at timestamp with time zone YES

Indexes:

  • ix_relationships_requester_addressee_type - UNIQUE on (requester_id, addressee_id, type_id)
  • ix_relationships_requester - on (requester_id)
  • ix_relationships_addressee - on (addressee_id)
  • ix_relationships_type_status - on (type_id, status_id)
  • IX_relationships_status_id - on (status_id) (auto-generated by EF for FK)

6.2 Table: relationship_types (Enumeration)

Column Type Nullable Constraints
id integer NO PK, ValueGeneratedNever
name varchar(50) NO

Seed Data: (1, "Friendship"), (2, "Following")

6.3 Table: relationship_statuses (Enumeration)

Column Type Nullable Constraints
id integer NO PK, ValueGeneratedNever
name varchar(50) NO

Seed Data: (1, "Pending"), (2, "Accepted"), (3, "Rejected"), (4, "Cancelled")

6.4 Table: user_blocks

Column Type Nullable Constraints
id uuid NO PK, ValueGeneratedNever
blocker_id uuid NO
blocked_id uuid NO
reason varchar(500) YES
created_at timestamp with time zone NO

Indexes:

  • ix_user_blocks_blocker_blocked - UNIQUE on (blocker_id, blocked_id)
  • ix_user_blocks_blocker - on (blocker_id)
  • ix_user_blocks_blocked - on (blocked_id)

6.5 Table: user_profiles

Column Type Nullable Constraints
id uuid NO PK, ValueGeneratedNever
user_id uuid NO
display_name varchar(255) NO
avatar_url varchar(500) YES
bio varchar(500) YES
last_synced_at timestamp with time zone NO
created_at timestamp with time zone NO

Indexes:

  • ix_user_profiles_user_id - UNIQUE on (user_id)
  • ix_user_profiles_display_name - on (display_name)

7. Integration Events

7.1 Domain Events (Internal)

The following domain events are raised within the service and dispatched via MediatR INotification through the SocialServiceContext.DispatchDomainEventsAsync() method before SaveChanges. Currently, there are no domain event handlers registered in the service -- the events are raised but not consumed.

  • FriendRequestSentDomainEvent
  • FriendshipCreatedDomainEvent
  • UserFollowedDomainEvent
  • RelationshipStatusChangedDomainEvent
  • RelationshipRemovedDomainEvent
  • UserBlockedDomainEvent

7.2 Cross-Service Integration

  • Inbound (Expected): UserProfile.UpdateFromEvent() method exists for receiving IAM profile update events. However, there is no integration event handler or RabbitMQ consumer implemented -- the UserProfile entity and repository exist but have no controllers or commands for creating/updating profiles. Profiles must currently be populated directly in the database.
  • Outbound: No integration events are published to RabbitMQ or any message broker. Domain events remain internal only.

8. Dependencies

8.1 NuGet Packages

SocialService.API:

  • MediatR 12.4.1
  • FluentValidation 11.11.0
  • FluentValidation.DependencyInjectionExtensions 11.11.0
  • Microsoft.EntityFrameworkCore.Design 10.0.1
  • Swashbuckle.AspNetCore 7.2.0
  • Asp.Versioning.Mvc 8.1.0
  • Asp.Versioning.Mvc.ApiExplorer 8.1.0
  • AspNetCore.HealthChecks.NpgSql 8.0.2
  • AspNetCore.HealthChecks.Redis 8.0.1
  • Hellang.Middleware.ProblemDetails 6.5.1
  • Serilog.AspNetCore 8.0.3
  • Serilog.Sinks.Console 6.0.0
  • Serilog.Sinks.Seq 8.0.0

SocialService.Domain:

  • MediatR.Contracts 2.0.1

SocialService.Infrastructure:

  • Microsoft.EntityFrameworkCore 10.0.0
  • Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0
  • Microsoft.EntityFrameworkCore.Tools 10.0.0
  • MediatR 12.4.1
  • Dapper 2.1.35
  • Microsoft.Extensions.Http.Polly 9.0.0
  • Polly 8.5.0
  • StackExchange.Redis 2.8.16

Shared (Directory.Build.props):

  • Microsoft.SourceLink.GitHub 8.0.0
  • Target: net10.0, LangVersion 14.0, Nullable enabled, TreatWarningsAsErrors

Unit Tests:

  • Microsoft.NET.Test.Sdk 17.12.0
  • xunit 2.9.2
  • FluentAssertions 6.12.2
  • NSubstitute 5.3.0
  • coverlet.collector 6.0.2

Functional Tests:

  • Microsoft.AspNetCore.Mvc.Testing 10.0.0
  • Microsoft.EntityFrameworkCore.InMemory 10.0.0
  • FluentAssertions 6.12.2
  • Testcontainers.PostgreSql 4.1.0

8.2 External Service Dependencies

  • PostgreSQL (Neon cloud or local) - primary data store
  • Redis (referenced in health checks and .env.example, but not actively used in code -- no Redis caching is implemented)
  • IAM Service - UserProfile entity is designed to sync from IAM, but no integration is implemented

9. Configuration

9.1 appsettings.json

{
    "ConnectionStrings": {
        "DefaultConnection": "Host=...neon.tech;Database=social_service;..."
    },
    "Redis": {
        "ConnectionString": "localhost:6379"
    },
    "Jwt": {
        "Secret": "your-super-secret-key-min-32-characters",
        "Issuer": "goodgo-platform",
        "Audience": "goodgo-services",
        "AccessTokenExpiryMinutes": 15,
        "RefreshTokenExpiryDays": 7
    }
}

Note: JWT configuration is present in settings but JWT authentication middleware is NOT configured in Program.cs. There is no AddAuthentication() or UseAuthentication() call. All endpoints are currently unauthenticated.

9.2 Environment Variables (.env.example)

Variable Description Default
ASPNETCORE_ENVIRONMENT Environment name Development
DATABASE_URL PostgreSQL connection string localhost:5432
REDIS_URL Redis connection string localhost:6379
REDIS_PASSWORD Redis password (empty)
JWT_SECRET JWT signing key (placeholder)
JWT_ISSUER JWT issuer goodgo-platform
JWT_AUDIENCE JWT audience goodgo-services
JWT_ACCESS_TOKEN_EXPIRY_MINUTES Access token TTL 15
JWT_REFRESH_TOKEN_EXPIRY_DAYS Refresh token TTL 7
API_PORT API port 5000
OTEL_EXPORTER_OTLP_ENDPOINT OpenTelemetry endpoint localhost:4317
OTEL_SERVICE_NAME OpenTelemetry service name socialservice
SEQ_URL Seq logging endpoint localhost:5341
RATE_LIMIT_PERMITS_PER_MINUTE Rate limit 100
RATE_LIMIT_QUEUE_LIMIT Rate limit queue 10
HEALTHCHECK_TIMEOUT_SECONDS Health check timeout 5

9.3 MediatR Pipeline (Behavior Order)

  1. LoggingBehavior - Logs request name, measures execution time with Stopwatch
  2. ValidatorBehavior - Runs FluentValidation validators (none currently registered)
  3. TransactionBehavior - Wraps commands in a database transaction (skips requests ending in "Query"), uses CreateExecutionStrategy() for retry support

9.4 DI Registration (Infrastructure)

Registered in DependencyInjection.AddInfrastructure():

  • SocialServiceContext (DbContext with Npgsql, retry on failure: 5 retries, 30s delay)
  • IRelationshipRepository -> RelationshipRepository (Scoped)
  • IUserProfileRepository -> UserProfileRepository (Scoped)
  • IUserBlockRepository -> UserBlockRepository (Scoped)
  • IGraphQueryService -> GraphQueryService (Scoped)
  • IRequestManager -> RequestManager (Scoped)

9.5 Docker Configuration

  • Multi-stage build: sdk:10.0 (build) -> aspnet:10.0 (runtime)
  • Non-root user: dotnetuser (UID 1001, GID 1001)
  • Port: 8080 (ASPNETCORE_URLS=http://+:8080)
  • Health check: curl -f http://localhost:8080/health/live (30s interval, 3 retries)

9.6 SDK Version

  • .NET SDK: 10.0.101 (rollForward: latestMinor)

10. Test Coverage

10.1 Unit Tests (SocialService.UnitTests)

Handler Tests:

  • SendFriendRequestCommandHandlerTests (4 tests):

    • Valid request creates new relationship
    • Block exists throws exception
    • Already friends throws exception
    • Request already pending throws exception
    • Mutual request auto-accepts
  • BlockUserCommandHandlerTests (5 tests):

    • Valid request creates block
    • Self-block throws exception
    • Already blocked throws exception
    • With existing friendship removes friendship
    • No reason creates block without reason

Domain Tests:

  • RelationshipAggregateTests (14 tests): Constructor, Accept, Reject, Cancel, Remove with valid and invalid state transitions
  • UserBlockAggregateTests (6 tests): Constructor with valid/invalid inputs, domain event verification

10.2 Functional Tests (SocialService.FunctionalTests)

Uses CustomWebApplicationFactory with InMemoryDatabase.

  • RelationshipsControllerTests (4 tests): GET friends, mutual friends, suggestions, health check
  • BlocksControllerTests (2 tests): GET blocked users, with pagination
  • AdminControllerTests (6 tests): GET all relationships (with/without filter), GET by ID (404), DELETE relationship (404), GET all blocks, DELETE block (404), GET statistics

11. Known Gaps and Notes

  1. No Authentication: JWT config exists in settings but authentication middleware is not wired up. All endpoints are public.
  2. No FluentValidation Validators: The ValidatorBehavior is in the pipeline but no AbstractValidator<T> classes exist for any command.
  3. No UserProfile CRUD API: The UserProfile entity and repository exist, but there are no controllers or commands to create/update profiles. The GetFriendsQuery and other queries reference UserProfile for display names, but profiles must be populated externally.
  4. No IAM Integration Event Consumer: UserProfile.UpdateFromEvent() method exists but no RabbitMQ consumer or integration event handler is implemented.
  5. No Redis Usage: Redis package is referenced and health check is configured, but no caching logic exists.
  6. UserUnblockedDomainEvent: Defined but never raised -- the UnblockUserCommandHandler does not add this domain event before removing the block.
  7. SampleDomainException: Leftover from template, unused.
  8. Idempotency: IRequestManager/RequestManager and ClientRequest are registered but not used by any command handler.
  9. CORS: Configured to allow any origin/method/header (development-only concern).
  10. Swagger: Only enabled in Development environment.