feat: add /api/server/getInfo to handle frontend get server info.
chore: rebuild `/api/server/getStatus` to the same logic as `getInfo`.
This commit is contained in:
@@ -37,7 +37,8 @@ nukumizu-backend/
|
|||||||
│ └── variables.go # Software build metadata (name/version/developer)
|
│ └── variables.go # Software build metadata (name/version/developer)
|
||||||
├── handler/
|
├── handler/
|
||||||
│ ├── user.go # /api/user/login, /api/user/register
|
│ ├── user.go # /api/user/login, /api/user/register
|
||||||
│ ├── server.go # /api/server/list, getStatus, exec
|
│ ├── server.go # /api/server/list, getInfo, getStatus, exec
|
||||||
|
│ ├── settings.go # /api/settings/get, set
|
||||||
│ └── health.go # /health
|
│ └── health.go # /health
|
||||||
├── database/
|
├── database/
|
||||||
│ └── user.go # user.db (SQLite) user store
|
│ └── user.go # user.db (SQLite) user store
|
||||||
@@ -276,8 +277,11 @@ Tokens idle for more than 1 hour are expired (cleaned every 10 minutes); any aut
|
|||||||
| `/api/user/login` | POST | None | Log in. Body `{username, password}`. Returns `{token, userID, username, level, registerDate}`. |
|
| `/api/user/login` | POST | None | Log in. Body `{username, password}`. Returns `{token, userID, username, level, registerDate}`. |
|
||||||
| `/api/user/register` | POST | None | Register the first user. Body `{username, password}`. Only allowed while no user exists; otherwise `403`. Returns `{token, userID, username, level}`. |
|
| `/api/user/register` | POST | None | Register the first user. Body `{username, password}`. Only allowed while no user exists; otherwise `403`. Returns `{token, userID, username, level}`. |
|
||||||
| `/api/server/list` | GET | bot / admin | List all monitored servers. |
|
| `/api/server/list` | GET | bot / admin | List all monitored servers. |
|
||||||
| `/api/server/getStatus` | GET | bot / admin | Recent live status for a server. Query `?uuid=<uuid>`. Returns `{uuid, report}` or `404`. |
|
| `/api/server/getInfo` | GET | admin | Static server info (mirrors the Bot's `/info`). Query `?uuid=<uuid>` (or `all`). Returns `{success, <uuid>: {uuid, name, info}}` — one entry per requested server. `404` for an unknown single uuid. |
|
||||||
|
| `/api/server/getStatus` | GET | admin | Live server status (mirrors the Bot's `/status`). Query `?uuid=<uuid>` (or `all`). Returns `{success, <uuid>: {uuid, name, online, report}}`; `report` is `null` when the node has not reported yet. `404` for an unknown single uuid. |
|
||||||
| `/api/server/exec` | POST | bot / admin | Execute a command. Body `{uuid: [<uuid>...], command}`. Dispatches a Komari task and polls until completion (or timeout). Returns `{taskID, results}`. |
|
| `/api/server/exec` | POST | bot / admin | Execute a command. Body `{uuid: [<uuid>...], command}`. Dispatches a Komari task and polls until completion (or timeout). Returns `{taskID, results}`. |
|
||||||
|
| `/api/settings/get` | GET | admin | `?type=global\|bot_user_config\|bot_node_config` | Returns `{success, config}`, where `config` is the selected config file's content (same layout as the JSON file). |
|
||||||
|
| `/api/settings/set` | POST | admin | `?type=<same types>` + JSON body of partial updates, e.g. `{"system":{"debugMode":true}}` | Deep-merges the body into the selected config file, persists it, and reloads it in memory. Only the given keys change; arrays replace. |
|
||||||
| `/health` | GET | None | Health check. Returns `{status, database}`. |
|
| `/health` | GET | None | Health check. Returns `{status, database}`. |
|
||||||
| `/api/system/getLogs` | WebSocket | None | Streams logs. Sends the last 100 buffered entries, then live `{level, content, timestamp}` events. |
|
| `/api/system/getLogs` | WebSocket | None | Streams logs. Sends the last 100 buffered entries, then live `{level, content, timestamp}` events. |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"nukumizu-backend/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
// writeTempConfig writes content to a fresh temp file and points the matching
|
||||||
|
// global.ConfigPath field at it, returning a cleanup that restores the original.
|
||||||
|
func writeTempConfig(t *testing.T, field *string, content string) {
|
||||||
|
t.Helper()
|
||||||
|
path := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||||
|
t.Fatalf("write temp config: %v", err)
|
||||||
|
}
|
||||||
|
original := *field
|
||||||
|
*field = path
|
||||||
|
t.Cleanup(func() { *field = original })
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSettingsDeepMerge(t *testing.T) {
|
||||||
|
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
|
||||||
|
"qq(napcat)": {
|
||||||
|
"admins": {
|
||||||
|
"3526453517": {
|
||||||
|
"event_status_notify": true,
|
||||||
|
"event_bot_started": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"trustedGroups": {
|
||||||
|
"740724778": {
|
||||||
|
"event_status_notify": true,
|
||||||
|
"event_bot_started": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
patch := map[string]interface{}{
|
||||||
|
"qq(napcat)": map[string]interface{}{
|
||||||
|
"admins": map[string]interface{}{
|
||||||
|
"3526453517": map[string]interface{}{
|
||||||
|
"event_status_notify": false, // toggle an existing nested flag
|
||||||
|
"event_reply": true, // add a key that is not in the file
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"trustedGroups": map[string]interface{}{
|
||||||
|
"12345": map[string]interface{}{ // add a whole new member
|
||||||
|
"event_bot_started": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := UpdateSettings(SettingBotUserConfig, patch); err != nil {
|
||||||
|
t.Fatalf("UpdateSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := GetSettings(SettingBotUserConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetSettings: %v", err)
|
||||||
|
}
|
||||||
|
got := string(data)
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
`"event_status_notify": false`,
|
||||||
|
`"event_reply": true`,
|
||||||
|
`"event_bot_started": true`,
|
||||||
|
`"event_status_notify": true`, // sibling under trustedGroups 740724778 kept
|
||||||
|
`"12345"`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("merged config missing %q:\n%s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The in-memory singleton must reflect the merged file too.
|
||||||
|
if C_botUserConfig == nil {
|
||||||
|
t.Fatal("C_botUserConfig not reloaded")
|
||||||
|
}
|
||||||
|
if C_botUserConfig.QQ.Admins["3526453517"].EventStatusNotify {
|
||||||
|
t.Error("expected reloaded admin event_status_notify = false")
|
||||||
|
}
|
||||||
|
if !C_botUserConfig.QQ.Admins["3526453517"].EventReply {
|
||||||
|
t.Error("expected reloaded admin event_reply = true")
|
||||||
|
}
|
||||||
|
if !C_botUserConfig.QQ.TrustedGroups["12345"].EventBotStarted {
|
||||||
|
t.Error("expected new trusted group event_bot_started = true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateSettingsReplacesArraysAndKeepsNumbers(t *testing.T) {
|
||||||
|
writeTempConfig(t, &global.ConfigPath.Global, `{
|
||||||
|
"system": {
|
||||||
|
"debugMode": true,
|
||||||
|
"listenPort": "8080"
|
||||||
|
},
|
||||||
|
"controllerMethod": {
|
||||||
|
"email": {
|
||||||
|
"enabled": false,
|
||||||
|
"smtpHost": "smtp.example.com",
|
||||||
|
"smtpPort": 587,
|
||||||
|
"to": ["old@example.com"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
patch := map[string]interface{}{
|
||||||
|
"system": map[string]interface{}{
|
||||||
|
"debugMode": false, // partial: listenPort must survive
|
||||||
|
},
|
||||||
|
"controllerMethod": map[string]interface{}{
|
||||||
|
"email": map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"smtpPort": json.Number("465"),
|
||||||
|
"to": []interface{}{"new@example.com"}, // arrays replace, not merge
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := UpdateSettings(SettingGlobal, patch); err != nil {
|
||||||
|
t.Fatalf("UpdateSettings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := GetSettings(SettingGlobal)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetSettings: %v", err)
|
||||||
|
}
|
||||||
|
got := string(data)
|
||||||
|
|
||||||
|
for _, want := range []string{
|
||||||
|
`"debugMode": false`,
|
||||||
|
`"listenPort": "8080"`, // sibling untouched
|
||||||
|
`"smtpHost": "smtp.example.com"`, // sibling untouched
|
||||||
|
`"smtpPort": 465`, // number kept verbatim, not 465.0
|
||||||
|
`"new@example.com"`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(got, want) {
|
||||||
|
t.Errorf("merged config missing %q:\n%s", want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.Contains(got, "old@example.com") {
|
||||||
|
t.Errorf("array was merged instead of replaced:\n%s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSettingsTypeValidation(t *testing.T) {
|
||||||
|
for _, valid := range []string{SettingGlobal, SettingBotUserConfig, SettingBotNodeConfig} {
|
||||||
|
if !IsValidSettingsType(valid) {
|
||||||
|
t.Errorf("expected %q to be a valid settings type", valid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, invalid := range []string{"", "system", "node", "bot"} {
|
||||||
|
if IsValidSettingsType(invalid) {
|
||||||
|
t.Errorf("expected %q to be an invalid settings type", invalid)
|
||||||
|
}
|
||||||
|
if _, err := GetSettings(invalid); err != ErrUnsupportedSettingsType {
|
||||||
|
t.Errorf("GetSettings(%q) error = %v, want ErrUnsupportedSettingsType", invalid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSettingsBotNodeMissingFile(t *testing.T) {
|
||||||
|
// Point at a temp path that does not exist yet.
|
||||||
|
writeTempConfig(t, &global.ConfigPath.BotNodeConfig, "")
|
||||||
|
os.Remove(global.ConfigPath.BotNodeConfig)
|
||||||
|
|
||||||
|
data, err := GetSettings(SettingBotNodeConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetSettings on missing bot_node_config: %v", err)
|
||||||
|
}
|
||||||
|
if string(data) != "{}" {
|
||||||
|
t.Errorf("expected empty object for missing bot_node_config, got %s", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
+82
-11
@@ -18,6 +18,38 @@ type ServerExecRequest struct {
|
|||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServerInfoValue is the per-server payload returned by GET /api/server/getInfo,
|
||||||
|
// mirroring the static server info the Bot prints for /info.
|
||||||
|
type ServerInfoValue struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Info *node.Info `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerStatusValue is the per-server payload returned by GET /api/server/getStatus,
|
||||||
|
// mirroring the live status the Bot prints for /status. Report is null when the
|
||||||
|
// node is known but has not delivered a status report yet.
|
||||||
|
type ServerStatusValue struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Online bool `json:"online"`
|
||||||
|
Report *node.Report `json:"report"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectTargetNodes resolves the uuid query parameter into the list of nodes
|
||||||
|
// the caller asked for. uuid "all" selects every tracked node; any other uuid
|
||||||
|
// selects that single node. A boolean reports whether the uuid was found.
|
||||||
|
func collectTargetNodes(tracker *node.Tracker, uuid string) ([]*node.Node, bool) {
|
||||||
|
if uuid == "all" {
|
||||||
|
return tracker.GetAllNodes(), true
|
||||||
|
}
|
||||||
|
n, exists := tracker.GetNode(uuid)
|
||||||
|
if !exists {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return []*node.Node{n}, true
|
||||||
|
}
|
||||||
|
|
||||||
// ServerListHandler handles GET /api/server/list.
|
// ServerListHandler handles GET /api/server/list.
|
||||||
func ServerListHandler(w http.ResponseWriter, r *http.Request) {
|
func ServerListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !utils.Auth(w, r, "GET", "bot") {
|
if !utils.Auth(w, r, "GET", "bot") {
|
||||||
@@ -38,9 +70,11 @@ func ServerListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerGetStatusHandler handles GET /api/server/getStatus?uuid=xxx.
|
// ServerGetInfoHandler handles GET /api/server/getInfo?uuid=xxx|all.
|
||||||
func ServerGetStatusHandler(w http.ResponseWriter, r *http.Request) {
|
// Returns the static info of the requested server(s) as a uuid-keyed object,
|
||||||
if !utils.Auth(w, r, "GET", "bot") {
|
// mirroring the data the Bot uses for /info.
|
||||||
|
func ServerGetInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !utils.Auth(w, r, "GET", "admin") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,19 +84,56 @@ func ServerGetStatusHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// First try to get data from the local tracker.
|
|
||||||
tracker := node.GetTracker()
|
tracker := node.GetTracker()
|
||||||
if tracker != nil {
|
if tracker == nil {
|
||||||
if n, exists := tracker.GetNode(uuid); exists && n.LatestReport != nil {
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "node tracker not initialized")
|
||||||
utils.SendSuccessResponse(w, "", map[string]interface{}{
|
|
||||||
"uuid": uuid,
|
|
||||||
"report": n.LatestReport,
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodes, found := collectTargetNodes(tracker, uuid)
|
||||||
|
if !found {
|
||||||
|
utils.SendErrorResponse(w, http.StatusNotFound, fmt.Sprintf("server with uuid %s not found", uuid))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.SendErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no recent data for uuid: %s", uuid))
|
result := make(map[string]interface{}, len(nodes))
|
||||||
|
for _, n := range nodes {
|
||||||
|
result[n.UUID] = ServerInfoValue{UUID: n.UUID, Name: n.Name, Info: n.Info}
|
||||||
|
}
|
||||||
|
utils.SendSuccessResponse(w, "", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerGetStatusHandler handles GET /api/server/getStatus?uuid=xxx|all.
|
||||||
|
// Returns the live status of the requested server(s) as a uuid-keyed object,
|
||||||
|
// mirroring the data the Bot uses for /status.
|
||||||
|
func ServerGetStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !utils.Auth(w, r, "GET", "admin") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := r.URL.Query().Get("uuid")
|
||||||
|
if uuid == "" {
|
||||||
|
utils.SendErrorResponse(w, http.StatusBadRequest, "missing uuid parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tracker := node.GetTracker()
|
||||||
|
if tracker == nil {
|
||||||
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "node tracker not initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, found := collectTargetNodes(tracker, uuid)
|
||||||
|
if !found {
|
||||||
|
utils.SendErrorResponse(w, http.StatusNotFound, fmt.Sprintf("server with uuid %s not found", uuid))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]interface{}, len(nodes))
|
||||||
|
for _, n := range nodes {
|
||||||
|
result[n.UUID] = ServerStatusValue{UUID: n.UUID, Name: n.Name, Online: n.Online, Report: n.LatestReport}
|
||||||
|
}
|
||||||
|
utils.SendSuccessResponse(w, "", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerExecHandler handles POST /api/server/exec.
|
// ServerExecHandler handles POST /api/server/exec.
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"nukumizu-backend/internal/node"
|
||||||
|
"nukumizu-backend/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// adminGet builds an authenticated GET request for the given path using an
|
||||||
|
// admin token and a fresh X-Timestamp.
|
||||||
|
func adminGet(t *testing.T, path string) *http.Request {
|
||||||
|
t.Helper()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||||
|
req.Header.Set("X-Token", "test-admin-token")
|
||||||
|
req.Header.Set("X-Timestamp", strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// seedTracker initializes the global node tracker with two nodes, one online
|
||||||
|
// with a status report and one offline that has not reported yet.
|
||||||
|
func seedTracker(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
node.InitTracker()
|
||||||
|
|
||||||
|
tracker := node.GetTracker()
|
||||||
|
tracker.UpdateNodeList(map[string]node.NodeListEntry{
|
||||||
|
"u1": {
|
||||||
|
Name: "alpha",
|
||||||
|
Info: &node.Info{},
|
||||||
|
},
|
||||||
|
"u2": {
|
||||||
|
Name: "beta",
|
||||||
|
Info: &node.Info{},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
report := node.Report{}
|
||||||
|
report.CPU.Usage = 12.5
|
||||||
|
report.RAM.Total = 1024
|
||||||
|
report.RAM.Used = 512
|
||||||
|
tracker.UpdateStatus([]string{"u1"}, map[string]node.Report{"u1": report})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupAdminToken() {
|
||||||
|
utils.AddToken("test-admin-token", 1, "admin", "tester")
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeResponse(t *testing.T, w *httptest.ResponseRecorder) map[string]json.RawMessage {
|
||||||
|
t.Helper()
|
||||||
|
var body map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatalf("decode response: %v; body=%s", err, w.Body.String())
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerGetInfoAll(t *testing.T) {
|
||||||
|
setupAdminToken()
|
||||||
|
seedTracker(t)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
ServerGetInfoHandler(w, adminGet(t, "/api/server/getInfo?uuid=all"))
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
body := decodeResponse(t, w)
|
||||||
|
for _, uuid := range []string{"u1", "u2"} {
|
||||||
|
if _, ok := body[uuid]; !ok {
|
||||||
|
t.Errorf("response missing uuid %q: %s", uuid, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var one struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Info *node.Info `json:"info"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body["u1"], &one); err != nil {
|
||||||
|
t.Fatalf("decode u1: %v", err)
|
||||||
|
}
|
||||||
|
if one.UUID != "u1" || one.Name != "alpha" {
|
||||||
|
t.Errorf("u1 = %+v", one)
|
||||||
|
}
|
||||||
|
if one.Info == nil {
|
||||||
|
t.Error("expected static info present for u1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerGetInfoSingleAndMissing(t *testing.T) {
|
||||||
|
setupAdminToken()
|
||||||
|
seedTracker(t)
|
||||||
|
|
||||||
|
// Single existing uuid: response is keyed by that uuid (uniform shape).
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
ServerGetInfoHandler(w, adminGet(t, "/api/server/getInfo?uuid=u1"))
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
body := decodeResponse(t, w)
|
||||||
|
if _, ok := body["u1"]; !ok {
|
||||||
|
t.Errorf("single-uuid response missing key u1: %s", w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown uuid yields 404.
|
||||||
|
w2 := httptest.NewRecorder()
|
||||||
|
ServerGetInfoHandler(w2, adminGet(t, "/api/server/getInfo?uuid=ghost"))
|
||||||
|
if w2.Code != http.StatusNotFound {
|
||||||
|
t.Errorf("missing uuid status = %d, want 404", w2.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerGetStatusAll(t *testing.T) {
|
||||||
|
setupAdminToken()
|
||||||
|
seedTracker(t)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
ServerGetStatusHandler(w, adminGet(t, "/api/server/getStatus?uuid=all"))
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, body = %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
body := decodeResponse(t, w)
|
||||||
|
|
||||||
|
var online struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Online bool `json:"online"`
|
||||||
|
Report *node.Report `json:"report"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body["u1"], &online); err != nil {
|
||||||
|
t.Fatalf("decode u1: %v", err)
|
||||||
|
}
|
||||||
|
if !online.Online || online.Report == nil {
|
||||||
|
t.Errorf("u1 should be online with a report: %+v", online)
|
||||||
|
}
|
||||||
|
|
||||||
|
var offline struct {
|
||||||
|
Online bool `json:"online"`
|
||||||
|
Report *node.Report `json:"report"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body["u2"], &offline); err != nil {
|
||||||
|
t.Fatalf("decode u2: %v", err)
|
||||||
|
}
|
||||||
|
if offline.Online {
|
||||||
|
t.Error("u2 should be offline")
|
||||||
|
}
|
||||||
|
if offline.Report != nil {
|
||||||
|
t.Errorf("u2 report should be null, got %+v", offline.Report)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ func SetupRouter() *http.ServeMux {
|
|||||||
|
|
||||||
// Server endpoints (authenticated).
|
// Server endpoints (authenticated).
|
||||||
mux.HandleFunc("/api/server/list", handler.ServerListHandler)
|
mux.HandleFunc("/api/server/list", handler.ServerListHandler)
|
||||||
|
mux.HandleFunc("/api/server/getInfo", handler.ServerGetInfoHandler)
|
||||||
mux.HandleFunc("/api/server/getStatus", handler.ServerGetStatusHandler)
|
mux.HandleFunc("/api/server/getStatus", handler.ServerGetStatusHandler)
|
||||||
mux.HandleFunc("/api/server/exec", handler.ServerExecHandler)
|
mux.HandleFunc("/api/server/exec", handler.ServerExecHandler)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user