# Scenario Registry — Business Logic Compatibility Tests
#
# Each scenario tests a specific user-facing behavior that must work
# identically between Salesforce and NexusCRM. Scenarios are discovered
# from real usage (manual testing, support tickets, QA reports).
#
# type: api    — verifiable via HTTP requests (go test)
# type: chrome — requires browser interaction (claude --chrome)
# type: both   — has api test + chrome verification step
#
# status: reproduced    — issue confirmed via testing
# status: reproduced_ab — issue confirmed via A/B comparison (SF vs NexusCRM)
# status: excluded      — A/B comparison proved this is NOT a backend issue
# status: pass          — scenario passes (no issue found)
# status: skip          — cannot test (missing prerequisites)
# status: pending       — not yet tested
#
# To run all API scenarios:
#   cd tests/contract && go test -v -count=1 ./scenarios/api/... -timeout 300s
#
# To run a single scenario:
#   cd tests/contract && go test -v -run "TestVAL001" ./scenarios/api/...

scenarios:

  # ── Validation Rules ──────────────────────────────────────────────

  - id: VAL-001
    name: "Validation rule with NOT() evaluates correctly"
    description: >
      A validation rule containing NOT(ISBLANK(Industry)) should reject
      records with blank Industry. Previously failed with "unknown name NOT".
    type: api
    test_file: api/validation_rules_test.go
    test_func: TestVAL001_NotFunction
    status: reproduced  # NOT() not evaluated, record passes through

  - id: VAL-002
    name: "Validation rule with ISCHANGED() detects field modifications"
    description: >
      A validation rule using ISCHANGED(Industry) should fire when the
      field value changes on update. Requires prior record context.
    type: api
    test_file: api/validation_rules_test.go
    test_func: TestVAL002_IsChangedFunction
    status: reproduced  # ISCHANGED unknown name error

  - id: VAL-003
    name: "Validation rules execute on single-record Insert"
    description: >
      Validation rules must fire on POST /sobjects/{object}, not just
      on PATCH. Previously only BulkInsert and Update ran validations.
    type: api
    test_file: api/validation_rules_test.go
    test_func: TestVAL003_InsertTriggersValidation
    status: reproduced  # DB constraint error instead of validation rule

  - id: VAL-005
    name: "$Profile.Name / $UserRole.Name context variables resolve in formula engine"
    description: >
      A validation rule referencing $Profile.Name should evaluate correctly.
      The PDF showed AllowCloseWon uses $Profile.Name and $UserRole.Name
      which the formula engine could not resolve.
    type: api
    test_file: api/validation_rules_test.go
    test_func: TestVAL005_ProfileContextVariables
    status: pass  # $Profile.Name resolves correctly

  - id: VAL-004
    name: "Validation rule error displays correctly in UI"
    description: >
      When a validation rule rejects a record save, the UI should show
      the rule's error message — not "internal error: unknown name NOT".
    type: chrome
    test_file: chrome/validation_rule_ui_error.md
    status: reproduced_ab  # SF shows friendly error, NexusCRM shows internal error

  # ── Flow Builder ──────────────────────────────────────────────────

  - id: FLOW-000
    name: "Baseline flow execution (string assignment, no globals)"
    description: >
      Create and execute a trivial AutoLaunchedFlow that assigns a string
      literal to an output variable. Verifies the flow engine itself works,
      isolating $GlobalConstant issues from engine-level problems.
    type: api
    test_file: api/flow_boolean_test.go
    test_func: TestFLOW000_BasicFlowExecution
    status: pass  # baseline flow execution works

  - id: FLOW-001
    name: "Flow assignment with $GlobalConstant.True resolves to boolean true"
    description: >
      When a Flow contains an Assignment node setting a variable to
      $GlobalConstant.True, the backend must resolve it to boolean true
      during execution (not null).
    type: api
    test_file: api/flow_boolean_test.go
    test_func: TestFLOW001_GlobalConstantTrue
    status: reproduced  # $GlobalConstant.True returned as raw reference

  - id: FLOW-002
    name: "Flow Builder UI shows True/False instead of {null}"
    description: >
      Open a Flow with boolean assignments in Flow Builder. The Value
      field should display "True" or "{!$GlobalConstant.True}", not "{null}".
    type: chrome
    test_file: chrome/flow_builder_boolean_display.md
    status: reproduced_ab  # SF canvas complete, NexusCRM assignment missing

  # ── Approval Process ──────────────────────────────────────────────

  - id: APPR-001
    name: "Deployed approval process auto-creates ProcessNode with resolved approver"
    description: >
      When deploying a ProcessDefinition with approval step XML containing
      an approver username, the backend must: (1) auto-create ProcessNode
      records from the XML metadata, and (2) resolve the approver username
      to a NexusCRM User ID stored as ApproverUserId. If ProcessNode is not
      auto-created, approval step data (including approver) is lost.
    type: api
    test_file: api/approval_deploy_test.go
    test_func: TestAPPR001_ApproverResolution
    status: reproduced  # ProcessNode not auto-created from approval XML

  - id: APPR-002
    name: "Approval step editor shows selectable user reference"
    description: >
      In the Approval Process editor, the Assigned Approver field should
      show a user picker (selectable reference), not plain text.
    type: chrome
    test_file: chrome/approval_user_picker.md
    status: reproduced  # Approval Queue shows "Unknown"

  - id: APPR-003
    name: "Approval Queue records display actual names, not Unknown"
    description: >
      Records in the Approval Queue should display the record's Name
      (e.g., "Acme Corp"), not "Unknown". Chrome testing found all 4
      pending approvals on NexusCRM backend showed "Unknown".
    type: api
    test_file: api/approval_queue_test.go
    test_func: TestAPPR003_ApprovalQueueRecordNames
    status: skip  # approval submission returns 500

  - id: APPR-004
    name: "Approval process redeploy/edit updates the existing ProcessDefinition"
    description: >
      Redeploying an existing ApprovalProcess metadata member must update
      the existing ProcessDefinition instead of failing validation by
      trying to set system audit fields like LastModifiedDate explicitly.
      The updated label, description, and active state should persist.
    type: api
    test_file: api/approval_deploy_test.go
    test_func: TestAPPR004_RedeployEditPersistsProcessDefinition
    status: reproduced  # issue #477 save/edit path failed with LastModifiedDate audit-field validation

  # ── Permissions & Roles ───────────────────────────────────────────

  - id: ROLE-001
    name: "Sales user can create business records (Account CRUD)"
    description: >
      Login as sales@test.com (Sales profile) via password grant.
      Create an Account record to verify the Sales user has basic CRUD
      permissions on standard business objects.
    type: api
    test_file: api/role_permission_test.go
    test_func: TestROLE001_SalesUserCRUD
    status: skip  # sales@test.com not provisioned

  - id: ROLE-002
    name: "Sales user cannot write to system tables"
    description: >
      Sales profile should NOT have write access to _System_* tables.
      POST to _System_Config as Sales user should return non-201.
    type: api
    test_file: api/role_permission_test.go
    test_func: TestROLE002_SalesUserSystemTableRestriction
    status: skip  # sales@test.com not provisioned

  - id: ROLE-003
    name: "Sales user login and token verification"
    description: >
      Verify sales@test.com can authenticate via password grant and
      that the returned token is functional for API requests.
    type: api
    test_file: api/role_permission_test.go
    test_func: TestROLE003_SalesUserLogin
    status: skip  # sales@test.com not provisioned

  - id: ROLE-004
    name: "Profile permission editor resolves the profile-owned PermissionSet"
    description: >
      On /setup/profiles, opening the permission editor for Sales User
      must resolve the profile's owned PermissionSet, load permissions by
      PermissionSet.Id, and render the object-permission grid without the
      red "No PermissionSet found" banner. Closeout validation for #306
      was re-run on current live build v17750358 with screenshots and
      authenticated live queries.
    type: chrome
    test_file: chrome/setup_test.md
    status: pass  # verified on live v17750358; issue #306 no longer reproduces
    checks:
      - page: /setup/profiles
        filter: "Sales User"
        verify: "Permission modal header renders"
      - verify: "No red 'No PermissionSet found' banner"
      - verify: "Object permission grid renders with checkboxes"

  # ── Lead Convert ────────────────────────────────────────────────

  - id: LEAD-001
    name: "Lead Convert endpoint exists and is functional"
    description: >
      The Lead Convert feature is required for the standard sales workflow
      (Lead → Convert → Account/Contact/Opportunity). PDF confirmed this
      is completely missing.
    type: api
    test_file: api/lead_convert_test.go
    test_func: TestLEAD001_ConvertEndpointExists
    status: reproduced  # Lead object does not exist

  # ── Object Completeness ────────────────────────────────────────────

  - id: OBJ-001
    name: "CampaignMemberStatus object exists"
    description: >
      CampaignMemberStatus object should exist. The PDF reported it was
      missing when adjusting Campaign Layout.
    type: api
    test_file: api/object_completeness_test.go
    test_func: TestOBJ001_CampaignMemberStatusExists
    status: reproduced  # CampaignMemberStatus 404

  # ── Performance ────────────────────────────────────────────────────

  - id: PERF-001
    name: "Session initialization completes within 30 seconds"
    description: >
      New session initialization should complete within a reasonable time.
      The PDF reported >2 minutes for User list, >1 minute for
      Accounts/Opportunities. Measures token acquisition + first API call.
    type: api
    test_file: api/performance_test.go
    test_func: TestPERF001_SessionInitLatency
    status: pass  # 868ms, within threshold

  # ── Data Integrity ────────────────────────────────────────────────

  - id: DATA-001
    name: "Field labels persist after data refresh"
    description: >
      After importing/refreshing data, field Labels should remain as
      human-readable text, not revert to API Names.
    type: chrome
    test_file: chrome/field_label_persistence.md
    status: reproduced_ab  # SF shows Labels, NexusCRM shows ApiName variants

  - id: DATA-002
    name: "ListView column changes persist on server"
    description: >
      Modify ListView columns via the list view UI, refresh the page,
      and verify the changes are still there.
    type: chrome
    test_file: chrome/listview_persistence.md
    status: excluded  # both SF and NexusCRM use localStorage

  - id: DATA-003
    name: "Navigation bar Object Names do not revert to API Names"
    description: >
      After customizing nav sidebar labels to use Label instead of API
      Name, adding a new Object should not revert existing labels.
      NOTE: A/B testing showed SF backend also only has SYSTEM nav —
      this may be an App configuration issue, not a backend issue.
    type: chrome
    test_file: chrome/nav_label_stability.md
    status: excluded  # both have SYSTEM-only sidebar

  - id: DATA-004
    name: "Field describe returns distinct Labels (not just ApiName echo)"
    description: >
      GET /sobjects/{object}/describe should return fields with Label
      different from ApiName for fields that have human-readable names.
      If all Labels equal ApiNames, metadata may have lost Label values.
    type: api
    test_file: api/data_integrity_test.go
    test_func: TestDATA004_FieldLabelsInDescribe
    status: pass  # 9/12 fields have distinct Labels

  - id: DATA-005
    name: "ListView changes persist via API (write-then-read)"
    description: >
      Update a ListView label via API, then re-read to confirm the
      change persisted. Tests the API-level persistence that the UI
      depends on.
    type: api
    test_file: api/data_integrity_test.go
    test_func: TestDATA005_ListViewPersistence
    status: skip  # ListView PATCH returns 400

  - id: DATA-006
    name: "Describe response has required Salesforce shape"
    description: >
      Account describe must include required top-level keys (name, label,
      queryable, fields, urls) and field-level keys (name, label, type,
      createable, updateable) matching the Salesforce API contract.
    type: api
    test_file: api/data_integrity_test.go
    test_func: TestDATA006_DescribeShapeComparison
    status: pass  # describe response has correct shape

  - id: DATA-007
    name: "Composite validation formula (NOT + ISBLANK combined)"
    description: >
      The PDF showed AllowCloseWon uses a composite formula combining
      NOT(), ISCHANGED(), and $Profile.Name. This tests a combined formula
      NOT(ISBLANK(Industry)) && ISBLANK(Name) to verify the engine handles
      multi-function expressions, not just individual functions.
    type: api
    test_file: api/data_integrity_test.go
    test_func: TestDATA007_CompositeValidationFormula
    status: pass  # composite formula evaluated without 500

  - id: DATA-008
    name: "Flow import preserves boolean assignment values (not null)"
    description: >
      When a flow with $GlobalConstant.True in elementReference format
      is created/imported, the value should be preserved when read back.
      The PDF issue is about IMPORTED flows showing {null} — this tests
      the storage/retrieval round-trip.
    type: api
    test_file: api/data_integrity_test.go
    test_func: TestDATA008_FlowImportBooleanPreservation
    status: pass  # $GlobalConstant.True preserved in storage

  # ── Field Label Quality (from #42 regression) ──────────────────────

  - id: LABEL-001
    name: "Reference field labels strip 'ID' suffix"
    description: >
      Fields like OpportunityId, RecordTypeId, ContactId, PriceBookId
      should display as "Opportunity", "Record Type", "Contact", "Price Book"
      (without "ID" suffix). Regression test found: "Opportunity ID",
      "Record Type ID", "Price Book ID", "Contact ID" on Quote form.
    type: chrome
    test_file: chrome/field_label_quality.md
    status: reproduced
    checks:
      - page: /object/Quote/new
        field: OpportunityId
        expected_label: "Opportunity"
        actual_label: "Opportunity ID"
      - page: /object/Quote/new
        field: RecordTypeId
        expected_label: "Record Type"
        actual_label: "Record Type ID"

  - id: LABEL-002
    name: "Custom field labels use human-readable form (no underscores/camelCase)"
    description: >
      SF-imported custom fields like Customers_Cluster_Configuration,
      IsConvertQuote, ApprovedPrice should have proper Labels set during
      import. Regression found raw API names displayed on Quote form.
    type: chrome
    test_file: chrome/field_label_quality.md
    status: reproduced
    checks:
      - field: Customers_Cluster_Configuration
        expected: "Customer Cluster Configuration"
      - field: IsConvertQuote
        expected: "Is Convert Quote"
      - field: ApprovedPrice
        expected: "Approved Price"

  - id: LABEL-003
    name: "Detail page first field is not SystemModstamp"
    description: >
      Quote detail page shows SystemModstamp as the first field in the
      layout. Business fields (Name, Status, Opportunity) should be first.
    type: chrome
    test_file: chrome/field_label_quality.md
    status: reproduced

  # ── List View Quality (from #40 regression) ────────────────────────

  - id: LIST-001
    name: "Analytics cards show relevant business metrics"
    description: >
      Quick Analytics on Quote list should show metrics like "Total Quote
      Value", "Quotes by Status", not "Total ACV Amount (do not use)" or
      "Total Auto Number" or "Total Discount/" (trailing slash).
    type: chrome
    test_file: chrome/listview_quality.md
    status: reproduced
    checks:
      - bad_card: "Total ACV Amount (do not use)"
      - bad_card: "Total Auto Number"
      - bad_card: "Total Discount/"

  # ── Approval Chain Display (from #43 regression) ───────────────────

  - id: APPR-004
    name: "Approval chain displays history on record detail page"
    description: >
      After submitting and approving a Quote, navigating back to the
      Quote detail page should show the approval history (not "No approval
      history"). The SOQL subquery for StepsAndWorkitems must not error.
      Regression found: "expected FROM" error in console, approval chain
      shows nothing, Submit button still active after approval.
    type: chrome
    test_file: chrome/approval_chain_display.md
    status: reproduced
    checks:
      - verify: "No 'expected FROM' errors in console"
      - verify: "Approval Chain section shows approved entry"
      - verify: "Submit for Approval button hidden after approval"
      - verify: "Approval status banner visible"

  # ── Approval Badge Sync (from #44 regression) ─────────────────────

  - id: APPR-005
    name: "Sidebar badge decrements after approving from queue"
    description: >
      After approving an item from the Approval Queue, the sidebar badge
      count should decrement immediately (not require page refresh).
      Regression found: page header updates to 0, sidebar still shows 1.
    type: chrome
    test_file: chrome/approval_badge_sync.md
    status: reproduced
    checks:
      - step: "Submit quote for approval"
        verify: "Badge shows 1"
      - step: "Approve from queue"
        verify: "Page header shows 0 pending"
      - step: "Check sidebar badge"
        verify: "Badge shows 0 or disappears"

  - id: APPR-006
    name: "Approval Queue COMMENTS column shows submission comment"
    description: >
      When submitting a quote for approval with a comment, the COMMENTS
      column in the Approval Queue should display that comment (not "—").
      Backend now persists Comments on WorkItem, but frontend getPending()
      SOQL does not SELECT Comments field yet — frontend change needed.
    type: chrome
    test_file: chrome/approval_badge_sync.md
    status: partially_fixed  # backend persists, frontend doesn't read

  # ── Security: Permission Phase 0 ─────────────────────────────────

  - id: SEC-001
    name: "RLS manual share subquery uses correct column names"
    description: >
      Create a RecordShare for a non-admin user, then query as that user.
      If rls_filter.go uses wrong column names (SobjectType instead of
      ObjectApiName), TiDB returns "Unknown column" error.
    type: api
    test_file: api/security_rls_test.go
    test_func: TestSEC001_ManualShareRLSColumnNames
    status: pending

  - id: SEC-002
    name: "RLS team member subquery uses correct column names"
    description: >
      Create a TeamMember for a non-admin user, then query as that user.
      Validates ObjectApiName column (not SobjectType) in generated SQL.
    type: api
    test_file: api/security_rls_test.go
    test_func: TestSEC002_TeamMemberRLSColumnNames
    status: pending

  - id: SEC-003
    name: "ControlledByParent single-record access inherits from parent"
    description: >
      Contact (ControlledByParent) — user has access to parent Account
      but is not Contact owner. GET /sobjects/Contact/:id should succeed.
      Without fix: falls through to ownership check → access denied.
    type: api
    test_file: api/security_rls_test.go
    test_func: TestSEC003_ControlledByParentSingleRecord
    status: pending

  - id: SEC-004
    name: "Deleted user session is revoked immediately"
    description: >
      Login as user A, get token. Admin deletes user A. User A's token
      should be rejected on next API call (not valid until JWT expiry).
    type: api
    test_file: api/security_session_test.go
    test_func: TestSEC004_DeletedUserSessionRevoked
    status: pending

  - id: SEC-005
    name: "pprof endpoints require authentication"
    description: >
      GET /debug/pprof/heap without auth token should return 401/403,
      not a heap dump. Requires ENABLE_PPROF=true on server.
    type: api
    test_file: api/security_route_test.go
    test_func: TestSEC005_PprofRequiresAuth
    status: pending

  - id: SEC-006
    name: "File download requires object permission"
    description: >
      Attempt file download without authentication. Should return
      401/403, not serve the file.
    type: api
    test_file: api/security_route_test.go
    test_func: TestSEC006_FileDownloadRequiresPermission
    status: pending

  - id: SEC-007
    name: "BLOCKED tables deny even SuperUser writes"
    description: >
      Admin (system_admin profile) attempts POST to _System_Session.
      Should be rejected — BLOCKED tables are never writable via API.
    type: api
    test_file: api/security_route_test.go
    test_func: TestSEC007_BlockedTablesDenySuperUser
    status: pending

  - id: SEC-008
    name: "Auth endpoints have stricter rate limit than global"
    description: >
      Send 20 rapid invalid token requests. With dedicated 5 req/s
      limiter, later requests get 429. Tests brute-force protection.
    type: api
    test_file: api/security_route_test.go
    test_func: TestSEC008_AuthRateLimitStricterThanGlobal
    status: pending

  - id: SEC-009
    name: "ControlledByParent bulk query matches single-record access"
    description: >
      For the same Contact record, compare SOQL query vs GET by ID.
      Both must agree — this is the Path A / Path B consistency invariant.
    type: api
    test_file: api/security_rls_test.go
    test_func: TestSEC009_BulkVsSingleRecordConsistency
    status: pending

  - id: SEC-010
    name: "Role hierarchy cycle is rejected on update"
    description: >
      Create roles A→B. Attempt to set B's parent to A (creating cycle).
      Should return validation error, not succeed silently.
    type: api
    test_file: api/security_route_test.go
    test_func: TestSEC010_RoleHierarchyCycleRejected
    status: pending
