//go:build unit package provider import ( "strings" "testing" "github.com/Wei-Shaw/sub2api/internal/payment" ) func TestMapWxState(t *testing.T) { t.Parallel() tests := []struct { name string input string want string }{ { name: "SUCCESS maps to paid", input: wxpayTradeStateSuccess, want: payment.ProviderStatusPaid, }, { name: "REFUND maps to refunded", input: wxpayTradeStateRefund, want: payment.ProviderStatusRefunded, }, { name: "CLOSED maps to failed", input: wxpayTradeStateClosed, want: payment.ProviderStatusFailed, }, { name: "PAYERROR maps to failed", input: wxpayTradeStatePayError, want: payment.ProviderStatusFailed, }, { name: "unknown state maps to pending", input: "NOTPAY", want: payment.ProviderStatusPending, }, { name: "empty string maps to pending", input: "", want: payment.ProviderStatusPending, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := mapWxState(tt.input) if got != tt.want { t.Errorf("mapWxState(%q) = %q, want %q", tt.input, got, tt.want) } }) } } func TestWxSV(t *testing.T) { t.Parallel() tests := []struct { name string input *string want string }{ { name: "nil pointer returns empty string", input: nil, want: "", }, { name: "non-nil pointer returns value", input: strPtr("hello"), want: "hello", }, { name: "pointer to empty string returns empty string", input: strPtr(""), want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := wxSV(tt.input) if got != tt.want { t.Errorf("wxSV() = %q, want %q", got, tt.want) } }) } } func strPtr(s string) *string { return &s } func TestFormatPEM(t *testing.T) { t.Parallel() tests := []struct { name string key string keyType string want string }{ { name: "raw key gets wrapped with headers", key: "MIIBIjANBgkqhki...", keyType: "PUBLIC KEY", want: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----", }, { name: "already formatted key is returned as-is", key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----", keyType: "PRIVATE KEY", want: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----", }, { name: "key with leading/trailing whitespace is trimmed before check", key: " \n MIIBIjANBgkqhki... \n ", keyType: "PUBLIC KEY", want: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...\n-----END PUBLIC KEY-----", }, { name: "already formatted key with whitespace is trimmed and returned", key: " -----BEGIN RSA PRIVATE KEY-----\ndata\n-----END RSA PRIVATE KEY----- ", keyType: "RSA PRIVATE KEY", want: "-----BEGIN RSA PRIVATE KEY-----\ndata\n-----END RSA PRIVATE KEY-----", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := formatPEM(tt.key, tt.keyType) if got != tt.want { t.Errorf("formatPEM(%q, %q) =\n%s\nwant:\n%s", tt.key, tt.keyType, got, tt.want) } }) } } func TestNewWxpay(t *testing.T) { t.Parallel() validConfig := map[string]string{ "appId": "wx1234567890", "mchId": "1234567890", "privateKey": "fake-private-key", "apiV3Key": "12345678901234567890123456789012", // exactly 32 bytes "publicKey": "fake-public-key", "publicKeyId": "key-id-001", "certSerial": "SERIAL001", } // helper to clone and override config fields withOverride := func(overrides map[string]string) map[string]string { cfg := make(map[string]string, len(validConfig)) for k, v := range validConfig { cfg[k] = v } for k, v := range overrides { cfg[k] = v } return cfg } tests := []struct { name string config map[string]string wantErr bool errSubstr string }{ { name: "valid config succeeds", config: validConfig, wantErr: false, }, { name: "missing appId", config: withOverride(map[string]string{"appId": ""}), wantErr: true, errSubstr: "appId", }, { name: "missing mchId", config: withOverride(map[string]string{"mchId": ""}), wantErr: true, errSubstr: "mchId", }, { name: "missing privateKey", config: withOverride(map[string]string{"privateKey": ""}), wantErr: true, errSubstr: "privateKey", }, { name: "missing apiV3Key", config: withOverride(map[string]string{"apiV3Key": ""}), wantErr: true, errSubstr: "apiV3Key", }, { name: "missing publicKey", config: withOverride(map[string]string{"publicKey": ""}), wantErr: true, errSubstr: "publicKey", }, { name: "missing publicKeyId", config: withOverride(map[string]string{"publicKeyId": ""}), wantErr: true, errSubstr: "publicKeyId", }, { name: "apiV3Key too short", config: withOverride(map[string]string{"apiV3Key": "short"}), wantErr: true, errSubstr: "exactly 32 bytes", }, { name: "apiV3Key too long", config: withOverride(map[string]string{"apiV3Key": "123456789012345678901234567890123"}), // 33 bytes wantErr: true, errSubstr: "exactly 32 bytes", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := NewWxpay("test-instance", tt.config) if tt.wantErr { if err == nil { t.Fatal("expected error, got nil") } if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) { t.Errorf("error %q should contain %q", err.Error(), tt.errSubstr) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if got == nil { t.Fatal("expected non-nil Wxpay instance") } if got.instanceID != "test-instance" { t.Errorf("instanceID = %q, want %q", got.instanceID, "test-instance") } }) } }