Files
2026-10-20 12:23:25 -04:00

157 lines
4.2 KiB
Go

package server
import (
"context"
"encoding/json"
"net"
"testing"
"time"
"github.com/duxlabs-dev/dux-go/ipc"
"github.com/duxlabs-dev/dux-shell/core/internal/server/bluez"
"github.com/duxlabs-dev/dux-shell/core/internal/server/network"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetCapabilities(t *testing.T) {
originalNetworkManager := networkManager
defer func() { networkManager = originalNetworkManager }()
t.Run("capabilities without network manager", func(t *testing.T) {
networkManager = nil
caps := getCapabilities()
assert.NotContains(t, caps.Capabilities, "network")
})
t.Run("capabilities with network manager", func(t *testing.T) {
networkManager = &network.Manager{}
caps := getCapabilities()
assert.Contains(t, caps.Capabilities, "network")
})
}
func TestCupsCapabilityIsSticky(t *testing.T) {
originalEverAvailable := cupsEverAvailable
originalSubscriberCount := cupsSubscriberCount
defer func() {
cupsEverAvailable = originalEverAvailable
cupsSubscriberCount = originalSubscriberCount
}()
cupsEverAvailable = false
assert.NotContains(t, getCapabilities().Capabilities, "cups")
cupsEverAvailable = true
cupsSubscriberCount = 1
assert.Contains(t, getCapabilities().Capabilities, "cups")
releaseCupsSubscriber()
assert.Equal(t, 0, cupsSubscriberCount)
assert.Contains(t, getCapabilities().Capabilities, "cups", "capability must survive the manager shutting down")
releaseCupsSubscriber()
assert.Equal(t, 0, cupsSubscriberCount, "release must not drive the subscriber count negative")
}
type mockConn struct {
net.Conn
written []byte
}
func (m *mockConn) Write(b []byte) (n int, err error) {
m.written = append(m.written, b...)
return len(b), nil
}
func (m *mockConn) Close() error {
return nil
}
func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
func TestExclusiveServiceRequiresExplicitSubscription(t *testing.T) {
tests := []struct {
name string
services []string
includeAll bool
want bool
}{
{name: "explicit", services: []string{"mpris.command"}, want: true},
{name: "all excluded", services: []string{"all"}, want: false},
{name: "regular service via all", services: []string{"all"}, includeAll: true, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
service := "mpris.command"
if tt.includeAll {
service = "bluetooth"
}
assert.Equal(t, tt.want, serviceSubscribed(tt.services, service, tt.includeAll))
})
}
}
func TestSubscriptionCancellationPromotesMPRISWaiter(t *testing.T) {
originalBluezManager := bluezManager
manager := &bluez.Manager{}
bluezManager = manager
defer func() { bluezManager = originalBluezManager }()
serverConn, clientConn := net.Pipe()
defer clientConn.Close()
defer serverConn.Close()
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() {
defer close(done)
handleSubscribe(ctx, ipc.NewConnWriter(serverConn), ipc.Request{
ID: 42,
Method: "subscribe",
Params: map[string]any{"services": []any{"mpris.command"}},
})
}()
require.NoError(t, clientConn.SetReadDeadline(time.Now().Add(2*time.Second)))
decoder := json.NewDecoder(clientConn)
var ownerLease string
for ownerLease == "" {
var response ipc.Response[ServiceEvent]
require.NoError(t, decoder.Decode(&response))
if response.Result == nil || response.Result.Service != "mpris.command" {
continue
}
data, ok := response.Result.Data.(map[string]any)
require.True(t, ok)
ownerLease, _ = data["lease"].(string)
}
require.NotEmpty(t, ownerLease)
waiter, err := manager.SubscribePlayerCommands("waiter")
require.NoError(t, err)
select {
case event := <-waiter:
t.Fatalf("waiting subscriber received unexpected event: %#v", event)
default:
}
cancel()
select {
case event := <-waiter:
require.NotEmpty(t, event.Lease)
assert.NotEqual(t, ownerLease, event.Lease)
case <-time.After(2 * time.Second):
t.Fatal("waiting MPRIS subscriber was not promoted after connection cancellation")
}
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("subscription handler did not stop after connection cancellation")
}
manager.UnsubscribePlayerCommands("waiter")
}