cast-ghl-plugin/internal/cast/client_test.go
Head of Product & Engineering 6a853f6566
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
fix: resolve all golangci-lint v2 failures
Production fixes:
- cmd/server/main.go: refactor to run() helper to eliminate exitAfterDefer
  (os.Exit in main() no longer bypasses deferred s.Close)
- internal/cast/client.go: use _ = resp.Body.Close() (errcheck)
- internal/ghl/api.go: wrap both defers as func(){ _ = resp.Body.Close() }()

Test fixes:
- internal/cast/client_test.go: replace err.(*CastAPIError) type assertions
  with errors.As (errorlint)

Config:
- .golangci.yml: use explicit path regex .*_test\\.go and add errorlint
  to test-file exclusions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-05 21:52:36 +02:00

172 lines
5.3 KiB
Go

package cast
import (
"context"
"encoding/json"
"errors"
"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")
}
var castErr *CastAPIError
if !errors.As(err, &castErr) {
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")
}
var castErr *CastAPIError
if !errors.As(err, &castErr) {
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")
}
var castErr *CastAPIError
if !errors.As(err, &castErr) {
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())
}
}