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:

ModuleEndpointsLegacy source
Auth (login)POST /api/v1/auth/loginaspnet_Membership + MnxUserAuth (new)
User accessGET /api/v1/users/me/accessaspmnx_groupUsers + role/module joins
GroupsGET /api/v1/groups, POST, PUT/{id}, DELETE/{id}aspmnx_Groups
LicenseGET /api/v1/license, /summaryMICSSYS + aspmnx_ActiveUsers
HelpGET /api/v1/help/sections, /documents, /documents/{id}MnxUserGuideModule + mnxUserGuideDocuments
FavoritesGET/POST/DELETE /api/v1/users/me/favoriteswmReportsUserFavorites
Main-menuGET /api/v1/navigation/menuMnxModule + MnxModuleRelationship
SettingsGET /api/v1/settings, /{id}, PUT /{id}MnxSettingsManagement
SearchGET /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}/access restrict, failed-attempt tracking (BFC-31/32/33/34 — blocked on ops/reviewer/product)

2. Roles#

RoleResponsibility
QA leadRuns the scripts below against staging, files any failures in Jira as sub-tasks of the failing ticket
ReviewerSigns off on auth flow + security-sensitive endpoints (Groups, Settings PUT)
ProductSigns 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_04 staging instance (read-only for legacy tables per ADR 0004)
  • Backend: phase-1/core-security branch 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 → false
    • FeatureFlags:EnableFavoritesWrites → false
    • FeatureFlags: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:

  1. Log in first (script below) → capture accessToken
  2. Send request with Authorization: Bearer <accessToken>
  3. 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.accessToken is a non-empty JWT (three base64 segments separated by .)
  • data.claims.mod_admin includes at least one module for compAdmin
  • MnxUserAuth row for this user has LastLoginUtc updated (verify in DB)
  • MnxLoginActivity row inserted with Outcome=Success

Fail cases to try:

  • Wrong password → HTTP 401 + MnxLoginActivity with Outcome=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 | jq

Pass 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 | jq

Pass 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 | jq

Pass 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 | jq

Pass 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 | jq

Pass if:

  • No item with IsModuleShow=false appears
  • Items with IsPermission=true appear only when caller has matching mod_admin claim
  • 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" | jq

Pass if response excludes any row with IsShowSetting=false.

curl -sS -H "Authorization: Bearer $TOKEN" \
  "https://staging.<host>/api/v1/search?q=comp&types=user,group,module&take=20" | jq

Pass if:

  • Empty q → HTTP 400
  • q longer than 200 chars → HTTP 400
  • Per-type cap of 15 respected
  • Modules with IsModuleShow=false never appear
  • Type discriminator matches the source domain per row

6. Cross-cutting checks#

CheckHow
HTTPS enforcedAny http:// request should 307 to https://
CORSOrigin not in Cors:AllowedOrigins → preflight fails
Rate limit100 rapid requests as same user → 429 responses appear
Global exception middlewareForce a 500 (e.g. bad DB conn) → response is JSON ProblemDetails, not stack trace
Correlation IDEvery 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)
  • GroupWrite policy only requires mod_admin presence, not a specific module (BFC-32)
  • /users/{userId}/access currently trusts the caller’s mod_admin — no cross-tenant guard yet (BFC-33)
  • No failed-attempt lockout — reuses legacy aspnet_Membership.IsLockedOut at login time only (BFC-34)

8. Sign-off procedure#

  1. QA lead runs all §5 scripts, records pass/fail in a Confluence page linked from BFC-35
  2. Reviewer inspects auth flow + §7 known limitations, adds comment on BFC-35
  3. Product inspects §5.8/§5.10 response shapes, adds comment on BFC-35
  4. 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:

  1. Point staging DNS back to legacy Cube 1.x backend
  2. Optionally drop MnxUserAuth + MnxLoginActivity tables (idempotent CREATE from deploy/sql/)

No legacy data touched → no restore-from-backup needed.