lib/api: Add /rest/config endpoint (fixes #6540) (#7001)

This commit is contained in:
Simon Frei
2020-10-22 19:54:35 +02:00
committed by GitHub
parent 1c2be84e4e
commit f0f60ba2e7
12 changed files with 717 additions and 154 deletions
+178 -2
View File
@@ -40,8 +40,13 @@ import (
var (
confDir = filepath.Join("testdata", "config")
token = filepath.Join(confDir, "csrftokens.txt")
dev1 protocol.DeviceID
)
func init() {
dev1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
}
func TestMain(m *testing.M) {
orig := locations.GetBaseDir(locations.ConfigBaseDir)
locations.SetBaseDir(locations.ConfigBaseDir, confDir)
@@ -396,6 +401,56 @@ func TestAPIServiceRequests(t *testing.T) {
Type: "text/plain",
Prefix: "",
},
// /rest/config
{
URL: "/rest/config/folders",
Code: 200,
Type: "application/json",
Prefix: "",
},
{
URL: "/rest/config/folders/missing",
Code: 404,
Type: "text/plain",
Prefix: "",
},
{
URL: "/rest/config/devices",
Code: 200,
Type: "application/json",
Prefix: "",
},
{
URL: "/rest/config/devices/illegalid",
Code: 400,
Type: "text/plain",
Prefix: "",
},
{
URL: "/rest/config/devices/" + protocol.GlobalDeviceID.String(),
Code: 404,
Type: "text/plain",
Prefix: "",
},
{
URL: "/rest/config/options",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/config/gui",
Code: 200,
Type: "application/json",
Prefix: "{",
},
{
URL: "/rest/config/ldap",
Code: 200,
Type: "application/json",
Prefix: "{",
},
}
for _, tc := range cases {
@@ -520,7 +575,7 @@ func TestHTTPLogin(t *testing.T) {
}
}
func startHTTP(cfg *mockedConfig) (string, *suture.Supervisor, error) {
func startHTTP(cfg config.Wrapper) (string, *suture.Supervisor, error) {
m := new(mockedModel)
assetDir := "../../gui"
eventSub := new(mockedEventSub)
@@ -552,7 +607,7 @@ func startHTTP(cfg *mockedConfig) (string, *suture.Supervisor, error) {
return "", nil, fmt.Errorf("weird address from API service: %w", err)
}
host, _, _ := net.SplitHostPort(cfg.gui.RawAddress)
host, _, _ := net.SplitHostPort(cfg.GUI().RawAddress)
if host == "" || host == "0.0.0.0" {
host = "127.0.0.1"
}
@@ -1174,6 +1229,127 @@ func TestShouldRegenerateCertificate(t *testing.T) {
}
}
func TestConfigChanges(t *testing.T) {
t.Parallel()
const testAPIKey = "foobarbaz"
cfg := config.Configuration{
GUI: config.GUIConfiguration{
RawAddress: "127.0.0.1:0",
RawUseTLS: false,
APIKey: testAPIKey,
},
}
tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
if err != nil {
panic(err)
}
defer os.Remove(tmpFile.Name())
w := config.Wrap(tmpFile.Name(), cfg, events.NoopLogger)
tmpFile.Close()
baseURL, sup, err := startHTTP(w)
if err != nil {
t.Fatal("Unexpected error from getting base URL:", err)
}
defer sup.Stop()
cli := &http.Client{
Timeout: time.Second,
}
do := func(req *http.Request, status int) *http.Response {
t.Helper()
req.Header.Set("X-API-Key", testAPIKey)
resp, err := cli.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != status {
t.Errorf("Expected status %v, got %v", status, resp.StatusCode)
}
return resp
}
mod := func(method, path string, data interface{}) {
t.Helper()
bs, err := json.Marshal(data)
if err != nil {
t.Fatal(err)
}
req, _ := http.NewRequest(method, baseURL+path, bytes.NewReader(bs))
do(req, http.StatusOK).Body.Close()
}
get := func(path string) *http.Response {
t.Helper()
req, _ := http.NewRequest(http.MethodGet, baseURL+path, nil)
return do(req, http.StatusOK)
}
dev1Path := "/rest/config/devices/" + dev1.String()
// Create device
mod(http.MethodPut, "/rest/config/devices", []config.DeviceConfiguration{{DeviceID: dev1}})
// Check its there
get(dev1Path).Body.Close()
// Modify just a single attribute
mod(http.MethodPatch, dev1Path, map[string]bool{"Paused": true})
// Check that attribute
resp := get(dev1Path)
var dev config.DeviceConfiguration
if err := unmarshalTo(resp.Body, &dev); err != nil {
t.Fatal(err)
}
if !dev.Paused {
t.Error("Expected device to be paused")
}
folder2Path := "/rest/config/folders/folder2"
// Create a folder and add another
mod(http.MethodPut, "/rest/config/folders", []config.FolderConfiguration{{ID: "folder1", Path: "folder1"}})
mod(http.MethodPut, folder2Path, config.FolderConfiguration{ID: "folder2", Path: "folder2"})
// Check they are there
get("/rest/config/folders/folder1").Body.Close()
get(folder2Path).Body.Close()
// Modify just a single attribute
mod(http.MethodPatch, folder2Path, map[string]bool{"Paused": true})
// Check that attribute
resp = get(folder2Path)
var folder config.FolderConfiguration
if err := unmarshalTo(resp.Body, &folder); err != nil {
t.Fatal(err)
}
if !dev.Paused {
t.Error("Expected folder to be paused")
}
// Delete folder2
req, _ := http.NewRequest(http.MethodDelete, baseURL+folder2Path, nil)
do(req, http.StatusOK)
// Check folder1 is still there and folder2 gone
get("/rest/config/folders/folder1").Body.Close()
req, _ = http.NewRequest(http.MethodGet, baseURL+folder2Path, nil)
do(req, http.StatusNotFound)
mod(http.MethodPatch, "/rest/config/options", map[string]int{"maxSendKbps": 50})
resp = get("/rest/config/options")
var opts config.OptionsConfiguration
if err := unmarshalTo(resp.Body, &opts); err != nil {
t.Fatal(err)
}
if opts.MaxSendKbps != 50 {
t.Error("Exepcted 50 for MaxSendKbps, got", opts.MaxSendKbps)
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false