diff --git a/CLAUDE.md b/CLAUDE.md index 0d391ce..ed6ffcd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -188,7 +188,7 @@ Tests run against **live local services** — there is no mocking. Failures are | `admin` | the above **plus** the Next.js admin frontend on `ADMIN_BASE_URL` | Every admin test fails in the `setup` project before its own body runs | | both | `tests/.env` populated from `tests/.env.example` | `Missing required env var "X" — check tests/.env` (thrown by `roles.data.ts`) | -The simplest way to satisfy both rows is the full application stack from wcc-backend, driven from here: `npm run env:up` (= `${WCC_BACKEND_DIR:-../wcc-backend}/scripts/app-stack.sh up`). It starts the backend and the admin portal on `http://localhost:3000`, then runs the backend's seed script, which creates the MENTORSHIP_ADMIN, MENTOR (`mentor@wcc.dev`, long-term) and LEADER accounts the role fixtures log in as — plus `mentor-adhoc@wcc.dev` (ad-hoc mentor) and `member@wcc.dev` (plain member), not yet wired to fixtures — the MENTORS page and an open mentorship cycle (the backend itself bootstraps only `admin@wcc.dev`). Without the seed, only the admin account exists. `npm run env:purge` resets the database; `npm run env:cycle -- ` switches which cycle is open. `tests/.env.example` already holds the stack's values — copy it to `tests/.env` as-is. +The simplest way to satisfy both rows is the full application stack from wcc-backend, driven from here: `npm run env:up` (= `${WCC_BACKEND_DIR:-../wcc-backend}/scripts/app-stack.sh up`). It starts the backend and the admin portal on `http://localhost:3000`, then runs the backend's seed script, which creates the MENTORSHIP_ADMIN, MENTOR (`mentor@wcc.dev`, long-term), LEADER, ad-hoc MENTOR (`mentor-adhoc@wcc.dev`), VIEWER (`member@wcc.dev`) and MENTEE (`mentee@wcc.dev`) accounts the role fixtures log in as — the MENTORS page and an open mentorship cycle (the backend itself bootstraps only `admin@wcc.dev`). Without the seed, only the admin account exists. `npm run env:purge` resets the database; `npm run env:cycle -- ` switches which cycle is open. `tests/.env.example` already holds the stack's values — copy it to `tests/.env` as-is. `admin` depends on the `setup` project, which drives a real browser login per role and writes `tests/admin/.auth/*.json`. A wall of admin failures with no assertion errors means setup could not reach the frontend. diff --git a/helpers/datafactory/constants/admin-nav.data.ts b/helpers/datafactory/constants/admin-nav.data.ts index fe1bcfb..b1171de 100644 --- a/helpers/datafactory/constants/admin-nav.data.ts +++ b/helpers/datafactory/constants/admin-nav.data.ts @@ -9,6 +9,7 @@ export const NAV_LINKS: Record = { [Role.MENTORSHIP_ADMIN]: ["Dashboard", "Mentors", "Members", "Mentees", "Mentorship", "Cycles"], [Role.LEADER]: ["Dashboard", "Mentors", "Members", "Users"], [Role.MENTOR]: ["Dashboard", "Mentor Dashboard", "My Profile"], + [Role.MENTEE]: ["Dashboard"], [Role.MENTOR_AD_HOC]: ["Dashboard", "Mentor Dashboard", "My Profile"], [Role.MEMBER]: ["Dashboard"], }; diff --git a/helpers/datafactory/constants/roles.data.ts b/helpers/datafactory/constants/roles.data.ts index de4b9c5..145273d 100644 --- a/helpers/datafactory/constants/roles.data.ts +++ b/helpers/datafactory/constants/roles.data.ts @@ -3,6 +3,7 @@ export enum Role { ADMIN = "admin", LEADER = "leader", MENTOR = "mentor", + MENTEE = "mentee", MENTORSHIP_ADMIN = "mentorshipAdmin", MEMBER = "member", MENTOR_AD_HOC = "mentorAdHoc", @@ -39,6 +40,11 @@ export const USERS: Record = { password: getEnvVariable("MENTOR_PASSWORD"), storageState: "tests/admin/.auth/mentor.json", }, + [Role.MENTEE]: { + email: getEnvVariable("MENTEE_EMAIL"), + password: getEnvVariable("MENTEE_PASSWORD"), + storageState: "tests/admin/.auth/mentee.json", + }, [Role.LEADER]: { email: getEnvVariable("LEADER_EMAIL"), password: getEnvVariable("LEADER_PASSWORD"), diff --git a/helpers/fixtures/common.fixtures.ts b/helpers/fixtures/common.fixtures.ts index f637fb8..52beb52 100644 --- a/helpers/fixtures/common.fixtures.ts +++ b/helpers/fixtures/common.fixtures.ts @@ -11,7 +11,7 @@ import { INVALID_TOKEN } from "helpers/datafactory/constants/auth.data"; * authApi X-API-KEY only public / registration endpoints * noApiKeyApi no headers at all asserting the API-key filter * invalidTokenApi X-API-KEY + unknown token asserting rejected sessions - * Api X-API-KEY + role's token adminApi, leaderApi, mentorApi, mentorshipAdminApi, memberApi, mentorAdHocApi + * Api X-API-KEY + role's token adminApi, leaderApi, mentorApi, menteeApi, mentorshipAdminApi, memberApi, mentorAdHocApi * apiForRole(role) same, for role-matrix tests * mailhog no headers, MAILHOG_HOST email delivery assertions (not an APIService: different host) * @@ -27,6 +27,7 @@ type TestFixtures = { adminApi: APIService; leaderApi: APIService; mentorApi: APIService; + menteeApi: APIService; mentorshipAdminApi: APIService; memberApi: APIService; mentorAdHocApi: APIService; @@ -115,6 +116,7 @@ export const test = base.extend({ adminApi: async ({ apiForRole }, use) => use(await apiForRole(Role.ADMIN)), leaderApi: async ({ apiForRole }, use) => use(await apiForRole(Role.LEADER)), mentorApi: async ({ apiForRole }, use) => use(await apiForRole(Role.MENTOR)), + menteeApi: async ({ apiForRole }, use) => use(await apiForRole(Role.MENTEE)), mentorshipAdminApi: async ({ apiForRole }, use) => use(await apiForRole(Role.MENTORSHIP_ADMIN)), memberApi: async ({ apiForRole }, use) => use(await apiForRole(Role.MEMBER)), mentorAdHocApi: async ({ apiForRole }, use) => use(await apiForRole(Role.MENTOR_AD_HOC)), diff --git a/tests/.env.example b/tests/.env.example index c78f7a6..8aaa4a0 100644 --- a/tests/.env.example +++ b/tests/.env.example @@ -29,6 +29,9 @@ MEMBER_PASSWORD=wcc-admin MENTOR_AD_HOC_EMAIL=mentor-adhoc@wcc.dev MENTOR_AD_HOC_PASSWORD=wcc-admin +MENTEE_EMAIL=mentee@wcc.dev +MENTEE_PASSWORD=wcc-admin + # Only for the @ad-hoc phase of `npm test`: where wcc-backend is checked out, relative to this repo. # WCC_BACKEND_DIR=../wcc-backend diff --git a/tests/api/TEST_PLAN.md b/tests/api/TEST_PLAN.md index 2eb1952..23765ca 100644 --- a/tests/api/TEST_PLAN.md +++ b/tests/api/TEST_PLAN.md @@ -4,18 +4,17 @@ **File:** `tests/api/tests/auth/auth.flow.spec.ts` -| ID | Title | Endpoint | Expected Result | -| ------- | --------------------------------------------------- | ---------------------- | ------------------------------------------------------ | -| AUTH-01 | Login with valid credentials returns token | `POST /api/auth/login` | 200, body contains `token`, `expiresAt`, `roles` | -| AUTH-02 | Login with missing email and password returns 400 | `POST /api/auth/login` | 400 (`@NotNull` validation; blank strings return 401) | -| AUTH-03 | Login with wrong password returns 401 | `POST /api/auth/login` | 401 | -| AUTH-04 | Get current user with valid token returns user info | `GET /api/auth/me` | 200, `roles` contains the role, `member.email` matches | -| AUTH-05 | Get current user without token returns 401 | `GET /api/auth/me` | 401 | -| AUTH-06 | Get current user with invalid token returns 401 | `GET /api/auth/me` | 401 | -| AUTH-07 | Get users with admin token returns user list | `GET /api/auth/users` | 200, non-empty list | -| AUTH-08 | Get current user without API key returns 401 | `GET /api/auth/me` | 401 | - ---- +| ID | Title | Endpoint | Expected Result | +| ------- | -------------------------------------------------------------------------- | ---------------------- | --------------------------------------------------------- | +| AUTH-01 | Login with valid credentials returns token | `POST /api/auth/login` | 200, body contains `token`, `expiresAt`, `roles` | +| AUTH-02 | Login with missing email and password returns 400 | `POST /api/auth/login` | 400 (`@NotNull` validation; blank strings return 401) | +| AUTH-03 | Login with wrong password returns 401 | `POST /api/auth/login` | 401 | +| AUTH-04 | Get current user with valid token returns user info | `GET /api/auth/me` | 200, `roles` contains the role, `member.email` matches | +| AUTH-05 | Get current user without token returns 401 | `GET /api/auth/me` | 401 | +| AUTH-06 | Get current user with invalid token returns 401 | `GET /api/auth/me` | 401 | +| AUTH-07 | Get users with admin token returns user list | `GET /api/auth/users` | 200, non-empty list | +| AUTH-08 | Get current user without API key returns 401 | `GET /api/auth/me` | 401 | +| AUTH-09 | Get current user with each seeded role's token returns that role's account | `GET /api/auth/me` | 200, `member.email` matches the role's account in `USERS` | ## Flow 2: Mentor — Register and Accept diff --git a/tests/api/tests/auth/auth.flow.spec.ts b/tests/api/tests/auth/auth.flow.spec.ts index 1027072..e5564c6 100644 --- a/tests/api/tests/auth/auth.flow.spec.ts +++ b/tests/api/tests/auth/auth.flow.spec.ts @@ -79,3 +79,15 @@ test.describe("AUTH-08: Request without API key", () => { expect(response.status()).toBe(401); }); }); + +test.describe("AUTH-09: Every seeded role can log in", () => { + for (const role of Object.values(Role)) { + test(`Get current user with ${role} token returns role's account`, { tag: "@smoke" }, async ({ apiForRole }) => { + const api = await apiForRole(role); + const response = await api.authentication.me(true); + + const { member } = meResponseSchema.parse(await response.json()); + expect(member.email).toBe(USERS[role].email); + }); + } +});