Scout/internal/httpapi/auth_test.go
2026-08-31 15:34:36 +08:00

176 lines
5.1 KiB
Go

package httpapi
import (
"bytes"
"context"
"encoding/json"
"mime/multipart"
"net/http"
"net/http/httptest"
"path/filepath"
"sync"
"testing"
"time"
"scout/internal/notify"
"scout/internal/registry"
"scout/internal/users"
)
type testMailer struct {
mu sync.Mutex
messages []notify.Message
}
func (mailer *testMailer) Send(_ context.Context, message notify.Message) error {
mailer.mu.Lock()
defer mailer.mu.Unlock()
mailer.messages = append(mailer.messages, message)
return nil
}
func (mailer *testMailer) count() int {
mailer.mu.Lock()
defer mailer.mu.Unlock()
return len(mailer.messages)
}
func TestAuthenticationRolesAndNotifications(t *testing.T) {
store, err := registry.New(t.TempDir())
if err != nil {
t.Fatal(err)
}
userStore, err := users.New(filepath.Join(t.TempDir(), "users.json"))
if err != nil {
t.Fatal(err)
}
admin, err := userStore.EnsureAdmin("admin@example.com", "管理员", "admin-pass-123")
if err != nil {
t.Fatal(err)
}
mailer := &testMailer{}
server := httptest.NewServer((&Server{Registry: store, Users: userStore, Mailer: mailer}).Handler())
defer server.Close()
registerResponse := postJSONRequest(t, server.URL+"/api/v1/auth/register", map[string]string{"email": "dev@example.com", "name": "开发者", "password": "developer-pass-123"}, "")
if registerResponse.StatusCode != http.StatusCreated {
t.Fatalf("注册状态错误: %d", registerResponse.StatusCode)
}
registerResponse.Body.Close()
developer, ok := userStore.FindByEmail("dev@example.com")
if !ok {
t.Fatal("开发者用户不存在")
}
if _, err := userStore.Update(developer.ID, users.RoleDeveloper, true); err != nil {
t.Fatal(err)
}
loginResponse := postJSONRequest(t, server.URL+"/api/v1/auth/login", map[string]string{"email": "dev@example.com", "password": "developer-pass-123"}, "")
if loginResponse.StatusCode != http.StatusOK {
t.Fatalf("登录状态错误: %d", loginResponse.StatusCode)
}
var login userResponse
if err := json.NewDecoder(loginResponse.Body).Decode(&login); err != nil {
t.Fatal(err)
}
loginResponse.Body.Close()
if login.Session == "" || login.User.Role != users.RoleDeveloper {
t.Fatalf("登录结果错误: %+v", login)
}
ordinary, err := userStore.Register("user@example.com", "普通用户", "ordinary-pass-123")
if err != nil {
t.Fatal(err)
}
ordinarySession, _, err := userStore.CreateSession(ordinary.ID, time.Hour)
if err != nil {
t.Fatal(err)
}
ordinaryUpload := postMultipartRequest(t, server.URL+"/api/v1/packages/upload", ordinarySession, "auth-demo", "1.0.0", "demo.pkg", "content")
if ordinaryUpload.StatusCode != http.StatusForbidden {
t.Fatalf("普通用户上传状态错误: %d", ordinaryUpload.StatusCode)
}
ordinaryUpload.Body.Close()
developerUpload := postMultipartRequest(t, server.URL+"/api/v1/packages/upload", login.Session, "auth-demo", "1.0.0", "demo.pkg", "content")
if developerUpload.StatusCode != http.StatusCreated {
t.Fatalf("开发者上传状态错误: %d", developerUpload.StatusCode)
}
developerUpload.Body.Close()
if mailer.count() < 3 {
t.Fatalf("邮件通知数量不足: %d", mailer.count())
}
adminSession, _, err := userStore.CreateSession(admin.ID, time.Hour)
if err != nil {
t.Fatal(err)
}
adminResponse := getRequest(t, server.URL+"/api/v1/admin/users", adminSession)
if adminResponse.StatusCode != http.StatusOK {
t.Fatalf("管理员查询用户状态错误: %d", adminResponse.StatusCode)
}
adminResponse.Body.Close()
}
func postJSONRequest(t *testing.T, target string, value any, token string) *http.Response {
t.Helper()
body, err := json.Marshal(value)
if err != nil {
t.Fatal(err)
}
request, err := http.NewRequest(http.MethodPost, target, bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
request.Header.Set("Content-Type", "application/json")
if token != "" {
request.Header.Set("Authorization", "Bearer "+token)
}
response, err := http.DefaultClient.Do(request)
if err != nil {
t.Fatal(err)
}
return response
}
func postMultipartRequest(t *testing.T, target, token, name, version, fileName, content string) *http.Response {
t.Helper()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
_ = writer.WriteField("name", name)
_ = writer.WriteField("version", version)
_ = writer.WriteField("platform", "any")
_ = writer.WriteField("architecture", "universal")
fileWriter, err := writer.CreateFormFile("file", fileName)
if err != nil {
t.Fatal(err)
}
_, _ = fileWriter.Write([]byte(content))
_ = writer.Close()
request, err := http.NewRequest(http.MethodPost, target, body)
if err != nil {
t.Fatal(err)
}
request.Header.Set("Content-Type", writer.FormDataContentType())
request.Header.Set("Authorization", "Bearer "+token)
response, err := http.DefaultClient.Do(request)
if err != nil {
t.Fatal(err)
}
return response
}
func getRequest(t *testing.T, target, token string) *http.Response {
t.Helper()
request, err := http.NewRequest(http.MethodGet, target, nil)
if err != nil {
t.Fatal(err)
}
request.Header.Set("Authorization", "Bearer "+token)
response, err := http.DefaultClient.Do(request)
if err != nil {
t.Fatal(err)
}
return response
}