package server_test import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" "chat-agent-system/internal/projectworkflow" ) func TestTaskRunAuthz_OutsiderForbidden_PG(t *testing.T) { pool := openPGPoolWithMigrations(t) if pool == nil { return } defer pool.Close() ctx := context.Background() handler, _ := setupPGGovernanceServer(t, pool) creatorToken := loginTestUser(t, handler, "zhanggong", "123456") outsiderToken := loginTestUser(t, handler, "lizhuguan", "123456") createBody, _ := json.Marshal(projectworkflow.CreateProjectInput{Name: fmt.Sprintf("Task Run Authz Test %d", time.Now().UnixNano())}) reqCreate := httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody)) withBearerAuth(reqCreate, creatorToken) recCreate := httptest.NewRecorder() handler.ServeHTTP(recCreate, reqCreate) if recCreate.Code != http.StatusCreated { t.Fatalf("create project: %d %s", recCreate.Code, recCreate.Body.String()) } var proj projectworkflow.Project _ = json.Unmarshal(recCreate.Body.Bytes(), &proj) t.Cleanup(func() { cleanupProject(t, pool, proj.ID) }) var wfRunsBefore, taskRunsBefore int _ = pool.QueryRow(ctx, `SELECT COUNT(*) FROM workflow_runs WHERE project_id = $1`, proj.ID).Scan(&wfRunsBefore) _ = pool.QueryRow(ctx, `SELECT COUNT(*) FROM task_runs WHERE project_id = $1`, proj.ID).Scan(&taskRunsBefore) syncBody, _ := json.Marshal(map[string]interface{}{ "task_key": "intake", "project_id": proj.ID, "backend": "stub", }) reqSync := httptest.NewRequest(http.MethodPost, "/api/v1/tasks/run", bytes.NewReader(syncBody)) withBearerAuth(reqSync, outsiderToken) recSync := httptest.NewRecorder() handler.ServeHTTP(recSync, reqSync) if recSync.Code != http.StatusForbidden { t.Fatalf("outsider sync tasks/run expected 403, got %d: %s", recSync.Code, recSync.Body.String()) } streamBody, _ := json.Marshal(map[string]interface{}{ "task_key": "intake", "project_id": proj.ID, "backend": "stub", }) reqStream := httptest.NewRequest(http.MethodPost, "/api/v1/tasks/run/stream", bytes.NewReader(streamBody)) withBearerAuth(reqStream, outsiderToken) recStream := httptest.NewRecorder() handler.ServeHTTP(recStream, reqStream) if recStream.Code != http.StatusForbidden { t.Fatalf("outsider stream tasks/run/stream expected 403, got %d: %s", recStream.Code, recStream.Body.String()) } var wfRunsAfter, taskRunsAfter int _ = pool.QueryRow(ctx, `SELECT COUNT(*) FROM workflow_runs WHERE project_id = $1`, proj.ID).Scan(&wfRunsAfter) _ = pool.QueryRow(ctx, `SELECT COUNT(*) FROM task_runs WHERE project_id = $1`, proj.ID).Scan(&taskRunsAfter) if wfRunsAfter != wfRunsBefore { t.Fatalf("workflow_runs changed for forbidden sync: before=%d after=%d", wfRunsBefore, wfRunsAfter) } if taskRunsAfter != taskRunsBefore { t.Fatalf("task_runs changed for forbidden stream: before=%d after=%d", taskRunsBefore, taskRunsAfter) } // 匿名请求 reqAnon := httptest.NewRequest(http.MethodPost, "/api/v1/tasks/run", bytes.NewReader(syncBody)) recAnon := httptest.NewRecorder() handler.ServeHTTP(recAnon, reqAnon) if recAnon.Code != http.StatusUnauthorized { t.Fatalf("anonymous tasks/run expected 401, got %d", recAnon.Code) } } func TestTaskRunAuthz_MemberAllowed_PG(t *testing.T) { pool := openPGPoolWithMigrations(t) if pool == nil { return } defer pool.Close() handler, _ := setupPGGovernanceServer(t, pool) creatorToken := loginTestUser(t, handler, "zhanggong", "123456") createBody, _ := json.Marshal(projectworkflow.CreateProjectInput{Name: fmt.Sprintf("Task Run Member %d", time.Now().UnixNano())}) reqCreate := httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody)) withBearerAuth(reqCreate, creatorToken) recCreate := httptest.NewRecorder() handler.ServeHTTP(recCreate, reqCreate) if recCreate.Code != http.StatusCreated { t.Fatalf("create project: %d", recCreate.Code) } var proj projectworkflow.Project _ = json.Unmarshal(recCreate.Body.Bytes(), &proj) t.Cleanup(func() { cleanupProject(t, pool, proj.ID) }) syncBody, _ := json.Marshal(map[string]interface{}{ "task_key": "intake", "project_id": proj.ID, "backend": "stub", }) reqSync := httptest.NewRequest(http.MethodPost, "/api/v1/tasks/run", bytes.NewReader(syncBody)) withBearerAuth(reqSync, creatorToken) recSync := httptest.NewRecorder() handler.ServeHTTP(recSync, reqSync) if recSync.Code != http.StatusOK { t.Fatalf("member sync tasks/run expected 200, got %d: %s", recSync.Code, recSync.Body.String()) } }