cast-ghl-plugin/internal/cast/client_test.go
Head of Product & Engineering dcf1e3070e
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
test: expand test coverage — uninstall, dedup, 401, token refresh
- cast/client_test: add TestSendSMS_Unauthorized (401 → CastAPIError)
- ghl/webhook_test: add duplicate messageId, 450-char message, HandleUninstall (valid/invalid sig)
- ghl/oauth_test: add GetValidToken auto-refresh and refresh-failure tests
- ghl/oauth: make tokenURL a struct field (default ghlTokenURL) so tests can inject a mock endpoint

Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-05 00:36:45 +02:00

171 lines
5.2 KiB
Go

package cast
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
)
func TestSendSMS_Success(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/sms/send" {
t.Errorf("expected /api/sms/send, got %s", r.URL.Path)
}
if r.Header.Get("X-API-Key") != "cast_testkey" {
t.Errorf("expected X-API-Key cast_testkey, got %s", r.Header.Get("X-API-Key"))
}
var body SendRequest
json.NewDecoder(r.Body).Decode(&body)
if body.To != "09171234567" {
t.Errorf("expected to=09171234567, got %s", body.To)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(SendResponse{Success: true, MessageID: "abc123", Parts: 1})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_testkey", "")
resp, err := client.SendSMS(context.Background(), "09171234567", "test message")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !resp.Success || resp.MessageID != "abc123" {
t.Errorf("unexpected response: %+v", resp)
}
}
func TestSendSMS_APIError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusPaymentRequired)
json.NewEncoder(w).Encode(SendResponse{Success: false, Error: "insufficient credits"})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_testkey", "")
_, err := client.SendSMS(context.Background(), "09171234567", "test")
if err == nil {
t.Fatal("expected error, got nil")
}
castErr, ok := err.(*CastAPIError)
if !ok {
t.Fatalf("expected CastAPIError, got %T", err)
}
if castErr.StatusCode != http.StatusPaymentRequired {
t.Errorf("expected 402, got %d", castErr.StatusCode)
}
}
func TestSendSMS_SuccessFalseInBody(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(SendResponse{Success: false, Error: "invalid number"})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_testkey", "")
_, err := client.SendSMS(context.Background(), "09171234567", "test")
if err == nil {
t.Fatal("expected error, got nil")
}
castErr, ok := err.(*CastAPIError)
if !ok {
t.Fatalf("expected CastAPIError, got %T", err)
}
if castErr.APIError != "invalid number" {
t.Errorf("expected 'invalid number', got %s", castErr.APIError)
}
}
func TestSendSMS_WithSenderID(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body SendRequest
json.NewDecoder(r.Body).Decode(&body)
if body.SenderID != "CAST" {
t.Errorf("expected sender_id=CAST, got %q", body.SenderID)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(SendResponse{Success: true, MessageID: "x1", Parts: 1})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_testkey", "CAST")
_, err := client.SendSMS(context.Background(), "09171234567", "test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestSendSMS_WithoutSenderID(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var rawBody map[string]interface{}
json.NewDecoder(r.Body).Decode(&rawBody)
if _, ok := rawBody["sender_id"]; ok {
t.Error("sender_id should be omitted when empty")
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(SendResponse{Success: true, MessageID: "x2", Parts: 1})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_testkey", "")
_, err := client.SendSMS(context.Background(), "09171234567", "test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
// TC5: Failed Cast API key — 401 response is returned as a CastAPIError.
func TestSendSMS_Unauthorized(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(SendResponse{Success: false, Error: "invalid api key"})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_badkey", "")
_, err := client.SendSMS(context.Background(), "09171234567", "test")
if err == nil {
t.Fatal("expected error for 401, got nil")
}
castErr, ok := err.(*CastAPIError)
if !ok {
t.Fatalf("expected CastAPIError, got %T: %v", err, err)
}
if castErr.StatusCode != http.StatusUnauthorized {
t.Errorf("expected status 401, got %d", castErr.StatusCode)
}
}
func TestSendSMS_RetryOn429(t *testing.T) {
var callCount atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
n := callCount.Add(1)
if n <= 2 {
w.Header().Set("Retry-After", "0")
w.WriteHeader(http.StatusTooManyRequests)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(SendResponse{Success: true, MessageID: "retry-ok", Parts: 1})
}))
defer srv.Close()
client := NewClient(srv.URL, "cast_testkey", "")
resp, err := client.SendSMS(context.Background(), "09171234567", "test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.MessageID != "retry-ok" {
t.Errorf("expected retry-ok, got %s", resp.MessageID)
}
if callCount.Load() != 3 {
t.Errorf("expected 3 calls, got %d", callCount.Load())
}
}