mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-05 20:12:07 -04:00
* feat(tailscale): add Tailscale control center widget Full-stack Tailscale integration for DMS control center: Backend (Go): - Event-driven manager via WatchIPNBus (no polling) - Reconnects with exponential backoff when tailscaled unavailable - Typed conversion from ipnstate.Status to QML-friendly IPC types - Testable via tailscaleClient interface with mock watcher - Manager cleanup in cleanupManagers() - 19 unit tests Frontend (QML): - TailscaleService with WebSocket subscription - TailscaleWidget with peer list, filter chips, search - Copy-to-clipboard for IPs and DNS names - Daemon lifecycle handling (offline/stopped states) Dependencies: - Add tailscale.com v1.96.1 (official local API client) - Bump Go to 1.26.1 (required by tailscale.com) * cleanups --------- Co-authored-by: bbedward <bbedward@gmail.com>
98 lines
2.5 KiB
Go
98 lines
2.5 KiB
Go
package tailscale
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/ipn/ipnstate"
|
|
)
|
|
|
|
type mockConn struct {
|
|
*bytes.Buffer
|
|
}
|
|
|
|
func (m *mockConn) Close() error { return nil }
|
|
func (m *mockConn) LocalAddr() net.Addr { return nil }
|
|
func (m *mockConn) RemoteAddr() net.Addr { return nil }
|
|
func (m *mockConn) SetDeadline(t time.Time) error { return nil }
|
|
func (m *mockConn) SetReadDeadline(t time.Time) error { return nil }
|
|
func (m *mockConn) SetWriteDeadline(t time.Time) error { return nil }
|
|
|
|
func handlerTestManager() *Manager {
|
|
client := &mockClient{
|
|
watchFn: func(ctx context.Context, mask ipn.NotifyWatchOpt) (ipnBusWatcher, error) {
|
|
<-ctx.Done()
|
|
return nil, ctx.Err()
|
|
},
|
|
statusFn: func(ctx context.Context) (*ipnstate.Status, error) {
|
|
return runningStatus(), nil
|
|
},
|
|
}
|
|
m := newManager(client)
|
|
m.RefreshState()
|
|
return m
|
|
}
|
|
|
|
func TestHandleGetStatus(t *testing.T) {
|
|
m := handlerTestManager()
|
|
defer m.Close()
|
|
|
|
buf := &bytes.Buffer{}
|
|
conn := &mockConn{Buffer: buf}
|
|
|
|
req := models.Request{ID: 1, Method: "tailscale.getStatus"}
|
|
handleGetStatus(conn, req, m)
|
|
|
|
var resp models.Response[TailscaleState]
|
|
err := json.NewDecoder(buf).Decode(&resp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, resp.ID)
|
|
assert.NotNil(t, resp.Result)
|
|
assert.True(t, resp.Result.Connected)
|
|
assert.Equal(t, "cachyos", resp.Result.Self.Hostname)
|
|
}
|
|
|
|
func TestHandleRefresh(t *testing.T) {
|
|
m := handlerTestManager()
|
|
defer m.Close()
|
|
|
|
buf := &bytes.Buffer{}
|
|
conn := &mockConn{Buffer: buf}
|
|
|
|
req := models.Request{ID: 1, Method: "tailscale.refresh"}
|
|
handleRefresh(conn, req, m)
|
|
|
|
var resp models.Response[models.SuccessResult]
|
|
err := json.NewDecoder(buf).Decode(&resp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, resp.ID)
|
|
assert.NotNil(t, resp.Result)
|
|
assert.True(t, resp.Result.Success)
|
|
}
|
|
|
|
func TestHandleRequest_UnknownMethod(t *testing.T) {
|
|
m := handlerTestManager()
|
|
defer m.Close()
|
|
|
|
buf := &bytes.Buffer{}
|
|
conn := &mockConn{Buffer: buf}
|
|
|
|
req := models.Request{ID: 1, Method: "tailscale.unknownMethod"}
|
|
HandleRequest(conn, req, m)
|
|
|
|
var resp models.Response[any]
|
|
err := json.NewDecoder(buf).Decode(&resp)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, resp.Result)
|
|
assert.NotEmpty(t, resp.Error)
|
|
assert.Contains(t, resp.Error, "unknown method")
|
|
}
|