mirror of
https://github.com//cppla/ServerStatus
synced 2026-08-06 10:13:57 +08:00
159 lines
8.0 KiB
Go
159 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestHTTPAPIAndStaticUI(t *testing.T) {
|
|
app := newTestApp(t, minimalTestConfig())
|
|
router := app.router()
|
|
|
|
response := performRequest(router, http.MethodGet, "/", "", "")
|
|
if response.Code != 200 || !strings.Contains(response.Body.String(), "test-ui") {
|
|
t.Fatalf("static UI: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodGet, "/api/health", "", "")
|
|
if response.Code != 200 || !strings.Contains(response.Body.String(), `"enabled":true`) {
|
|
t.Fatalf("health: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodGet, "/api/openapi.json", "", "")
|
|
if response.Code != 200 {
|
|
t.Fatalf("openapi: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
var openapi map[string]any
|
|
if err := json.Unmarshal(response.Body.Bytes(), &openapi); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
paths := openapi["paths"].(map[string]any)
|
|
if openapi["openapi"] != "3.1.0" || paths["/api/servers/{username}"] == nil || paths["/api/watchdog/{id}"] == nil {
|
|
t.Fatalf("OpenAPI document is incomplete: %#v", openapi)
|
|
}
|
|
monitorOperations := paths["/api/monitors"].(map[string]any)
|
|
createResponses := monitorOperations["post"].(map[string]any)["responses"].(map[string]any)
|
|
if createResponses["201"] == nil || createResponses["200"] != nil {
|
|
t.Fatalf("OpenAPI create response must describe HTTP 201: %#v", createResponses)
|
|
}
|
|
listResponses := monitorOperations["get"].(map[string]any)["responses"].(map[string]any)
|
|
listSchema := listResponses["200"].(map[string]any)["content"].(map[string]any)["application/json"].(map[string]any)["schema"].(map[string]any)
|
|
if listSchema["properties"].(map[string]any)["monitors"] == nil {
|
|
t.Fatalf("OpenAPI list response does not expose monitors: %#v", listSchema)
|
|
}
|
|
restartResponses := paths["/api/restart"].(map[string]any)["post"].(map[string]any)["responses"].(map[string]any)
|
|
if restartResponses["202"] == nil {
|
|
t.Fatalf("OpenAPI restart response must describe HTTP 202: %#v", restartResponses)
|
|
}
|
|
response = performRequest(router, http.MethodGet, "/api/config", "", "wrong")
|
|
if response.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401, got %d", response.Code)
|
|
}
|
|
response = performRequest(router, http.MethodGet, "/api/config", "", "test-token")
|
|
if response.Code != 200 {
|
|
t.Fatalf("config: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
|
|
monitor := `{"name":"新增服务","host":"tcp://127.0.0.1:80","type":"tcp","interval":"30"}`
|
|
response = performRequest(router, http.MethodPost, "/api/monitors", monitor, "test-token")
|
|
if response.Code != http.StatusCreated || !strings.Contains(response.Body.String(), "新增服务") {
|
|
t.Fatalf("create monitor: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
path := "/api/monitors/" + url.PathEscape("新增服务")
|
|
monitor = `{"name":"更新服务","host":"https://example.org","type":"https","interval":45}`
|
|
response = performRequest(router, http.MethodPut, path, monitor, "test-token")
|
|
if response.Code != 200 || !strings.Contains(response.Body.String(), "更新服务") {
|
|
t.Fatalf("update monitor: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodDelete, "/api/monitors/"+url.PathEscape("更新服务"), "", "test-token")
|
|
if response.Code != 200 {
|
|
t.Fatalf("delete monitor: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
percentMonitor := `{"name":"rate%check","host":"https://example.com","type":"https","interval":30}`
|
|
response = performRequest(router, http.MethodPost, "/api/monitors", percentMonitor, "test-token")
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("create percent monitor: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodDelete, "/api/monitors/"+url.PathEscape("rate%check"), "", "test-token")
|
|
if response.Code != 200 {
|
|
t.Fatalf("delete percent monitor: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
|
|
server := `{"username":"s02","name":"node2","type":"kvm","host":"host2","location":"JP","password":"secret","monthstart":31}`
|
|
response = performRequest(router, http.MethodPost, "/api/servers", server, "test-token")
|
|
if response.Code != http.StatusCreated || !strings.Contains(response.Body.String(), `"monthstart":28`) {
|
|
t.Fatalf("create server: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodDelete, "/api/servers/s02", "", "test-token")
|
|
if response.Code != 200 {
|
|
t.Fatalf("delete server: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
numericServer := `{"username":"0","name":"numeric","type":"kvm","host":"host0","location":"US","password":"secret","monthstart":1}`
|
|
response = performRequest(router, http.MethodPost, "/api/servers", numericServer, "test-token")
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("create numeric server: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
numericServer = `{"username":"0","name":"numeric-updated","type":"kvm","host":"host0","location":"US","password":"secret","monthstart":1}`
|
|
response = performRequest(router, http.MethodPut, "/api/servers/0", numericServer, "test-token")
|
|
if response.Code != 200 || !strings.Contains(response.Body.String(), "numeric-updated") {
|
|
t.Fatalf("update numeric server: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodDelete, "/api/servers/0", "", "test-token")
|
|
if response.Code != 200 || !strings.Contains(response.Body.String(), "numeric-updated") {
|
|
t.Fatalf("delete numeric server: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
if app.RuntimeSnapshot().Servers[0].Username != "s01" {
|
|
t.Fatal("numeric username operation modified the server at numeric index")
|
|
}
|
|
|
|
response = performRequest(router, http.MethodPost, "/api/servers/s01/reset-traffic", "", "test-token")
|
|
if response.Code != http.StatusConflict {
|
|
t.Fatalf("offline reset should conflict: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodPost, "/api/reload", "", "test-token")
|
|
if response.Code != 200 {
|
|
t.Fatalf("reload: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodPost, "/api/restart", "", "test-token")
|
|
if response.Code != http.StatusAccepted || !strings.Contains(response.Body.String(), "in-process") {
|
|
t.Fatalf("restart: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
|
|
data, err := os.ReadFile(app.opts.ConfigPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var persisted map[string]any
|
|
if err := json.Unmarshal(data, &persisted); err != nil {
|
|
t.Fatalf("persisted config is invalid: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHTTPRejectsInvalidAndOversizedBodies(t *testing.T) {
|
|
app := newTestApp(t, minimalTestConfig())
|
|
router := app.router()
|
|
response := performRequest(router, http.MethodPost, "/api/servers", `{"name":`, "test-token")
|
|
if response.Code != 400 {
|
|
t.Fatalf("invalid JSON: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
response = performRequest(router, http.MethodPost, "/api/servers", `{"name":"x"}`, "test-token")
|
|
if response.Code != 400 {
|
|
t.Fatalf("missing fields: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
oversized := `{"name":"` + strings.Repeat("x", maxRequestBody) + `"}`
|
|
response = performRequest(router, http.MethodPost, "/api/servers", oversized, "test-token")
|
|
if response.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("oversized body: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
|
|
doc := app.ConfigSnapshot()
|
|
doc["watchdog"] = []any{map[string]any{"name": "broken", "rule": "cpu >", "interval": 10}}
|
|
data, _ := json.Marshal(doc)
|
|
response = performRequest(router, http.MethodPut, "/api/config", string(data), "test-token")
|
|
if response.Code != 400 {
|
|
t.Fatalf("invalid watchdog: status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
}
|