diff --git a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md index d98ceeac..bdf30d3c 100644 --- a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md +++ b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md @@ -37,10 +37,10 @@ Hermes plan landing map: ## Current Baseline -- Current tracking checkpoint: 2026-06-06 after this audit/tooling checkpoint, prepared as `docs: expand help center route parity tracking`. -- Latest implementation checkpoint: this checkpoint, prepared as `feat(help-center): align article payloads`. +- Current tracking checkpoint: 2026-06-06 after this implementation checkpoint, prepared as `feat(conversations): align custom attribute response`. +- Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): align custom attribute response`. - Latest documentation/tooling checkpoint: this checkpoint, prepared as `docs: expand help center route parity tracking`; this document is now the active follow-up plan and supersedes `.hermes/plans/*`. -- Worktree status at this audit/tooling checkpoint: Route parity tracking now includes the Chatwoot help-center portal/category/article route family from `reference/chatwoot/config/routes.rb:385-404`, so the audited critical set covers portal CRUD/member actions, nested categories, nested articles, and article bulk actions. P3.7c help center article parity matches the reused dashboard `helpCenter/articles.js` client and Chatwoot `ArticlesController`/`Articles::BulkActionsController`/Jbuilder surface for the dashboard article path: no-trailing-slash `GET/POST /portals/:portal_id/articles`, frontend `PATCH /articles/:article_id`, `PATCH /articles/bulk_actions/update_status`, `PATCH /articles/bulk_actions/update_category`, `DELETE /articles/bulk_actions/delete_articles`, and `POST /articles/bulk_actions/translate` are registered; portal route IDs resolve by account-scoped slug with numeric fallback; create/update accept raw frontend article bodies plus `{ article: ... }`; create/show/edit/update/list/search return Chatwoot `{ payload }` or `{ payload, meta }` article serializers with category, author, views, Unix `updated_at`, `meta`, and associated article payloads; delete/reorder/bulk updates return empty `200 OK`; reorder accepts Chatwoot `positions_hash`; bulk action errors return `422 { error }`; and scoped repository/service methods keep mutations inside the resolved portal. P3.7a portal and P3.7b category parity are already in Review. Campaign create/update already mirrors Chatwoot `Campaign#ensure_correct_campaign_attributes`; account campaign CRUD already matches the reused dashboard `campaigns` store and Chatwoot Jbuilder contract. Agent/settings/profile/notification/contact/custom-field/report enterprise drifts listed below remain at their prior checkpoint states. The fresh placeholder audit found remaining `chatwootParityStub` routes only in webhook handler-not-injected fallbacks, not in the reused dashboard account/contact/conversation/message/inbox critical path. P3.6 custom filters/custom attribute definitions and label CRUD are already in Review. B11.1a-B11.3e Captain/Copilot slices remain in Review; B12.1/B12.2/B12.3 smoke harnesses remain in Review; P5 durable job work through P5.13b is in Review. Next active implementation slice is Phase 6 placeholder burn-down or a new Phase 2/3 drift slice from fresh reference/smoke evidence. +- Worktree status at this implementation checkpoint: P6 conversation custom-attribute response parity now matches Chatwoot `ConversationsController#custom_attributes`, `custom_attributes.json.jbuilder`, and the reused dashboard conversation store action: `POST /conversations/:conversation_id/custom_attributes` returns only `{ custom_attributes: ... }`, omits local conversation/payload/meta wrappers, and serializes empty/null JSON as `{}` for frontend merge stability. Route parity tracking includes the Chatwoot help-center portal/category/article route family from `reference/chatwoot/config/routes.rb:385-404`, so the audited critical set covers portal CRUD/member actions, nested categories, nested articles, and article bulk actions. P3.7c help center article parity matches the reused dashboard `helpCenter/articles.js` client and Chatwoot `ArticlesController`/`Articles::BulkActionsController`/Jbuilder surface. Campaign create/update already mirrors Chatwoot `Campaign#ensure_correct_campaign_attributes`; account campaign CRUD already matches the reused dashboard `campaigns` store and Chatwoot Jbuilder contract. Agent/settings/profile/notification/contact/custom-field/report enterprise drifts listed below remain at their prior checkpoint states. The fresh placeholder audit found remaining `chatwootParityStub` routes only in webhook handler-not-injected fallbacks, not in the reused dashboard account/contact/conversation/message/inbox critical path. P3.6 custom filters/custom attribute definitions and label CRUD are already in Review. B11.1a-B11.3e Captain/Copilot slices remain in Review; B12.1/B12.2/B12.3 smoke harnesses remain in Review; P5 durable job work through P5.13b is in Review. Next active implementation slice is another Phase 6 placeholder burn-down item or a new Phase 2/3 drift slice from fresh reference/smoke evidence. - `go test ./...` passes. - Route dump succeeds with `TOTAL: 860` after adding no-trailing-slash article index/create aliases, frontend-used article PATCH update route, and frontend article bulk-action routes. - Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`. @@ -110,7 +110,7 @@ This table is the shortest authoritative handoff view. If an older lower section | Priority | Workstream | Current state | Next checkpoint | Commit close rule | | --- | --- | --- | --- | --- | | 1 | Phase 2/3 drift | Tracked route parity is 0 missing for the current 305-route critical set; help-center portal/category/article routes from `routes.rb:385-404` are now explicitly tracked. Notification list/action serializers, user notification-settings raw payloads, campaigns raw payload/display-id routes, Devise password reset/confirmation payloads, CRM shared attachment payloads plus fixed 100-row attachment pagination, account/settings payloads, assignable-agent payloads, agent index full-list behavior, agent create/update/delete defaults/errors/scope, label CRUD payloads, custom filters, custom attribute definitions, and help-center portal/category/article payloads now match the inspected Chatwoot frontend contract. | Run the next evidence-backed route/controller/serializer drift audit. | Regenerate parity artifacts when routes change and add endpoint-family fixture tests. | -| 2 | Phase 6 placeholder audit | Widget/public/webhook critical placeholders are burned down; inbox WhatsApp health/register-webhook and sync-template drift are closed; fresh `rg` audit shows only webhook nil-handler fallbacks still call `chatwootParityStub`; dashboard conversation transcript response drift is closed. | Continue targeted account/contact/conversation/message/inbox drift from reference/smoke evidence. | `rg` placeholder audit is recorded and no reused-frontend blocker is ownerless. | +| 2 | Phase 6 placeholder audit | Widget/public/webhook critical placeholders are burned down; inbox WhatsApp health/register-webhook and sync-template drift are closed; fresh `rg` audit shows only webhook nil-handler fallbacks still call `chatwootParityStub`; dashboard conversation transcript and custom-attribute response drift are closed. | Continue targeted account/contact/conversation/message/inbox drift from reference/smoke evidence. | `rg` placeholder audit is recorded and no reused-frontend blocker is ownerless. | | 3 | B12 optional live smoke | API/browser/enterprise smoke commands are checked in; live runs need PostgreSQL, Redis, Meilisearch, Vite, and Chrome. | Run full live smoke when environment is available and map failures to the board. | `docs/parity/frontend_smoke_report.md` records pass/fail and linked owners. | | 4 | B9.3 delayed automation actions | Current reference exposes no delayed automation action params; scheduled-item work is already P5.12; `send_email_to_team` is durable and `add_sla` mutates conversation/applied SLA state. | Keep automation drift closed if future reference/smoke exposes delayed params or unsupported action shapes. | Automation worker/action fixtures verify queued team email replay, retry visibility through worker jobs, and SLA action idempotency. | | 5 | P5.13 reports/analytics | P5.13a derives visible report aggregates from persisted rows; P5.13b adds lazy rollup freshness, durable day rollup jobs, and `/reports` metric timeseries. | Keep report drift closed as frontend smoke or reference inspection exposes additional metrics. | Report fixtures verify timeseries values, cache/freshness behavior, and no hidden placeholder JSON. | @@ -132,7 +132,7 @@ These rows are the executable development plan from this point forward. A checkp | P3.7b help center category route/payload parity | `internal/router/router.go`, `internal/handler/api/v1/category_handler.go`, `internal/service/category_service.go`, `internal/repository/category_repo.go`, category tests | `reference/chatwoot/app/controllers/api/v1/accounts/categories_controller.rb`, `app/views/api/v1/accounts/categories/*.json.jbuilder`, dashboard `api/helpCenter/categories.js` | Category APIs now resolve portal slugs account-scoped for `/portals/:portal_id/categories`, accept raw category bodies plus wrappers, expose frontend no-trailing-slash and `PATCH` routes, return Chatwoot `{ payload, meta }` and `{ payload: category }` serializers with locale filtering and article counts, make delete/reorder empty `200 OK`, accept Chatwoot `positions_hash`, and keep category mutations scoped to the resolved portal. | Review by `feat(help-center): align category payloads`; focused category handler/service/repository tests, route dump/parity regeneration, full `go test ./...`, and `git diff --check` passed. | | P3.7c help center article and bulk-action parity | `internal/router/router.go`, `internal/handler/api/v1/article_handler.go`, `internal/service/article_service.go`, `internal/repository/article_repo.go`, article tests | `reference/chatwoot/app/controllers/api/v1/accounts/articles_controller.rb`, `app/controllers/api/v1/accounts/articles/bulk_actions_controller.rb`, `app/views/api/v1/accounts/articles/*.json.jbuilder`, dashboard `api/helpCenter/articles.js` | Article APIs now resolve portal slugs account-scoped with numeric fallback, accept raw article bodies plus wrappers, expose no-trailing-slash index/create plus frontend `PATCH`, return Chatwoot `{ payload, meta }` list/search payloads and `{ payload: article }` show/create/update/edit payloads, serialize category/author/views/Unix updated-at/meta/associated articles, make delete/reorder/bulk actions empty `200 OK`, accept Chatwoot `positions_hash`, scope mutations to the resolved portal, and implement frontend bulk update-status/update-category/delete routes plus `translate` `501`. | Review by `feat(help-center): align article payloads`; focused article handler tests, service/repository article tests, router test, route dump/parity regeneration, escalated full `go test ./...`, and `git diff --check` passed. | | P6 conversation transcript response parity | `internal/handler/api/v1/conversation_handler.go`, conversation handler tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb`, dashboard `api/inbox/conversation.js` | Account conversation transcript now follows Chatwoot's controller contract: missing email returns `422 { error: "email param missing" }`, nonblank email schedules through the existing service boundary and returns empty `200 OK`, and invalid-looking but nonblank email values are not rejected by local email format validation. | Review by `feat(conversations): align transcript responses`; focused transcript handler/service tests, combined handler/service/router tests, full `go test ./...`, and `git diff --check` must pass. | -| P6 conversation custom attributes response parity | `internal/handler/api/v1/conversation_handler.go`, conversation handler tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb`, `app/views/api/v1/accounts/conversations/custom_attributes.json.jbuilder`, dashboard `api/inbox/conversation.js`, conversation store action | Account conversation custom attribute updates now return Chatwoot `{ custom_attributes: ... }` only, matching the store action that reads `response.data.custom_attributes`, instead of returning the full conversation serializer with unrelated fields. | Review by `feat(conversations): align custom attribute response`; focused handler tests, combined handler/service/router tests, full `go test ./...`, and `git diff --check` must pass. | +| P6 conversation custom attributes response parity | `internal/handler/api/v1/conversation_handler.go`, conversation handler tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb`, `app/views/api/v1/accounts/conversations/custom_attributes.json.jbuilder`, dashboard `api/inbox/conversation.js`, conversation store action | Account conversation custom attribute updates now return Chatwoot `{ custom_attributes: ... }` only, matching the store action that reads `response.data.custom_attributes`, instead of returning the full conversation serializer with unrelated fields. Empty/null JSON serializes as `{}`. | Review by `feat(conversations): align custom attribute response`; focused handler tests, combined handler/service/router tests, full `go test ./...`, and `git diff --check` passed. | | Phase 2/3 drift audit | `cmd/route_parity`, `docs/parity/*`, serializer tests | `reference/chatwoot/config/routes.rb`, controller Jbuilder views, reused frontend API clients | Convert any smoke/reference mismatch into a named route, controller, or serializer slice. Static route extraction remains acceptable until Ruby/Bundler is available. | Regenerated route parity shows 0 missing tracked frontend routes; new serializer fixtures cover the drift. | | Phase 6 placeholder burn-down | Account/contact/conversation/message/inbox handlers and services | Matching reference controllers/Jbuilder views plus reused frontend screens | Re-run placeholder audit and assign every frontend-reachable stub to a specific owner. Burn down the highest-impact stubs before broad feature expansion. | `rg` placeholder audit is recorded here; no reused-frontend critical path is ownerless. | | B12 live smoke | `scripts/parity_frontend_smoke.sh`, `docs/parity/frontend_smoke_report.md`, `cmd/gochat` | Reused `reference/chatwoot` Vite frontend, dashboard route/API clients | Run optional live API/browser/enterprise smoke with PostgreSQL, Redis, Meilisearch, GoChat, Vite, and Chrome. Convert failures into named rows above. | Smoke report records command, environment, pass/fail, artifacts, and linked follow-up owners. | @@ -168,6 +168,7 @@ This ledger records the committed parity checkpoints that future slices should b | Commit | Scope | Verification summary | Follow-up state | | --- | --- | --- | --- | +| `feat(conversations): align custom attribute response` | Advances P6 conversation response parity by matching Chatwoot `ConversationsController#custom_attributes`, `custom_attributes.json.jbuilder`, and the reused dashboard `ConversationApi.updateCustomAttributes` store action. The account conversation custom-attributes route now returns only `{ custom_attributes: ... }`, excludes local conversation/payload/meta wrappers, and converts empty/null JSON to `{}` for frontend merge stability. The placeholder audit was rerun and still shows only webhook handler-not-injected fallbacks calling `chatwootParityStub`. | `go test ./internal/handler/api/v1 -run 'ConversationHandler.*UpdateCustomAttributes\|TestConversationHandler' -count=1`; `go test ./internal/handler/api/v1 ./internal/service ./internal/router -run 'UpdateCustomAttributes\|ConversationHandler\|ConversationService\|Router' -count=1`; `rg -n "chatwootParityStub\|TODO.*placeholder\|placeholder\|stub" internal/handler internal/service internal/router -g'*.go'`; `go test ./...`; `git diff --check`. | P6 conversation custom-attribute response drift moves to Review; continue Phase 6 placeholder burn-down from the remaining webhook nil-handler fallbacks or fresh reference/smoke evidence. | | `docs: expand help center route parity tracking` | Audit/tooling checkpoint that expands `cmd/route_parity` to track the full Chatwoot help-center portal/category/article route family from `reference/chatwoot/config/routes.rb:385-404`, including portal member actions, nested category CRUD/reorder, nested article CRUD/reorder, and article bulk actions. Regenerated `docs/parity/route_parity.md` now reports `298 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 305 tracked critical routes`; `docs/parity/gochat_routes.txt` remains `TOTAL: 860`. | `go run ./cmd/route_parity`; `go test ./cmd/route_parity -count=1`; `git diff --check`. | Continue Phase 6 placeholder burn-down or the next evidence-backed Phase 2/3 drift slice; keep route artifacts regenerated whenever the tracked set changes. | | `feat(help-center): align article payloads` | Advances P3.7c help-center parity by matching Chatwoot `ArticlesController`, `Articles::BulkActionsController`, article Jbuilder views, and reused dashboard `helpCenter/articles.js`. Article index/create now expose no-trailing-slash routes; update exposes frontend `PATCH`; frontend bulk update-status/update-category/delete and translate routes are registered; portal IDs resolve through account-scoped slug lookup with numeric fallback; create/update accept raw frontend article bodies plus wrappers; list/search return `{ payload, meta }`; show/create/update/edit return `{ payload: article }` with category, author, views, Unix `updated_at`, `meta`, and associated article fields; delete/reorder/bulk actions return empty `200 OK`; reorder accepts Chatwoot `positions_hash`; and bulk action errors return `422 { error }`. | `go test ./internal/handler/api/v1 -run ArticleHandler -count=1`; `go test ./internal/service ./internal/repository -run Article -count=1`; `go test ./internal/router -run Router -count=1`; `go run ./cmd/dump_routes > docs/parity/gochat_routes.txt`; `go run ./cmd/route_parity`; sandboxed full `go test ./...` failed only on local socket restrictions; escalated full `go test ./...` passed; `git diff --check`. Route dump is `TOTAL: 860`; tracked route parity remains `270 exact, 7 parameter-compatible, 0 missing`. | P3.7c moves to Review; continue Phase 2/3 drift audit or Phase 6 placeholder burn-down from fresh reference/smoke evidence. | | `docs: lock parity execution tracker` | Documentation-only checkpoint that makes this file the single active tracker after user confirmation. It locks direct Chatwoot frontend reuse, local `reference/chatwoot` as the only baseline, `go test ./...` first, Meilisearch as mandatory final search, and enterprise scope excluding only SSO/SAML/LDAP/OIDC. It also maps the remaining Hermes search and automation/macro/CSAT notes into the existing Phase 1/B6, B8/B9, Phase 5, B7-B12, and active P3.7 help-center rows. | `git diff --check`. No Go code changed. | Continue P3.7c help-center articles and article bulk actions; future slices update this tracker in the same commit. | @@ -2031,7 +2032,7 @@ Tracking table: | --- | --- | --- | --- | --- | | P6.1 | Account APIs | `docs/ROUTE_GAP_ANALYSIS.md`, account handlers | Replace placeholder responses with repository-backed behavior and serializer tests. | Todo | | P6.2 | Contact APIs | `docs/ROUTE_GAP_ANALYSIS.md`, contact handlers/services | Finish merge, import/export/data-import, notes serializer depth, and Meilisearch-backed CRM search. | Doing | -| P6.3 | Conversation APIs | `docs/ROUTE_GAP_ANALYSIS.md`, conversation handlers/services | Implement frontend-critical filters, assignment, status, snooze, merge, bulk actions. | Todo | +| P6.3 | Conversation APIs | `docs/ROUTE_GAP_ANALYSIS.md`, conversation handlers/services | Frontend-critical filters, assignment, status, snooze, merge, bulk actions, transcript responses, and custom-attribute responses are implemented or covered by focused drift slices; continue only from fresh reference/smoke evidence. | Review | | P6.4 | Message APIs | `docs/ROUTE_GAP_ANALYSIS.md`, message handlers/services | Implement create/list/delete, private notes, attachments, source attribution, events. | Todo | | P6.5 | Inbox APIs | `docs/ROUTE_GAP_ANALYSIS.md`, inbox handlers/services | Implement CRUD, assignable agents, avatar, campaigns, channel settings, reset secret. WhatsApp Cloud health/register-webhook/sync-template member actions now match the reference concern. | Doing | | P6.6 | Widget/public APIs | `docs/ROUTE_GAP_ANALYSIS.md`, widget/channel provider code, `chatwootParityStub` routes | Widget/public frontend-critical route behavior is handler-backed, including public inbox flow, direct uploads/attachments, and public CSAT survey submission. | Done | @@ -2120,6 +2121,7 @@ Verification milestone gates: ## Progress Log +- 2026-06-06: P6 conversation custom-attribute response checkpoint prepared as `feat(conversations): align custom attribute response`; audited Chatwoot `ConversationsController#custom_attributes`, `custom_attributes.json.jbuilder`, reused dashboard `api/inbox/conversation.js`, and the conversation store action that reads `response.data.custom_attributes`. Account conversation custom-attribute updates now return only `{ custom_attributes: ... }`, exclude local conversation/payload/meta wrappers, and serialize empty/null custom attributes as `{}` for frontend merge stability. Focused handler tests, combined handler/service/router tests, placeholder audit, full `go test ./...`, and `git diff --check` passed; continue Phase 6 placeholder burn-down or a fresh evidence-backed Phase 2/3 drift slice. - 2026-06-06: Audit/tooling checkpoint prepared as `docs: expand help center route parity tracking`; expanded `cmd/route_parity` to cover the full Chatwoot help-center portal/category/article route family from `reference/chatwoot/config/routes.rb:385-404`, including portal CRUD/member actions, nested category CRUD/reorder, nested article CRUD/reorder, and article bulk actions. Regenerated route parity reports `298 exact, 0 method-compatible, 7 parameter-compatible, 0 missing out of 305 tracked critical routes`; route dump remains `TOTAL: 860`. Verification for this checkpoint: `go run ./cmd/route_parity`, `go test ./cmd/route_parity -count=1`, and `git diff --check`; continue Phase 6 placeholder burn-down or a fresh evidence-backed Phase 2/3 drift slice. - 2026-06-06: P3.7c help-center article checkpoint prepared as `feat(help-center): align article payloads`; audited Chatwoot `ArticlesController`, `Articles::BulkActionsController`, article Jbuilder views, and reused dashboard `helpCenter/articles.js`. Account article APIs now accept raw frontend create/update bodies plus wrappers, resolve portal route IDs by account-scoped slug with numeric fallback, return Chatwoot `{ payload, meta }` list/search payloads and `{ payload: article }` mutation/show/edit payloads, expose no-trailing-slash index/create plus frontend `PATCH`, return empty `200 OK` for delete/reorder/bulk actions, support status/category/locale/query/author filters and dashboard article counts, accept Chatwoot reorder `positions_hash`, and implement frontend bulk update-status/update-category/delete routes with `422 { error }` failures. Focused article handler tests, service/repository article tests, router test, route dump/parity, escalated full `go test ./...`, and `git diff --check` passed; continue Phase 2/3 drift audit or Phase 6 placeholder burn-down from fresh evidence. - 2026-06-06: Documentation checkpoint prepared as `docs: lock parity execution tracker`; user decisions are now locked into the tracker: reuse Chatwoot frontend directly, treat local `reference/chatwoot` as the only baseline, keep `go test ./...` green before deeper parity, require Meilisearch as final search, exclude only SSO/SAML/LDAP/OIDC, and keep the remaining enterprise families in scope. The older Hermes search and automation/macro/CSAT plans are mapped to active tracker sections so future work can be followed from this document alone. Verification for this docs-only checkpoint: `git diff --check`; next implementation slice remains P3.7c help-center articles and bulk actions. diff --git a/internal/handler/api/v1/conversation_handler.go b/internal/handler/api/v1/conversation_handler.go index 9dc87ce2..b222c969 100644 --- a/internal/handler/api/v1/conversation_handler.go +++ b/internal/handler/api/v1/conversation_handler.go @@ -746,7 +746,7 @@ func (h *ConversationHandler) UpdateCustomAttributes(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"custom_attributes": conversation.CustomAttributes}) + c.JSON(http.StatusOK, gin.H{"custom_attributes": jsonObject(conversation.CustomAttributes)}) } // ListAttachments returns paginated message attachments for a conversation. diff --git a/internal/handler/api/v1/conversation_handler_test.go b/internal/handler/api/v1/conversation_handler_test.go index 83a7e0fd..9ba9797e 100644 --- a/internal/handler/api/v1/conversation_handler_test.go +++ b/internal/handler/api/v1/conversation_handler_test.go @@ -338,6 +338,28 @@ func (s *ConversationHandlerTestSuite) TestUpdateCustomAttributes_Success() { assert.NoError(s.T(), err) assert.NotContains(s.T(), rawResp, "id") assert.NotContains(s.T(), rawResp, "success") + assert.NotContains(s.T(), rawResp, "payload") + assert.NotContains(s.T(), rawResp, "meta") +} + +func (s *ConversationHandlerTestSuite) TestUpdateCustomAttributes_EmptyObjectReturnsChatwootShape() { + body := map[string]interface{}{ + "custom_attributes": map[string]interface{}{}, + } + bodyBytes, _ := json.Marshal(body) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", s.accountURL()+"/conversations/"+strconv.FormatUint(uint64(s.testConv.ID), 10)+"/custom_attributes", bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + s.router.ServeHTTP(w, req) + + assert.Equal(s.T(), http.StatusOK, w.Code) + + var rawResp map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &rawResp) + assert.NoError(s.T(), err) + assert.Equal(s.T(), map[string]interface{}{}, rawResp["custom_attributes"]) + assert.Len(s.T(), rawResp, 1) } func (s *ConversationHandlerTestSuite) TestUpdateCustomAttributes_InvalidAccountID() {