mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 07:44:01 -04:00
221 lines
8.2 KiB
Go
221 lines
8.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/apppicker"
|
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
|
"github.com/AvengeMedia/dankgo/files"
|
|
"github.com/AvengeMedia/dankgo/ipc"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRouteRequestUnavailableManagers(t *testing.T) {
|
|
tests := []struct {
|
|
method string
|
|
err string
|
|
}{
|
|
{"network.getState", "network manager not initialized"},
|
|
{"theme.auto.getState", "theme mode manager not initialized"},
|
|
{"wallpaper.getState", "wallpaper manager not initialized"},
|
|
{"loginctl.getState", "loginctl manager not initialized"},
|
|
{"freedesktop.getState", "freedesktop manager not initialized"},
|
|
{"wayland.gamma.getState", "wayland manager not initialized"},
|
|
{"bluetooth.getState", "bluetooth manager not initialized"},
|
|
{"browser.open", "apppicker manager not initialized"},
|
|
{"apppicker.open", "apppicker manager not initialized"},
|
|
{"tailscale.getState", "Tailscale not available"},
|
|
{"brightness.getState", "brightness manager not initialized"},
|
|
{"wlroutput.getState", "wlroutput manager not initialized"},
|
|
{"evdev.getState", "evdev manager not initialized"},
|
|
{"dbus.subscribe", "dbus manager not initialized"},
|
|
{"clipboard.getState", "clipboard manager not initialized"},
|
|
{"location.getState", "location manager not initialized"},
|
|
{"wellbeing.getState", "wellbeing manager not initialized"},
|
|
{"notify.invoke", "notification action manager not initialized"},
|
|
{"sysupdate.getState", "sysupdate manager not initialized"},
|
|
{"files.list", "files service not initialized"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.method, func(t *testing.T) {
|
|
conn := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 17, Method: tt.method})
|
|
require.JSONEq(t, `{"id":17,"error":"`+tt.err+`"}`, string(conn.written))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouteRequestUnknownMethods(t *testing.T) {
|
|
for _, method := range []string{"", "missing", "network", "networking.getState", "theme.auto", "matugen.missing", "plugins.missing", "themes.missing", "registries.missing", "mime.missing", "dgop.missing", "lyrics.missing"} {
|
|
t.Run(method, func(t *testing.T) {
|
|
conn := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 18, Method: method})
|
|
require.JSONEq(t, `{"id":18,"error":"unknown method: `+method+`"}`, string(conn.written))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRouteRequestExactMethods(t *testing.T) {
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
conn := &mockConn{}
|
|
writer := ipc.NewConnWriter(conn)
|
|
RouteRequest(context.Background(), writer, ipc.Request{Method: "ping"})
|
|
require.JSONEq(t, `{"result":"pong"}`, string(conn.written))
|
|
|
|
conn.written = nil
|
|
RouteRequest(context.Background(), writer, ipc.Request{ID: 19, Method: "getServerInfo"})
|
|
var info ipc.Response[ServerInfo]
|
|
require.NoError(t, json.Unmarshal(conn.written, &info))
|
|
require.Equal(t, 19, info.ID)
|
|
require.NotNil(t, info.Result)
|
|
require.Equal(t, getServerInfo(), *info.Result)
|
|
|
|
conn.written = nil
|
|
RouteRequest(context.Background(), writer, ipc.Request{ID: 20, Method: "clipboard.setConfig", Params: map[string]any{"maxHistory": float64(42)}})
|
|
require.JSONEq(t, `{"id":20,"result":{"success":true,"message":"config updated"}}`, string(conn.written))
|
|
|
|
conn.written = nil
|
|
RouteRequest(context.Background(), writer, ipc.Request{ID: 21, Method: "clipboard.getConfig"})
|
|
var config ipc.Response[clipboard.Config]
|
|
require.NoError(t, json.Unmarshal(conn.written, &config))
|
|
require.Equal(t, 21, config.ID)
|
|
require.NotNil(t, config.Result)
|
|
expected := clipboard.DefaultConfig()
|
|
expected.MaxHistory = 42
|
|
require.Equal(t, expected, *config.Result)
|
|
}
|
|
|
|
func TestRouteRequestBrowserAlias(t *testing.T) {
|
|
original := appPickerManager
|
|
appPickerManager = apppicker.NewManager()
|
|
t.Cleanup(func() {
|
|
appPickerManager.Close()
|
|
appPickerManager = original
|
|
})
|
|
events := appPickerManager.Subscribe("route-test")
|
|
for _, method := range []string{"browser.open", "apppicker.open"} {
|
|
t.Run(method, func(t *testing.T) {
|
|
conn := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 22, Method: method, Params: map[string]any{"url": "https://example.com"}})
|
|
require.JSONEq(t, `{"id":22,"result":"ok"}`, string(conn.written))
|
|
select {
|
|
case event := <-events:
|
|
require.Equal(t, apppicker.OpenEvent{Target: "https://example.com", RequestType: "url"}, event)
|
|
default:
|
|
t.Fatal("missing apppicker event")
|
|
}
|
|
})
|
|
}
|
|
conn := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 23, Method: "browser.missing"})
|
|
require.JSONEq(t, `{"id":23,"error":"unknown method"}`, string(conn.written))
|
|
}
|
|
|
|
func useFilesService(t *testing.T) {
|
|
t.Helper()
|
|
original := filesService
|
|
filesService = files.NewService(&filesEvents, t.TempDir(), nil)
|
|
filesService.AttachOnOpen()
|
|
t.Cleanup(func() {
|
|
filesService.Close()
|
|
filesService = original
|
|
})
|
|
}
|
|
|
|
func TestRouteRequestFiles(t *testing.T) {
|
|
useFilesService(t)
|
|
root := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(root, "a.txt"), nil, 0o644))
|
|
|
|
conn := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 24, Method: "files.list", Params: map[string]any{"path": root}})
|
|
var listed ipc.Response[struct {
|
|
Path string `json:"path"`
|
|
Entries []map[string]any `json:"entries"`
|
|
}]
|
|
require.NoError(t, json.Unmarshal(conn.written, &listed))
|
|
require.Equal(t, 24, listed.ID)
|
|
require.NotNil(t, listed.Result)
|
|
require.Equal(t, root, listed.Result.Path)
|
|
require.Len(t, listed.Result.Entries, 1)
|
|
require.Equal(t, "a.txt", listed.Result.Entries[0]["name"])
|
|
|
|
conn.written = nil
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 25, Method: "files.missing"})
|
|
require.JSONEq(t, `{"id":25,"error":"unknown files method: files.missing","code":"NOTSUPPORTED"}`, string(conn.written))
|
|
}
|
|
|
|
func TestFilesWatchLivesAsLongAsTheConnection(t *testing.T) {
|
|
t.Setenv("XDG_RUNTIME_DIR", t.TempDir())
|
|
useFilesService(t)
|
|
|
|
s := New()
|
|
require.NoError(t, s.Listen())
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
served := make(chan error, 1)
|
|
go func() { served <- s.ipc.Serve(ctx) }()
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
require.NoError(t, <-served)
|
|
})
|
|
|
|
conn, err := net.Dial("unix", s.SocketPath())
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
require.NoError(t, conn.SetDeadline(time.Now().Add(5*time.Second)))
|
|
decoder := json.NewDecoder(conn)
|
|
|
|
var caps ipc.Capabilities
|
|
require.NoError(t, decoder.Decode(&caps))
|
|
require.Contains(t, caps.Capabilities, "files")
|
|
|
|
require.NoError(t, json.NewEncoder(conn).Encode(ipc.Request{ID: 1, Method: "files.watch", Params: map[string]any{"path": t.TempDir()}}))
|
|
var opened ipc.Response[struct {
|
|
WatchID string `json:"watchId"`
|
|
}]
|
|
require.NoError(t, decoder.Decode(&opened))
|
|
require.NotNil(t, opened.Result)
|
|
watchID := opened.Result.WatchID
|
|
require.NotEmpty(t, watchID)
|
|
|
|
pageWatch := func() string {
|
|
mc := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(mc), ipc.Request{ID: 2, Method: "files.list", Params: map[string]any{"watchId": watchID}})
|
|
return string(mc.written)
|
|
}
|
|
require.Contains(t, pageWatch(), `"watchId":"`+watchID+`"`, "the watch outlives the request that opened it")
|
|
|
|
require.NoError(t, conn.Close())
|
|
require.Eventually(t, func() bool {
|
|
return pageWatch() == `{"id":2,"error":"unknown watch: `+watchID+`","code":"EINVAL"}`+"\n"
|
|
}, 5*time.Second, 10*time.Millisecond, "closing the connection closes its watches")
|
|
}
|
|
|
|
func BenchmarkRouteRequest(b *testing.B) {
|
|
conn := &mockConn{}
|
|
writer := ipc.NewConnWriter(conn)
|
|
req := ipc.Request{ID: 1, Method: "ping"}
|
|
b.ReportAllocs()
|
|
for b.Loop() {
|
|
conn.written = conn.written[:0]
|
|
RouteRequest(context.Background(), writer, req)
|
|
}
|
|
}
|
|
|
|
// Routing only: the no-network invariant itself is TestLookupWithoutConsentNeverFetches.
|
|
func TestRouteLyricsGetWithoutConsentStaysLocal(t *testing.T) {
|
|
t.Setenv("XDG_CACHE_HOME", t.TempDir())
|
|
conn := &mockConn{}
|
|
RouteRequest(context.Background(), ipc.NewConnWriter(conn), ipc.Request{ID: 19, Method: "lyrics.get", Params: map[string]any{
|
|
"title": "Snake Eater",
|
|
"artist": "Cynthia Harrell",
|
|
}})
|
|
require.Contains(t, string(conn.written), `"found":false`)
|
|
}
|