package lingxian import ( "chatapp3-golang/internal/config" "chatapp3-golang/internal/model" "chatapp3-golang/internal/service/gameprovider" "context" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestSyncAdminGamesFetchesCatalogAndImportsUnifiedList(t *testing.T) { ctx := context.Background() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.LingxianProviderConfig{}, &model.LingxianGameCatalog{}, &model.SysGameListConfig{}, &model.SysGameListVendorExt{}, ); err != nil { t.Fatalf("migrate: %v", err) } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`[{"gameId":"1","name":"Fruit Party","title":"水果奇缘","ver":11,"full_url":"https://example.test/full.html","half_url":"https://example.test/half.html"}]`)) })) defer server.Close() now := time.Now() if err := db.Create(&model.LingxianProviderConfig{ ID: 1, SysOrigin: "LIKEI", Profile: profileProd, Active: true, GameListURL: server.URL, AppKey: "test-key", CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed provider: %v", err) } service := NewService(config.Config{HTTP: config.HTTPConfig{Timeout: time.Second}}, db, nil) resp, err := service.SyncAdminGames(ctx, SyncCatalogRequest{SysOrigin: "LIKEI", Profile: profileProd}) if err != nil { t.Fatalf("SyncAdminGames: %v", err) } if resp.Imported != 1 { t.Fatalf("imported = %d, want 1", resp.Imported) } var cfg model.SysGameListConfig if err := db.Where("sys_origin = ? AND game_origin = ? AND game_id = ?", "LIKEI", vendorType, "1").First(&cfg).Error; err != nil { t.Fatalf("unified config missing: %v", err) } var ext model.SysGameListVendorExt if err := db.Where("game_list_config_id = ? AND vendor_type = ? AND profile = ?", cfg.ID, vendorType, profileProd).First(&ext).Error; err != nil { t.Fatalf("vendor ext missing: %v", err) } if ext.VendorGameID != "1" || ext.PackageURL != "https://example.test/full.html" { t.Fatalf("vendor ext = %#v", ext) } if !cfg.Showcase { t.Fatalf("showcase = false, want true by default") } } func TestImportCatalogGamesCanDefaultToHidden(t *testing.T) { ctx := context.Background() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.LingxianProviderConfig{}, &model.LingxianGameCatalog{}, &model.SysGameListConfig{}, &model.SysGameListVendorExt{}, ); err != nil { t.Fatalf("migrate: %v", err) } now := time.Now() if err := db.Create(&model.LingxianProviderConfig{ ID: 11, SysOrigin: "LIKEI", Profile: profileProd, Active: true, AppKey: "test-key", TestUID: "test-uid", TestToken: "test-token", TestRoomID: "test-room", CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed provider: %v", err) } if err := db.Create(&model.LingxianGameCatalog{ ID: 12, SysOrigin: "LIKEI", Profile: profileProd, InternalGameID: "lx_2", VendorGameID: "2", Name: "Hidden Lingxian", Status: catalogEnabled, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed catalog: %v", err) } service := NewService(config.Config{HTTP: config.HTTPConfig{Timeout: time.Second}}, db, nil) resp, err := service.ImportCatalogGames(ctx, "LIKEI", profileProd, []string{"2"}, false) if err != nil { t.Fatalf("ImportCatalogGames: %v", err) } if resp.Imported != 1 { t.Fatalf("imported = %d, want 1", resp.Imported) } var cfg model.SysGameListConfig if err := db.Where("sys_origin = ? AND game_origin = ? AND game_id = ?", "LIKEI", vendorType, "lx_2").First(&cfg).Error; err != nil { t.Fatalf("unified config missing: %v", err) } if cfg.Showcase { t.Fatalf("showcase = true, want false") } } func TestImportCatalogGamesMarksHalfScreenFromCatalog(t *testing.T) { ctx := context.Background() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.LingxianProviderConfig{}, &model.LingxianGameCatalog{}, &model.SysGameListConfig{}, &model.SysGameListVendorExt{}, ); err != nil { t.Fatalf("migrate: %v", err) } now := time.Now() if err := db.Create(&model.LingxianProviderConfig{ ID: 31, SysOrigin: "LIKEI", Profile: profileProd, Active: true, AppKey: "test-key", CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed provider: %v", err) } if err := db.Create(&model.LingxianGameCatalog{ ID: 32, SysOrigin: "LIKEI", Profile: profileProd, InternalGameID: "1", VendorGameID: "1", Name: "Half Lingxian", PreviewURL: "https://example.test/fruit_party_half/index.html", DownloadURL: "https://example.test/fruit_party_half/index.html", RawJSON: `{"gameId":"1","half_url":"https://example.test/fruit_party_half/index.html","full_url":""}`, Status: catalogEnabled, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed catalog: %v", err) } service := NewService(config.Config{HTTP: config.HTTPConfig{Timeout: time.Second}}, db, nil) resp, err := service.ImportCatalogGames(ctx, "LIKEI", profileProd, []string{"1"}) if err != nil { t.Fatalf("ImportCatalogGames: %v", err) } if resp.Imported != 1 { t.Fatalf("imported = %d, want 1", resp.Imported) } var cfg model.SysGameListConfig if err := db.Where("sys_origin = ? AND game_origin = ? AND game_id = ?", "LIKEI", vendorType, "1").First(&cfg).Error; err != nil { t.Fatalf("unified config missing: %v", err) } if cfg.FullScreen { t.Fatalf("fullScreen = true, want false") } } func TestImportCatalogGamesRepairsExistingHalfScreenConfig(t *testing.T) { ctx := context.Background() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.LingxianProviderConfig{}, &model.LingxianGameCatalog{}, &model.SysGameListConfig{}, &model.SysGameListVendorExt{}, ); err != nil { t.Fatalf("migrate: %v", err) } now := time.Now() if err := db.Create(&model.LingxianProviderConfig{ ID: 41, SysOrigin: "LIKEI", Profile: profileProd, Active: true, AppKey: "test-key", CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed provider: %v", err) } if err := db.Create(&model.LingxianGameCatalog{ ID: 42, SysOrigin: "LIKEI", Profile: profileProd, InternalGameID: "1", VendorGameID: "1", Name: "Half Lingxian", PreviewURL: "https://example.test/fruit_party_half/index.html", DownloadURL: "https://example.test/fruit_party_half/index.html", RawJSON: `{"gameId":"1","half_url":"https://example.test/fruit_party_half/index.html","full_url":""}`, Status: catalogEnabled, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed catalog: %v", err) } if err := db.Create(&model.SysGameListConfig{ ID: 43, SysOrigin: "LIKEI", GameOrigin: vendorType, GameID: "1", Name: "Half Lingxian", Category: roomCategory, GameCode: "1", Showcase: true, Sort: 1, FullScreen: true, ClientOrigin: "COMMON", GameMode: "[3]", }).Error; err != nil { t.Fatalf("seed config: %v", err) } if err := db.Create(&model.SysGameListVendorExt{ ID: 44, GameListConfigID: 43, SysOrigin: "LIKEI", Profile: profileProd, VendorType: vendorType, VendorGameID: "1", LaunchMode: launchModeH5, PackageURL: "https://example.test/fruit_party_half/index.html", PreviewURL: "https://example.test/fruit_party_half/index.html", BridgeSchemaVersion: "1.0", ExtraJSON: `{"gameType":"LINGXIAN"}`, Enabled: true, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed vendor ext: %v", err) } service := NewService(config.Config{HTTP: config.HTTPConfig{Timeout: time.Second}}, db, nil) resp, err := service.ImportCatalogGames(ctx, "LIKEI", profileProd, []string{"1"}) if err != nil { t.Fatalf("ImportCatalogGames: %v", err) } if resp.Existing != 1 { t.Fatalf("existing = %d, want 1", resp.Existing) } var cfg model.SysGameListConfig if err := db.First(&cfg, 43).Error; err != nil { t.Fatalf("config missing: %v", err) } if cfg.FullScreen { t.Fatalf("fullScreen = true, want false after repair") } } func TestSaveProviderConfigPersistsTestRoomID(t *testing.T) { ctx := context.Background() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&model.LingxianProviderConfig{}); err != nil { t.Fatalf("migrate: %v", err) } service := NewService(config.Config{HTTP: config.HTTPConfig{Timeout: time.Second}}, db, nil) saved, err := service.SaveProviderConfig(ctx, SaveProviderConfigRequest{ SysOrigin: "LIKEI", Profile: profileProd, GameListURL: "https://example.test/list.json", AppKey: "test-key", TestUID: "test-uid", TestToken: "test-token", TestRoomID: "test-room", }) if err != nil { t.Fatalf("SaveProviderConfig: %v", err) } if saved.TestRoomID != "test-room" { t.Fatalf("saved testRoomId = %q, want test-room", saved.TestRoomID) } runtimeCfg, err := service.resolveRuntimeConfigForProfile(ctx, "LIKEI", profileProd) if err != nil { t.Fatalf("resolveRuntimeConfigForProfile: %v", err) } if runtimeCfg.TestRoomID != "test-room" { t.Fatalf("runtime testRoomId = %q, want test-room", runtimeCfg.TestRoomID) } } func TestAppProviderUsesHalfURLForHalfScreenGames(t *testing.T) { ctx := context.Background() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.LingxianProviderConfig{}, &model.LingxianGameCatalog{}, &model.SysGameListConfig{}, &model.SysGameListVendorExt{}, ); err != nil { t.Fatalf("migrate: %v", err) } now := time.Now() if err := db.Create(&model.LingxianProviderConfig{ ID: 21, SysOrigin: "LIKEI", Profile: profileProd, Active: true, AppKey: "test-key", TestUID: "test-uid", TestToken: "test-token", TestRoomID: "test-room", CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed provider: %v", err) } if err := db.Create(&model.LingxianGameCatalog{ ID: 22, SysOrigin: "LIKEI", Profile: profileProd, InternalGameID: "1", VendorGameID: "1", Name: "Half Lingxian", PreviewURL: "https://example.test/half.html", DownloadURL: "https://example.test/full.html", PackageVersion: "11", Status: catalogEnabled, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed catalog: %v", err) } if err := db.Create(&model.SysGameListConfig{ ID: 23, SysOrigin: "LIKEI", GameOrigin: vendorType, GameID: "1", Name: "Half Lingxian", Category: roomCategory, GameCode: "1", Showcase: true, Sort: 1, FullScreen: false, ClientOrigin: "COMMON", GameMode: "[3]", }).Error; err != nil { t.Fatalf("seed config: %v", err) } orientation := 1 if err := db.Create(&model.SysGameListVendorExt{ ID: 24, GameListConfigID: 23, SysOrigin: "LIKEI", Profile: profileProd, VendorType: vendorType, VendorGameID: "1", LaunchMode: launchModeH5, PackageVersion: "11", PackageURL: "https://example.test/full.html", PreviewURL: "https://example.test/half.html", Orientation: &orientation, SafeHeight: 640, BridgeSchemaVersion: "1.0", ExtraJSON: `{"gameType":"LINGXIAN"}`, Enabled: true, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed vendor ext: %v", err) } service := NewService(config.Config{HTTP: config.HTTPConfig{Timeout: time.Second}}, db, nil) provider := NewAppProvider(service) user := gameprovider.AuthUser{UserID: 1001, SysOrigin: "LIKEI", Token: "user-token"} list, err := provider.ListRoomGames(ctx, user, "room-1", roomCategory) if err != nil { t.Fatalf("ListRoomGames: %v", err) } if len(list.Items) != 1 { t.Fatalf("items = %d, want 1", len(list.Items)) } item := list.Items[0] if item.FullScreen { t.Fatalf("fullScreen = true, want false") } if item.SafeHeight != 640 { t.Fatalf("safeHeight = %d, want 640", item.SafeHeight) } if item.Orientation != 1 { t.Fatalf("orientation = %d, want 1", item.Orientation) } if got := item.LaunchParams["screenMode"]; got != "half" { t.Fatalf("screenMode = %v, want half", got) } launch, err := provider.LaunchGame(ctx, user, gameprovider.LaunchRequest{RoomID: "room-1", GameID: "1"}, "") if err != nil { t.Fatalf("LaunchGame: %v", err) } if !strings.HasPrefix(launch.Entry.EntryURL, "https://example.test/half.html?") { t.Fatalf("entryUrl = %q, want half url with query", launch.Entry.EntryURL) } if strings.Contains(launch.Entry.EntryURL, "full.html") { t.Fatalf("entryUrl = %q, should not use full url", launch.Entry.EntryURL) } if launch.Entry.SafeHeight != 640 { t.Fatalf("launch safeHeight = %d, want 640", launch.Entry.SafeHeight) } parsed, err := url.Parse(launch.Entry.EntryURL) if err != nil { t.Fatalf("parse entry url: %v", err) } query := parsed.Query() if got := query.Get("uid"); got != "test-uid" { t.Fatalf("query uid = %q, want test-uid", got) } if got := query.Get("token"); got != "test-token" { t.Fatalf("query token = %q, want test-token", got) } if got := query.Get("lang"); got != defaultLaunchLang { t.Fatalf("query lang = %q, want %s", got, defaultLaunchLang) } if got := query.Get("roomid"); got != "test-room" { t.Fatalf("query roomid = %q, want test-room", got) } if got := query.Get("roomId"); got != "test-room" { t.Fatalf("query roomId = %q, want test-room", got) } if got := launch.LaunchConfig.(map[string]any)["roomid"]; got != "test-room" { t.Fatalf("launchConfig.roomid = %v, want test-room", got) } if got := launch.LaunchConfig.(map[string]any)["lang"]; got != defaultLaunchLang { t.Fatalf("launchConfig.lang = %v, want %s", got, defaultLaunchLang) } if got := launch.RoomState.RoomID; got != "room-1" { t.Fatalf("roomState.roomId = %q, want real room room-1", got) } }