- Kompletna Go implementacija licencnog servera (19 Go fajlova) - Klijentski API: activate, deactivate, validate - Admin API: CRUD licence, stats, audit log - Admin dashboard: htmx + Go templates - RSA-2048 potpisivanje licencnih podataka - Rate limiting i API key autentifikacija - MySQL migracije i seed podaci (ESIR, ARV, LIGHT_TICKET) - Unit testovi: keygen, crypto, model, middleware (24 testa) - Dokumentacija: SPEC.md, ARCHITECTURE.md, SETUP.md, API.md, TESTING.md, README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
222 lines
5.0 KiB
Go
222 lines
5.0 KiB
Go
package model
|
|
|
|
import (
|
|
"database/sql"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestIsExpired_Perpetual(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{Valid: false},
|
|
}
|
|
if l.IsExpired() {
|
|
t.Error("PERPETUAL licenca ne sme biti istekla")
|
|
}
|
|
}
|
|
|
|
func TestIsExpired_Active(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{
|
|
Time: time.Now().Add(24 * time.Hour),
|
|
Valid: true,
|
|
},
|
|
}
|
|
if l.IsExpired() {
|
|
t.Error("licenca koja istice sutra ne sme biti istekla")
|
|
}
|
|
}
|
|
|
|
func TestIsExpired_Expired(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{
|
|
Time: time.Now().Add(-24 * time.Hour),
|
|
Valid: true,
|
|
},
|
|
}
|
|
if !l.IsExpired() {
|
|
t.Error("licenca koja je istekla juce mora biti istekla")
|
|
}
|
|
}
|
|
|
|
func TestIsInGrace_NotExpired(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{
|
|
Time: time.Now().Add(24 * time.Hour),
|
|
Valid: true,
|
|
},
|
|
GraceDays: 30,
|
|
}
|
|
if l.IsInGrace() {
|
|
t.Error("licenca koja nije istekla ne sme biti u grace periodu")
|
|
}
|
|
}
|
|
|
|
func TestIsInGrace_InGrace(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{
|
|
Time: time.Now().Add(-5 * 24 * time.Hour), // istekla pre 5 dana
|
|
Valid: true,
|
|
},
|
|
GraceDays: 30,
|
|
}
|
|
if !l.IsInGrace() {
|
|
t.Error("licenca istekla pre 5 dana sa 30 dana grace-a mora biti u grace-u")
|
|
}
|
|
}
|
|
|
|
func TestIsInGrace_GraceExpired(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{
|
|
Time: time.Now().Add(-35 * 24 * time.Hour), // istekla pre 35 dana
|
|
Valid: true,
|
|
},
|
|
GraceDays: 30,
|
|
}
|
|
if l.IsInGrace() {
|
|
t.Error("licenca kojoj je grace prosao ne sme biti u grace-u")
|
|
}
|
|
}
|
|
|
|
func TestIsInGrace_Perpetual(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{Valid: false},
|
|
GraceDays: 30,
|
|
}
|
|
if l.IsInGrace() {
|
|
t.Error("PERPETUAL licenca ne sme biti u grace periodu")
|
|
}
|
|
}
|
|
|
|
func TestIsGraceExpired(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{
|
|
Time: time.Now().Add(-40 * 24 * time.Hour),
|
|
Valid: true,
|
|
},
|
|
GraceDays: 30,
|
|
}
|
|
if !l.IsGraceExpired() {
|
|
t.Error("grace period je prosao, mora biti expired")
|
|
}
|
|
}
|
|
|
|
func TestIsGraceExpired_Perpetual(t *testing.T) {
|
|
l := &License{
|
|
ExpiresAt: sql.NullTime{Valid: false},
|
|
GraceDays: 30,
|
|
}
|
|
if l.IsGraceExpired() {
|
|
t.Error("PERPETUAL licenca ne moze imati istekao grace")
|
|
}
|
|
}
|
|
|
|
func TestMaskedKey(t *testing.T) {
|
|
tests := []struct {
|
|
key string
|
|
prefix string
|
|
expected string
|
|
}{
|
|
{"LT-K7M2-9P4N-R3W8-J6T1", "LT-", "LT-K7M2-****-****-J6T1"},
|
|
{"ESIR-A3B5-C8D2-E7F4-G9H6", "ESIR-", "ESIR-A3B5-****-****-G9H6"},
|
|
{"ARV-X2Y3-Z4A5-B6C7-D8E9", "ARV-", "ARV-X2Y3-****-****-D8E9"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
l := &License{LicenseKey: tt.key, ProductPrefix: tt.prefix}
|
|
got := l.MaskedKey()
|
|
if got != tt.expected {
|
|
t.Errorf("MaskedKey(%q) = %q, ocekivano %q", tt.key, got, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStatusText(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
license License
|
|
expected string
|
|
}{
|
|
{
|
|
name: "revoked",
|
|
license: License{Revoked: true, Active: true},
|
|
expected: "Opozvana",
|
|
},
|
|
{
|
|
name: "inactive",
|
|
license: License{Active: false},
|
|
expected: "Neaktivna",
|
|
},
|
|
{
|
|
name: "active",
|
|
license: License{
|
|
Active: true,
|
|
ExpiresAt: sql.NullTime{Time: time.Now().Add(24 * time.Hour), Valid: true},
|
|
},
|
|
expected: "Aktivna",
|
|
},
|
|
{
|
|
name: "trial",
|
|
license: License{
|
|
Active: true,
|
|
LicenseType: "TRIAL",
|
|
ExpiresAt: sql.NullTime{Time: time.Now().Add(24 * time.Hour), Valid: true},
|
|
},
|
|
expected: "Trial",
|
|
},
|
|
{
|
|
name: "in_grace",
|
|
license: License{
|
|
Active: true,
|
|
ExpiresAt: sql.NullTime{Time: time.Now().Add(-5 * 24 * time.Hour), Valid: true},
|
|
GraceDays: 30,
|
|
},
|
|
expected: "Grace period",
|
|
},
|
|
{
|
|
name: "grace_expired",
|
|
license: License{
|
|
Active: true,
|
|
ExpiresAt: sql.NullTime{Time: time.Now().Add(-40 * 24 * time.Hour), Valid: true},
|
|
GraceDays: 30,
|
|
},
|
|
expected: "Istekla (grace)",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := tt.license.StatusText()
|
|
if got != tt.expected {
|
|
t.Errorf("StatusText() = %q, ocekivano %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStatusClass(t *testing.T) {
|
|
l := &License{Active: true, ExpiresAt: sql.NullTime{Time: time.Now().Add(24 * time.Hour), Valid: true}}
|
|
if l.StatusClass() != "status-active" {
|
|
t.Errorf("StatusClass() = %q, ocekivano 'status-active'", l.StatusClass())
|
|
}
|
|
|
|
l2 := &License{Revoked: true, Active: true}
|
|
if l2.StatusClass() != "status-revoked" {
|
|
t.Errorf("StatusClass() = %q, ocekivano 'status-revoked'", l2.StatusClass())
|
|
}
|
|
}
|
|
|
|
func TestExpiresAtFormatted(t *testing.T) {
|
|
l := &License{ExpiresAt: sql.NullTime{Valid: false}}
|
|
if l.ExpiresAtFormatted() != "Neograniceno" {
|
|
t.Errorf("ocekivano 'Neograniceno', dobijeno %q", l.ExpiresAtFormatted())
|
|
}
|
|
|
|
date := time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC)
|
|
l2 := &License{ExpiresAt: sql.NullTime{Time: date, Valid: true}}
|
|
expected := "01.04.2026"
|
|
if l2.ExpiresAtFormatted() != expected {
|
|
t.Errorf("ocekivano %q, dobijeno %q", expected, l2.ExpiresAtFormatted())
|
|
}
|
|
}
|