package server_test import ( "bytes" "context" "encoding/json" "net" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strconv" "strings" "testing" "time" "github.com/jackc/pgx/v5/pgxpool" "chat-agent-system/internal/auth" "chat-agent-system/internal/config" "chat-agent-system/internal/db" "chat-agent-system/internal/workflowdef" ) func loginTestAdmin(t *testing.T, handler http.Handler, cfg *config.Config) string { t.Helper() username := cfg.AdminUsername password := cfg.AdminPassword if username == "" { username = "admin" } if password == "" { password = "admin-secret-password-123" } body, _ := json.Marshal(map[string]string{ "username": username, "password": password, }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("admin login failed: %d %s", w.Code, w.Body.String()) } var session auth.SessionInfo if err := json.NewDecoder(w.Body).Decode(&session); err != nil { t.Fatalf("decode login session: %v", err) } if session.Token == "" { t.Fatal("expected non-empty session token") } return session.Token } func withBearerAuth(req *http.Request, token string) { req.Header.Set("Authorization", "Bearer "+token) } func loginTestUser(t *testing.T, handler http.Handler, username, password string) string { t.Helper() body, _ := json.Marshal(map[string]string{ "username": username, "password": password, }) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body)) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("login %s failed: %d %s", username, w.Code, w.Body.String()) } var session auth.SessionInfo if err := json.NewDecoder(w.Body).Decode(&session); err != nil { t.Fatalf("decode login session: %v", err) } return session.Token } func withUserIDAuth(req *http.Request, token string) { req.Header.Set("Authorization", "Bearer "+token) } func configFromTestDatabaseURL(t *testing.T) *config.Config { t.Helper() dsn := db.SkipIfNoTestDatabaseURL(t) parsed, err := url.Parse(dsn) if err != nil { t.Fatalf("parse TEST_DATABASE_URL: %v", err) } host, portStr, splitErr := net.SplitHostPort(parsed.Host) if splitErr != nil { host = parsed.Host portStr = "5432" } port, _ := strconv.Atoi(portStr) user := parsed.User.Username() password, _ := parsed.User.Password() dbName := strings.TrimPrefix(parsed.Path, "/") return &config.Config{ Env: "test", DataDir: t.TempDir(), AgentBackend: "stub", AllowInMemory: false, ContextDbHost: host, ContextDbPort: port, ContextDbUser: user, ContextDbPassword: password, ContextDbName: dbName, AdminUsername: envOr("ADMIN_USERNAME", "admin"), AdminPassword: envOr("ADMIN_PASSWORD", "admin-secret-password-123"), } } func envOr(key, fallback string) string { if v := strings.TrimSpace(os.Getenv(key)); v != "" { return v } return fallback } func piAgentSharedDataDir(t *testing.T) string { t.Helper() if d := strings.TrimSpace(os.Getenv("DATA_DIR")); d != "" { return d } cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } for _, rel := range []string{".", "..", "../..", "../../.."} { base := filepath.Clean(filepath.Join(cwd, rel)) for _, envName := range []string{"dev", "test"} { candidate := filepath.Join(base, "data", envName) ws := filepath.Join(candidate, "workspaces") if st, statErr := os.Stat(ws); statErr == nil && st.IsDir() { return candidate } } } t.Skip("shared pi-agent DATA_DIR not found (mount data/test/workspaces or data/dev/workspaces)") return "" } func seedOfficialWorkflowManifest(t *testing.T, pool *pgxpool.Pool, workflowKey string) { t.Helper() filename, ok := workflowdef.V2ManifestFiles[workflowKey] if !ok { t.Fatalf("unknown official workflow key %q", workflowKey) } def, err := workflowdef.LoadManifestDefinition(filename) if err != nil { t.Fatalf("load manifest %s: %v", filename, err) } wfStore := workflowdef.NewStore(pool) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := wfStore.UpsertPublishedSeed(ctx, workflowdef.CreateDefinitionInput{ Key: workflowKey, Name: workflowdef.OfficialWorkflowName(workflowKey), Definition: def, }, 1); err != nil { t.Fatalf("seed official workflow %s: %v", workflowKey, err) } }