package model import ( "sync" "testing" "github.com/glebarez/sqlite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "gorm.io/gorm/logger" ) func setupUserAssetChannelDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file:user_asset_channels?mode=memory&cache=shared"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) sqlDB, err := db.DB() require.NoError(t, err) sqlDB.SetMaxOpenConns(1) origDB := DB origGroupCol := commonGroupCol DB = db commonGroupCol = "`group`" require.NoError(t, db.AutoMigrate(&UserAssetChannel{})) t.Cleanup(func() { DB = origDB commonGroupCol = origGroupCol require.NoError(t, sqlDB.Close()) }) return db } func TestGetUserAssetChannel_NotFound(t *testing.T) { setupUserAssetChannelDB(t) binding, err := GetUserAssetChannel(1, 2, "default") require.NoError(t, err) assert.Nil(t, binding) } func TestBindUserAssetChannel_CreateAndUpdate(t *testing.T) { db := setupUserAssetChannelDB(t) require.NoError(t, BindUserAssetChannel(1, 2, "default", 100)) binding, err := GetUserAssetChannel(1, 2, "default") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 100, binding.ChannelId) assert.NotZero(t, binding.CreatedAt) assert.NotZero(t, binding.UpdatedAt) require.NoError(t, BindUserAssetChannel(1, 2, "default", 200)) binding, err = GetUserAssetChannel(1, 2, "default") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 200, binding.ChannelId) var count int64 require.NoError(t, db.Model(&UserAssetChannel{}).Count(&count).Error) assert.Equal(t, int64(1), count) } func TestGetUserAssetChannelsByTypesSortsLatestFirst(t *testing.T) { db := setupUserAssetChannelDB(t) require.NoError(t, BindUserAssetChannel(1, 2, "default", 100)) require.NoError(t, BindUserAssetChannel(1, 3, "default", 200)) require.NoError(t, BindUserAssetChannel(1, 4, "default", 300)) require.NoError(t, db.Model(&UserAssetChannel{}). Where("user_id = ? AND channel_type = ?", 1, 2). Update("updated_at", int64(100)).Error) require.NoError(t, db.Model(&UserAssetChannel{}). Where("user_id = ? AND channel_type = ?", 1, 3). Update("updated_at", int64(200)).Error) bindings, err := GetUserAssetChannelsByTypes(1, []int{2, 3}, "default") require.NoError(t, err) require.Len(t, bindings, 2) assert.Equal(t, 3, bindings[0].ChannelType) assert.Equal(t, 200, bindings[0].ChannelId) assert.Equal(t, 2, bindings[1].ChannelType) } func TestBindUserAssetChannelWithTxUpserts(t *testing.T) { db := setupUserAssetChannelDB(t) require.NoError(t, BindUserAssetChannel(1, 2, "default", 100)) require.NoError(t, BindUserAssetChannel(1, 2, "default", 200)) binding, err := GetUserAssetChannel(1, 2, "default") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 200, binding.ChannelId) var count int64 require.NoError(t, db.Model(&UserAssetChannel{}).Count(&count).Error) assert.Equal(t, int64(1), count) } func TestDeleteUserAssetChannelsByTypesWithTxDeletesOnlyRequestedRows(t *testing.T) { db := setupUserAssetChannelDB(t) require.NoError(t, BindUserAssetChannel(1, 2, "default", 100)) require.NoError(t, BindUserAssetChannel(1, 3, "default", 200)) require.NoError(t, BindUserAssetChannel(1, 4, "default", 300)) require.NoError(t, BindUserAssetChannel(1, 2, "vip", 400)) require.NoError(t, BindUserAssetChannel(2, 2, "default", 500)) require.NoError(t, db.Transaction(func(tx *gorm.DB) error { return DeleteUserAssetChannelsByTypesWithTx(tx, 1, []int{2, 3}, "default") })) binding, err := GetUserAssetChannel(1, 2, "default") require.NoError(t, err) assert.Nil(t, binding) binding, err = GetUserAssetChannel(1, 3, "default") require.NoError(t, err) assert.Nil(t, binding) binding, err = GetUserAssetChannel(1, 4, "default") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 300, binding.ChannelId) binding, err = GetUserAssetChannel(1, 2, "vip") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 400, binding.ChannelId) binding, err = GetUserAssetChannel(2, 2, "default") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 500, binding.ChannelId) } func TestBindUserAssetChannel_IsolatesUserTypeGroup(t *testing.T) { setupUserAssetChannelDB(t) require.NoError(t, BindUserAssetChannel(1, 2, "default", 100)) require.NoError(t, BindUserAssetChannel(2, 2, "default", 200)) require.NoError(t, BindUserAssetChannel(1, 3, "default", 300)) require.NoError(t, BindUserAssetChannel(1, 2, "vip", 400)) cases := []struct { userId int channelType int group string channelId int }{ {1, 2, "default", 100}, {2, 2, "default", 200}, {1, 3, "default", 300}, {1, 2, "vip", 400}, } for _, tc := range cases { binding, err := GetUserAssetChannel(tc.userId, tc.channelType, tc.group) require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, tc.channelId, binding.ChannelId) } } func TestBindUserAssetChannel_ConcurrentUpsert(t *testing.T) { db := setupUserAssetChannelDB(t) const workers = 20 var wg sync.WaitGroup errCh := make(chan error, workers) expected := make(map[int]bool, workers) for i := 0; i < workers; i++ { channelId := 1000 + i expected[channelId] = true wg.Add(1) go func() { defer wg.Done() errCh <- BindUserAssetChannel(1, 2, "default", channelId) }() } wg.Wait() close(errCh) for err := range errCh { require.NoError(t, err) } var count int64 require.NoError(t, db.Model(&UserAssetChannel{}).Count(&count).Error) assert.Equal(t, int64(1), count) binding, err := GetUserAssetChannel(1, 2, "default") require.NoError(t, err) require.NotNil(t, binding) assert.True(t, expected[binding.ChannelId]) } func TestUnbindUserAssetChannel(t *testing.T) { setupUserAssetChannelDB(t) require.NoError(t, BindUserAssetChannel(1, 2, "default", 100)) require.NoError(t, BindUserAssetChannel(1, 2, "vip", 200)) require.NoError(t, UnbindUserAssetChannel(1, 2, "default")) binding, err := GetUserAssetChannel(1, 2, "default") require.NoError(t, err) assert.Nil(t, binding) binding, err = GetUserAssetChannel(1, 2, "vip") require.NoError(t, err) require.NotNil(t, binding) assert.Equal(t, 200, binding.ChannelId) }