http: POST /api/users — create + setup-token + audit

This commit is contained in:
2026-05-05 09:38:59 +01:00
parent a985d45daa
commit a74dc33c1c
3 changed files with 204 additions and 0 deletions
+71
View File
@@ -1,9 +1,11 @@
package http
import (
"bytes"
"encoding/json"
"io"
stdhttp "net/http"
"strings"
"testing"
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
@@ -33,3 +35,72 @@ func TestAPIUsersList(t *testing.T) {
t.Errorf("count: got %d want 2", len(got.Users))
}
}
func TestAPIUserCreate(t *testing.T) {
t.Parallel()
srv, ts, _ := rawTestServerWithUI(t)
adminID := makeUser(t, srv, "admin1", store.RoleAdmin)
cookie := loginAs(t, srv, adminID)
body, _ := json.Marshal(map[string]any{
"username": "Bob", "email": "bob@example.com", "role": "operator",
})
req, _ := stdhttp.NewRequest("POST", ts.URL+"/api/users", bytes.NewReader(body))
req.AddCookie(cookie)
req.Header.Set("Content-Type", "application/json")
res, err := stdhttp.DefaultClient.Do(req)
if err != nil {
t.Fatalf("POST: %v", err)
}
defer res.Body.Close()
if res.StatusCode != stdhttp.StatusCreated {
body, _ := io.ReadAll(res.Body)
t.Fatalf("status: got %d body=%s", res.StatusCode, body)
}
var got struct {
ID string `json:"id"`
SetupURL string `json:"setup_url"`
}
_ = json.NewDecoder(res.Body).Decode(&got)
if got.ID == "" || got.SetupURL == "" {
t.Errorf("missing fields: %+v", got)
}
if !strings.Contains(got.SetupURL, "/setup?token=") {
t.Errorf("setup_url shape: %q", got.SetupURL)
}
// Verify lowercase-normalised.
u, err := srv.deps.Store.GetUserByUsername(t.Context(), "bob")
if err != nil {
t.Fatalf("get: %v", err)
}
if u.Username != "bob" {
t.Errorf("username: got %q want bob", u.Username)
}
if !u.MustChangePassword {
t.Error("must_change_password not set")
}
}
func TestAPIUserCreateRejectsDuplicateEnabled(t *testing.T) {
t.Parallel()
srv, ts, _ := rawTestServerWithUI(t)
adminID := makeUser(t, srv, "admin1", store.RoleAdmin)
makeUser(t, srv, "alice", store.RoleOperator)
cookie := loginAs(t, srv, adminID)
body, _ := json.Marshal(map[string]any{
"username": "ALICE", "role": "operator",
})
req, _ := stdhttp.NewRequest("POST", ts.URL+"/api/users", bytes.NewReader(body))
req.AddCookie(cookie)
req.Header.Set("Content-Type", "application/json")
res, err := stdhttp.DefaultClient.Do(req)
if err != nil {
t.Fatalf("POST: %v", err)
}
defer res.Body.Close()
if res.StatusCode != stdhttp.StatusConflict {
t.Errorf("status: got %d want 409", res.StatusCode)
}
}