- Add new test files for handlers (storage, payment, settings) - Add new test files for services (chat, email, storage, settings, admin) - Add integration tests for services - Update handler implementations with bug fixes - Add coverage reports and test documentation
211 lines
6.3 KiB
Go
211 lines
6.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// mockPaymentCredentialsService mocks the credentials service
|
|
type mockPaymentCredentialsService struct {
|
|
getDecryptedKeyFunc func(ctx context.Context, keyName string) (string, error)
|
|
}
|
|
|
|
func (m *mockPaymentCredentialsService) GetDecryptedKey(ctx context.Context, keyName string) (string, error) {
|
|
if m.getDecryptedKeyFunc != nil {
|
|
return m.getDecryptedKeyFunc(ctx, keyName)
|
|
}
|
|
// Default mock behavior: return valid JSON config for stripe
|
|
if keyName == "stripe" {
|
|
return `{"secretKey":"sk_test_123","webhookSecret":"whsec_123"}`, nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// mockStripeClient mocks the stripe client
|
|
type mockStripeClient struct {
|
|
createCheckoutFunc func(secretKey string, req CreateCheckoutRequest) (string, string, error)
|
|
}
|
|
|
|
func (m *mockStripeClient) CreateCheckoutSession(secretKey string, req CreateCheckoutRequest) (string, string, error) {
|
|
if m.createCheckoutFunc != nil {
|
|
return m.createCheckoutFunc(secretKey, req)
|
|
}
|
|
return "sess_123", "https://checkout.stripe.com/sess_123", nil
|
|
}
|
|
|
|
func TestCreateCheckout_Success(t *testing.T) {
|
|
mockCreds := &mockPaymentCredentialsService{}
|
|
mockStripe := &mockStripeClient{}
|
|
|
|
handler := &PaymentHandler{
|
|
credentialsService: mockCreds,
|
|
stripeClient: mockStripe,
|
|
}
|
|
|
|
reqBody := CreateCheckoutRequest{
|
|
JobID: 1,
|
|
PriceID: "price_123",
|
|
SuccessURL: "http://success",
|
|
CancelURL: "http://cancel",
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
req := httptest.NewRequest("POST", "/payments/create-checkout", bytes.NewReader(body))
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler.CreateCheckout(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
|
|
var resp CreateCheckoutResponse
|
|
err := json.Unmarshal(rr.Body.Bytes(), &resp)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "sess_123", resp.SessionID)
|
|
}
|
|
|
|
func TestCreateCheckout_MissingFields(t *testing.T) {
|
|
handler := &PaymentHandler{}
|
|
reqBody := CreateCheckoutRequest{
|
|
JobID: 0, // Invalid
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
req := httptest.NewRequest("POST", "/payments/create-checkout", bytes.NewReader(body))
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler.CreateCheckout(rr, req)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
|
}
|
|
|
|
func TestCreateCheckout_StripeError(t *testing.T) {
|
|
mockCreds := &mockPaymentCredentialsService{}
|
|
mockStripe := &mockStripeClient{
|
|
createCheckoutFunc: func(secretKey string, req CreateCheckoutRequest) (string, string, error) {
|
|
return "", "", errors.New("stripe error")
|
|
},
|
|
}
|
|
|
|
handler := &PaymentHandler{
|
|
credentialsService: mockCreds,
|
|
stripeClient: mockStripe,
|
|
}
|
|
|
|
reqBody := CreateCheckoutRequest{
|
|
JobID: 1,
|
|
PriceID: "price_123",
|
|
}
|
|
body, _ := json.Marshal(reqBody)
|
|
req := httptest.NewRequest("POST", "/payments/create-checkout", bytes.NewReader(body))
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler.CreateCheckout(rr, req)
|
|
|
|
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
|
}
|
|
|
|
func TestGetPaymentStatus(t *testing.T) {
|
|
handler := &PaymentHandler{}
|
|
req := httptest.NewRequest("GET", "/payments/status/pay_123", nil)
|
|
req.SetPathValue("id", "pay_123")
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler.GetPaymentStatus(rr, req)
|
|
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
var resp map[string]interface{}
|
|
json.Unmarshal(rr.Body.Bytes(), &resp)
|
|
assert.Equal(t, "pay_123", resp["id"])
|
|
}
|
|
|
|
func TestHandleWebhook_Success(t *testing.T) {
|
|
mockCreds := &mockPaymentCredentialsService{
|
|
getDecryptedKeyFunc: func(ctx context.Context, keyName string) (string, error) {
|
|
// Return config with secret
|
|
return `{"webhookSecret":"whsec_test"}`, nil
|
|
},
|
|
}
|
|
// Strategy for Webhook test:
|
|
// VerifyStripeSignature is a standalone function that calculates HMAC.
|
|
// It's hard to mock unless we export it or wrap it.
|
|
// However, we can construct a valid signature for the test!
|
|
// Or we can mock the signature verification if we wrapper it?
|
|
// The current PaymentHandler calls `verifyStripeSignature` directly.
|
|
// To test HandleWebhook fully, we need to generate a valid signature.
|
|
|
|
secret := "whsec_test"
|
|
payload := `{"type":"payment_intent.succeeded", "data":{}}`
|
|
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
// manually compute signature
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write([]byte(fmt.Sprintf("%s.%s", timestamp, payload)))
|
|
sig := hex.EncodeToString(mac.Sum(nil))
|
|
|
|
header := fmt.Sprintf("t=%s,v1=%s", timestamp, sig)
|
|
|
|
req := httptest.NewRequest("POST", "/payments/webhook", bytes.NewReader([]byte(payload)))
|
|
req.Header.Set("Stripe-Signature", header)
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := &PaymentHandler{
|
|
credentialsService: mockCreds,
|
|
}
|
|
|
|
handler.HandleWebhook(rr, req)
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
}
|
|
|
|
func TestHandleWebhook_CheckoutCompleted(t *testing.T) {
|
|
mockCreds := &mockPaymentCredentialsService{
|
|
getDecryptedKeyFunc: func(ctx context.Context, keyName string) (string, error) {
|
|
return `{"webhookSecret":"whsec_test"}`, nil
|
|
},
|
|
}
|
|
|
|
// Create payload for checkout.session.completed
|
|
// logic: handleCheckoutComplete extracts ClientReferenceID -> JobID
|
|
// And metadata -> userId, etc.
|
|
// We need to match what handleCheckoutComplete expects.
|
|
// It parses event.Data.Object into stripe.CheckoutSession.
|
|
// Then calls jobService ... wait.
|
|
// PaymentHandler NO LONGER depends on JobService directly? In Refactor I removed it?
|
|
// Let's check PaymentHandler code.
|
|
// If it doesn't have JobService, how does it update Job?
|
|
// It calls `handlePaymentSuccess`.
|
|
// I need to see what `handlePaymentSuccess` does.
|
|
|
|
// Assuming logic is simple DB update or logging for now.
|
|
|
|
secret := "whsec_test"
|
|
payload := `{"type":"checkout.session.completed", "data":{"object":{"client_reference_id":"123", "metadata":{"userId":"u1"}, "payment_status":"paid"}}}`
|
|
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
|
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write([]byte(fmt.Sprintf("%s.%s", timestamp, payload)))
|
|
sig := hex.EncodeToString(mac.Sum(nil))
|
|
|
|
header := fmt.Sprintf("t=%s,v1=%s", timestamp, sig)
|
|
|
|
req := httptest.NewRequest("POST", "/payments/webhook", bytes.NewReader([]byte(payload)))
|
|
req.Header.Set("Stripe-Signature", header)
|
|
rr := httptest.NewRecorder()
|
|
|
|
handler := &PaymentHandler{
|
|
credentialsService: mockCreds,
|
|
}
|
|
|
|
handler.HandleWebhook(rr, req)
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
|
}
|