package store import ( "context" "errors" "testing" "time" ) func TestMemoryModuleSnapshotLockCancellationAndCleanup(t *testing.T) { m := NewMemory() first, err := m.lockModuleSnapshot(context.Background(), "tenant-a", "module-a") if err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond) defer cancel() second, err := m.lockModuleSnapshot(ctx, "tenant-a", "module-a") if second != nil || !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("waiter not cancelled: unlock=%v err=%v", second != nil, err) } if len(m.snapshotLocks) != 1 || m.snapshotLocks[moduleSnapshotKey("tenant-a", "module-a")].refs != 1 { t.Fatalf("waiter leaked lock ref: %+v", m.snapshotLocks) } first() first() if len(m.snapshotLocks) != 0 { t.Fatalf("idle lock leaked: %+v", m.snapshotLocks) } third, err := m.lockModuleSnapshot(context.Background(), "tenant-a", "module-a") if err != nil { t.Fatal(err) } third() } func TestMemoryModuleSnapshotLockScopesTenantAndModule(t *testing.T) { m := NewMemory() first, err := m.lockModuleSnapshot(context.Background(), "tenant-a", "module-x") if err != nil { t.Fatal(err) } defer first() for _, pair := range [][2]string{{"tenant-b", "module-x"}, {"tenant-a", "module-y"}} { ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) unlock, err := m.lockModuleSnapshot(ctx, pair[0], pair[1]) cancel() if err != nil { t.Fatalf("unrelated key blocked %v: %v", pair, err) } unlock() } ctx, cancel := context.WithCancel(context.Background()) cancel() unlock, err := m.lockModuleSnapshot(ctx, "tenant-a", "module-x") if unlock != nil || !errors.Is(err, context.Canceled) { t.Fatalf("cancelled context acquired: unlock=%v err=%v", unlock != nil, err) } }