Files
2026-05-05 08:44:55 +00:00

173 lines
4.5 KiB
Go

package client_test
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"gitea.tecamino.local/paadi/licenseServer/client"
"gitea.tecamino.local/paadi/licenseServer/client/hwid"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
)
const testMachineID = "aabbccdd11223344aabbccdd11223344aabbccdd11223344aabbccdd11223344"
var testSecret = []byte("test-cache-secret-32bytes-minimum!")
func newTestServer(valid bool, message string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"valid": valid, "message": message})
}))
}
func newClient(t *testing.T, serverURL string) *client.Client {
t.Helper()
c, err := client.New(client.Config{
ServerURL: serverURL,
LicenseKey: "TEST-1234-ABCD-5678",
AppName: "lictest_" + t.Name(),
GracePeriod: 7 * 24 * time.Hour,
CacheSecret: testSecret,
}, testMachineID)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.Remove(c.CachePath()) })
return c
}
func signCache(key []byte, licenseKey, machineID, ts string) string {
payload := licenseKey + "|" + machineID + "|" + ts
mac := hmac.New(sha256.New, key)
mac.Write([]byte(payload))
return hex.EncodeToString(mac.Sum(nil))
}
func writeCache(t *testing.T, path string, licenseKey, machineID string, validatedAt time.Time) {
t.Helper()
ts := validatedAt.UTC().Format(time.RFC3339)
entry := map[string]any{
"license_key": licenseKey,
"machine_id": machineID,
"last_validated": ts,
"sig": signCache(testSecret, licenseKey, machineID, ts),
}
os.MkdirAll(filepath.Dir(path), 0700)
data, _ := json.Marshal(entry)
os.WriteFile(path, data, 0600)
}
func TestOnlineValid(t *testing.T) {
srv := newTestServer(true, "")
defer srv.Close()
result := newClient(t, srv.URL).Check()
if !result.Valid || result.Offline {
t.Fatalf("expected online valid, got: %s", result.Message)
}
}
func TestOnlineInvalid(t *testing.T) {
srv := newTestServer(false, "license revoked")
defer srv.Close()
result := newClient(t, srv.URL).Check()
if result.Valid {
t.Fatal("expected invalid")
}
if result.Message != "license revoked" {
t.Fatalf("unexpected message: %s", result.Message)
}
}
func TestOnlineValidSavesCache(t *testing.T) {
srv := newTestServer(true, "")
defer srv.Close()
c := newClient(t, srv.URL)
c.Check()
if _, err := os.Stat(c.CachePath()); err != nil {
t.Fatal("expected cache file to be created after online validation")
}
}
func TestOfflineWithinGrace(t *testing.T) {
srv := newTestServer(true, "")
c := newClient(t, srv.URL)
c.Check()
srv.Close()
result := c.Check()
if !result.Valid || !result.Offline {
t.Fatalf("expected offline valid in grace period, got: %s", result.Message)
}
if result.GraceLeft <= 0 {
t.Fatal("expected positive grace remaining")
}
}
func TestOfflineExpiredGrace(t *testing.T) {
c := newClient(t, "http://127.0.0.1:19999")
writeCache(t, c.CachePath(), "TEST-1234-ABCD-5678", testMachineID, time.Now().Add(-8*24*time.Hour))
result := c.Check()
if result.Valid {
t.Fatal("expected invalid after grace period expired")
}
}
func TestOfflineNoServerNoCache(t *testing.T) {
result := newClient(t, "http://127.0.0.1:19999").Check()
if result.Valid {
t.Fatal("expected invalid with no server and no cache")
}
}
func TestOfflineWrongKey(t *testing.T) {
c := newClient(t, "http://127.0.0.1:19999")
writeCache(t, c.CachePath(), "DIFF-ERENT-KEY0-0000", testMachineID, time.Now())
result := c.Check()
if result.Valid {
t.Fatal("expected invalid when cached key does not match")
}
}
func TestOfflineWrongMachine(t *testing.T) {
c := newClient(t, "http://127.0.0.1:19999")
writeCache(t, c.CachePath(), "TEST-1234-ABCD-5678", "different-machine-id", time.Now())
result := c.Check()
if result.Valid {
t.Fatal("expected invalid when cached machine does not match")
}
}
func TestTamperedCache(t *testing.T) {
srv := newTestServer(true, "")
c := newClient(t, srv.URL)
c.Check()
srv.Close()
data, _ := os.ReadFile(c.CachePath())
var entry map[string]any
json.Unmarshal(data, &entry)
entry["last_validated"] = time.Now().Add(10 * time.Second).Format(time.RFC3339)
tampered, _ := json.Marshal(entry)
os.WriteFile(c.CachePath(), tampered, 0600)
result := c.Check()
if result.Valid {
t.Fatal("tampered cache should be rejected")
}
}
func TestGetMachineHash(t *testing.T) {
fp, err := hwid.Collect()
if err != nil {
t.Fatal(err)
}
t.Log(fp.Hash())
}