package service import ( "context" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" notifPb "github.com/topfans/backend/pkg/proto/notification" "gorm.io/driver/postgres" "gorm.io/gorm" ) // setupUserDeviceDB 测试 DB 入口;无 DB 时返回 false 由 caller skip。 func setupUserDeviceDB(t *testing.T) (*gorm.DB, bool) { t.Helper() dsn := os.Getenv("TEST_DB_DSN") if dsn == "" { dsn = "postgres://postgres:postgres@localhost:5432/top_fans_test?sslmode=disable" } db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { return nil, false } sqlDB, err := db.DB() if err != nil { return nil, false } if err := sqlDB.Ping(); err != nil { return nil, false } return db, true } // TestUserDeviceService_RegisterDevice_Validation 覆盖校验路径(无需 DB)。 // 仅覆盖 service 层校验分支;真 DB upsert 见 TestUserDeviceService_UpsertDeactivate。 func TestUserDeviceService_RegisterDevice_Validation(t *testing.T) { svc := NewUserDeviceService(nil) tests := []struct { name string userID int64 req *notifPb.RegisterDeviceRequest }{ {"missing user", 0, ¬ifPb.RegisterDeviceRequest{Cid: "c"}}, {"missing cid", 1, ¬ifPb.RegisterDeviceRequest{}}, {"nil req", 1, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := svc.RegisterDevice(context.Background(), tt.userID, tt.req) assert.Error(t, err) }) } } // TestUserDeviceService_UnregisterDevice_Validation 覆盖校验路径。 func TestUserDeviceService_UnregisterDevice_Validation(t *testing.T) { svc := NewUserDeviceService(nil) _, err := svc.UnregisterDevice(context.Background(), 0, ¬ifPb.UnregisterDeviceRequest{}) require.Error(t, err) _, err = svc.UnregisterDevice(context.Background(), 1, nil) require.Error(t, err) } // TestUserDeviceService_UpsertDeactivate 需要真实 DB;缺 DB 时 skip。 func TestUserDeviceService_UpsertDeactivate(t *testing.T) { db, ok := setupUserDeviceDB(t) if !ok { t.Skip("skipping: test DB not available") } // 确保表存在(与 main.go AutoMigrate 同源) require.NoError(t, db.AutoMigrate(struct{}{})) // noop svc := NewUserDeviceService(db) ctx := context.Background() userID := int64(880100) cid := "test-cid-001" // cleanup t.Cleanup(func() { _ = db.WithContext(ctx).Exec(`DELETE FROM public.user_devices WHERE cid = $1`, cid).Error }) // 1) 首次注册 resp1, err := svc.RegisterDevice(ctx, userID, ¬ifPb.RegisterDeviceRequest{ Cid: cid, Platform: "ios", AppVersion: "1.0.0", DeviceModel: "iPhone14,2", }) require.NoError(t, err) require.NotNil(t, resp1) assert.Greater(t, resp1.Id, int64(0)) // 2) 重复注册同 cid:应更新(归属/版本变化) resp2, err := svc.RegisterDevice(ctx, userID, ¬ifPb.RegisterDeviceRequest{ Cid: cid, Platform: "android", AppVersion: "1.0.1", DeviceModel: "Pixel7", }) require.NoError(t, err) assert.Equal(t, resp1.Id, resp2.Id, "同 cid upsert 后 id 应不变") // 3) 拉取活跃 cid 应包含该 cid cids, err := svc.ListActiveCIDs(ctx, userID) require.NoError(t, err) assert.Contains(t, cids, cid) // 4) 注销 unreg, err := svc.UnregisterDevice(ctx, userID, ¬ifPb.UnregisterDeviceRequest{Cid: cid}) require.NoError(t, err) assert.GreaterOrEqual(t, unreg.Affected, int32(1)) // 5) 注销后不再出现在活跃列表 cids2, err := svc.ListActiveCIDs(ctx, userID) require.NoError(t, err) assert.NotContains(t, cids2, cid) }