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()) }