package service import ( "context" "errors" "os" "testing" assetPb "github.com/topfans/backend/pkg/proto/asset" "github.com/topfans/backend/pkg/logger" "github.com/topfans/backend/services/socialService/client" "github.com/stretchr/testify/assert" ) // TestMain 在测试启动时初始化 logger(service 代码内部使用 logger.Logger) func TestMain(m *testing.M) { _ = logger.Init(logger.Config{ServiceName: "social-service-test", Environment: "test"}) os.Exit(m.Run()) } // TestFireLikeNotification_OwnerUidZero_Skips // 验证 owner_uid <= 0 时直接跳过,不调用 mock client func TestFireLikeNotification_OwnerUidZero_Skips(t *testing.T) { mock := &client.MockNotificationClient{} svc := &AssetLikeService{notificationClient: mock} ctx := context.Background() asset := &assetPb.GetAssetForRPCResponse{ AssetId: 1, OwnerUid: 0, Name: "some-asset", CoverUrl: "https://example.com/cover.png", } svc.fireLikeNotification(ctx, asset, 1, 2, 1) assert.Equal(t, int32(0), mock.CreateCallCount, "notification client should NOT be called when owner_uid is invalid") } // TestFireLikeNotification_NilAsset_Skips // 防御 nil asset 入参 func TestFireLikeNotification_NilAsset_Skips(t *testing.T) { mock := &client.MockNotificationClient{} svc := &AssetLikeService{notificationClient: mock} ctx := context.Background() svc.fireLikeNotification(ctx, nil, 1, 2, 1) assert.Equal(t, int32(0), mock.CreateCallCount, "notification client should NOT be called when asset is nil") } // TestFireLikeNotification_NilClient_Skips // 防御 nil notificationClient 配置 func TestFireLikeNotification_NilClient_Skips(t *testing.T) { svc := &AssetLikeService{notificationClient: nil} ctx := context.Background() asset := &assetPb.GetAssetForRPCResponse{ AssetId: 1, OwnerUid: 100, Name: "x", CoverUrl: "y", } // 应直接 return,不 panic svc.fireLikeNotification(ctx, asset, 1, 2, 1) } // TestFireLikeNotification_ClientErr_JustLogs // 验证 notification client 报错时不 panic,不影响主路径 // 这是核心需求:"通知调用失败不能影响点赞主路径" func TestFireLikeNotification_ClientErr_JustLogs(t *testing.T) { mock := &client.MockNotificationClient{CreateErr: errors.New("rpc down")} svc := &AssetLikeService{notificationClient: mock} ctx := context.Background() asset := &assetPb.GetAssetForRPCResponse{ AssetId: 1, OwnerUid: 100, Name: "藏品A", CoverUrl: "https://cdn.example.com/a.png", } // 不应 panic svc.fireLikeNotification(ctx, asset, 1, 2, 1) assert.Equal(t, int32(1), mock.CreateCallCount, "notification client should be called exactly once") assert.NotNil(t, mock.LastRequest, "LastRequest should be recorded") } // TestFireLikeNotification_Success_BuildsCorrectRequest // 验证 happy path:构造的 request 字段全部正确 func TestFireLikeNotification_Success_BuildsCorrectRequest(t *testing.T) { mock := &client.MockNotificationClient{} svc := &AssetLikeService{notificationClient: mock} ctx := context.Background() asset := &assetPb.GetAssetForRPCResponse{ AssetId: 42, OwnerUid: 100, Name: "藏品X", CoverUrl: "https://cdn.example.com/x.png", } svc.fireLikeNotification(ctx, asset, 42, 7, 3) assert.Equal(t, int32(1), mock.CreateCallCount) req := mock.LastRequest if assert.NotNil(t, req) { assert.Equal(t, int64(100), req.UserId, "通知收件人应为资产 owner") assert.Equal(t, int64(3), req.StarId) assert.Equal(t, "like", req.Type) assert.Equal(t, "新点赞", req.Title) assert.Contains(t, req.Content, "7", "Content 应包含 actor userID") assert.NotNil(t, req.Data, "Data 结构体不能为空") } }