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) 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 }