package financewithdrawal import ( "context" "database/sql" "strings" "testing" "github.com/DATA-DOG/go-sqlmock" ) func TestCreateApplicationRequiresFreezeCommandID(t *testing.T) { command := normalizeCreateApplicationCommand(CreateApplicationCommand{ AppCode: "huwaa", UserID: 42001, SalaryAssetType: "point", WithdrawAmount: "9.50", WithdrawAmountMinor: 1_000_000, WithdrawAddress: "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", FreezeTransactionID: "tx-freeze", CreatedAtMS: 1700000000000, }) if err := validateCreateApplicationCommand(command); err == nil || !strings.Contains(err.Error(), "freeze_command_id") { t.Fatalf("missing freeze command id should be rejected, got %v", err) } } func TestCreateApplicationReturnsExistingByAppAndFreezeCommand(t *testing.T) { db, mock, closeDB := newWriterSQLMock(t) defer closeDB() writer := &MySQLWriter{db: db} mock.ExpectQuery(`(?s)FROM admin_user_withdrawal_applications\s+WHERE app_code = \? AND freeze_command_id = \?`). WithArgs("huwaa", "cmd-point-freeze"). WillReturnRows(withdrawalApplicationRows().AddRow( int64(77), "huwaa", "42001", "POINT", "9.50", int64(1000000), MethodUSDTTRC20, "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", "cmd-point-freeze", "tx-freeze", StatusPending, int64(1700000000000), int64(1700000000000), )) got, err := writer.CreateApplication(context.Background(), CreateApplicationCommand{ AppCode: "huwaa", UserID: 42001, SalaryAssetType: "POINT", WithdrawAmount: "9.50", WithdrawAmountMinor: 1_000_000, WithdrawMethod: MethodUSDTTRC20, WithdrawAddress: "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", FreezeCommandID: "cmd-point-freeze", FreezeTransactionID: "tx-new-should-not-insert", CreatedAtMS: 1700000009999, }) if err != nil { t.Fatalf("CreateApplication existing lookup failed: %v", err) } if got.ID != 77 || got.AppCode != "huwaa" || got.UserID != 42001 || got.FreezeTransactionID != "tx-freeze" { t.Fatalf("existing withdrawal application mismatch: %+v", got) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("sql expectations mismatch: %v", err) } } func TestCreateApplicationInsertsAfterAppScopedMiss(t *testing.T) { db, mock, closeDB := newWriterSQLMock(t) defer closeDB() writer := &MySQLWriter{db: db} mock.ExpectQuery(`(?s)FROM admin_user_withdrawal_applications\s+WHERE app_code = \? AND freeze_command_id = \?`). WithArgs("huwaa", "cmd-point-freeze"). WillReturnRows(withdrawalApplicationRows()) mock.ExpectExec(`(?s)INSERT INTO admin_user_withdrawal_applications`). WithArgs( "huwaa", "42001", "POINT", "9.50", int64(1000000), MethodUSDTTRC20, "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", "cmd-point-freeze", "tx-freeze", StatusPending, int64(1700000000000), int64(1700000000000), ). WillReturnResult(sqlmock.NewResult(88, 1)) got, err := writer.CreateApplication(context.Background(), CreateApplicationCommand{ AppCode: "huwaa", UserID: 42001, SalaryAssetType: "point", WithdrawAmount: "9.50", WithdrawAmountMinor: 1_000_000, WithdrawAddress: "TQY9pFbZHuR4fUN9WgXPYj5nmB6nQiLQfF", FreezeCommandID: "cmd-point-freeze", FreezeTransactionID: "tx-freeze", CreatedAtMS: 1700000000000, }) if err != nil { t.Fatalf("CreateApplication insert failed: %v", err) } if got.ID != 88 || got.AppCode != "huwaa" || got.SalaryAssetType != "POINT" || got.WithdrawMethod != MethodUSDTTRC20 { t.Fatalf("inserted withdrawal application mismatch: %+v", got) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("sql expectations mismatch: %v", err) } } func withdrawalApplicationRows() *sqlmock.Rows { return sqlmock.NewRows([]string{ "id", "app_code", "user_id", "salary_asset_type", "withdraw_amount", "withdraw_amount_minor", "withdraw_method", "withdraw_address", "freeze_command_id", "freeze_transaction_id", "status", "created_at_ms", "updated_at_ms", }) } func newWriterSQLMock(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() } }