mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
- Add persistent volume mounts for Go/npm caches (faster rebuilds) - Add shell config with helpful aliases and custom prompt - Add comprehensive devcontainer documentation - Add pre-commit hooks for Go formatting and linting - Use go-version-file in CI workflows instead of hardcoded versions - Simplify docker compose commands with --wait flag - Add gitignore entries for devcontainer auth files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
202 lines
5.8 KiB
Go
202 lines
5.8 KiB
Go
package license
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestPersistence(t *testing.T) {
|
|
// Create a temporary directory for config
|
|
tmpDir, err := os.MkdirTemp("", "pulse-license-test-*")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
p, err := NewPersistence(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create persistence: %v", err)
|
|
}
|
|
|
|
testLicenseKey := "test-license-key-123"
|
|
|
|
t.Run("Save and Load", func(t *testing.T) {
|
|
err := p.Save(testLicenseKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save license: %v", err)
|
|
}
|
|
|
|
if !p.Exists() {
|
|
t.Error("License file should exist")
|
|
}
|
|
|
|
loadedKey, err := p.Load()
|
|
if err != nil {
|
|
t.Fatalf("Failed to load license: %v", err)
|
|
}
|
|
|
|
if loadedKey != testLicenseKey {
|
|
t.Errorf("Expected license key %s, got %s", testLicenseKey, loadedKey)
|
|
}
|
|
})
|
|
|
|
t.Run("Save and Load with Grace Period", func(t *testing.T) {
|
|
gracePeriodEnd := time.Now().Add(7 * 24 * time.Hour).Unix()
|
|
err := p.SaveWithGracePeriod(testLicenseKey, &gracePeriodEnd)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save license with grace period: %v", err)
|
|
}
|
|
|
|
persisted, err := p.LoadWithMetadata()
|
|
if err != nil {
|
|
t.Fatalf("Failed to load license with metadata: %v", err)
|
|
}
|
|
|
|
if persisted.LicenseKey != testLicenseKey {
|
|
t.Errorf("Expected license key %s, got %s", testLicenseKey, persisted.LicenseKey)
|
|
}
|
|
|
|
if persisted.GracePeriodEnd == nil || *persisted.GracePeriodEnd != gracePeriodEnd {
|
|
t.Errorf("Expected grace period end %v, got %v", gracePeriodEnd, persisted.GracePeriodEnd)
|
|
}
|
|
})
|
|
|
|
t.Run("Load non-existent", func(t *testing.T) {
|
|
tmpDirEmpty, _ := os.MkdirTemp("", "pulse-license-test-empty-*")
|
|
defer os.RemoveAll(tmpDirEmpty)
|
|
|
|
pEmpty, _ := NewPersistence(tmpDirEmpty)
|
|
key, err := pEmpty.Load()
|
|
if err != nil {
|
|
t.Fatalf("Expected no error for non-existent license, got %v", err)
|
|
}
|
|
if key != "" {
|
|
t.Errorf("Expected empty key, got %s", key)
|
|
}
|
|
})
|
|
|
|
t.Run("Delete", func(t *testing.T) {
|
|
err := p.Save(testLicenseKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save license: %v", err)
|
|
}
|
|
|
|
if !p.Exists() {
|
|
t.Fatal("License should exist before delete")
|
|
}
|
|
|
|
err = p.Delete()
|
|
if err != nil {
|
|
t.Fatalf("Failed to delete license: %v", err)
|
|
}
|
|
|
|
if p.Exists() {
|
|
t.Error("License should not exist after delete")
|
|
}
|
|
})
|
|
|
|
t.Run("Encryption check", func(t *testing.T) {
|
|
err := p.Save(testLicenseKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save license: %v", err)
|
|
}
|
|
|
|
// Read the file directly - it should be base64 encoded and encrypted
|
|
licensePath := filepath.Join(tmpDir, LicenseFileName)
|
|
data, err := os.ReadFile(licensePath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read license file: %v", err)
|
|
}
|
|
|
|
if string(data) == testLicenseKey {
|
|
t.Error("License file should be encrypted, not raw text")
|
|
}
|
|
|
|
// Ensure it's not JSON either in raw form
|
|
if data[0] == '{' {
|
|
t.Error("License file should be encrypted, not raw JSON")
|
|
}
|
|
})
|
|
|
|
t.Run("Decrypt with wrong key material", func(t *testing.T) {
|
|
err := p.Save(testLicenseKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save license: %v", err)
|
|
}
|
|
|
|
// Create a new persistence with different encryption key
|
|
pWrong := &Persistence{
|
|
configDir: tmpDir,
|
|
encryptionKey: "different-encryption-key",
|
|
machineID: "different-machine-id",
|
|
}
|
|
|
|
_, err = pWrong.Load()
|
|
if err == nil {
|
|
t.Error("Expected error when decrypting with wrong key material")
|
|
}
|
|
})
|
|
|
|
t.Run("Backwards compatibility with machine-id", func(t *testing.T) {
|
|
// This tests the scenario where a license was saved with machine-id
|
|
// (old behavior before persistent key feature) and we're now loading it
|
|
// with a different primary key but same machine-id as fallback
|
|
|
|
tmpDirCompat, _ := os.MkdirTemp("", "pulse-license-compat-*")
|
|
defer os.RemoveAll(tmpDirCompat)
|
|
|
|
testKey := "compat-test-key"
|
|
machineID := "test-machine-id-12345"
|
|
|
|
// Simulate old behavior: directly encrypt with machine-id (without calling Save
|
|
// which would create a persistent key). This is what old installations have.
|
|
pOld := &Persistence{
|
|
configDir: tmpDirCompat,
|
|
encryptionKey: machineID, // Old behavior used machine-id as encryption key
|
|
machineID: machineID,
|
|
}
|
|
|
|
// Manually encrypt and save without creating persistent key
|
|
persisted := PersistedLicense{LicenseKey: testKey}
|
|
jsonData, _ := json.Marshal(persisted)
|
|
encrypted, err := pOld.encrypt(jsonData)
|
|
if err != nil {
|
|
t.Fatalf("Failed to encrypt license: %v", err)
|
|
}
|
|
|
|
// Write directly to simulate old installation
|
|
os.MkdirAll(tmpDirCompat, 0700)
|
|
licensePath := filepath.Join(tmpDirCompat, LicenseFileName)
|
|
encoded := base64.StdEncoding.EncodeToString(encrypted)
|
|
os.WriteFile(licensePath, []byte(encoded), 0600)
|
|
|
|
// Verify no persistent key file exists (simulating old installation)
|
|
keyPath := filepath.Join(tmpDirCompat, PersistentKeyFileName)
|
|
if _, err := os.Stat(keyPath); err == nil {
|
|
t.Fatal("Persistent key should not exist for this test")
|
|
}
|
|
|
|
// Now try to load with a new primary key but same machine-id as fallback
|
|
// This simulates what happens when a Docker user upgrades and gets a
|
|
// persistent key file, but their old license was encrypted with machine-id
|
|
pNew := &Persistence{
|
|
configDir: tmpDirCompat,
|
|
encryptionKey: "new-persistent-key", // Different primary key (simulating new container)
|
|
machineID: machineID, // Same machine-id for fallback
|
|
}
|
|
|
|
loaded, err := pNew.LoadWithMetadata()
|
|
if err != nil {
|
|
t.Fatalf("Failed to load license with machine-id fallback: %v\n"+
|
|
"This means backwards compatibility is broken for existing Docker users", err)
|
|
}
|
|
|
|
if loaded.LicenseKey != testKey {
|
|
t.Errorf("Expected license key %s, got %s", testKey, loaded.LicenseKey)
|
|
}
|
|
})
|
|
}
|