wails/v3/pkg/application/single_instance_test.go
Lea Anthony 2604ecc0f8
test(v3): add comprehensive unit tests for pkg/application (#4827)
* test(v3): add comprehensive unit tests for pkg/application

Add 11 new test files to improve test coverage of the pkg/application
package from 13.6% to 17.7%.

New test files:
- context_test.go: Context struct operations
- services_test.go: Service management and lifecycle
- parameter_test.go: Parameter and CallError types
- dialogs_test.go: Dialog utilities and button methods
- webview_window_options_test.go: Window options and constants
- application_options_test.go: ChainMiddleware and app config
- keys_test.go: Keyboard accelerator parsing
- single_instance_test.go: Single instance management and encryption
- menuitem_internal_test.go: Menu item internal functions
- menu_internal_test.go: Menu internal functions
- screenmanager_internal_test.go: Screen geometry and transformations

The 40% target was not fully achievable because ~50% of the codebase
is platform-specific code that can only be tested on respective
platforms. Tests focus on pure Go logic, utility functions, and
data structures that can be tested cross-platform.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address CodeRabbit review comments

- dialogs_test.go: improve ID recycling test to verify either recycled
  ID (id3 == id1) or new unique ID (id3 > id2)
- keys_test.go: make accelerator String() tests platform-agnostic by
  checking suffix patterns rather than exact platform-specific strings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: normalize temp dir path for macOS compatibility in test

os.TempDir() returns a trailing slash on macOS, causing path comparison
to fail. Use filepath.Clean() to normalize both paths.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove REVIEW.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: simplify changelog entry

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 09:53:24 +11:00

229 lines
5.8 KiB
Go

package application
import (
"os"
"path/filepath"
"testing"
)
func TestEncryptDecrypt(t *testing.T) {
key := [32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
}
plaintext := []byte("Hello, World! This is a test message.")
encrypted, err := encrypt(key, plaintext)
if err != nil {
t.Fatalf("encrypt failed: %v", err)
}
if encrypted == "" {
t.Error("encrypted should not be empty")
}
if encrypted == string(plaintext) {
t.Error("encrypted should be different from plaintext")
}
decrypted, err := decrypt(key, encrypted)
if err != nil {
t.Fatalf("decrypt failed: %v", err)
}
if string(decrypted) != string(plaintext) {
t.Errorf("decrypted = %q, want %q", string(decrypted), string(plaintext))
}
}
func TestEncryptDecrypt_EmptyData(t *testing.T) {
key := [32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
}
plaintext := []byte{}
encrypted, err := encrypt(key, plaintext)
if err != nil {
t.Fatalf("encrypt failed: %v", err)
}
decrypted, err := decrypt(key, encrypted)
if err != nil {
t.Fatalf("decrypt failed: %v", err)
}
if len(decrypted) != 0 {
t.Errorf("decrypted should be empty, got %d bytes", len(decrypted))
}
}
func TestDecrypt_InvalidData(t *testing.T) {
key := [32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
}
tests := []struct {
name string
data string
}{
{"invalid base64", "not-valid-base64!!!"},
{"too short", "YWJj"}, // "abc" base64 encoded (3 bytes)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := decrypt(key, tt.data)
if err == nil {
t.Error("decrypt should return error for invalid data")
}
})
}
}
func TestDecrypt_WrongKey(t *testing.T) {
key1 := [32]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
key2 := [32]byte{0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}
plaintext := []byte("Secret message")
encrypted, err := encrypt(key1, plaintext)
if err != nil {
t.Fatalf("encrypt failed: %v", err)
}
_, err = decrypt(key2, encrypted)
if err == nil {
t.Error("decrypt with wrong key should return error")
}
}
func TestGetLockPath(t *testing.T) {
uniqueID := "com.example.myapp"
path := getLockPath(uniqueID)
if path == "" {
t.Error("getLockPath should return non-empty path")
}
expectedFileName := uniqueID + ".lock"
actualFileName := filepath.Base(path)
if actualFileName != expectedFileName {
t.Errorf("filename = %q, want %q", actualFileName, expectedFileName)
}
// Path should be in temp directory
// Use filepath.Clean to normalize paths (os.TempDir may have trailing slash on macOS)
tmpDir := filepath.Clean(os.TempDir())
if filepath.Dir(path) != tmpDir {
t.Errorf("path should be in temp directory %q, got %q", tmpDir, filepath.Dir(path))
}
}
func TestGetCurrentWorkingDir(t *testing.T) {
dir := getCurrentWorkingDir()
// Should return a non-empty path
if dir == "" {
t.Error("getCurrentWorkingDir should return non-empty path")
}
// Should match os.Getwd()
expected, err := os.Getwd()
if err != nil {
t.Skipf("os.Getwd failed: %v", err)
}
if dir != expected {
t.Errorf("getCurrentWorkingDir() = %q, want %q", dir, expected)
}
}
func TestSecondInstanceData_Fields(t *testing.T) {
data := SecondInstanceData{
Args: []string{"arg1", "arg2"},
WorkingDir: "/home/user",
AdditionalData: map[string]string{
"key1": "value1",
"key2": "value2",
},
}
if len(data.Args) != 2 {
t.Error("Args not set correctly")
}
if data.WorkingDir != "/home/user" {
t.Error("WorkingDir not set correctly")
}
if len(data.AdditionalData) != 2 {
t.Error("AdditionalData not set correctly")
}
}
func TestSingleInstanceOptions_Defaults(t *testing.T) {
opts := SingleInstanceOptions{}
if opts.UniqueID != "" {
t.Error("UniqueID should default to empty string")
}
if opts.OnSecondInstanceLaunch != nil {
t.Error("OnSecondInstanceLaunch should default to nil")
}
if opts.AdditionalData != nil {
t.Error("AdditionalData should default to nil")
}
if opts.ExitCode != 0 {
t.Error("ExitCode should default to 0")
}
var zeroKey [32]byte
if opts.EncryptionKey != zeroKey {
t.Error("EncryptionKey should default to zero array")
}
}
func TestSingleInstanceManager_Cleanup_Nil(t *testing.T) {
// Calling cleanup on nil manager should not panic
var m *singleInstanceManager
m.cleanup() // Should not panic
}
func TestSingleInstanceManager_Cleanup_NilLock(t *testing.T) {
// Calling cleanup with nil lock should not panic
m := &singleInstanceManager{}
m.cleanup() // Should not panic
}
func TestNewSingleInstanceManager_NilOptions(t *testing.T) {
manager, err := newSingleInstanceManager(nil, nil)
if err != nil {
t.Errorf("newSingleInstanceManager(nil, nil) should not return error: %v", err)
}
if manager != nil {
t.Error("newSingleInstanceManager(nil, nil) should return nil manager")
}
}
func TestAlreadyRunningError(t *testing.T) {
if alreadyRunningError == nil {
t.Error("alreadyRunningError should not be nil")
}
if alreadyRunningError.Error() != "application is already running" {
t.Errorf("alreadyRunningError.Error() = %q", alreadyRunningError.Error())
}
}