fix: enable CI tests with build tags and add billing tests
Some checks failed
ci/woodpecker/push/build Pipeline failed
Some checks failed
ci/woodpecker/push/build Pipeline failed
- Add build tag to studio/embed.go to exclude from test builds - Create stub embed_testing.go for test builds - Update CI pipeline to use -tags testing - Add tests for webhook signature verification and status normalization Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cb4bebb42c
commit
b2b2a42ca9
4 changed files with 206 additions and 1 deletions
|
|
@ -6,7 +6,7 @@ steps:
|
|||
- name: test
|
||||
image: golang:1.24-alpine
|
||||
commands:
|
||||
- go test ./...
|
||||
- go test -tags testing ./...
|
||||
|
||||
- name: build
|
||||
image: docker:27-cli
|
||||
|
|
|
|||
185
internal/billing/billing_test.go
Normal file
185
internal/billing/billing_test.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package billing
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVerifyWebhook(t *testing.T) {
|
||||
secret := "test-webhook-secret"
|
||||
client := &LemonClient{webhookSecret: secret}
|
||||
|
||||
t.Run("valid signature", func(t *testing.T) {
|
||||
payload := []byte(`{"meta":{"event_name":"subscription_created"}}`)
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write(payload)
|
||||
signature := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if !client.VerifyWebhook(payload, signature) {
|
||||
t.Error("expected valid signature to pass verification")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid signature", func(t *testing.T) {
|
||||
payload := []byte(`{"meta":{"event_name":"subscription_created"}}`)
|
||||
signature := "invalid-signature"
|
||||
|
||||
if client.VerifyWebhook(payload, signature) {
|
||||
t.Error("expected invalid signature to fail verification")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong secret", func(t *testing.T) {
|
||||
payload := []byte(`{"meta":{"event_name":"subscription_created"}}`)
|
||||
|
||||
mac := hmac.New(sha256.New, []byte("different-secret"))
|
||||
mac.Write(payload)
|
||||
signature := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if client.VerifyWebhook(payload, signature) {
|
||||
t.Error("expected signature with wrong secret to fail")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty secret", func(t *testing.T) {
|
||||
clientNoSecret := &LemonClient{webhookSecret: ""}
|
||||
payload := []byte(`{"test": true}`)
|
||||
|
||||
if clientNoSecret.VerifyWebhook(payload, "any-signature") {
|
||||
t.Error("expected empty secret to fail verification")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty payload", func(t *testing.T) {
|
||||
payload := []byte{}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write(payload)
|
||||
signature := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if !client.VerifyWebhook(payload, signature) {
|
||||
t.Error("expected empty payload with valid signature to pass")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNormalizeStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"on_trial", "active"},
|
||||
{"active", "active"},
|
||||
{"paused", "past_due"},
|
||||
{"past_due", "past_due"},
|
||||
{"unpaid", "past_due"},
|
||||
{"cancelled", "cancelled"},
|
||||
{"expired", "cancelled"},
|
||||
{"unknown_status", "unknown_status"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := normalizeStatus(tt.input)
|
||||
if got != tt.expected {
|
||||
t.Errorf("normalizeStatus(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseWebhookEvent(t *testing.T) {
|
||||
client := &LemonClient{}
|
||||
|
||||
t.Run("valid event", func(t *testing.T) {
|
||||
payload := []byte(`{
|
||||
"meta": {
|
||||
"event_name": "subscription_created",
|
||||
"custom_data": {"tenant_id": "tenant-123", "user_id": "user-456"}
|
||||
},
|
||||
"data": {"id": "sub-789"}
|
||||
}`)
|
||||
|
||||
event, err := client.ParseWebhookEvent(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if event.Meta.EventName != "subscription_created" {
|
||||
t.Errorf("EventName = %q, want %q", event.Meta.EventName, "subscription_created")
|
||||
}
|
||||
|
||||
if event.Meta.CustomData["tenant_id"] != "tenant-123" {
|
||||
t.Errorf("tenant_id = %q, want %q", event.Meta.CustomData["tenant_id"], "tenant-123")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid JSON", func(t *testing.T) {
|
||||
payload := []byte(`{invalid json}`)
|
||||
|
||||
_, err := client.ParseWebhookEvent(payload)
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid JSON")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSubscriptionData(t *testing.T) {
|
||||
event := &WebhookEvent{
|
||||
Data: []byte(`{
|
||||
"id": "sub-123",
|
||||
"attributes": {
|
||||
"customer_id": 456,
|
||||
"variant_name": "Pro Monthly",
|
||||
"user_email": "test@example.com",
|
||||
"status": "active"
|
||||
}
|
||||
}`),
|
||||
}
|
||||
|
||||
data, err := event.GetSubscriptionData()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if data.ID != "sub-123" {
|
||||
t.Errorf("ID = %q, want %q", data.ID, "sub-123")
|
||||
}
|
||||
|
||||
if data.Attributes.CustomerID != 456 {
|
||||
t.Errorf("CustomerID = %d, want %d", data.Attributes.CustomerID, 456)
|
||||
}
|
||||
|
||||
if data.Attributes.Status != "active" {
|
||||
t.Errorf("Status = %q, want %q", data.Attributes.Status, "active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderData(t *testing.T) {
|
||||
event := &WebhookEvent{
|
||||
Data: []byte(`{
|
||||
"id": "order-123",
|
||||
"attributes": {
|
||||
"user_name": "John Doe",
|
||||
"user_email": "john@example.com",
|
||||
"total_usd": 500
|
||||
}
|
||||
}`),
|
||||
}
|
||||
|
||||
data, err := event.GetOrderData()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if data.ID != "order-123" {
|
||||
t.Errorf("ID = %q, want %q", data.ID, "order-123")
|
||||
}
|
||||
|
||||
if data.Attributes.TotalUsd != 500 {
|
||||
t.Errorf("TotalUsd = %d, want %d", data.Attributes.TotalUsd, 500)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
//go:build !testing
|
||||
|
||||
package studio
|
||||
|
||||
import (
|
||||
|
|
|
|||
18
studio/embed_testing.go
Normal file
18
studio/embed_testing.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//go:build testing
|
||||
|
||||
package studio
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var distFS fs.FS
|
||||
|
||||
func Handler() http.Handler {
|
||||
return http.NotFoundHandler()
|
||||
}
|
||||
|
||||
func Read(name string) ([]byte, error) {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue