Files
wehub-resource-sync bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

214 lines
4.9 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package lockfile
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
)
func newTestLock(t *testing.T) *LockFile {
t.Helper()
return New(filepath.Join(t.TempDir(), "test.lock"))
}
func TestTryLock_Success(t *testing.T) {
l := newTestLock(t)
if err := l.TryLock(); err != nil {
t.Fatalf("TryLock failed: %v", err)
}
defer l.Unlock()
if _, err := os.Stat(l.Path()); os.IsNotExist(err) {
t.Error("lock file should exist after TryLock")
}
}
func TestTryLock_Conflict(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
l1 := New(path)
if err := l1.TryLock(); err != nil {
t.Fatalf("first TryLock failed: %v", err)
}
defer l1.Unlock()
l2 := New(path)
err := l2.TryLock()
if err == nil {
l2.Unlock()
t.Fatal("second TryLock should fail when lock is held by another instance")
}
if !errors.Is(err, ErrHeld) {
t.Errorf("expected error to wrap ErrHeld, got: %v", err)
}
}
func TestTryLock_AlreadyHeld(t *testing.T) {
l := newTestLock(t)
if err := l.TryLock(); err != nil {
t.Fatalf("TryLock failed: %v", err)
}
defer l.Unlock()
err := l.TryLock()
if err == nil {
t.Fatal("double TryLock on same instance should fail")
}
if !errors.Is(err, ErrHeld) {
t.Errorf("expected error to wrap ErrHeld, got: %v", err)
}
}
func TestTryLock_InvalidPath(t *testing.T) {
l := New(filepath.Join(t.TempDir(), "no-such-dir", "test.lock"))
err := l.TryLock()
if err == nil {
l.Unlock()
t.Fatal("TryLock should fail for non-existent parent directory")
}
if !strings.Contains(err.Error(), "open lock file") {
t.Errorf("error should mention 'open lock file', got: %v", err)
}
}
func TestUnlock_ReleasesLock(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
l1 := New(path)
if err := l1.TryLock(); err != nil {
t.Fatalf("TryLock failed: %v", err)
}
if err := l1.Unlock(); err != nil {
t.Fatalf("Unlock failed: %v", err)
}
l2 := New(path)
if err := l2.TryLock(); err != nil {
t.Fatalf("TryLock after Unlock should succeed: %v", err)
}
defer l2.Unlock()
}
func TestUnlock_KeepsFileOnDisk(t *testing.T) {
l := newTestLock(t)
if err := l.TryLock(); err != nil {
t.Fatalf("TryLock failed: %v", err)
}
path := l.Path()
if err := l.Unlock(); err != nil {
t.Fatalf("Unlock failed: %v", err)
}
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Error("lock file should remain on disk after Unlock")
}
}
func TestUnlock_Idempotent(t *testing.T) {
l := newTestLock(t)
if err := l.Unlock(); err != nil {
t.Fatalf("Unlock without lock should not error: %v", err)
}
if err := l.TryLock(); err != nil {
t.Fatalf("TryLock failed: %v", err)
}
if err := l.Unlock(); err != nil {
t.Fatalf("first Unlock failed: %v", err)
}
if err := l.Unlock(); err != nil {
t.Fatalf("second Unlock should not error: %v", err)
}
}
func TestPath(t *testing.T) {
l := New("/tmp/test.lock")
if l.Path() != "/tmp/test.lock" {
t.Errorf("Path() = %q, want /tmp/test.lock", l.Path())
}
}
func TestForSubscribe(t *testing.T) {
dir := t.TempDir()
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", dir)
l, err := ForSubscribe("cli_test123")
if err != nil {
t.Fatalf("ForSubscribe failed: %v", err)
}
expected := filepath.Join(dir, "locks", "subscribe_cli_test123.lock")
if l.Path() != expected {
t.Errorf("Path() = %q, want %q", l.Path(), expected)
}
lockDir := filepath.Join(dir, "locks")
if _, err := os.Stat(lockDir); os.IsNotExist(err) {
t.Error("locks directory should be created by ForSubscribe")
}
}
func TestForSubscribe_SanitizesAppID(t *testing.T) {
dir := t.TempDir()
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", dir)
for _, tt := range []struct {
name string
appID string
wantBase string
}{
{"path traversal", "../../tmp/evil", "subscribe_.._.._tmp_evil.lock"},
{"slashes", "cli/app/id", "subscribe_cli_app_id.lock"},
{"normal id", "cli_a1b2c3", "subscribe_cli_a1b2c3.lock"},
{"special chars", "app@id:123", "subscribe_app_id_123.lock"},
} {
t.Run(tt.name, func(t *testing.T) {
l, err := ForSubscribe(tt.appID)
if err != nil {
t.Fatalf("ForSubscribe(%q) failed: %v", tt.appID, err)
}
gotBase := filepath.Base(l.Path())
if gotBase != tt.wantBase {
t.Errorf("Base(Path()) = %q, want %q", gotBase, tt.wantBase)
}
locksDir := filepath.Join(dir, "locks")
if !strings.HasPrefix(l.Path(), locksDir) {
t.Errorf("path %q escapes locks dir %q", l.Path(), locksDir)
}
})
}
}
func TestForSubscribe_RejectsEmptyAppID(t *testing.T) {
_, err := ForSubscribe("")
if err == nil {
t.Fatal("ForSubscribe should reject empty app ID")
}
}
func TestErrHeld_RelockAfterUnlock(t *testing.T) {
l := newTestLock(t)
if err := l.TryLock(); err != nil {
t.Fatalf("first TryLock failed: %v", err)
}
if err := l.Unlock(); err != nil {
t.Fatalf("Unlock failed: %v", err)
}
if err := l.TryLock(); err != nil {
t.Errorf("TryLock after Unlock should succeed; got: %v", err)
}
l.Unlock()
}