docs: rewrite TEST_USERS.md with current credentials and bug post-mortems

- Correct all passwords (were wrong/outdated)
- Add full table of current seeded users (superadmin, admins, candidates)
- Document 3 bugs fixed 2026-02-24:
  - superadmin placeholder hash (seeder fix)
  - users.status VARCHAR(20) crash loop (migration fix)
  - PASSWORD_PEPPER alignment requirement
- Add bash ! expansion gotcha for curl testing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tiago Yamamoto 2026-02-24 05:55:10 -06:00
parent 6b1c058e8e
commit 222006fb6b

View file

@ -1,55 +1,140 @@
# 🧪 Test Users & Data Scenarios - GoHorseJobs
# 🧪 Test Users & Credentials — GoHorseJobs
When running the platform locally (via `start.sh`) or on the DEV server (`local.gohorsejobs.com`), the `seeder-api` provisions a rich set of test accounts representing distinct personas.
All accounts below are provisioned by `seeder-api` against the DEV environment (`local.gohorsejobs.com` / `api-local.gohorsejobs.com`).
All accounts use the exact same password to simplify development.
**Universal Test Password:** `Admin@2025!`
> **Last verified:** 2026-02-24
> **Environment:** Coolify DEV (Redbull VPS)
> **Auth endpoint:** `POST https://api-local.gohorsejobs.com/api/v1/auth/login`
---
## 👥 Core Personas (The "Golden" Accounts)
## Login Payload Format
These are the primary accounts you should use for daily development and E2E testing.
| Role | Identifier (Username) | Email | Purpose |
| :--- | :--- | :--- | :--- |
| **Superadmin** | `lol` | `lol@gohorsejobs.com` | Complete system access. Bypass company walls. Manage features. |
| **Superadmin** | `superadmin` | `admin@gohorsejobs.com` | Secondary global admin. |
| **Admin** | `admin` | `moderator@gohorsejobs.com` | Reviews pending companies/jobs in backoffice. |
| **Recruiter** | `recruiter` | `hr@techcorp.com` | Posts jobs, manages applicants for "TechCorp" (Company ID: 1). |
| **Recruiter** | `jane_hr` | `jane.doe@startup.io` | Recruiter for a pending/unapproved company to test gating. |
| **Candidate** | `candidate` | `carlos.dev@gmail.com` | Standard job seeker. Pre-filled resume and skills. Has 3 active applications. |
| **Candidate** | `newbie` | `new.user@hotmail.com` | Fresh account with 0 applications and empty profile to test onboarding flows. |
---
## 🏢 Auto-Generated Companies & Jobs
The DB seeder simulates a healthy marketplace.
If you run `npm run seed:lite`, the DB receives:
* **50 Companies**: Ranging from "TechCorp" (Active) to "SuspiciousLTDA" (Pending/Rejected).
* **100 Jobs**: Distributed among the active companies. Various statuses (`published`, `draft`, `closed`).
* **200 Applications**: Dummy candidates applying to random jobs, in varied pipelines (`pending`, `reviewing`, `accepted`).
---
## 🚨 Login & Auth "Gotchas"
### 1. `Invalid Credentials` Right After Fresh Seed
If you completely reset the DB using `start.sh` (Option 6) or manually clear Postgres, and your first login returns "Invalid credentials", **do not panic and do not manually change the DB hash**.
* **Cause**: The Node.js seeder calculates the bcrypt hash using an environment variable called `PASSWORD_PEPPER` (`gohorse-pepper`). If this variable was empty or mismatched with the Backend API, the hash is physically wrong.
* **Fix**: Ensure your `.env` files in both `backend` and `seeder-api` contain `PASSWORD_PEPPER=gohorse-pepper`. Then simply run `cd seeder-api && npm run seed` again to fix the hashes.
### 2. Login Payload Fields
The frontend sends:
```json
{
"email": "lol",
"password": "..."
}
{ "email": "<identifier OR email>", "password": "<password>" }
```
**Important:** Even though the frontend passes the username `lol`, the JSON key **must** be `"email"`. The Go backend natively handles resolving `"email"` against the `identifier` OR `email` database columns.
### 3. Account Status Flags
The `users` table has an `is_active` boolean. If a user is manually deactivated by an admin, the login endpoint will return a generic `401 Unauthorized` (or specific Account Locked error depending on the exact route version) to prevent user enumeration.
The `"email"` JSON key accepts **both** the username (`identifier`) and the actual e-mail address. The backend resolves `WHERE email = $1 OR identifier = $1`.
---
## 👑 SuperAdmin
| Identifier | Email | Password | Role |
|---|---|---|---|
| `lol` | `lol@gohorsejobs.com` | `Admin@2025!` | superadmin |
| `superadmin` | `admin@gohorsejobs.com` | `Admin@2025!` | superadmin |
Both identifiers grant full system access. `lol` is the primary dev account; `superadmin` is the alias created by migration 010.
---
## 🏢 Admins / Recruiters
| Identifier | Email | Password | Role | Company |
|---|---|---|---|---|
| `takeshi_yamamoto` | `takeshi@techcorp.com` | `Takeshi@2025` | admin | TechCorp |
| `kenji` | `kenji@appmakers.mobile` | `Takeshi@2025` | admin | AppMakers |
| `maria_santos` | `maria@designhub.com` | `User@2025` | recruiter | DesignHub |
| `wile_e_coyote` | `wile@acme.corp` | `MeepMeep@123` | admin | ACME Corporation |
---
## 🙋 Candidates
### Unified Users (identifier-based login)
| Identifier | Email | Password | Name |
|---|---|---|---|
| `paulo_santos` | `paulo@email.com` | `User@2025` | Paulo Santos |
| `maria_email` | `maria@email.com` | `User@2025` | Maria Reyes |
### Legacy Candidates (email-based login)
| Email | Password | Name |
|---|---|---|
| `ana.silva@example.com` | `User@2025` | Ana Silva |
| `carlos.santos@example.com` | `User@2025` | Carlos Santos |
| `maria.oliveira@example.com` | `User@2025` | Maria Oliveira |
| `pedro.costa@example.com` | `User@2025` | Pedro Costa |
| `juliana.ferreira@example.com` | `User@2025` | Juliana Ferreira |
---
## 🌱 Seeding & Reset
```bash
# Reset all data (drops and recreates all public tables)
curl -X POST https://s-local.gohorsejobs.com/reset
# Re-seed everything (locations, users, companies, jobs, applications)
curl https://s-local.gohorsejobs.com/seed/stream
# After reset+seed, the backend must be reachable so migrations have already run.
# The seeder does NOT apply Go migrations — they run on backend startup.
```
> **Order matters after a full DB wipe:**
> 1. Wait for the backend container to finish running migrations (check `GET /api/v1/jobs` returns `[]`)
> 2. Then run `POST /reset``GET /seed/stream`
---
## ⚠️ Known Issues / Gotchas
### 1. `superadmin` login broken after DB reset (FIXED 2026-02-24)
**Bug:** Migration `010_seed_super_admin.sql` creates the `superadmin` user with a hardcoded placeholder hash (`$invalid-placeholder-run-seeder$`). The seeder previously only updated `lol`'s hash, leaving `superadmin` permanently unable to log in after a DB reset.
**Root cause:** `ON CONFLICT DO NOTHING` in migration 010 means the user exists in the DB but with an unusable hash. The seeder's upsert only covered the `lol` identifier.
**Fix (commit `6b1c058`):** `seeder-api/src/seeders/users.js` now also upserts `superadmin` with the same runtime-generated hash as `lol`:
```js
// Uses the same bcrypt(pass + PASSWORD_PEPPER) hash already computed for lol
await pool.query(`
INSERT INTO users (identifier, password_hash, ...) VALUES ('superadmin', $1, ...)
ON CONFLICT (identifier) DO UPDATE SET password_hash = EXCLUDED.password_hash, status = 'active'
`, [superAdminHash]);
```
**Affected versions:** All environments before `6b1c058`. Triggered on every fresh DB seed.
---
### 2. `users.status` column too short — backend crash loop on startup (FIXED 2026-02-24)
**Bug:** Migration `009_unify_schema.sql` defined `status VARCHAR(20)`. Migration `010_seed_super_admin.sql` tried to insert `'force_change_password'` (21 chars), crashing the backend with `pq: value too long for type character varying(20)` on every restart.
**Fix (commit `e5e4397`):**
- `009`: `VARCHAR(20)``VARCHAR(30)`
- `010`: initial status changed from `'force_change_password'``'pending'`
- New migration `046_fix_user_status_varchar.sql`: `ALTER TABLE users ALTER COLUMN status TYPE VARCHAR(30)` for existing deployments
---
### 3. `PASSWORD_PEPPER` must match between seeder and backend
All password hashes are stored as `bcrypt(password + PASSWORD_PEPPER)`. Both the seeder-api and backend must have the **same** `PASSWORD_PEPPER` env var, or all logins return `401 invalid credentials`.
**DEV value:** `gohorse-pepper`
**Confirmed in:** Coolify backend env + Coolify seeder env
If logins break after a deploy: check `docker inspect <backend_container> --format '{{.Config.Env}}'` for `PASSWORD_PEPPER`, then re-run the seeder.
---
### 4. Bash `!` expansion breaks login tests via curl
When testing logins from the shell, passwords containing `!` (e.g. `Admin@2025!`) must be in a file or use `$'...'` syntax. Using `"Admin@2025!"` in double-quotes causes bash history expansion (`\!`), producing invalid JSON and a `400 Invalid Request`.
```bash
# WRONG (bash expands !)
curl ... -d '{"email":"lol","password":"Admin@2025!"}' # OK in single quotes
curl ... -d "{\"email\":\"lol\",\"password\":\"Admin@2025!\"}" # BROKEN
# CORRECT — use a file
cat > /tmp/login.json << 'EOF'
{"email":"lol","password":"Admin@2025!"}
EOF
curl -X POST .../auth/login -d @/tmp/login.json
```