mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-27 20:07:48 +02:00
The WebSocket path reused sys.data, and CBOR leaves omitted fields untouched, so stale values carried over (e.g. Details re-saved every poll, SystemdServicesUpdated stuck true, old map entries kept). sys.data is now set only by the regular update for alerts. Removes the Wi-Fi-only workaround in UnmarshalResponse.
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
//go:build testing
|
|
|
|
package systems
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/blang/semver"
|
|
"github.com/fxamacker/cbor/v2"
|
|
"github.com/henrygd/beszel/internal/common"
|
|
esystem "github.com/henrygd/beszel/internal/entities/system"
|
|
"github.com/henrygd/beszel/internal/hub/ws"
|
|
"github.com/lxzan/gws"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// sequenceDataClient answers each GetData request with the next queued payload.
|
|
type sequenceDataClient struct {
|
|
gws.BuiltinEventHandler
|
|
responses chan esystem.CombinedData
|
|
}
|
|
|
|
func (c *sequenceDataClient) OnMessage(conn *gws.Conn, message *gws.Message) {
|
|
defer message.Close()
|
|
var req common.HubRequest[cbor.RawMessage]
|
|
if err := cbor.Unmarshal(message.Bytes(), &req); err != nil || req.Action != common.GetData {
|
|
return
|
|
}
|
|
data, _ := cbor.Marshal(<-c.responses)
|
|
response, _ := cbor.Marshal(common.AgentResponse{Id: req.Id, Data: data})
|
|
_ = conn.WriteMessage(gws.OpcodeBinary, response)
|
|
}
|
|
|
|
// Fields the agent omits must not carry over from a previous response.
|
|
func TestFetchDataViaWebSocketDoesNotRetainOmittedFields(t *testing.T) {
|
|
connections := make(chan *ws.WsConn, 1)
|
|
upgrader := gws.NewUpgrader(&monitorSyncServer{}, nil)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := upgrader.Upgrade(w, r)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
wsConn := ws.NewWsConnection(conn, semver.MustParse("0.20.0"))
|
|
conn.Session().Store("wsConn", wsConn)
|
|
connections <- wsConn
|
|
conn.ReadLoop()
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
|
|
client := &sequenceDataClient{responses: make(chan esystem.CombinedData, 2)}
|
|
client.responses <- esystem.CombinedData{
|
|
Details: &esystem.Details{Hostname: "host"},
|
|
SystemdServicesUpdated: true,
|
|
Stats: esystem.Stats{Batteries: map[string]uint8{"BAT0": 50, "BAT1": 60}},
|
|
Info: esystem.Info{WiFi: map[string]esystem.WiFi{"wlan0": {SSID: "home"}}},
|
|
}
|
|
client.responses <- esystem.CombinedData{
|
|
Stats: esystem.Stats{Batteries: map[string]uint8{"BAT0": 40}},
|
|
}
|
|
conn, _, err := gws.NewClient(client, &gws.ClientOption{Addr: "ws" + strings.TrimPrefix(server.URL, "http")})
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = conn.NetConn().Close() })
|
|
go conn.ReadLoop()
|
|
|
|
var sys *System
|
|
select {
|
|
case wsConn := <-connections:
|
|
sys = &System{WsConn: wsConn}
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("websocket connection was not established")
|
|
}
|
|
|
|
first, err := sys.fetchDataFromAgent(common.DataRequestOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, first.Details)
|
|
require.Len(t, first.Stats.Batteries, 2)
|
|
|
|
second, err := sys.fetchDataFromAgent(common.DataRequestOptions{})
|
|
require.NoError(t, err)
|
|
require.Nil(t, second.Details)
|
|
require.False(t, second.SystemdServicesUpdated)
|
|
require.Equal(t, map[string]uint8{"BAT0": 40}, second.Stats.Batteries)
|
|
require.Empty(t, second.Info.WiFi)
|
|
|
|
// the first result is not mutated by the second fetch
|
|
require.Len(t, first.Stats.Batteries, 2)
|
|
}
|