CI / Nginx config check (push) Successful in 13s
CI / Env docs check (push) Successful in 15s
CI / Docker compose check (push) Successful in 15s
CI / Frontend major deps (push) Failing after 24s
CI / Frontend deps check (push) Successful in 30s
CI / Secrets scan (push) Successful in 38s
CI / Go build (push) Successful in 39s
CI / Frontend build (push) Successful in 1m3s
CI / Knip (push) Successful in 45s
CI / Go vet (prod) (push) Failing after 1m42s
CI / Frontend a11y check (push) Successful in 2m34s
CI / Go vet (dev) (push) Successful in 2m29s
CI / Staticcheck (prod) (push) Failing after 2m38s
CI / go mod tidy (push) Successful in 1m3s
CI / Staticcheck (dev) (push) Successful in 2m55s
CI / Frontend QC (audit) (push) Successful in 51s
CI / golangci-lint (push) Successful in 3m22s
CI / Go vulnerabilities (push) Successful in 1m26s
CI / Frontend QC (typecheck) (push) Successful in 2m18s
CI / Security scan (prod) (push) Successful in 4m18s
CI / Security scan (dev) (push) Successful in 4m40s
CI / Tests (prod) (push) Has been skipped
CI / Tests (dev) (push) Has been skipped
CI / Race (prod) (push) Has been skipped
CI / Race (dev) (push) Has been skipped
CI / Frontend QC (lint) (push) Successful in 2m18s
CI / Svelte strict check (push) Successful in 43s
New test files cover previously untested paths across DAV, validators, S3, Square, mw, bookings, user, and payments packages. Includes mock fix: HoldCheckouts flag on MockClient allows tests to pause auto-complete goroutine for testing PENDING checkout states. Coverage: 50.4% → 65.0% (+14.6pp)
1061 lines
37 KiB
Go
1061 lines
37 KiB
Go
//go:build test
|
|
|
|
package admin
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"crussell/testutils"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func intPtr(i int) *int { return &i }
|
|
func float64Ptr(f float64) *float64 { return &f }
|
|
|
|
// ─── Public info ──────────────────────────────────────────────────────────────
|
|
|
|
// TestGetPublicBusinessInfo verifies that GET /business-info returns the public
|
|
// subset of business settings (no admin-only fields like currency_code,
|
|
// website_url, gift_card_expiry_months, voucher_type).
|
|
func TestGetPublicBusinessInfo(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET
|
|
business_name = 'Test Nail Salon',
|
|
business_address = '456 High Street',
|
|
business_phone = '+441234567890',
|
|
business_email = 'info@testsalon.com',
|
|
is_vat_registered = true,
|
|
vat_registration_number = 'GB123456789',
|
|
default_vat_rate = 20.00,
|
|
currency_code = 'GBP',
|
|
website_url = 'https://testsalon.com',
|
|
gift_card_expiry_months = 24,
|
|
voucher_type = 'SPV'
|
|
`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed business settings: %v", err)
|
|
}
|
|
|
|
// GetPublicBusinessInfo does NOT require admin auth — it's a public endpoint.
|
|
handler := http.HandlerFunc(GetPublicBusinessInfo)
|
|
req := httptest.NewRequest("GET", "/business-info", nil)
|
|
req = req.WithContext(ctx)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var info PublicBusinessInfo
|
|
if err := parseResponseBody(w, &info); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if info.BusinessName != "Test Nail Salon" {
|
|
t.Errorf("expected BusinessName 'Test Nail Salon', got '%s'", info.BusinessName)
|
|
}
|
|
if info.BusinessAddress != "456 High Street" {
|
|
t.Errorf("expected BusinessAddress '456 High Street', got '%s'", info.BusinessAddress)
|
|
}
|
|
if info.BusinessPhone == nil || *info.BusinessPhone != "+441234567890" {
|
|
t.Errorf("expected BusinessPhone '+441234567890', got %v", info.BusinessPhone)
|
|
}
|
|
if info.BusinessEmail == nil || *info.BusinessEmail != "info@testsalon.com" {
|
|
t.Errorf("expected BusinessEmail 'info@testsalon.com', got %v", info.BusinessEmail)
|
|
}
|
|
if !info.IsVATRegistered {
|
|
t.Errorf("expected IsVATRegistered to be true")
|
|
}
|
|
if info.VATRegistrationNumber == nil || *info.VATRegistrationNumber != "GB123456789" {
|
|
t.Errorf("expected VATRegistrationNumber 'GB123456789', got %v", info.VATRegistrationNumber)
|
|
}
|
|
if info.DefaultVATRate != 20.00 {
|
|
t.Errorf("expected DefaultVATRate 20.00, got %f", info.DefaultVATRate)
|
|
}
|
|
}
|
|
|
|
// TestGetPublicBusinessInfo_NotAdminSafe verifies that the public info endpoint
|
|
// does NOT expose admin-only fields (currency_code, website_url,
|
|
// gift_card_expiry_months, voucher_type).
|
|
func TestGetPublicBusinessInfo_NotAdminSafe(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET
|
|
business_name = 'Test Salon', business_address = '123 St',
|
|
currency_code = 'GBP', website_url = 'https://secret.admin.url',
|
|
gift_card_expiry_months = 48, voucher_type = 'MPV'
|
|
`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed business settings: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetPublicBusinessInfo)
|
|
req := httptest.NewRequest("GET", "/business-info", nil)
|
|
req = req.WithContext(ctx)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
bodyStr := w.Body.String()
|
|
// Admin-only fields MUST NOT appear in the JSON response
|
|
if strings.Contains(bodyStr, "currency_code") {
|
|
t.Error("expected currency_code to be excluded from public info response")
|
|
}
|
|
if strings.Contains(bodyStr, "website_url") {
|
|
t.Error("expected website_url to be excluded from public info response")
|
|
}
|
|
if strings.Contains(bodyStr, "gift_card_expiry_months") {
|
|
t.Error("expected gift_card_expiry_months to be excluded from public info response")
|
|
}
|
|
if strings.Contains(bodyStr, "voucher_type") {
|
|
t.Error("expected voucher_type to be excluded from public info response")
|
|
}
|
|
}
|
|
|
|
// ─── GET ─────────────────────────────────────────────────────────────────────
|
|
|
|
// TestGetBusinessSettings verifies that GET /api/admin/settings returns the
|
|
// current business settings row.
|
|
func TestGetBusinessSettings(t *testing.T) {
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET business_name = 'Test Salon', business_address = '123 Test St', currency_code = 'GBP', gift_card_expiry_months = 12, voucher_type = 'SPV'`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed business settings: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(GetBusinessSettings)
|
|
w := makeAdminRequest(handler, "GET", "/api/admin/settings", nil, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if s.BusinessName != "Test Salon" {
|
|
t.Errorf("expected BusinessName 'Test Salon', got '%s'", s.BusinessName)
|
|
}
|
|
if s.BusinessAddress != "123 Test St" {
|
|
t.Errorf("expected BusinessAddress '123 Test St', got '%s'", s.BusinessAddress)
|
|
}
|
|
if s.CurrencyCode != "GBP" {
|
|
t.Errorf("expected CurrencyCode 'GBP', got '%s'", s.CurrencyCode)
|
|
}
|
|
if s.GiftCardExpiryMonths != 12 {
|
|
t.Errorf("expected GiftCardExpiryMonths 12, got %d", s.GiftCardExpiryMonths)
|
|
}
|
|
if s.VoucherType != "SPV" {
|
|
t.Errorf("expected VoucherType 'SPV', got '%s'", s.VoucherType)
|
|
}
|
|
}
|
|
|
|
// ─── Successful updates ──────────────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_SingleField verifies updating a single field (business_name).
|
|
func TestUpdateBusinessSettings_SingleField(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessName: stringPtr("Updated Salon Name"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if s.BusinessName != "Updated Salon Name" {
|
|
t.Errorf("expected BusinessName 'Updated Salon Name', got '%s'", s.BusinessName)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_MultipleFields verifies that updating several fields
|
|
// at once works correctly.
|
|
func TestUpdateBusinessSettings_MultipleFields(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessName: stringPtr("Multi Update Salon"),
|
|
BusinessAddress: stringPtr("456 New St"),
|
|
BusinessPhone: stringPtr("+441234567890"),
|
|
BusinessEmail: stringPtr("salon@example.com"),
|
|
WebsiteURL: stringPtr("https://salon.example.com"),
|
|
GiftCardExpiryMonths: intPtr(24),
|
|
VoucherType: stringPtr("MPV"),
|
|
DefaultVATRate: float64Ptr(20.00),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if s.BusinessName != "Multi Update Salon" {
|
|
t.Errorf("expected BusinessName 'Multi Update Salon', got '%s'", s.BusinessName)
|
|
}
|
|
if s.BusinessAddress != "456 New St" {
|
|
t.Errorf("expected BusinessAddress '456 New St', got '%s'", s.BusinessAddress)
|
|
}
|
|
if s.BusinessPhone == nil || *s.BusinessPhone != "+441234567890" {
|
|
t.Errorf("expected BusinessPhone '+441234567890', got %v", s.BusinessPhone)
|
|
}
|
|
if s.BusinessEmail == nil || *s.BusinessEmail != "salon@example.com" {
|
|
t.Errorf("expected BusinessEmail 'salon@example.com', got %v", s.BusinessEmail)
|
|
}
|
|
if s.WebsiteURL == nil || *s.WebsiteURL != "https://salon.example.com" {
|
|
t.Errorf("expected WebsiteURL 'https://salon.example.com', got %v", s.WebsiteURL)
|
|
}
|
|
if s.GiftCardExpiryMonths != 24 {
|
|
t.Errorf("expected GiftCardExpiryMonths 24, got %d", s.GiftCardExpiryMonths)
|
|
}
|
|
if s.VoucherType != "MPV" {
|
|
t.Errorf("expected VoucherType 'MPV', got '%s'", s.VoucherType)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_PartialUpdate verifies that updating a single field
|
|
// leaves other fields unchanged.
|
|
func TestUpdateBusinessSettings_PartialUpdate(t *testing.T) {
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET business_name = 'Test Salon', business_address = '123 Test St', currency_code = 'GBP', gift_card_expiry_months = 12, voucher_type = 'SPV'`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed business settings: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
GiftCardExpiryMonths: intPtr(36),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if s.GiftCardExpiryMonths != 36 {
|
|
t.Errorf("expected GiftCardExpiryMonths 36, got %d", s.GiftCardExpiryMonths)
|
|
}
|
|
if s.BusinessName != "Test Salon" {
|
|
t.Errorf("expected BusinessName 'Test Salon' (unchanged), got '%s'", s.BusinessName)
|
|
}
|
|
if s.BusinessAddress != "123 Test St" {
|
|
t.Errorf("expected BusinessAddress '123 Test St' (unchanged), got '%s'", s.BusinessAddress)
|
|
}
|
|
if s.VoucherType != "SPV" {
|
|
t.Errorf("expected VoucherType 'SPV' (unchanged), got '%s'", s.VoucherType)
|
|
}
|
|
if s.CurrencyCode != "GBP" {
|
|
t.Errorf("expected CurrencyCode 'GBP' (unchanged), got '%s'", s.CurrencyCode)
|
|
}
|
|
}
|
|
|
|
// ─── Empty request ───────────────────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_NoFields verifies that an empty request body
|
|
// (no fields to update) returns 400.
|
|
func TestUpdateBusinessSettings_NoFields(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", UpdateBusinessSettingsRequest{}, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if w.Body.String() != "No fields to update\n" {
|
|
t.Errorf("unexpected error message: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// ─── business_name validation ────────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_Name_Empty rejects an empty business_name.
|
|
func TestUpdateBusinessSettings_Name_Empty(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessName: stringPtr(""),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for empty name, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "business_name") {
|
|
t.Errorf("expected error about business_name, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Name_TooLong rejects a business_name > 255 chars.
|
|
func TestUpdateBusinessSettings_Name_TooLong(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
longName := strings.Repeat("A", 256)
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessName: stringPtr(longName),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for long name, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "business_name") {
|
|
t.Errorf("expected error about business_name, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Name_Boundary accepts a 255-char name (DB max).
|
|
func TestUpdateBusinessSettings_Name_Boundary(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
name := strings.Repeat("A", 255)
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessName: stringPtr(name),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for 255-char name, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.BusinessName != name {
|
|
t.Errorf("expected business name to match, got %q", s.BusinessName)
|
|
}
|
|
}
|
|
|
|
// ─── business_address validation ─────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_Address_Empty rejects an empty business_address.
|
|
func TestUpdateBusinessSettings_Address_Empty(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessAddress: stringPtr(""),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for empty address, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "business_address") {
|
|
t.Errorf("expected error about business_address, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// ─── business_phone validation ───────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_Phone_TooLong rejects a phone > 20 chars.
|
|
func TestUpdateBusinessSettings_Phone_TooLong(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessPhone: stringPtr("+44" + strings.Repeat("1", 18)),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for long phone, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "business_phone") {
|
|
t.Errorf("expected error about business_phone, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Phone_Boundary accepts a 20-char phone (DB max).
|
|
func TestUpdateBusinessSettings_Phone_Boundary(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
phone := "+44" + strings.Repeat("1", 16) // 19 chars — within limit
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessPhone: stringPtr(phone),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for valid phone, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.BusinessPhone == nil || *s.BusinessPhone != phone {
|
|
t.Errorf("expected phone %q, got %v", phone, s.BusinessPhone)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Phone_Null clears phone by setting it to null.
|
|
func TestUpdateBusinessSettings_Phone_Null(t *testing.T) {
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
// Seed a phone value first
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET business_phone = '+441234567890'`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed phone: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
nullPhone := ""
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessPhone: &nullPhone,
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for clearing phone, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.BusinessPhone == nil || *s.BusinessPhone != "" {
|
|
t.Errorf("expected BusinessPhone to be cleared, got %v", s.BusinessPhone)
|
|
}
|
|
}
|
|
|
|
// ─── business_email validation ───────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_Email_TooLong rejects an email > 254 chars.
|
|
func TestUpdateBusinessSettings_Email_TooLong(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
local := strings.Repeat("a", 250)
|
|
email := local + "@b.co"
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessEmail: stringPtr(email),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for long email, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "business_email") {
|
|
t.Errorf("expected error about business_email, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Email_Null clears email by setting it to null.
|
|
func TestUpdateBusinessSettings_Email_Null(t *testing.T) {
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET business_email = 'test@example.com'`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed email: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
nullEmail := ""
|
|
body := UpdateBusinessSettingsRequest{
|
|
BusinessEmail: &nullEmail,
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for clearing email, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.BusinessEmail == nil || *s.BusinessEmail != "" {
|
|
t.Errorf("expected BusinessEmail to be cleared, got %v", s.BusinessEmail)
|
|
}
|
|
}
|
|
|
|
// ─── vat_registration_number validation ──────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_VatNumber_TooLong rejects a VAT number > 20 chars.
|
|
func TestUpdateBusinessSettings_VatNumber_TooLong(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
VATRegistrationNumber: stringPtr(strings.Repeat("A", 21)),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for long VAT number, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "vat_registration_number") {
|
|
t.Errorf("expected error about vat_registration_number, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_VatNumber_Boundary accepts a valid UK VAT number.
|
|
func TestUpdateBusinessSettings_VatNumber_Boundary(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
// Standard UK VAT number: GB + 9 digits
|
|
vatNum := "GB123456789"
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
VATRegistrationNumber: stringPtr(vatNum),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for valid VAT number, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.VATRegistrationNumber == nil || *s.VATRegistrationNumber != vatNum {
|
|
t.Errorf("expected VAT number %q, got %v", vatNum, s.VATRegistrationNumber)
|
|
}
|
|
|
|
// UK branch VAT number: GB + 12 digits
|
|
branchVat := "GB123456789012"
|
|
body2 := UpdateBusinessSettingsRequest{
|
|
VATRegistrationNumber: stringPtr(branchVat),
|
|
}
|
|
w2 := makeAdminRequest(handler, "PUT", "/api/admin/settings", body2, ctx)
|
|
if w2.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for valid branch VAT number, got %d. body: %s", w2.Code, w2.Body.String())
|
|
}
|
|
}
|
|
|
|
// ─── website_url validation ──────────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_Website_NoScheme rejects a URL without http/https.
|
|
func TestUpdateBusinessSettings_Website_NoScheme(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
WebsiteURL: stringPtr("www.example.com"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for no scheme, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), "website_url") {
|
|
t.Errorf("expected error about website_url, got: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Website_FTPScheme rejects a URL with ftp://.
|
|
func TestUpdateBusinessSettings_Website_FTPScheme(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
WebsiteURL: stringPtr("ftp://files.example.com"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for ftp scheme, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Website_EmptyString clears the website URL.
|
|
func TestUpdateBusinessSettings_Website_EmptyString(t *testing.T) {
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET website_url = 'https://example.com'`)
|
|
if err != nil {
|
|
t.Fatalf("failed to seed website: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
empty := ""
|
|
body := UpdateBusinessSettingsRequest{
|
|
WebsiteURL: &empty,
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for clearing website, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.WebsiteURL != nil && *s.WebsiteURL != "" {
|
|
t.Errorf("expected WebsiteURL to be cleared, got %v", *s.WebsiteURL)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Website_ValidHTTPS accepts a valid https URL.
|
|
func TestUpdateBusinessSettings_Website_ValidHTTPS(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
WebsiteURL: stringPtr("https://www.example.com"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for valid website, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.WebsiteURL == nil || *s.WebsiteURL != "https://www.example.com" {
|
|
t.Errorf("expected website URL, got %v", s.WebsiteURL)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_Website_ValidHTTP accepts a valid http URL.
|
|
func TestUpdateBusinessSettings_Website_ValidHTTP(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
WebsiteURL: stringPtr("http://localhost:3000"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for valid website, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// ─── voucher_type validation ─────────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_InvalidVoucherType verifies that an invalid
|
|
// voucher_type value returns 400.
|
|
func TestUpdateBusinessSettings_InvalidVoucherType(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
VoucherType: stringPtr("INVALID"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if w.Body.String() != "voucher_type must be 'SPV' or 'MPV'\n" {
|
|
t.Errorf("unexpected error message: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_InvalidVoucherType_Empty rejects an empty voucher_type.
|
|
func TestUpdateBusinessSettings_InvalidVoucherType_Empty(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
VoucherType: stringPtr(""),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for empty voucher_type, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_VoucherType_SPV accepts SPV.
|
|
func TestUpdateBusinessSettings_VoucherType_SPV(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
VoucherType: stringPtr("SPV"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for SPV, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.VoucherType != "SPV" {
|
|
t.Errorf("expected VoucherType 'SPV', got '%s'", s.VoucherType)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_VoucherType_MPV accepts MPV.
|
|
func TestUpdateBusinessSettings_VoucherType_MPV(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
VoucherType: stringPtr("MPV"),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for MPV, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.VoucherType != "MPV" {
|
|
t.Errorf("expected VoucherType 'MPV', got '%s'", s.VoucherType)
|
|
}
|
|
}
|
|
|
|
// ─── gift_card_expiry_months validation ──────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_NegativeExpiryMonths verifies that a
|
|
// gift_card_expiry_months value less than 1 returns 400.
|
|
func TestUpdateBusinessSettings_NegativeExpiryMonths(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
GiftCardExpiryMonths: intPtr(0),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if w.Body.String() != "gift_card_expiry_months must be at least 1\n" {
|
|
t.Errorf("unexpected error message: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_ExpiryMonths_Negative rejects negative values.
|
|
func TestUpdateBusinessSettings_ExpiryMonths_Negative(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
GiftCardExpiryMonths: intPtr(-5),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for negative expiry, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if w.Body.String() != "gift_card_expiry_months must be at least 1\n" {
|
|
t.Errorf("unexpected error message: %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_ExpiryMonths_LargeValue accepts a large but valid value.
|
|
func TestUpdateBusinessSettings_ExpiryMonths_LargeValue(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
GiftCardExpiryMonths: intPtr(9999),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for large expiry, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// ─── default_vat_rate validation ─────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_InvalidVATRate verifies that a default_vat_rate
|
|
// outside the 0-100 range returns 400.
|
|
func TestUpdateBusinessSettings_InvalidVATRate(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
|
|
body := UpdateBusinessSettingsRequest{
|
|
DefaultVATRate: float64Ptr(-1),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for negative rate, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
if w.Body.String() != "default_vat_rate must be between 0 and 100\n" {
|
|
t.Errorf("unexpected error message: %s", w.Body.String())
|
|
}
|
|
|
|
body2 := UpdateBusinessSettingsRequest{
|
|
DefaultVATRate: float64Ptr(101),
|
|
}
|
|
w2 := makeAdminRequest(handler, "PUT", "/api/admin/settings", body2, ctx)
|
|
|
|
if w2.Code != http.StatusBadRequest {
|
|
t.Errorf("expected status 400 for rate > 100, got %d. body: %s", w2.Code, w2.Body.String())
|
|
}
|
|
if w2.Body.String() != "default_vat_rate must be between 0 and 100\n" {
|
|
t.Errorf("unexpected error message: %s", w2.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_VATRate_Boundary_Zero accepts 0% VAT rate.
|
|
func TestUpdateBusinessSettings_VATRate_Boundary_Zero(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
DefaultVATRate: float64Ptr(0),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for 0%% VAT rate, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_VATRate_Boundary_100 accepts 100% VAT rate.
|
|
func TestUpdateBusinessSettings_VATRate_Boundary_100(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
DefaultVATRate: float64Ptr(100),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for 100%% VAT rate, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_VATRate_Fractional accepts fractional VAT rates.
|
|
func TestUpdateBusinessSettings_VATRate_Fractional(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
body := UpdateBusinessSettingsRequest{
|
|
DefaultVATRate: float64Ptr(5.5),
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 for 5.5%% VAT rate, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.DefaultVATRate != 5.5 {
|
|
t.Errorf("expected DefaultVATRate 5.5, got %f", s.DefaultVATRate)
|
|
}
|
|
}
|
|
|
|
// ─── is_vat_registered toggle ────────────────────────────────────────────────
|
|
|
|
// TestUpdateBusinessSettings_EnableVATRegistration enables VAT registration.
|
|
func TestUpdateBusinessSettings_EnableVATRegistration(t *testing.T) {
|
|
ctx, _ := testutils.SetupTestTx(t)
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
trueVal := true
|
|
vatRate := 20.0
|
|
vatNum := "GB123456789"
|
|
body := UpdateBusinessSettingsRequest{
|
|
IsVATRegistered: &trueVal,
|
|
DefaultVATRate: &vatRate,
|
|
VATRegistrationNumber: &vatNum,
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 when enabling VAT, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if !s.IsVATRegistered {
|
|
t.Errorf("expected IsVATRegistered to be true")
|
|
}
|
|
if s.DefaultVATRate != 20.0 {
|
|
t.Errorf("expected DefaultVATRate 20.0, got %f", s.DefaultVATRate)
|
|
}
|
|
if s.VATRegistrationNumber == nil || *s.VATRegistrationNumber != "GB123456789" {
|
|
t.Errorf("expected VATRegistrationNumber 'GB123456789', got %v", s.VATRegistrationNumber)
|
|
}
|
|
}
|
|
|
|
// TestUpdateBusinessSettings_DisableVATRegistration disables VAT registration.
|
|
// ─── validateURL tests ───────────────────────────────────────────────────────
|
|
|
|
func TestValidateURL_ValidHTTPS(t *testing.T) {
|
|
if err := validateURL("https://example.com"); err != nil {
|
|
t.Errorf("expected no error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_ValidHTTP(t *testing.T) {
|
|
if err := validateURL("http://example.com"); err != nil {
|
|
t.Errorf("expected no error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_ValidHTTP_Localhost(t *testing.T) {
|
|
if err := validateURL("http://localhost:3000"); err != nil {
|
|
t.Errorf("expected no error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_ValidHTTPS_WithPath(t *testing.T) {
|
|
if err := validateURL("https://example.com/path/to/page?q=1"); err != nil {
|
|
t.Errorf("expected no error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_NoScheme(t *testing.T) {
|
|
err := validateURL("www.example.com")
|
|
if err == nil {
|
|
t.Fatal("expected error for missing scheme")
|
|
}
|
|
if !strings.Contains(err.Error(), "http") {
|
|
t.Errorf("expected error mentioning http/https, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_FTPScheme(t *testing.T) {
|
|
err := validateURL("ftp://files.example.com")
|
|
if err == nil {
|
|
t.Fatal("expected error for ftp scheme")
|
|
}
|
|
if !strings.Contains(err.Error(), "http") {
|
|
t.Errorf("expected error mentioning http/https, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_EmptyHost(t *testing.T) {
|
|
err := validateURL("https://")
|
|
if err == nil {
|
|
t.Fatal("expected error for empty host")
|
|
}
|
|
if !strings.Contains(err.Error(), "host") {
|
|
t.Errorf("expected error about host, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_InvalidString(t *testing.T) {
|
|
err := validateURL("not a url at all")
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid URL")
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_EmptyString(t *testing.T) {
|
|
err := validateURL("")
|
|
// url.Parse("") returns a valid url.URL with empty Scheme
|
|
// So validateURL should reject it (no scheme, no host)
|
|
if err == nil {
|
|
t.Fatal("expected error for empty string")
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_HTTPSOnlyScheme(t *testing.T) {
|
|
if err := validateURL("https://"); err == nil {
|
|
t.Fatal("expected error for https:// with no host")
|
|
}
|
|
}
|
|
|
|
func TestValidateURL_IPAddress(t *testing.T) {
|
|
if err := validateURL("https://192.168.1.1"); err != nil {
|
|
t.Errorf("expected no error for HTTPS IP, got %v", err)
|
|
}
|
|
}
|
|
|
|
// ─── Disable VAT registration ─────────────────────────────────────────────────
|
|
|
|
func TestUpdateBusinessSettings_DisableVATRegistration(t *testing.T) {
|
|
ctx, tx := testutils.SetupTestTx(t)
|
|
|
|
_, err := tx.Exec(ctx, `UPDATE business_settings SET is_vat_registered = TRUE, default_vat_rate = 20.00`)
|
|
if err != nil {
|
|
t.Fatalf("failed to enable VAT: %v", err)
|
|
}
|
|
|
|
handler := http.HandlerFunc(UpdateBusinessSettings)
|
|
falseVal := false
|
|
body := UpdateBusinessSettingsRequest{
|
|
IsVATRegistered: &falseVal,
|
|
}
|
|
w := makeAdminRequest(handler, "PUT", "/api/admin/settings", body, ctx)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected status 200 when disabling VAT, got %d. body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var s BusinessSettings
|
|
if err := parseResponseBody(w, &s); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
if s.IsVATRegistered {
|
|
t.Errorf("expected IsVATRegistered to be false")
|
|
}
|
|
}
|
|
|
|
func TestGetPublicBusinessInfo_DBError(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
req := httptest.NewRequest("GET", "/business-info", nil).WithContext(ctx)
|
|
w := httptest.NewRecorder()
|
|
GetPublicBusinessInfo(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
func TestGetBusinessSettings_DBError(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
req := httptest.NewRequest("GET", "/api/admin/settings", nil)
|
|
req = req.WithContext(ctx)
|
|
w := httptest.NewRecorder()
|
|
GetBusinessSettings(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|