package payment import ( "context" "database/sql" "database/sql/driver" "strings" "testing" "time" "hyapp-admin-server/internal/config" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/repository" "github.com/DATA-DOG/go-sqlmock" "go.mongodb.org/mongo-driver/v2/bson" ) func TestLegacyRegionDocumentsToMoneyDataFiltersYumiRegions(t *testing.T) { objectID, err := bson.ObjectIDFromHex("69d88849bdbcc07ebc3f118c") if err != nil { t.Fatalf("build object id failed: %v", err) } cfg := config.MoneyRegionSourceConfig{ AppCode: "yumi", AppName: "Yumi", SysOrigin: "LIKEI", } apps, regions, countries, err := legacyRegionDocumentsToMoneyData(cfg, []legacyRegionDocument{ {ID: objectID, RegionCode: "OTHER", SysOrigin: "LIKEI", RegionName: "Default", CountryCodes: "", Del: false}, {ID: "1001", RegionCode: "MIDDLE_EAST", SysOrigin: "LIKEI", RegionName: "中东区", CountryCodes: "SA, AE", Del: false}, {ID: "1002", RegionCode: "DELETED", SysOrigin: "LIKEI", RegionName: "已删区域", CountryCodes: "IN", Del: true}, {ID: "1003", RegionCode: "OTHER", SysOrigin: "OTHER", RegionName: "其他系统", CountryCodes: "US", Del: false}, }, repository.MoneyAccess{All: true}) if err != nil { t.Fatalf("legacy money data failed: %v", err) } if len(apps) != 1 || apps[0].AppCode != "yumi" || apps[0].AppName != "Yumi" || apps[0].Platform != "legacy" { t.Fatalf("apps mismatch: %+v", apps) } if len(regions) != 2 || regions[0].RegionID < legacySyntheticRegionIDBase || regions[0].RegionCode != "OTHER" || regions[1].RegionID != 1001 || regions[1].RegionCode != "MIDDLE_EAST" { t.Fatalf("regions mismatch: %+v", regions) } if len(countries) != 2 || countries[0].CountryCode != "SA" || countries[1].CountryCode != "AE" || countries[0].RegionID != 1001 { t.Fatalf("countries mismatch: %+v", countries) } } func TestLoadMoneyMasterDataIncludesScopedMoneyRegionSource(t *testing.T) { userDB, sqlMock, cleanup := newMoneyRegionSourceUserSQLMock(t) defer cleanup() expectEmptyMoneyApps(sqlMock, "yumi") handler := New(&mockPaymentWallet{}, userDB, nil, nil, nil, WithMoneyRegionSources(&staticMoneyRegionSource{ apps: []moneyAppDTO{{AppCode: "yumi", AppName: "Yumi", Platform: "legacy", Status: "active"}}, regions: []moneyRegionDTO{ {AppCode: "yumi", RegionID: 1001, RegionCode: "MIDDLE_EAST", Name: "中东区", Status: "active"}, {AppCode: "yumi", RegionID: 1002, RegionCode: "INDIA", Name: "印度区", Status: "active"}, }, })) _, regions, _, err := handler.loadMoneyMasterData(context.Background(), repository.MoneyAccess{ Scopes: []model.UserMoneyScope{{AppCode: "yumi", RegionID: 1002}}, }) if err != nil { t.Fatalf("load money master data failed: %v", err) } if len(regions) != 1 || regions[0].RegionID != 1002 || regions[0].RegionCode != "INDIA" { t.Fatalf("scoped regions mismatch: %+v", regions) } if err := sqlMock.ExpectationsWereMet(); err != nil { t.Fatalf("sql expectations mismatch: %v", err) } } func TestLegacyRegionDocumentsMapNonNumericRegionIDStably(t *testing.T) { cfg := config.MoneyRegionSourceConfig{ AppCode: "yumi", AppName: "Yumi", SysOrigin: "LIKEI", } document := legacyRegionDocument{ID: "69d88849bdbcc07ebc3f118c", RegionCode: "OTHER", SysOrigin: "LIKEI"} first, rawFirst, err := legacyMoneyRegionID(cfg, document) if err != nil { t.Fatalf("map legacy id failed: %v", err) } second, rawSecond, err := legacyMoneyRegionID(cfg, document) if err != nil { t.Fatalf("map legacy id second time failed: %v", err) } if first != second || rawFirst != rawSecond || first < legacySyntheticRegionIDBase { t.Fatalf("legacy id mapping is not stable: first=%d/%s second=%d/%s", first, rawFirst, second, rawSecond) } } func TestLegacyRegionDocumentsRejectEmptyRegionID(t *testing.T) { _, _, _, err := legacyRegionDocumentsToMoneyData(config.MoneyRegionSourceConfig{ AppCode: "yumi", AppName: "Yumi", SysOrigin: "LIKEI", }, []legacyRegionDocument{{ID: "", RegionCode: "MIDDLE_EAST", SysOrigin: "LIKEI"}}, repository.MoneyAccess{All: true}) if err == nil { t.Fatalf("expected empty region id error") } } func TestCountryBelongsToRegionUsesMoneyRegionSource(t *testing.T) { handler := New(&mockPaymentWallet{}, nil, nil, nil, nil, WithMoneyRegionSources(&staticMoneyRegionSource{ regions: []moneyRegionDTO{{AppCode: "yumi", RegionID: 1002, RegionCode: "INDIA", Name: "印度区", Countries: []string{"IN", "PK"}}}, })) ok, err := handler.countryBelongsToRegion(context.Background(), "yumi", 1002, "in") if err != nil { t.Fatalf("country belongs to legacy region failed: %v", err) } if !ok { t.Fatalf("expected legacy source country match") } } type staticMoneyRegionSource struct { apps []moneyAppDTO regions []moneyRegionDTO countries []moneyCountryDTO } func (s *staticMoneyRegionSource) ListMoneyMasterData(_ context.Context, access repository.MoneyAccess) ([]moneyAppDTO, []moneyRegionDTO, []moneyCountryDTO, error) { regions := []moneyRegionDTO{} for _, region := range s.regions { if access.All || access.Allows(region.AppCode, region.RegionID) { regions = append(regions, region) } } return s.apps, regions, s.countries, nil } func (s *staticMoneyRegionSource) CountryBelongsToRegion(_ context.Context, appCode string, regionID int64, countryCode string) (bool, bool, error) { for _, region := range s.regions { if region.AppCode != appCode || region.RegionID != regionID { continue } for _, item := range region.Countries { if item == strings.ToUpper(strings.TrimSpace(countryCode)) { return true, true, nil } } return false, true, nil } return false, false, nil } func newMoneyRegionSourceUserSQLMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock, func()) { t.Helper() db, mock, err := sqlmock.New() if err != nil { t.Fatalf("create sql mock failed: %v", err) } return db, mock, func() { _ = db.Close() } } func expectEmptyMoneyApps(mock sqlmock.Sqlmock, appCodes ...string) { query := mock.ExpectQuery(`(?s)SELECT app_id, app_code, app_name, package_name, platform, status\s+FROM apps\s+WHERE status = 'active'`) if len(appCodes) > 0 { args := make([]driver.Value, 0, len(appCodes)) for _, appCode := range appCodes { args = append(args, appCode) } query.WithArgs(args...) } query.WillReturnRows(sqlmock.NewRows([]string{"app_id", "app_code", "app_name", "package_name", "platform", "status"})) } func TestMoneyRegionSourceConfigDefaults(t *testing.T) { cfg := config.Default() if len(cfg.MoneyRegionSources) != 2 || cfg.MoneyRegionSources[0].RequestTimeout != 5*time.Second || cfg.MoneyRegionSources[1].AppCode != "aslan" { t.Fatalf("default money region source mismatch: %+v", cfg.MoneyRegionSources) } }