package policyconfig import ( "context" "database/sql" "testing" "github.com/DATA-DOG/go-sqlmock" "hyapp-admin-server/internal/integration/activityclient" activityv1 "hyapp.local/api/proto/activity/v1" ) func TestGetTemplateJoinsVersionByTemplateCodeAndVersion(t *testing.T) { adminDB, mock, closeDB := newPolicySQLMock(t) defer closeDB() svc := NewService(adminDB, nil, nil, nil) mock.ExpectQuery(`(?s)JOIN admin_policy_template_versions v ON v\.template_code = t\.template_code AND v\.template_version = t\.template_version.*WHERE t\.template_code = \? AND v\.template_version = \?`). WithArgs("huwaa_point_policy", "v2"). WillReturnRows(sqlmock.NewRows([]string{ "template_id", "version_id", "template_code", "template_version", "name", "status", "rule_json", "description", "created_at_ms", "updated_at_ms", }).AddRow(1, 22, "huwaa_point_policy", "v2", "Huwaa Point Policy", "active", `{"tasks":{"reward_asset_type":"POINT"}}`, "version scoped", int64(1700000000000), int64(1700000001000))) got, err := svc.getTemplate(context.Background(), "huwaa_point_policy", "v2") if err != nil { t.Fatalf("getTemplate failed: %v", err) } if got.TemplateCode != "huwaa_point_policy" || got.TemplateVersion != "v2" || got.VersionID != 22 { t.Fatalf("template version join result mismatch: %+v", got) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("admin sql expectations mismatch: %v", err) } } func TestPublishRuntimeWritesWalletRowsAndActivityTaskRewardPolicy(t *testing.T) { walletDB, walletMock, closeWallet := newPolicySQLMock(t) defer closeWallet() activity := &fakePolicyActivityClient{} svc := NewService(nil, walletDB, nil, activity) ctx := context.Background() nowMS := int64(1700000005000) item := policyInstanceRow{ AppCode: "huwaa", InstanceCode: "huwaa-point-live", TemplateCode: "huwaa_point_policy", TemplateVersion: "v2", Status: policyStatusActive, EffectiveFromMS: 1700000000000, EffectiveToMS: 0, } compiled := compiledPolicy{ HostPointRatioPPM: 700000, PointsPerUSD: 100000, WithdrawFeeBPS: 500, TaskRewardAssetType: "POINT", RuleJSON: []byte(`{"tasks":{"reward_asset_type":"POINT"},"host":{"point_ratio_percent":70}}`), } walletMock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS wallet_policy_instances`).WillReturnResult(sqlmock.NewResult(0, 0)) walletMock.ExpectBegin() walletMock.ExpectExec(`DELETE FROM wallet_policy_instances WHERE app_code = \? AND instance_code = \?`). WithArgs("huwaa", "huwaa-point-live"). WillReturnResult(sqlmock.NewResult(0, 2)) for _, regionID := range []int64{0, 12} { walletMock.ExpectExec(`(?s)INSERT INTO wallet_policy_instances`). WithArgs( "huwaa", "huwaa-point-live", "huwaa_point_policy", "v2", regionID, policyStatusActive, int64(1700000000000), int64(0), int64(700000), int64(100000), int64(500), string(compiled.RuleJSON), uint(90001), nowMS, nowMS, nowMS, ). WillReturnResult(sqlmock.NewResult(1, 1)) } walletMock.ExpectCommit() if err := svc.publishWalletRuntime(ctx, item, compiled, []int64{0, 12}, 90001, nowMS); err != nil { t.Fatalf("publishWalletRuntime failed: %v", err) } if err := svc.publishActivityRuntime(ctx, item, compiled, 90001, nowMS); err != nil { t.Fatalf("publishActivityRuntime failed: %v", err) } if activity.last == nil || activity.last.GetMeta().GetAppCode() != "huwaa" || activity.last.GetInstanceCode() != "huwaa-point-live" || activity.last.GetTemplateCode() != "huwaa_point_policy" || activity.last.GetTemplateVersion() != "v2" || activity.last.GetStatus() != policyStatusActive || activity.last.GetRewardAssetType() != "POINT" || activity.last.GetOperatorAdminId() != 90001 || activity.last.GetPublishedAtMs() != nowMS { t.Fatalf("activity task reward policy payload mismatch: %+v", activity.last) } if err := walletMock.ExpectationsWereMet(); err != nil { t.Fatalf("wallet sql expectations mismatch: %v", err) } } func TestUpdateInstanceStatusSyncsWalletRuntimeAndActivityRuntime(t *testing.T) { adminDB, adminMock, closeAdmin := newPolicySQLMock(t) defer closeAdmin() walletDB, walletMock, closeWallet := newPolicySQLMock(t) defer closeWallet() activity := &fakePolicyActivityClient{} svc := NewService(adminDB, walletDB, nil, activity) ctx := context.Background() ruleJSON := `{"points_per_usd":100000,"host":{"point_ratio_percent":70,"withdraw_fee_bps":500},"tasks":{"reward_asset_type":"POINT"}}` adminMock.ExpectQuery(`(?s)SELECT instance_id, app_code, instance_code, template_code, template_version, region_scope, status,\s+effective_from_ms, effective_to_ms\s+FROM admin_policy_instances\s+WHERE app_code = \? AND instance_id = \?`). WithArgs("huwaa", uint64(9)). WillReturnRows(sqlmock.NewRows([]string{ "instance_id", "app_code", "instance_code", "template_code", "template_version", "region_scope", "status", "effective_from_ms", "effective_to_ms", }).AddRow(9, "huwaa", "huwaa-point-live", "huwaa_point_policy", "v2", "all_active_regions", policyStatusActive, int64(1700000000000), int64(0))) adminMock.ExpectQuery(`(?s)SELECT COALESCE\(CAST\(rule_json AS CHAR\), '\{\}'\)\s+FROM admin_policy_template_versions\s+WHERE template_code = \? AND template_version = \?`). WithArgs("huwaa_point_policy", "v2"). WillReturnRows(sqlmock.NewRows([]string{"rule_json"}).AddRow(ruleJSON)) adminMock.ExpectExec(`(?s)UPDATE admin_policy_instances\s+SET status = \?, updated_by_admin_id = \?, updated_at_ms = \?\s+WHERE app_code = \? AND instance_id = \?`). WithArgs(policyStatusDisabled, uint(90002), sqlmock.AnyArg(), "huwaa", uint64(9)). WillReturnResult(sqlmock.NewResult(0, 1)) walletMock.ExpectExec(`(?s)CREATE TABLE IF NOT EXISTS wallet_policy_instances`).WillReturnResult(sqlmock.NewResult(0, 0)) walletMock.ExpectExec(`(?s)UPDATE wallet_policy_instances\s+SET status = \?, updated_at_ms = \?\s+WHERE app_code = \? AND instance_code = \?`). WithArgs(policyStatusDisabled, sqlmock.AnyArg(), "huwaa", "huwaa-point-live"). WillReturnResult(sqlmock.NewResult(0, 1)) adminMock.ExpectQuery(`(?s)SELECT instance_id, app_code, instance_code, template_code, template_version, region_scope, status,\s+effective_from_ms, effective_to_ms, publish_status, publish_error, last_published_at_ms,\s+created_at_ms, updated_at_ms\s+FROM admin_policy_instances\s+WHERE app_code = \? AND instance_id = \?`). WithArgs("huwaa", uint64(9)). WillReturnRows(sqlmock.NewRows([]string{ "instance_id", "app_code", "instance_code", "template_code", "template_version", "region_scope", "status", "effective_from_ms", "effective_to_ms", "publish_status", "publish_error", "last_published_at_ms", "created_at_ms", "updated_at_ms", }).AddRow(9, "huwaa", "huwaa-point-live", "huwaa_point_policy", "v2", "all_active_regions", policyStatusDisabled, int64(1700000000000), int64(0), publishStatusPublished, "", int64(1700000005000), int64(1700000000000), int64(1700000006000))) got, err := svc.UpdateInstanceStatus(ctx, " Huwaa ", 90002, 9, "disabled") if err != nil { t.Fatalf("UpdateInstanceStatus failed: %v", err) } if got.Status != policyStatusDisabled { t.Fatalf("instance status mismatch: %+v", got) } if activity.last == nil || activity.last.GetMeta().GetAppCode() != "huwaa" || activity.last.GetInstanceCode() != "huwaa-point-live" || activity.last.GetStatus() != policyStatusDisabled || activity.last.GetRewardAssetType() != "POINT" { t.Fatalf("activity status sync payload mismatch: %+v", activity.last) } if err := adminMock.ExpectationsWereMet(); err != nil { t.Fatalf("admin sql expectations mismatch: %v", err) } if err := walletMock.ExpectationsWereMet(); err != nil { t.Fatalf("wallet sql expectations mismatch: %v", err) } } type fakePolicyActivityClient struct { activityclient.Client last *activityv1.PublishTaskRewardPolicyRequest } func (f *fakePolicyActivityClient) PublishTaskRewardPolicy(_ context.Context, req *activityv1.PublishTaskRewardPolicyRequest) (*activityv1.PublishTaskRewardPolicyResponse, error) { f.last = req return &activityv1.PublishTaskRewardPolicyResponse{ AppCode: req.GetMeta().GetAppCode(), InstanceCode: req.GetInstanceCode(), Status: req.GetStatus(), RewardAssetType: req.GetRewardAssetType(), PublishedAtMs: req.GetPublishedAtMs(), }, nil } func newPolicySQLMock(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() } }