From 5df0a256c600f827ac1a7ef207b77d4fb299b3b2 Mon Sep 17 00:00:00 2001 From: user01010111 <12504630+user01010111@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:48:57 +1300 Subject: [PATCH] fix(hub): pad short monitor IDs to the schema minimum (#2454) --- internal/hub/network_monitors.go | 4 +- internal/hub/network_monitors_test.go | 65 +++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/internal/hub/network_monitors.go b/internal/hub/network_monitors.go index 9fcc86407..5529aa584 100644 --- a/internal/hub/network_monitors.go +++ b/internal/hub/network_monitors.go @@ -1,6 +1,7 @@ package hub import ( + "fmt" "strconv" "time" @@ -21,7 +22,8 @@ func generateMonitorID(systemId string, config monitor.Config) string { if config.Protocol == "dns" { args = append(args, config.Server) } - return systems.MakeStableHashId(args...) + // Meet the record ID minimum without changing existing IDs of six or more characters. + return fmt.Sprintf("%06s", systems.MakeStableHashId(args...)) } // bindNetworkMonitorsEvents keeps monitor records and agent monitor state in sync. diff --git a/internal/hub/network_monitors_test.go b/internal/hub/network_monitors_test.go index c0d60b3d7..71dd4b82a 100644 --- a/internal/hub/network_monitors_test.go +++ b/internal/hub/network_monitors_test.go @@ -87,6 +87,18 @@ func TestGenerateMonitorID(t *testing.T) { config monitor.Config expected string }{ + { + name: "Short hash meets the record ID minimum", + systemID: "000000000012369", + config: monitor.Config{Protocol: "icmp", Target: "gateway.example.com"}, + expected: "0b3fd2", + }, + { + name: "Existing six-character ID is unchanged", + systemID: "000000000000274", + config: monitor.Config{Protocol: "icmp", Target: "gateway.example.com"}, + expected: "77a996", + }, { name: "HTTP monitor on example.com", systemID: "sys123", @@ -217,6 +229,59 @@ func TestGenerateMonitorID(t *testing.T) { } } +func TestNetworkMonitorShortIDRequests(t *testing.T) { + for _, method := range []string{http.MethodPost, http.MethodPatch} { + t.Run(method, func(t *testing.T) { + hub, testApp, err := createTestHub(t) + require.NoError(t, err) + defer cleanupTestHub(hub, testApp) + bindNetworkMonitorsEvents(hub) + user, err := createTestUser(hub) + require.NoError(t, err) + system, err := createTestRecord(hub, "systems", map[string]any{ + "id": "000000000012369", "name": "Paused", "host": "localhost", "port": "45876", + "status": "paused", "users": []string{user.Id}, + }) + require.NoError(t, err) + payload := map[string]any{ + "system": system.Id, "target": "gateway.example.com", "protocol": "icmp", + "interval": 60, "enabled": true, + } + url := "/api/collections/network_monitors/records" + var oldID string + if method == http.MethodPatch { + previous, err := createTestRecord(hub, "network_monitors", map[string]any{ + "system": system.Id, "target": "website.example.com", "protocol": "icmp", "interval": 60, + }) + require.NoError(t, err) + oldID = previous.Id + url += "/" + oldID + } + data, err := json.Marshal(payload) + require.NoError(t, err) + token, err := user.NewAuthToken() + require.NoError(t, err) + router, err := apis.NewRouter(hub) + require.NoError(t, err) + handler, err := router.BuildMux() + require.NoError(t, err) + request := httptest.NewRequest(method, url, bytes.NewReader(data)) + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", token) + response := httptest.NewRecorder() + handler.ServeHTTP(response, request) + require.Equal(t, http.StatusOK, response.Code, response.Body.String()) + record, err := hub.FindRecordById("network_monitors", "0b3fd2") + require.NoError(t, err) + assert.Equal(t, "gateway.example.com", record.GetString("target")) + if oldID != "" { + _, err = hub.FindRecordById("network_monitors", oldID) + require.Error(t, err) + } + }) + } +} + func TestCopyMonitorToNewRecordDropsResultFields(t *testing.T) { hub, testApp, err := createTestHub(t) require.NoError(t, err)