Files

160 lines
6.0 KiB
Go

package server
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// newTestServer is a server on a fresh database with an admin signed in (the
// cookie) and one machine, x1.
func newTestServer(t *testing.T) (*Server, *http.Cookie) {
t.Helper()
db, err := OpenDB(t.TempDir() + "/t.db")
if err != nil {
t.Fatal(err)
}
s, err := New(Options{DB: db, DistDir: t.TempDir()})
if err != nil {
t.Fatal(err)
}
hash, _ := hashPassword("Passw0rd12345")
if err := db.CreateAdmin("adm", hash); err != nil {
t.Fatal(err)
}
_ = db.SetConfig(cfgSetupDone, "1")
a, _ := db.AdminByUsername("adm")
if err := db.CreateSession("sid-test", a.ID, sessionTTL); err != nil {
t.Fatal(err)
}
if err := db.CreateAgent(Agent{ID: "x1", Name: "t", Hostname: "h", OS: "linux", Arch: "amd64", TokenHash: "x"}); err != nil {
t.Fatal(err)
}
return s, &http.Cookie{Name: sessionCookie, Value: "sid-test"}
}
func get(t *testing.T, s *Server, c *http.Cookie, path string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodGet, path, nil)
req.AddCookie(c)
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
return w
}
func TestFragmentHasNoChrome(t *testing.T) {
s, cookie := newTestServer(t)
w := get(t, s, cookie, "/settings?frag=1")
body := w.Body.String()
if w.Code != 200 || strings.Contains(body, "<html") || strings.Contains(body, "topbar") {
t.Fatalf("fragment: code %d, has chrome: %q", w.Code, body[:min(200, len(body))])
}
if !strings.Contains(body, `data-module="settings"`) {
t.Fatalf("fragment has no module root")
}
}
func TestShellRoutes(t *testing.T) {
s, cookie := newTestServer(t)
for _, p := range []string{"/", "/workspace", "/terminal", "/settings", "/agents/x1/files", "/agents/x1/terminal"} {
if w := get(t, s, cookie, p); !strings.Contains(w.Body.String(), `id="app"`) {
t.Errorf("%s: not the shell (code %d)", p, w.Code)
}
}
}
func TestSSHFSOpen(t *testing.T) {
s, admin := newTestServer(t)
// Long gone: the machine is offline.
if _, err := s.db.sql.Exec(`UPDATE agents SET last_seen = 0`); err != nil {
t.Fatal(err)
}
post := func(c *http.Cookie) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodPost, "/api/panel/agents/x1/sshfs/open", strings.NewReader(`{"ssh":{"host":"h","user":"u","password":"p"}}`))
req.AddCookie(c)
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
return w
}
// The machine exists but is offline: the route answers in JSON.
if w := post(admin); w.Code != 200 || !strings.Contains(w.Body.String(), "офлайн") {
t.Fatalf("admin: %d %s", w.Code, w.Body.String())
}
// A user without the machine does not reach it.
hash, _ := hashPassword("userpass123")
id, _ := s.db.CreateUser("bob", hash, RoleUser)
_ = s.db.CreateSession("sid-bob", id, sessionTTL)
if w := post(&http.Cookie{Name: sessionCookie, Value: "sid-bob"}); w.Code != http.StatusNotFound {
t.Fatalf("a user without the machine got %d", w.Code)
}
}
// Renaming from the machine dialog keeps it open on «Изменить».
func TestRenameStaysOnEdit(t *testing.T) {
s, c := newTestServer(t)
tok := "tok-test"
var req *http.Request
req = httptest.NewRequest(http.MethodPost, "/agents/x1/rename", strings.NewReader("csrf="+tok+"&name=new"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.AddCookie(c)
req.AddCookie(&http.Cookie{Name: csrfCookie, Value: tok})
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
if loc := w.Header().Get("Location"); w.Code != http.StatusSeeOther || loc != "/agents/x1/edit" {
t.Fatalf("rename: %d to %q", w.Code, loc)
}
}
// Настройки show the assistant's model as it is set, key included.
func TestSettingsShowAI(t *testing.T) {
s, c := newTestServer(t)
_ = s.db.SetConfig(cfgAIKey, "key-123")
body := get(t, s, c, "/settings?frag=1").Body.String()
if !strings.Contains(body, `value="key-123"`) || !strings.Contains(body, `value="`+DefaultAIURL+`"`) {
t.Fatal("the AI card is empty")
}
}
// The look of the panel is each user's: kept on the server, only known
// settings with known values, and given to the page at once.
func TestViewPrefs(t *testing.T) {
s, c := newTestServer(t)
post := func(body string) string {
req := httptest.NewRequest(http.MethodPost, "/api/panel/account/prefs", strings.NewReader(body))
req.AddCookie(c)
w := httptest.NewRecorder()
s.Handler().ServeHTTP(w, req)
return w.Body.String()
}
if got := post(`{"tabIcons":"right","termFont":16,"compact":true,"motion":"off","evil":"<x>","fileTime":"maybe"}`); !strings.Contains(got, `"ok":true`) {
t.Fatal(got)
}
got := get(t, s, c, "/api/panel/account/prefs").Body.String()
if !strings.Contains(got, `"tabIcons":"right"`) || !strings.Contains(got, `"termFont":16`) || strings.Contains(got, "evil") || strings.Contains(got, "maybe") {
t.Fatalf("prefs: %s", got)
}
if body := get(t, s, c, "/workspace").Body.String(); !strings.Contains(body, `"tabIcons":"right"`) {
t.Fatal("the page does not get the prefs")
}
if got := post(`{"termFont":99}`); !strings.Contains(got, `"ok":true`) || strings.Contains(get(t, s, c, "/api/panel/account/prefs").Body.String(), "99") {
t.Fatal("a font size out of range was kept")
}
post(`{"openHere":"off"}`)
if got := get(t, s, c, "/api/panel/account/prefs").Body.String(); !strings.Contains(got, `"openHere":"off"`) {
t.Fatalf("openHere off not kept: %s", got)
}
post(`{"aiModelPick":"always","aiModel":"a1b2c3"}`)
if got := get(t, s, c, "/api/panel/account/prefs").Body.String(); !strings.Contains(got, `"aiModelPick":"always"`) || !strings.Contains(got, `"aiModel":"a1b2c3"`) {
t.Fatalf("the model choice not kept: %s", got)
}
post(`{"aiModelPick":"often","aiModel":"<x>"}`)
if got := get(t, s, c, "/api/panel/account/prefs").Body.String(); strings.Contains(got, "aiModel") {
t.Fatalf("an unknown model choice was kept: %s", got)
}
post(`{"openHere":"sometimes"}`)
if got := get(t, s, c, "/api/panel/account/prefs").Body.String(); strings.Contains(got, "openHere") {
t.Fatalf("an unknown openHere was kept: %s", got)
}
}