Phase 1 — User Acceptance Test Plan#
Scope: everything shipped under Phase 1 (branch phase-1/core-security — 8 modules, 7 ADRs, JWT auth). This document is what the reviewer / QA / product will follow to sign off Phase 1 before it lands in the Cube 2.0 staging environment.
1. Scope#
In scope:
| Module | Endpoints | Legacy source |
|---|---|---|
| Auth (login) | POST /api/v1/auth/login | aspnet_Membership + MnxUserAuth (new) |
| User access | GET /api/v1/users/me/access | aspmnx_groupUsers + role/module joins |
| Groups | GET /api/v1/groups, POST, PUT/{id}, DELETE/{id} | aspmnx_Groups |
| License | GET /api/v1/license, /summary | MICSSYS + aspmnx_ActiveUsers |
| Help | GET /api/v1/help/sections, /documents, /documents/{id} | MnxUserGuideModule + mnxUserGuideDocuments |
| Favorites | GET/POST/DELETE /api/v1/users/me/favorites | wmReportsUserFavorites |
| Main-menu | GET /api/v1/navigation/menu | MnxModule + MnxModuleRelationship |
| Settings | GET /api/v1/settings, /{id}, PUT /{id} | MnxSettingsManagement |
| Search | GET /api/v1/search?q=&types=&take= | LINQ over the entities above |
Out of scope:
- MX Chat (BFC-27 ADR published, MVP is BFC-28 — blocked on product)
- Calendar API (BFC-26 — blocked on product, no legacy table)
- Hardening tickets: machine keys, GroupWrite tighten,
/users/{userId}/accessrestrict, failed-attempt tracking (BFC-31/32/33/34 — blocked on ops/reviewer/product)
2. Roles#
| Role | Responsibility |
|---|---|
| QA lead | Runs the scripts below against staging, files any failures in Jira as sub-tasks of the failing ticket |
| Reviewer | Signs off on auth flow + security-sensitive endpoints (Groups, Settings PUT) |
| Product | Signs off on UI-facing shape (Navigation, Search results, Help sections) |
| Backend (Nadira) | On-call for defects during the UAT window |
3. Environment#
- Staging DB: existing
BcomMX_01_04staging instance (read-only for legacy tables per ADR 0004) - Backend:
phase-1/core-securitybranch deployed to staging via BFC-37 pipeline (blocked — until then, run locally against staging DB per Running in Docker) - Feature flags in staging (all writes OFF by default — must be explicitly enabled per test):
FeatureFlags:EnableGroupWrites→ falseFeatureFlags:EnableFavoritesWrites→ falseFeatureFlags:EnableSettingsWrites→ false
4. Test approach#
Manual API smoke tests via curl or Postman. Automated integration tests (91 passing at time of writing) already cover LINQ logic; UAT is about real staging data + JWT + real HTTP responses.
For every request:
- Log in first (script below) → capture
accessToken - Send request with
Authorization: Bearer <accessToken> - Verify HTTP status, response shape, and — where noted — the row-level effect in the DB
5. Golden-path scripts#
5.1 Login#
TOKEN=$(curl -sS -X POST https://staging.<host>/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"compAdmin","password":"<staging-password>"}' \
| jq -r .data.accessToken)Pass if:
- HTTP 200
data.accessTokenis a non-empty JWT (three base64 segments separated by.)data.claims.mod_adminincludes at least one module forcompAdminMnxUserAuthrow for this user hasLastLoginUtcupdated (verify in DB)MnxLoginActivityrow inserted withOutcome=Success
Fail cases to try:
- Wrong password → HTTP 401 +
MnxLoginActivitywithOutcome=BadPassword - Suspended user → HTTP 401 +
Outcome=Suspended - Non-existent user → HTTP 401 +
Outcome=UnknownUser
5.2 User access#
curl -sS -H "Authorization: Bearer $TOKEN" \
https://staging.<host>/api/v1/users/me/access | jqPass if the returned groups, roles, and modules match the caller’s rows in aspmnx_groupUsers / aspmnx_groupRoles / MnxModule.
5.3 Groups (read-only path)#
curl -sS -H "Authorization: Bearer $TOKEN" \
https://staging.<host>/api/v1/groups | jq '.data | length'Pass if count matches SELECT COUNT(*) FROM aspmnx_Groups.
5.4 Groups (write path — requires flag on)#
Enable EnableGroupWrites=true in staging, re-run. Any 4xx = feature flag NOT enabled → pass (fail-safe working).
5.5 License#
curl -sS -H "Authorization: Bearer $TOKEN" \
https://staging.<host>/api/v1/license/summary | jqPass if activeUsers matches SELECT COUNT(*) FROM aspmnx_ActiveUsers and expirationDate matches MICSSYS.LicExpDate.
5.6 Help#
curl -sS -H "Authorization: Bearer $TOKEN" \
https://staging.<host>/api/v1/help/sections | jqPass if response excludes any section with Display=false and any document with InternalOnly=true.
5.7 Favorites (own scope)#
curl -sS -H "Authorization: Bearer $TOKEN" \
https://staging.<host>/api/v1/users/me/favorites | jqPass if count matches SELECT COUNT(*) FROM wmReportsUserFavorites WHERE fkUserId = <caller-guid>.
Security check: there is no /users/{userId}/favorites endpoint — caller cannot access another user’s favorites. Confirm by inspecting the URL routes (SearchController has no user path parameter).
5.8 Navigation#
curl -sS -H "Authorization: Bearer $TOKEN" \
https://staging.<host>/api/v1/navigation/menu | jqPass if:
- No item with
IsModuleShow=falseappears - Items with
IsPermission=trueappear only when caller has matchingmod_adminclaim - Children of a hidden parent are also hidden
5.9 Settings#
curl -sS -H "Authorization: Bearer $TOKEN" \
"https://staging.<host>/api/v1/settings?moduleId=1" | jqPass if response excludes any row with IsShowSetting=false.
5.10 Search#
curl -sS -H "Authorization: Bearer $TOKEN" \
"https://staging.<host>/api/v1/search?q=comp&types=user,group,module&take=20" | jqPass if:
- Empty
q→ HTTP 400 qlonger than 200 chars → HTTP 400- Per-type cap of 15 respected
- Modules with
IsModuleShow=falsenever appear Typediscriminator matches the source domain per row
6. Cross-cutting checks#
| Check | How |
|---|---|
| HTTPS enforced | Any http:// request should 307 to https:// |
| CORS | Origin not in Cors:AllowedOrigins → preflight fails |
| Rate limit | 100 rapid requests as same user → 429 responses appear |
| Global exception middleware | Force a 500 (e.g. bad DB conn) → response is JSON ProblemDetails, not stack trace |
| Correlation ID | Every response has X-Correlation-ID header; check that it appears in Serilog output |
| Swagger | /docs renders in Development or when FeatureFlags:EnableSwagger=true, otherwise 404 |
7. Known limitations (call out during sign-off)#
Per docs/migration/phase-1-follow-ups.md — these do NOT block Phase 1 sign-off, but the reviewer should acknowledge:
- Machine keys still in
appsettings.json(BFC-31 — reviewer will lead secrets-store setup) GroupWritepolicy only requiresmod_adminpresence, not a specific module (BFC-32)/users/{userId}/accesscurrently trusts the caller’smod_admin— no cross-tenant guard yet (BFC-33)- No failed-attempt lockout — reuses legacy
aspnet_Membership.IsLockedOutat login time only (BFC-34)
8. Sign-off procedure#
- QA lead runs all §5 scripts, records pass/fail in a Confluence page linked from BFC-35
- Reviewer inspects auth flow + §7 known limitations, adds comment on BFC-35
- Product inspects §5.8/§5.10 response shapes, adds comment on BFC-35
- When all three approve → BFC-35 transitions to DEPLOYED and Phase 1 branch merges to
master
9. Rollback plan#
Phase 1 is additive-only (per ADR 0004: no writes to legacy tables) — rollback is:
- Point staging DNS back to legacy Cube 1.x backend
- Optionally drop
MnxUserAuth+MnxLoginActivitytables (idempotent CREATE fromdeploy/sql/)
No legacy data touched → no restore-from-backup needed.