287 lines
7.4 KiB
Go
287 lines
7.4 KiB
Go
// Copyright (C) 2016 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/d4l3k/messagediff"
|
|
"github.com/syncthing/syncthing/lib/config"
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
"github.com/syncthing/syncthing/lib/sync"
|
|
"github.com/thejerf/suture"
|
|
)
|
|
|
|
func TestCSRFToken(t *testing.T) {
|
|
t1 := newCsrfToken()
|
|
t2 := newCsrfToken()
|
|
|
|
t3 := newCsrfToken()
|
|
if !validCsrfToken(t3) {
|
|
t.Fatal("t3 should be valid")
|
|
}
|
|
|
|
for i := 0; i < 250; i++ {
|
|
if i%5 == 0 {
|
|
// t1 and t2 should remain valid by virtue of us checking them now
|
|
// and then.
|
|
if !validCsrfToken(t1) {
|
|
t.Fatal("t1 should be valid at iteration", i)
|
|
}
|
|
if !validCsrfToken(t2) {
|
|
t.Fatal("t2 should be valid at iteration", i)
|
|
}
|
|
}
|
|
|
|
// The newly generated token is always valid
|
|
t4 := newCsrfToken()
|
|
if !validCsrfToken(t4) {
|
|
t.Fatal("t4 should be valid at iteration", i)
|
|
}
|
|
}
|
|
|
|
if validCsrfToken(t3) {
|
|
t.Fatal("t3 should have expired by now")
|
|
}
|
|
}
|
|
|
|
func TestStopAfterBrokenConfig(t *testing.T) {
|
|
cfg := config.Configuration{
|
|
GUI: config.GUIConfiguration{
|
|
RawAddress: "127.0.0.1:0",
|
|
RawUseTLS: false,
|
|
},
|
|
}
|
|
w := config.Wrap("/dev/null", cfg)
|
|
|
|
srv, err := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv.started = make(chan struct{})
|
|
|
|
sup := suture.NewSimple("test")
|
|
sup.Add(srv)
|
|
sup.ServeBackground()
|
|
|
|
<-srv.started
|
|
|
|
// Service is now running, listening on a random port on localhost. Now we
|
|
// request a config change to a completely invalid listen address. The
|
|
// commit will fail and the service will be in a broken state.
|
|
|
|
newCfg := config.Configuration{
|
|
GUI: config.GUIConfiguration{
|
|
RawAddress: "totally not a valid address",
|
|
RawUseTLS: false,
|
|
},
|
|
}
|
|
if srv.CommitConfiguration(cfg, newCfg) {
|
|
t.Fatal("Config commit should have failed")
|
|
}
|
|
|
|
// Nonetheless, it should be fine to Stop() it without panic.
|
|
|
|
sup.Stop()
|
|
}
|
|
|
|
func TestAssetsDir(t *testing.T) {
|
|
// For any given request to $FILE, we should return the first found of
|
|
// - assetsdir/$THEME/$FILE
|
|
// - compiled in asset $THEME/$FILE
|
|
// - assetsdir/default/$FILE
|
|
// - compiled in asset default/$FILE
|
|
|
|
// The asset map contains compressed assets, so create a couple of gzip compressed assets here.
|
|
buf := new(bytes.Buffer)
|
|
gw := gzip.NewWriter(buf)
|
|
gw.Write([]byte("default"))
|
|
gw.Close()
|
|
def := buf.Bytes()
|
|
|
|
buf = new(bytes.Buffer)
|
|
gw = gzip.NewWriter(buf)
|
|
gw.Write([]byte("foo"))
|
|
gw.Close()
|
|
foo := buf.Bytes()
|
|
|
|
e := embeddedStatic{
|
|
theme: "foo",
|
|
mut: sync.NewRWMutex(),
|
|
assetDir: "testdata",
|
|
assets: map[string][]byte{
|
|
"foo/a": foo, // overridden in foo/a
|
|
"foo/b": foo,
|
|
"default/a": def, // overridden in default/a (but foo/a takes precedence)
|
|
"default/b": def, // overridden in default/b (but foo/b takes precedence)
|
|
"default/c": def,
|
|
},
|
|
}
|
|
|
|
s := httptest.NewServer(e)
|
|
defer s.Close()
|
|
|
|
// assetsdir/foo/a exists, overrides compiled in
|
|
expectURLToContain(t, s.URL+"/a", "overridden-foo")
|
|
|
|
// foo/b is compiled in, default/b is overriden, return compiled in
|
|
expectURLToContain(t, s.URL+"/b", "foo")
|
|
|
|
// only exists as compiled in default/c so use that
|
|
expectURLToContain(t, s.URL+"/c", "default")
|
|
|
|
// only exists as overriden default/d so use that
|
|
expectURLToContain(t, s.URL+"/d", "overridden-default")
|
|
}
|
|
|
|
func expectURLToContain(t *testing.T, url, exp string) {
|
|
res, err := http.Get(url)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
if res.StatusCode != 200 {
|
|
t.Errorf("Got %s instead of 200 OK", res.Status)
|
|
return
|
|
}
|
|
|
|
data, err := ioutil.ReadAll(res.Body)
|
|
res.Body.Close()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
if string(data) != exp {
|
|
t.Errorf("Got %q instead of %q on %q", data, exp, url)
|
|
return
|
|
}
|
|
}
|
|
|
|
func TestDirNames(t *testing.T) {
|
|
names := dirNames("testdata")
|
|
expected := []string{"default", "foo", "testfolder"}
|
|
if diff, equal := messagediff.PrettyDiff(expected, names); !equal {
|
|
t.Errorf("Unexpected dirNames return: %#v\n%s", names, diff)
|
|
}
|
|
}
|
|
|
|
func TestAPIServiceRequests(t *testing.T) {
|
|
model := new(mockedModel)
|
|
cfg := new(mockedConfig)
|
|
httpsCertFile := "../../test/h1/https-cert.pem"
|
|
httpsKeyFile := "../../test/h1/https-key.pem"
|
|
assetDir := "../../gui"
|
|
eventSub := new(mockedEventSub)
|
|
discoverer := new(mockedCachingMux)
|
|
relayService := new(mockedRelayService)
|
|
errorLog := new(mockedLoggerRecorder)
|
|
systemLog := new(mockedLoggerRecorder)
|
|
|
|
// Instantiate the API service
|
|
svc, err := newAPIService(protocol.LocalDeviceID, cfg, httpsCertFile, httpsKeyFile, assetDir, model,
|
|
eventSub, discoverer, relayService, errorLog, systemLog)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = svc
|
|
|
|
// Make sure the API service is listening, and get the URL to use.
|
|
addr := svc.listener.Addr()
|
|
if addr == nil {
|
|
t.Fatal("Nil listening address from API service")
|
|
}
|
|
tcpAddr, err := net.ResolveTCPAddr("tcp", addr.String())
|
|
if err != nil {
|
|
t.Fatal("Weird address from API service:", err)
|
|
}
|
|
baseURL := fmt.Sprintf("http://127.0.0.1:%d", tcpAddr.Port)
|
|
|
|
// Actually start the API service
|
|
supervisor := suture.NewSimple("API test")
|
|
supervisor.Add(svc)
|
|
supervisor.ServeBackground()
|
|
|
|
// Try requests to common URLs, all of which should succeed and return
|
|
// some sort of JSON object.
|
|
urls := []string{
|
|
"/rest/system/status",
|
|
"/rest/system/config",
|
|
"/rest/system/config/insync",
|
|
// "/rest/system/connections", does not return an object in the empty case ("null"), should be fixed
|
|
"/rest/system/discovery",
|
|
"/rest/system/error?since=0",
|
|
"/rest/system/ping",
|
|
// "/rest/system/upgrade", depends on Github API, not good for testing
|
|
"/rest/system/version",
|
|
"/rest/system/debug",
|
|
"/rest/system/log?since=0",
|
|
}
|
|
for _, url := range urls {
|
|
t.Log("Testing", url, "...")
|
|
testHTTPJSONObject(t, baseURL+url)
|
|
}
|
|
}
|
|
|
|
// testHTTPJSONObject tries the given URL and verifies that the HTTP request
|
|
// succeeds and that a JSON object (something beginning with "{") is
|
|
// returned. Returns the object data, or nil on failure.
|
|
func testHTTPJSONObject(t *testing.T, url string) []byte {
|
|
resp := testHTTPRequest(t, url)
|
|
if resp == nil {
|
|
return nil
|
|
}
|
|
|
|
ct := resp.Header.Get("Content-Type")
|
|
if !strings.HasPrefix(ct, "application/json") {
|
|
t.Errorf("The content type on %s should be application/json, not %q", url, ct)
|
|
return nil
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error reading %s: %v", url, err)
|
|
return nil
|
|
}
|
|
|
|
if !bytes.HasPrefix(data, []byte("{")) {
|
|
t.Errorf("Returned data from %s does not look like a JSON object: %s", url, data)
|
|
return nil
|
|
}
|
|
return data
|
|
}
|
|
|
|
// testHTTPRequest performs a HTTP GET request and verifies that the
|
|
// response is successfull (code 200). Returns the *http.Response or nil on
|
|
// failure.
|
|
func testHTTPRequest(t *testing.T, url string) *http.Response {
|
|
cli := &http.Client{
|
|
Timeout: time.Second,
|
|
}
|
|
resp, err := cli.Get(url)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error requesting %s: %v", url, err)
|
|
return nil
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
t.Errorf("Get on %s should have returned status code 200, not %s", url, resp.Status)
|
|
return nil
|
|
}
|
|
return resp
|
|
}
|