initital commit
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
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())
|
||||
}
|
||||
Reference in New Issue
Block a user