Add unit tests for middleware, models, services, handlers, and repository helpers
All checks were successful
CI / ci (push) Successful in 35s
All checks were successful
CI / ci (push) Successful in 35s
This commit is contained in:
97
internal/service/auth_test.go
Normal file
97
internal/service/auth_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func TestAuthService_GenerateAndValidateToken(t *testing.T) {
|
||||
s := &AuthService{jwtSecret: "test-secret"}
|
||||
|
||||
t.Run("valid access token", func(t *testing.T) {
|
||||
tokenStr, err := s.generateToken(1, "access", 15*time.Minute)
|
||||
if err != nil {
|
||||
t.Fatalf("generateToken error: %v", err)
|
||||
}
|
||||
|
||||
claims, err := s.validateToken(tokenStr, "access")
|
||||
if err != nil {
|
||||
t.Fatalf("validateToken error: %v", err)
|
||||
}
|
||||
|
||||
userID, ok := claims["user_id"].(float64)
|
||||
if !ok || int64(userID) != 1 {
|
||||
t.Errorf("expected user_id 1, got %v", claims["user_id"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong token type rejected", func(t *testing.T) {
|
||||
tokenStr, _ := s.generateToken(1, "refresh", time.Hour)
|
||||
_, err := s.validateToken(tokenStr, "access")
|
||||
if err != ErrInvalidToken {
|
||||
t.Errorf("expected ErrInvalidToken, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("expired token rejected", func(t *testing.T) {
|
||||
tokenStr, _ := s.generateToken(1, "access", -time.Hour)
|
||||
_, err := s.validateToken(tokenStr, "access")
|
||||
if err != ErrInvalidToken {
|
||||
t.Errorf("expected ErrInvalidToken, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong secret rejected", func(t *testing.T) {
|
||||
otherService := &AuthService{jwtSecret: "other-secret"}
|
||||
tokenStr, _ := otherService.generateToken(1, "access", time.Hour)
|
||||
_, err := s.validateToken(tokenStr, "access")
|
||||
if err != ErrInvalidToken {
|
||||
t.Errorf("expected ErrInvalidToken, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("tampered token rejected", func(t *testing.T) {
|
||||
tokenStr, _ := s.generateToken(1, "access", time.Hour)
|
||||
_, err := s.validateToken(tokenStr+"x", "access")
|
||||
if err != ErrInvalidToken {
|
||||
t.Errorf("expected ErrInvalidToken, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("HMAC signing method accepted", func(t *testing.T) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"user_id": float64(1),
|
||||
"type": "access",
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
})
|
||||
tokenStr, _ := token.SignedString([]byte("test-secret"))
|
||||
|
||||
claims, err := s.validateToken(tokenStr, "access")
|
||||
if err != nil {
|
||||
t.Fatalf("should accept HS256: %v", err)
|
||||
}
|
||||
if claims["type"] != "access" {
|
||||
t.Error("claims type mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrWeakPassword(t *testing.T) {
|
||||
if ErrWeakPassword.Error() != "password must be at least 8 characters" {
|
||||
t.Errorf("unexpected error message: %s", ErrWeakPassword.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrInvalidCredentials(t *testing.T) {
|
||||
if ErrInvalidCredentials.Error() != "invalid credentials" {
|
||||
t.Errorf("unexpected error message: %s", ErrInvalidCredentials.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrEmailNotVerified(t *testing.T) {
|
||||
if ErrEmailNotVerified.Error() != "email not verified" {
|
||||
t.Errorf("unexpected error message: %s", ErrEmailNotVerified.Error())
|
||||
}
|
||||
}
|
||||
35
internal/service/helpers_test.go
Normal file
35
internal/service/helpers_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package service
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDefaultString(t *testing.T) {
|
||||
tests := []struct {
|
||||
val, def, want string
|
||||
}{
|
||||
{"hello", "default", "hello"},
|
||||
{"", "default", "default"},
|
||||
{"", "", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := defaultString(tt.val, tt.def)
|
||||
if got != tt.want {
|
||||
t.Errorf("defaultString(%q, %q) = %q, want %q", tt.val, tt.def, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
val, def, want int
|
||||
}{
|
||||
{5, 10, 5},
|
||||
{0, 10, 10},
|
||||
{0, 0, 0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := defaultInt(tt.val, tt.def)
|
||||
if got != tt.want {
|
||||
t.Errorf("defaultInt(%d, %d) = %d, want %d", tt.val, tt.def, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
internal/service/interest_test.go
Normal file
66
internal/service/interest_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/daniil/homelab-api/internal/model"
|
||||
)
|
||||
|
||||
func TestCalculateInterestForDeposit_NotDeposit(t *testing.T) {
|
||||
s := &InterestService{}
|
||||
deposit := &model.SavingsCategory{IsDeposit: false}
|
||||
result, err := s.CalculateInterestForDeposit(deposit)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if result != "" {
|
||||
t.Errorf("expected empty result for non-deposit, got %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateInterestForDeposit_ZeroRate(t *testing.T) {
|
||||
s := &InterestService{}
|
||||
deposit := &model.SavingsCategory{IsDeposit: true, InterestRate: 0}
|
||||
result, err := s.CalculateInterestForDeposit(deposit)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if result != "" {
|
||||
t.Errorf("expected empty result for zero rate, got %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateInterestForDeposit_NoStartDate(t *testing.T) {
|
||||
s := &InterestService{}
|
||||
deposit := &model.SavingsCategory{
|
||||
IsDeposit: true,
|
||||
InterestRate: 10,
|
||||
DepositStartDate: sql.NullTime{Valid: false},
|
||||
}
|
||||
_, err := s.CalculateInterestForDeposit(deposit)
|
||||
if err == nil {
|
||||
t.Error("expected error for missing start date")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateInterestForDeposit_ExpiredDeposit(t *testing.T) {
|
||||
s := &InterestService{}
|
||||
deposit := &model.SavingsCategory{
|
||||
IsDeposit: true,
|
||||
InterestRate: 10,
|
||||
DepositTerm: 3, // 3 months
|
||||
DepositStartDate: sql.NullTime{
|
||||
Time: time.Now().AddDate(0, -6, 0), // 6 months ago
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
result, err := s.CalculateInterestForDeposit(deposit)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if result != "" {
|
||||
t.Errorf("expected empty result for expired deposit, got %q", result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user