test(parity): add enterprise browser smoke coverage
This commit is contained in:
+5
-1
@@ -131,9 +131,13 @@ func seedSmokeData(ctx context.Context, db *gorm.DB) (*smokeSeedSummary, error)
|
||||
inboxName := getenvDefault("GOCHAT_SEED_INBOX_NAME", "Test Website Inbox")
|
||||
|
||||
account := &model.Account{}
|
||||
if err := db.WithContext(ctx).Where("name = ?", accountName).FirstOrCreate(account, model.Account{Name: accountName, Locale: "en", Timezone: "UTC", Active: true, Status: "active", FeatureFlags: `{"captain":true,"csat":true}`}).Error; err != nil {
|
||||
smokeFeatureFlags := `{"advanced_assignment":true,"assignment_v2":true,"audit_logs":true,"automations":true,"captain_integration":true,"captain_integration_v2":true,"captain_tasks":true,"companies":true,"crm":true,"csat":true,"custom_roles":true,"custom_tools":true,"inbox_management":true,"macros":true,"reports":true,"sla":true}`
|
||||
if err := db.WithContext(ctx).Where("name = ?", accountName).FirstOrCreate(account, model.Account{Name: accountName, Locale: "en", Timezone: "UTC", Active: true, Status: "active", FeatureFlags: smokeFeatureFlags}).Error; err != nil {
|
||||
return nil, fmt.Errorf("seed account: %w", err)
|
||||
}
|
||||
if err := db.WithContext(ctx).Model(account).Updates(map[string]any{"active": true, "status": "active", "feature_flags": smokeFeatureFlags}).Error; err != nil {
|
||||
return nil, fmt.Errorf("update smoke account flags: %w", err)
|
||||
}
|
||||
|
||||
hashed, err := crypto.HashPassword(adminPassword)
|
||||
if err != nil {
|
||||
|
||||
@@ -17,9 +17,9 @@ Build GoChat as a Go backend that can directly reuse the frontend from `referenc
|
||||
## Current Baseline
|
||||
|
||||
- Plan freeze checkpoint: 2026-06-05 docs-only tracker landing after `13cb750 feat(captain): align task payload persistence`.
|
||||
- Latest implementation checkpoint: this checkpoint, prepared as `test(parity): add api smoke assertions`.
|
||||
- Latest implementation checkpoint: this checkpoint, prepared as `test(parity): add enterprise browser smoke coverage`.
|
||||
- Latest documentation checkpoint before this freeze: `3263ed9 docs: land copilot task execution plan`; this document now carries the active follow-up plan directly.
|
||||
- Worktree status at this implementation checkpoint: B11.1a aligns Captain assistant CRUD/tools/inbox bindings; B11.1b aligns Captain scenarios and custom tools; B11.1c aligns Captain documents, assistant responses, bulk actions, and custom-tool test payloads; B11.2 aligns Copilot thread/message create/list/get/delete payloads, account/user scoping, and no-LLM fallback persistence; B11.3a aligns Captain preferences show/update payloads and account-level model/feature storage; B11.3b aligns Captain playground request/response payloads, account scoping, v2 history handling, and no-LLM fallback; B11.3c adds the fakeable Captain document sync backend gate with disabled, failed, and fake-success states; B11.3d aligns Captain task request/response payloads, no-provider disabled states, follow-up context, suggestion persistence, and Copilot message tool-call key validation; B11.3e aligns Captain stream DTOs/disabled SSE fallbacks and Copilot push-event payload shapes; B12.1 adds the reusable GoChat server/seed entrypoint plus a Meilisearch-first reused Chatwoot frontend smoke harness and report; B12.2a adds API smoke assertions for auth/profile, inbox, conversation/messages, contact/company, widget config/message, and public CSAT. Next active implementation slice is B12.2b live browser navigation against the reused frontend.
|
||||
- Worktree status at this implementation checkpoint: B11.1a aligns Captain assistant CRUD/tools/inbox bindings; B11.1b aligns Captain scenarios and custom tools; B11.1c aligns Captain documents, assistant responses, bulk actions, and custom-tool test payloads; B11.2 aligns Copilot thread/message create/list/get/delete payloads, account/user scoping, and no-LLM fallback persistence; B11.3a aligns Captain preferences show/update payloads and account-level model/feature storage; B11.3b aligns Captain playground request/response payloads, account scoping, v2 history handling, and no-LLM fallback; B11.3c adds the fakeable Captain document sync backend gate with disabled, failed, and fake-success states; B11.3d aligns Captain task request/response payloads, no-provider disabled states, follow-up context, suggestion persistence, and Copilot message tool-call key validation; B11.3e aligns Captain stream DTOs/disabled SSE fallbacks and Copilot push-event payload shapes; B12.1 adds the reusable GoChat server/seed entrypoint plus a Meilisearch-first reused Chatwoot frontend smoke harness and report; B12.2a adds API smoke assertions for auth/profile, inbox, conversation/messages, contact/company, widget config/message, and public CSAT; B12.2b adds a zero-dependency Chrome DevTools browser smoke that loads the reused Chatwoot login and dashboard entrypoints through Vite and checks browser auth/dashboard API requests; B12.3a adds enterprise API smoke assertions for SLA reports/download, CSAT reports/download, automation/macros, audit/custom roles, capacity, Captain, and Copilot; B12.3b adds reused-frontend enterprise browser route navigation for SLA, CSAT, automation, macros, audit logs, custom roles, capacity, Captain, and Copilot request coverage. Next active implementation slice is Phase 5 job parity.
|
||||
- `go test ./...` passes.
|
||||
- Route dump succeeds with `TOTAL: 830` after adding the Chatwoot-compatible applied-SLA index route.
|
||||
- Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`.
|
||||
@@ -64,7 +64,7 @@ Open work after the current checkpoint:
|
||||
|
||||
| Area | Next concrete action | Tracking location | Done boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| B12 smoke | Add browser/API path assertions on top of the B12.1 GoChat plus reused Chatwoot frontend boot and seed path. | `B12 reused frontend verification breakdown` | `docs/parity/frontend_smoke_report.md` records checked pass/fail results and maps failures to slices. |
|
||||
| B12 smoke | Run optional live API/browser/enterprise smoke in a full PostgreSQL/Redis/Meilisearch/Vite environment and convert failures into named slices. | `B12 reused frontend verification breakdown` | `docs/parity/frontend_smoke_report.md` records checked pass/fail results and maps failures to slices. |
|
||||
| Phase 5 jobs | Decide durable worker mechanism and wire deferred SLA/automation/macro/export/template delivery jobs behind idempotent boundaries. | `Phase 5: Background Jobs And Integrations` | Worker tests prove enqueue, retry, idempotency, and fakeable external effects. |
|
||||
| Phase 2/3 drift | Expand tracked route/serializer fixtures when B12 exposes frontend-critical gaps. | `Phase 2`, `Phase 3`, `docs/parity/` | Route parity remains 0 missing for tracked frontend routes; serializers have reference fixtures. |
|
||||
| Phase 6 placeholders | Re-run placeholder audit and burn down any frontend-reachable stub. | `Phase 6: Core Product Placeholder Burn-down` | Stub list has no reused-frontend critical path without a named owner. |
|
||||
@@ -78,9 +78,9 @@ Open work after the current checkpoint:
|
||||
| Phase 2 | Route and controller parity audit | Doing | Ruby/Bundler unavailable, so Chatwoot route extraction currently uses static `routes.rb` fallback |
|
||||
| Phase 3 | Data and serializer parity | Doing | JSON fixture coverage is partial and still endpoint-family based |
|
||||
| Phase 4 | Enterprise feature completion | Doing | B7, B8, B9, B10, and B11 are in Review; B12 reused frontend smoke is the next broad verification gate |
|
||||
| Phase 5 | Background jobs and integrations | Planned | durable worker choice and job parity are open |
|
||||
| Phase 5 | Background jobs and integrations | Doing | durable worker choice and job parity are the next active slice after B12 harness review |
|
||||
| Phase 6 | Core placeholder burn-down | Doing | account/contact/conversation/message/inbox placeholder groups remain broad |
|
||||
| Phase 7 | Verification harness | Doing | B12.1 boot/readiness and B12.2a API assertions exist; live browser navigation and optional live Meilisearch run remain open |
|
||||
| Phase 7 | Verification harness | Review | B12.1 boot/readiness, B12.2a API assertions, B12.2b browser smoke harness, B12.3a enterprise API assertions, and B12.3b enterprise browser route navigation exist; optional live Meilisearch/full-browser runs remain environment-dependent |
|
||||
|
||||
## Tracking Artifacts
|
||||
|
||||
@@ -101,6 +101,9 @@ This ledger records the committed parity checkpoints that future slices should b
|
||||
|
||||
| Commit | Scope | Verification summary | Follow-up state |
|
||||
| --- | --- | --- | --- |
|
||||
| `test(parity): add enterprise browser smoke coverage` | Advances B12.3b with `scripts/parity_frontend_smoke.sh --enterprise-browser-smoke`. The browser driver now supports `--enterprise`, creates route-specific reused Chatwoot dashboard shells, and asserts browser-origin requests for SLA reports, CSAT reports, automation rules, macros, audit logs, custom roles, agent capacity list/edit, Captain settings/assistants, and Copilot thread requests. The smoke seed now enables the Chatwoot enterprise feature flags required by those route guards. | `bash -n scripts/parity_frontend_smoke.sh`; `node --check scripts/parity_frontend_browser_smoke.mjs`; `scripts/parity_frontend_smoke.sh --print`; `scripts/parity_frontend_smoke.sh --check`; `go test ./cmd/gochat -count=1`; full verification recorded in the B12.3b section. Live enterprise browser smoke requires running GoChat and Chatwoot Vite. | Move B12 verification harness to Review; continue Phase 5 job parity and convert any optional live-smoke failures into named slices. |
|
||||
| `test(parity): add enterprise api smoke coverage` | Advances B12.3a with `scripts/parity_frontend_smoke.sh --enterprise-smoke`. The command composes the core API smoke with enterprise API assertions for SLA reports/metrics/download, public and account CSAT reports/metrics/download, automation-rule create/list, macro create/execute, audit-log list, custom-role list, agent-capacity list/users, Captain preferences/assistants, and Copilot thread/message payloads. | `bash -n scripts/parity_frontend_smoke.sh`; `scripts/parity_frontend_smoke.sh --print`; `scripts/parity_frontend_smoke.sh --check`; focused verification recorded in the B12.3a section. Live enterprise smoke requires running GoChat with PostgreSQL/Redis/Meilisearch. | Continue B12.3b with reused-frontend browser navigation for the same enterprise screens. |
|
||||
| `test(parity): add browser smoke harness` | Advances B12.2b with `scripts/parity_frontend_smoke.sh --browser-smoke` and `scripts/parity_frontend_browser_smoke.mjs`. The browser smoke creates temporary Vite-served HTML shells for the reused Chatwoot `v3app` login and `dashboard` entrypoints, drives login through headless Chrome via the DevTools protocol without adding Node dependencies, confirms the Chatwoot auth cookie, and checks dashboard validation/conversation requests. | `bash -n scripts/parity_frontend_smoke.sh`; `node --check scripts/parity_frontend_browser_smoke.mjs`; `scripts/parity_frontend_smoke.sh --print`; `scripts/parity_frontend_smoke.sh --check`; full verification recorded in the B12.2b section. Live browser smoke requires running GoChat and Chatwoot Vite. | Continue B12.3 with enterprise screen browser/API smoke for SLA, CSAT reports, automation/macros, audit/custom roles, capacity, Captain, and Copilot. |
|
||||
| `test(parity): add api smoke assertions` | Advances B12.2 with a repeatable `scripts/parity_frontend_smoke.sh --api-smoke` path that seeds deterministic frontend data, signs in through Chatwoot auth headers, asserts current-user/profile, inbox list, conversation/message list and create, contact/company show, widget config/message, and public CSAT show. The seed now includes company, conversation display ID, and CSAT message IDs for smoke assertions. | `bash -n scripts/parity_frontend_smoke.sh`; `scripts/parity_frontend_smoke.sh --print`; `scripts/parity_frontend_smoke.sh --check`; `go test ./cmd/gochat -count=1`; full verification recorded in the B12.2a section. Live API smoke requires a running DB/backend and is not run in default check mode. | Continue B12.2b with live browser navigation/assertions against the reused Vite frontend; B12.3 enterprise screen smoke remains open. |
|
||||
| `test(parity): add frontend smoke harness` | Completes B12.1 readiness tooling: adds `cmd/gochat` as the reusable server/seed entrypoint, adds `scripts/parity_frontend_smoke.sh` to print/check/boot GoChat plus the reused `reference/chatwoot` Vite frontend, keeps the boot command Meilisearch-first, and writes `docs/parity/frontend_smoke_report.md` with seed credentials, command lines, logs, and the smoke matrix. | `bash -n scripts/parity_frontend_smoke.sh`; `scripts/parity_frontend_smoke.sh --print`; `scripts/parity_frontend_smoke.sh --check`; `go test ./cmd/gochat -count=1`; full verification recorded in the B12.1 section. | Continue B12.2 with browser/API assertions for auth, inbox, conversation, CRM, widget, CSAT, and enterprise screens. |
|
||||
| `feat(captain): align streaming fallbacks` | Completed B11.3e streaming/realtime compatibility: Captain task stream routes now reuse non-stream request DTOs, resolve conversations by account-scoped display ID or legacy ID, guard missing LLM providers with deterministic SSE `error`/`done` events, validate rewrite operations, and keep stream errors frontend-readable. Copilot message nested thread payloads now use Chatwoot `push_event_data` shape and event payload helpers omit REST-only fields. | Focused CaptainTask/Captain/Copilot handler and service tests passed; Copilot/Captain repository tests passed; full verification recorded below. | B11 moves to Review; start B12 reused frontend smoke. |
|
||||
@@ -454,7 +457,8 @@ Upcoming enterprise task boards:
|
||||
| B11 | B11.2 | Align Copilot threads, messages, tasks, preferences, playground/tool-call behavior, and disabled-state feature gates. | Copilot controllers/services/frontend clients under `reference/chatwoot`. | Copilot handler/service tests for persistence, disabled LLM state, and frontend payloads. | Review; thread/message payloads, account/user scoping, assistant scope, and no-LLM fallback are landed; tasks/preferences/tool-call/playground depth remains in B11.3 follow-up |
|
||||
| B11 | B11.3 | Add document sync/embedding/LLM job boundaries where external dependencies are required and finish remaining Copilot task/preference/tool-call/streaming depth. | Captain/Copilot jobs, document services, Copilot controllers/services/frontend clients. | Worker tests or explicit feature-gated fallback tests plus Copilot task/preference/tool-call fixtures. | Doing; B11.3a Captain preferences show/update payloads are landed, while document sync/indexing and remaining Copilot task/tool-call depth remain active |
|
||||
| B12 | B12.1 | Add a repeatable command to run the reused Chatwoot frontend against GoChat. | `reference/chatwoot` frontend boot/auth/API clients. | Smoke command documented and runnable locally. | Done by `test(parity): add frontend smoke harness` |
|
||||
| B12 | B12.2 | Cover login, inbox list/settings, conversation list/detail/message send, contact/company views, widget init/message, public CSAT, SLA/CSAT reports, and enterprise admin screens. | Dashboard route usage and frontend stores/API modules. | Smoke report checked into `docs/parity/` with pass/fail gaps. | Doing; B12.2a API assertion command landed, B12.2b browser navigation pending |
|
||||
| B12 | B12.2 | Cover login, inbox list/settings, conversation list/detail/message send, contact/company views, widget init/message, public CSAT, SLA/CSAT reports, and enterprise admin screens. | Dashboard route usage and frontend stores/API modules. | Smoke report checked into `docs/parity/` with pass/fail gaps. | Review; B12.2a API assertion command and B12.2b browser smoke harness landed; live run is environment-dependent |
|
||||
| B12 | B12.3 | Add enterprise smoke coverage for SLA reports/download, CSAT reports/download, automation/macros, audit/custom roles, capacity, Captain, and Copilot. | Enterprise dashboard clients and `reference/chatwoot` enterprise screens. | Enterprise smoke command records pass/fail and names failed route/serializer owners. | Doing; B12.3a API assertion command landed, browser screen navigation remains open |
|
||||
|
||||
B8 CSAT execution breakdown:
|
||||
|
||||
@@ -1009,8 +1013,8 @@ B12 reused frontend verification breakdown:
|
||||
| Step | Implementation target | Reference source | Required tests | Status |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| B12.1 | Add a repeatable smoke command that boots GoChat plus the reused `reference/chatwoot` frontend without adapters. | Local app boot scripts, Chatwoot dashboard frontend. | Checked command and gap report under `docs/parity/`. | Done by `scripts/parity_frontend_smoke.sh`, `cmd/gochat seed`, and `docs/parity/frontend_smoke_report.md` |
|
||||
| B12.2 | Cover login, current user, inbox list, conversation list/detail, message send, contact/company view, widget init/message, and public CSAT. | Dashboard/widget frontend routes and API clients. | Smoke output records pass/fail and links failed API calls to route/serializer tasks. | Doing; B12.2a API smoke command landed, B12.2b reused-frontend browser navigation pending |
|
||||
| B12.3 | Add enterprise smoke coverage as B8-B11 land: SLA reports, CSAT public/account reports, automation/macros, audit/custom roles, Captain/Copilot. | Enterprise frontend screens and clients. | Smoke output keeps enterprise failures as named follow-up tasks, not hidden browser-only debt. | Todo |
|
||||
| B12.2 | Cover login, current user, inbox list, conversation list/detail, message send, contact/company view, widget init/message, and public CSAT. | Dashboard/widget frontend routes and API clients. | Smoke output records pass/fail and links failed API calls to route/serializer tasks. | Review; API and browser harnesses are repeatable, live execution requires running GoChat/Vite |
|
||||
| B12.3 | Add enterprise smoke coverage as B8-B11 land: SLA reports, CSAT public/account reports, automation/macros, audit/custom roles, Captain/Copilot. | Enterprise frontend screens and clients. | Smoke output keeps enterprise failures as named follow-up tasks, not hidden browser-only debt. | Review; B12.3a API assertions and B12.3b reused-frontend browser route navigation landed, live run is environment-dependent |
|
||||
|
||||
B12 smoke harness contract:
|
||||
|
||||
@@ -1065,6 +1069,76 @@ Optional live API smoke, when PostgreSQL/Redis/Meilisearch and GoChat are runnin
|
||||
scripts/parity_frontend_smoke.sh --api-smoke
|
||||
```
|
||||
|
||||
B12.2b current checkpoint:
|
||||
|
||||
- `scripts/parity_frontend_smoke.sh --browser-smoke` runs the B12.2a API smoke first, verifies the reused Chatwoot Vite frontend is reachable, then launches headless Chrome through a dependency-free DevTools protocol driver.
|
||||
- `scripts/parity_frontend_browser_smoke.mjs` creates temporary Vite-served HTML shells under `reference/chatwoot/tmp/` that inject `window.chatwootConfig.apiHost`, `window.globalConfig`, and the exact Chatwoot `v3app` and `dashboard` entrypoints. It drives the login form, waits for the Chatwoot `cw_d_session_info` cookie, then loads the dashboard route and waits for `auth/validate_token` plus account conversation requests.
|
||||
- Browser captures are written to `.tmp/frontend-smoke/browser-smoke-report.json`; API payload captures remain under `.tmp/frontend-smoke/`.
|
||||
- This makes B12.2 browser navigation repeatable without adding Playwright or frontend adapters. Live execution still requires PostgreSQL, Redis, Meilisearch, GoChat, and Chatwoot Vite to be running.
|
||||
|
||||
B12.2b verification:
|
||||
|
||||
```bash
|
||||
bash -n scripts/parity_frontend_smoke.sh
|
||||
node --check scripts/parity_frontend_browser_smoke.mjs
|
||||
scripts/parity_frontend_smoke.sh --print
|
||||
scripts/parity_frontend_smoke.sh --check
|
||||
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Optional live browser smoke, when PostgreSQL/Redis/Meilisearch, GoChat, and Chatwoot Vite are running:
|
||||
|
||||
```bash
|
||||
scripts/parity_frontend_smoke.sh --browser-smoke
|
||||
```
|
||||
|
||||
B12.3a current checkpoint:
|
||||
|
||||
- `scripts/parity_frontend_smoke.sh --enterprise-smoke` now layers enterprise API assertions on top of the B12.2a core API smoke.
|
||||
- Covered enterprise paths: SLA applied report list/metrics/download, public CSAT submit, account CSAT report list/metrics/download, automation rule create/list, macro create/execute, custom role list, agent capacity policy list/users, audit log list, Captain preferences/assistants, and Copilot thread/message payloads.
|
||||
- Captures are written under `.tmp/frontend-smoke/enterprise_*`, keeping failed enterprise API calls inspectable before browser screen automation is added.
|
||||
- Live execution still requires PostgreSQL, Redis, Meilisearch, and GoChat. It does not require Chatwoot Vite unless B12.3b browser screen navigation is being run.
|
||||
|
||||
B12.3a verification:
|
||||
|
||||
```bash
|
||||
bash -n scripts/parity_frontend_smoke.sh
|
||||
scripts/parity_frontend_smoke.sh --print
|
||||
scripts/parity_frontend_smoke.sh --check
|
||||
```
|
||||
|
||||
Optional live enterprise API smoke, when PostgreSQL/Redis/Meilisearch and GoChat are running:
|
||||
|
||||
```bash
|
||||
scripts/parity_frontend_smoke.sh --enterprise-smoke
|
||||
```
|
||||
|
||||
B12.3b current checkpoint:
|
||||
|
||||
- `scripts/parity_frontend_smoke.sh --enterprise-browser-smoke` now runs the enterprise API smoke, verifies the reused Chatwoot Vite frontend, and launches the same dependency-free Chrome DevTools browser driver in enterprise mode.
|
||||
- `scripts/parity_frontend_browser_smoke.mjs --enterprise` creates route-specific dashboard shells for Chatwoot enterprise routes and asserts browser-origin requests for `/applied_slas`, `/csat_survey_responses`, `/automation_rules`, `/macros`, `/audit_logs`, `/custom_roles`, `/agent_capacity_policies`, `/captain/preferences`, `/captain/assistants`, and `/captain/copilot_threads`.
|
||||
- `cmd/gochat seed` now enables the enterprise feature flags that the reused Chatwoot route guards expect: SLA, reports, automations, macros, audit logs, custom roles, assignment v2, advanced assignment, Captain, Captain v2/tasks/custom tools, CRM, companies, and inbox management.
|
||||
- Browser captures are still written to `.tmp/frontend-smoke/browser-smoke-report.json`. Live execution requires PostgreSQL, Redis, Meilisearch, GoChat, Chatwoot Vite, and Chrome.
|
||||
|
||||
B12.3b verification:
|
||||
|
||||
```bash
|
||||
bash -n scripts/parity_frontend_smoke.sh
|
||||
node --check scripts/parity_frontend_browser_smoke.mjs
|
||||
scripts/parity_frontend_smoke.sh --print
|
||||
scripts/parity_frontend_smoke.sh --check
|
||||
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./cmd/gochat -count=1
|
||||
env TMPDIR=/home/rogee/Projects/gochat/.tmp/test-tmp GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Optional live enterprise browser smoke, when PostgreSQL/Redis/Meilisearch, GoChat, Chatwoot Vite, and Chrome are running:
|
||||
|
||||
```bash
|
||||
scripts/parity_frontend_smoke.sh --enterprise-browser-smoke
|
||||
```
|
||||
|
||||
Hermes plan material now mapped:
|
||||
|
||||
- `.hermes/plans/2025-05-24-global-search-meilisearch.md` maps to Phase 1/B6. The Meilisearch interface, config, documents, indexing hooks, reindex command, payload shape, and live gate are already tracked here. Remaining search work is only future payload gaps discovered by frontend smoke or route expansion.
|
||||
@@ -1489,7 +1563,18 @@ Excluded tracking table:
|
||||
|
||||
## Phase 5: Background Jobs And Integrations
|
||||
|
||||
Status: planned.
|
||||
Status: doing; this is the next active implementation slice after B12 smoke harness review.
|
||||
|
||||
Default implementation decision for tracking: use a PostgreSQL-backed durable job table first, with Redis used only for optional locks/rate limiting where existing code already depends on it. This keeps job state inspectable in the Go backend, avoids adding a new hard runtime beyond the already-required database, and still allows Chatwoot-style retry, scheduling, idempotency, and observability. If a specific reference behavior requires Redis timing semantics, record that under the owning P5 task before implementation.
|
||||
|
||||
Reference job families to keep in scope:
|
||||
|
||||
- Core dispatcher jobs: `reference/chatwoot/app/jobs/event_dispatcher_job.rb`, `action_cable_broadcast_job.rb`, `hook_job.rb`, `webhook_job.rb`, and provider webhook/event jobs.
|
||||
- Conversation/message jobs: `send_reply_job.rb`, `bulk_actions_job.rb`, `macros_execution_job.rb`, `conversations/resolution_job.rb`, `conversations/reopen_snoozed_conversations_job.rb`, `conversations/update_message_status_job.rb`, and activity/mention jobs.
|
||||
- CRM/import/export jobs: `account/contacts_export_job.rb`, company/avatar/label jobs, and migration/backfill jobs that affect frontend-visible data.
|
||||
- Enterprise SLA jobs: `enterprise/app/jobs/sla/trigger_slas_for_accounts_job.rb`, `process_account_applied_slas_job.rb`, and `process_applied_sla_job.rb`.
|
||||
- Captain/Copilot jobs: document schedule/perform/crawl/response-builder jobs, embedding update jobs, conversation response builder, and Copilot response jobs.
|
||||
- Channel template/status jobs: WhatsApp/Twilio template sync, CSAT template delivery, delivery status, and provider-specific outbound/inbound jobs.
|
||||
|
||||
Known hotspots:
|
||||
|
||||
@@ -1501,10 +1586,12 @@ Known hotspots:
|
||||
|
||||
Checklist:
|
||||
|
||||
- [ ] Map Chatwoot jobs/listeners to Go worker responsibilities.
|
||||
- [ ] Implement durable job dispatch for automation, CSAT, notifications, webhooks, and search indexing.
|
||||
- [ ] Add `background_jobs` persistence with job type, payload, queue, status, attempt counters, scheduled/locked timestamps, idempotency key, last error, and completion timestamps.
|
||||
- [ ] Replace `internal/worker/worker.go` placeholder with enqueue, schedule, perform, retry/backoff, dead-letter, and graceful shutdown behavior.
|
||||
- [ ] Map Chatwoot jobs/listeners to Go worker responsibilities and record out-of-scope migration-only jobs explicitly.
|
||||
- [ ] Implement durable job dispatch for automation, CSAT, notifications, webhooks, SLA processing, Captain/Copilot, contact export, macros, and search indexing.
|
||||
- [ ] Add retry and failure logging for external calls.
|
||||
- [ ] Add tests for job enqueueing and idempotency.
|
||||
- [ ] Add tests for enqueueing, uniqueness/idempotency, delayed execution, retries, worker restart pickup, and fakeable external side effects.
|
||||
|
||||
Acceptance:
|
||||
|
||||
@@ -1513,15 +1600,21 @@ Acceptance:
|
||||
|
||||
Tracking table:
|
||||
|
||||
| ID | Task | Current hotspot | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| P5.1 | Choose and wire durable job runner compatible with current Go stack. | `internal/worker/worker.go`, `internal/channel/dispatcher.go` | Todo |
|
||||
| P5.2 | Move search indexing into retryable jobs where Chatwoot uses callbacks/jobs. | search services and worker | Todo |
|
||||
| P5.3 | Implement automation webhook delivery with retry, timeout, and logs. | `internal/automation/action_service.go` | Todo |
|
||||
| P5.4 | Implement email transcript delivery once email infrastructure is available. | `internal/automation/action_service.go` | Todo |
|
||||
| P5.5 | Move CSAT survey send and channel-specific external template delivery into durable jobs. | `internal/automation/csat_survey_listener.go`, `internal/csat/listener.go`, channel send services | Todo |
|
||||
| P5.6 | Complete webhook signature verification for Facebook/WhatsApp/Twilio/provider paths. | `internal/auth/webhook_registry.go`, channel providers | Todo |
|
||||
| P5.7 | Replace placeholder analytics with real report builders/queries. | `internal/service/analytics_service.go`, reporting services | Todo |
|
||||
| ID | Task | Reference source | Current Go hotspot | Done boundary | Status |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| P5.1 | Implement durable worker core and job model. | `reference/chatwoot/app/jobs/application_job.rb`, `mutex_application_job.rb` | `internal/worker/worker.go` | Job table, repository, enqueue API, worker loop, retry/backoff, scheduled jobs, mutex/idempotency keys, dead-letter state, and restart pickup tests exist. | Next |
|
||||
| P5.2 | Route async dispatcher events through durable jobs. | `event_dispatcher_job.rb`, Chatwoot async dispatcher listeners | `internal/dispatch/dispatcher.go`, `internal/channel/dispatcher.go` | Heavy listeners can enqueue durable jobs without changing sync listener behavior; tests cover sync vs async routing and replay. | Todo |
|
||||
| P5.3 | Move Meilisearch indexing and reindex fan-out into retryable jobs. | Meilisearch plan plus Chatwoot callbacks/jobs that index searchable records | search services, contact/company/conversation indexing hooks | Create/update/delete indexing survives handler success, retries on Meilisearch failure, and optional live Meilisearch gate remains green. | Todo |
|
||||
| P5.4 | Queue automation webhook and transcript delivery. | `webhook_job.rb`, automation action execution services | `internal/automation/action_delivery.go`, `internal/automation/action_service.go` | Existing timeout/retry fakeable delivery is invoked by durable jobs; logs preserve attempt metadata and idempotency. | Todo |
|
||||
| P5.5 | Queue delayed automation actions and macro execution. | `trigger_scheduled_items_job.rb`, `macros_execution_job.rb` | automation rule listener, macro service | Delayed actions execute after schedule time, macro execute supports multi-conversation job fan-out, and repeated workers do not duplicate side effects. | Todo |
|
||||
| P5.6 | Queue CSAT survey sends and channel-specific templates. | CSAT listener/services, WhatsApp/Twilio template services/jobs | `internal/automation/csat_survey_listener.go`, `internal/csat/listener.go`, channel send services | Resolve-triggered CSAT send is durable; WhatsApp/Twilio template delivery and failure states are fakeable and observable. | Todo |
|
||||
| P5.7 | Queue SLA account-wide scans and applied-SLA processing. | `enterprise/app/jobs/sla/trigger_slas_for_accounts_job.rb`, `process_account_applied_slas_job.rb`, `process_applied_sla_job.rb` | `internal/service/applied_sla_service.go`, SLA event services | Scheduler enqueues account scans and per-SLA processing; threshold transitions remain idempotent and notification fan-out is retryable. | Todo |
|
||||
| P5.8 | Queue contact export artifact generation and completion notification/email. | `account/contacts_export_job.rb` | contact export service and mailer boundary | Export API returns immediately, artifact generation is durable, completion notification/email is fakeable, and download remains stable after restart. | Todo |
|
||||
| P5.9 | Queue inbound provider webhook processing where Chatwoot defers work. | `webhooks/*_events_job.rb`, `webhooks/*_delivery_job.rb` | webhook handlers, provider services | Provider HTTP ack behavior remains Chatwoot-compatible while persistence/dispatch runs through retryable jobs with signature validation preserved. | Todo |
|
||||
| P5.10 | Queue outbound message delivery and delivery-status updates. | `send_reply_job.rb`, provider delivery/status jobs | message send/channel services, delivery status handler | Outgoing message creation and provider delivery are separated; retries update message/delivery status exactly once. | Todo |
|
||||
| P5.11 | Queue Captain document sync, crawl, response building, embeddings, and Copilot responses. | Captain document/crawl/response/embedding/Copilot jobs | `internal/service/captain_document_service.go`, Captain/Copilot services | Existing fakeable disabled/failure gates run under durable jobs; document statuses and Copilot message persistence survive worker restart. | Todo |
|
||||
| P5.12 | Queue conversation maintenance jobs. | `conversations/resolution_job.rb`, `reopen_snoozed_conversations_job.rb`, `update_message_status_job.rb`, `bulk_actions_job.rb` | conversation service/handlers | Auto-resolution, snooze reopen, status updates, and bulk actions are scheduled/retryable with idempotent tests. | Todo |
|
||||
| P5.13 | Replace placeholder analytics/report builders that need background aggregation. | reporting jobs/services and report controllers | `internal/service/analytics_service.go`, reporting services | Frontend-visible reports no longer use placeholder values; any expensive aggregation is scheduled or cached with freshness rules. | Todo |
|
||||
|
||||
## Phase 6: Core Product Placeholder Burn-down
|
||||
|
||||
@@ -1711,3 +1804,6 @@ Verification milestone gates:
|
||||
- 2026-06-05: B11.3e streaming/realtime checkpoint prepared as `feat(captain): align streaming fallbacks`; Captain stream task routes now share non-stream DTO/account-scope behavior, return deterministic SSE disabled/error states for missing providers and validation errors, and preserve success chunk/done shapes. Copilot message REST payloads now nest thread `push_event_data`, dedicated push payload helpers match Chatwoot event data, and the legacy Copilot SSE route has a no-provider disabled guard. Focused CaptainTask/Captain/Copilot handler/service tests and Copilot/Captain repository tests passed; full verification is recorded in the B11.3e section. B11 moves to Review; next active slice is B12 reused Chatwoot frontend smoke.
|
||||
- 2026-06-05: B12.1 frontend smoke harness checkpoint prepared as `test(parity): add frontend smoke harness`; `cmd/gochat` now provides `serve` and deterministic `seed`, `.gitignore` no longer hides `cmd/gochat`, `scripts/parity_frontend_smoke.sh` can print/check/boot GoChat plus the reused `reference/chatwoot` Vite frontend with Meilisearch-first defaults, and `docs/parity/frontend_smoke_report.md` records commands, seed data, logs, and pending smoke matrix owners. Verification: `bash -n scripts/parity_frontend_smoke.sh`, `scripts/parity_frontend_smoke.sh --print`, `scripts/parity_frontend_smoke.sh --check`, `go test ./cmd/gochat -count=1`, full `go test ./...`, and `git diff --check`. Next slice is B12.2 browser/API path assertions.
|
||||
- 2026-06-05: B12.2a API smoke assertion checkpoint prepared as `test(parity): add api smoke assertions`; the smoke script now has `--api-smoke` for live GoChat API checks covering auth/profile, inbox, conversation/message list and create, contact/company show, widget config/message, and public CSAT show. Seed output now includes company, conversation display ID, and CSAT message identifiers. Default verification keeps `--api-smoke` optional because it needs a running backend/DB; `bash -n`, `--print`, `--check`, `go test ./cmd/gochat`, full `go test ./...`, and `git diff --check` passed locally. Next slice is B12.2b reused-frontend browser navigation.
|
||||
- 2026-06-05: B12.2b browser smoke harness checkpoint prepared as `test(parity): add browser smoke harness`; `scripts/parity_frontend_smoke.sh --browser-smoke` now runs the live API smoke, verifies Chatwoot Vite readiness, and drives the reused Chatwoot login/dashboard entrypoints through headless Chrome DevTools without adding Playwright or frontend adapters. The smoke captures browser requests and console output in `.tmp/frontend-smoke/browser-smoke-report.json`, while default `--check` keeps live browser execution optional because it needs PostgreSQL, Redis, Meilisearch, GoChat, Vite, and Chrome. Verification: `bash -n`, `node --check`, `--print`, `--check`, full `go test ./...`, and `git diff --check`. Next slice is B12.3 enterprise screen smoke.
|
||||
- 2026-06-05: B12.3a enterprise API smoke checkpoint prepared as `test(parity): add enterprise api smoke coverage`; `scripts/parity_frontend_smoke.sh --enterprise-smoke` now composes the core API smoke with enterprise checks for SLA reports/download, CSAT public submit plus reports/download, automation/macros, audit/custom roles, capacity, Captain preferences/assistants, and Copilot thread/message payloads. Default verification keeps the live enterprise run optional because it needs PostgreSQL, Redis, Meilisearch, and GoChat. Verification: `bash -n`, `--print`, and `--check` passed locally. Next slice is B12.3b reused-frontend enterprise browser navigation.
|
||||
- 2026-06-05: B12.3b enterprise browser smoke checkpoint prepared as `test(parity): add enterprise browser smoke coverage`; `scripts/parity_frontend_smoke.sh --enterprise-browser-smoke` now runs enterprise API smoke, verifies Chatwoot Vite, and drives reused Chatwoot enterprise dashboard routes through the Chrome DevTools browser harness. Browser assertions cover SLA, CSAT, automation, macros, audit logs, custom roles, agent capacity, Captain settings/assistants, and Copilot thread request paths. Seed now enables the corresponding enterprise feature flags for route guards. Verification: `bash -n`, `node --check`, `--print`, `--check`, `go test ./cmd/gochat`, full `go test ./...`, and `git diff --check` passed locally. Next slice is Phase 5 job parity.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Frontend Smoke Report
|
||||
|
||||
Updated: 2026-06-05T06:19:13Z
|
||||
Updated: 2026-06-05T07:19:19Z
|
||||
|
||||
## Status
|
||||
|
||||
@@ -18,6 +18,24 @@ scripts/parity_frontend_smoke.sh --boot-only
|
||||
scripts/parity_frontend_smoke.sh --api-smoke
|
||||
```
|
||||
|
||||
## Browser Smoke Command
|
||||
|
||||
```bash
|
||||
scripts/parity_frontend_smoke.sh --browser-smoke
|
||||
```
|
||||
|
||||
## Enterprise Smoke Command
|
||||
|
||||
```bash
|
||||
scripts/parity_frontend_smoke.sh --enterprise-smoke
|
||||
```
|
||||
|
||||
## Enterprise Browser Smoke Command
|
||||
|
||||
```bash
|
||||
scripts/parity_frontend_smoke.sh --enterprise-browser-smoke
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
- URL: http://127.0.0.1:3000
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdtempSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const root = process.env.GOCHAT_ROOT || process.cwd();
|
||||
const chatwootDir = process.env.CHATWOOT_DIR || path.join(root, 'reference/chatwoot');
|
||||
const logDir = process.env.GOCHAT_SMOKE_LOG_DIR || path.join(root, '.tmp/frontend-smoke');
|
||||
const apiHost = process.env.GOCHAT_SMOKE_API_HOST || '127.0.0.1';
|
||||
const apiPort = process.env.GOCHAT_SMOKE_API_PORT || '3000';
|
||||
const frontendHost = process.env.GOCHAT_SMOKE_FRONTEND_HOST || '127.0.0.1';
|
||||
const frontendPort = process.env.GOCHAT_SMOKE_FRONTEND_PORT || '3036';
|
||||
const chromePath = process.env.GOCHAT_SMOKE_CHROME || '/usr/bin/google-chrome';
|
||||
const frontendBaseURL = `http://${frontendHost}:${frontendPort}`;
|
||||
const apiBaseURL = `http://${apiHost}:${apiPort}`;
|
||||
const enterpriseMode = process.argv.includes('--enterprise');
|
||||
|
||||
mkdirSync(logDir, { recursive: true });
|
||||
|
||||
const seed = JSON.parse(readFileSync(path.join(logDir, 'seed.json'), 'utf8'));
|
||||
const report = {
|
||||
started_at: new Date().toISOString(),
|
||||
mode: enterpriseMode ? 'enterprise' : 'core',
|
||||
frontend_base_url: frontendBaseURL,
|
||||
api_base_url: apiBaseURL,
|
||||
account_id: seed.account_id,
|
||||
requests: [],
|
||||
console: [],
|
||||
checks: [],
|
||||
};
|
||||
|
||||
function smokeHTML(entrypoint, route) {
|
||||
const config = {
|
||||
apiHost: apiBaseURL,
|
||||
hostURL: frontendBaseURL,
|
||||
helpCenterURL: '',
|
||||
allowedLoginMethods: ['email'],
|
||||
signupEnabled: 'false',
|
||||
isEnterprise: 'true',
|
||||
isMfaEnabled: 'false',
|
||||
enabledLanguages: [{ iso_639_1_code: 'en', name: 'English' }],
|
||||
helpUrls: {},
|
||||
selectedLocale: 'en',
|
||||
};
|
||||
const globalConfig = {
|
||||
INSTALLATION_NAME: 'GoChat',
|
||||
BRAND_NAME: 'GoChat',
|
||||
LOGO: '/logo.png',
|
||||
LOGO_DARK: '',
|
||||
LOGO_THUMBNAIL: '/logo.png',
|
||||
IS_ENTERPRISE: 'true',
|
||||
DISABLE_USER_PROFILE_UPDATE: 'false',
|
||||
DIRECT_UPLOADS_ENABLED: 'false',
|
||||
MAXIMUM_FILE_UPLOAD_SIZE: '40',
|
||||
ACTIVE_PLATFORM_BANNERS: [],
|
||||
LOGOUT_REDIRECT_LINK: '/app/login',
|
||||
};
|
||||
return `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>GoChat Smoke</title>
|
||||
<script>
|
||||
history.replaceState({}, '', ${JSON.stringify(route)});
|
||||
window.chatwootConfig = ${JSON.stringify(config)};
|
||||
window.globalConfig = ${JSON.stringify(globalConfig)};
|
||||
window.browserConfig = { browser_name: 'chrome' };
|
||||
window.errorLoggingConfig = '';
|
||||
window.analyticsConfig = { token: '' };
|
||||
</script>
|
||||
<script type="module" src="/app/javascript/entrypoints/${entrypoint}.js"></script>
|
||||
</head>
|
||||
<body class="text-slate-600"><div id="app"></div></body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
const tmpHTMLDir = path.join(chatwootDir, 'tmp');
|
||||
mkdirSync(tmpHTMLDir, { recursive: true });
|
||||
|
||||
function writeSmokeShell(name, entrypoint, route) {
|
||||
writeFileSync(path.join(tmpHTMLDir, `${name}.html`), smokeHTML(entrypoint, route));
|
||||
return `${frontendBaseURL}/tmp/${name}.html`;
|
||||
}
|
||||
|
||||
const smokePages = {
|
||||
login: writeSmokeShell('gochat-smoke-login', 'v3app', '/app/login'),
|
||||
dashboard: writeSmokeShell('gochat-smoke-dashboard', 'dashboard', `/app/accounts/${seed.account_id}/dashboard`),
|
||||
};
|
||||
|
||||
const enterprisePages = [
|
||||
{
|
||||
label: 'SLA reports screen',
|
||||
name: 'gochat-smoke-enterprise-sla-reports',
|
||||
route: `/app/accounts/${seed.account_id}/reports/sla`,
|
||||
requests: ['/applied_slas', '/applied_slas/metrics'],
|
||||
},
|
||||
{
|
||||
label: 'CSAT reports screen',
|
||||
name: 'gochat-smoke-enterprise-csat-reports',
|
||||
route: `/app/accounts/${seed.account_id}/reports/csat`,
|
||||
requests: ['/csat_survey_responses', '/csat_survey_responses/metrics'],
|
||||
},
|
||||
{
|
||||
label: 'automation rules screen',
|
||||
name: 'gochat-smoke-enterprise-automation',
|
||||
route: `/app/accounts/${seed.account_id}/settings/automation/list`,
|
||||
requests: ['/automation_rules'],
|
||||
},
|
||||
{
|
||||
label: 'macros screen',
|
||||
name: 'gochat-smoke-enterprise-macros',
|
||||
route: `/app/accounts/${seed.account_id}/settings/macros`,
|
||||
requests: ['/macros'],
|
||||
},
|
||||
{
|
||||
label: 'audit logs screen',
|
||||
name: 'gochat-smoke-enterprise-audit-logs',
|
||||
route: `/app/accounts/${seed.account_id}/settings/audit-logs/list`,
|
||||
requests: ['/audit_logs'],
|
||||
},
|
||||
{
|
||||
label: 'custom roles screen',
|
||||
name: 'gochat-smoke-enterprise-custom-roles',
|
||||
route: `/app/accounts/${seed.account_id}/settings/custom-roles/list`,
|
||||
requests: ['/custom_roles'],
|
||||
},
|
||||
{
|
||||
label: 'agent capacity screen',
|
||||
name: 'gochat-smoke-enterprise-agent-capacity',
|
||||
route: `/app/accounts/${seed.account_id}/settings/assignment-policy/capacity`,
|
||||
requests: ['/agent_capacity_policies'],
|
||||
},
|
||||
{
|
||||
label: 'agent capacity edit screen',
|
||||
name: 'gochat-smoke-enterprise-agent-capacity-edit',
|
||||
route: `/app/accounts/${seed.account_id}/settings/assignment-policy/capacity/edit/${seed.capacity_policy_id}`,
|
||||
requests: [`/agent_capacity_policies/${seed.capacity_policy_id}`, `/agent_capacity_policies/${seed.capacity_policy_id}/users`],
|
||||
},
|
||||
{
|
||||
label: 'Captain settings screen',
|
||||
name: 'gochat-smoke-enterprise-captain-settings',
|
||||
route: `/app/accounts/${seed.account_id}/settings/captain`,
|
||||
requests: ['/captain/preferences'],
|
||||
},
|
||||
{
|
||||
label: 'Captain assistants screen',
|
||||
name: 'gochat-smoke-enterprise-captain-assistants',
|
||||
route: `/app/accounts/${seed.account_id}/captain/captain_assistants_responses_index`,
|
||||
requests: ['/captain/assistants'],
|
||||
},
|
||||
].map(page => ({
|
||||
...page,
|
||||
url: writeSmokeShell(page.name, 'dashboard', page.route),
|
||||
}));
|
||||
|
||||
class CDPPage {
|
||||
constructor(ws, chrome) {
|
||||
this.ws = ws;
|
||||
this.chrome = chrome;
|
||||
this.nextID = 1;
|
||||
this.pending = new Map();
|
||||
this.listeners = new Map();
|
||||
ws.onmessage = event => this.handleMessage(JSON.parse(event.data));
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
if (message.id && this.pending.has(message.id)) {
|
||||
const { resolve, reject } = this.pending.get(message.id);
|
||||
this.pending.delete(message.id);
|
||||
if (message.error) reject(new Error(message.error.message));
|
||||
else resolve(message.result || {});
|
||||
return;
|
||||
}
|
||||
const handlers = this.listeners.get(message.method) || [];
|
||||
handlers.forEach(handler => handler(message.params || {}));
|
||||
}
|
||||
|
||||
send(method, params = {}) {
|
||||
const id = this.nextID++;
|
||||
this.ws.send(JSON.stringify({ id, method, params }));
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pending.set(id, { resolve, reject });
|
||||
setTimeout(() => {
|
||||
if (this.pending.has(id)) {
|
||||
this.pending.delete(id);
|
||||
reject(new Error(`CDP command timed out: ${method}`));
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
|
||||
on(method, handler) {
|
||||
const handlers = this.listeners.get(method) || [];
|
||||
handlers.push(handler);
|
||||
this.listeners.set(method, handlers);
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.send('Page.enable');
|
||||
await this.send('Runtime.enable');
|
||||
await this.send('Network.enable');
|
||||
this.on('Runtime.consoleAPICalled', params => {
|
||||
report.console.push({
|
||||
type: params.type,
|
||||
text: (params.args || []).map(arg => arg.value || arg.description || '').join(' '),
|
||||
});
|
||||
});
|
||||
this.on('Runtime.exceptionThrown', params => {
|
||||
report.console.push({ type: 'exception', text: params.exceptionDetails?.text || 'exception' });
|
||||
});
|
||||
this.on('Network.responseReceived', params => {
|
||||
report.requests.push({
|
||||
url: params.response.url,
|
||||
status: params.response.status,
|
||||
type: params.type,
|
||||
});
|
||||
});
|
||||
this.on('Network.loadingFailed', params => {
|
||||
report.requests.push({ url: params.requestId, status: 0, errorText: params.errorText });
|
||||
});
|
||||
}
|
||||
|
||||
async navigate(url) {
|
||||
const loaded = new Promise(resolve => this.on('Page.loadEventFired', resolve));
|
||||
await this.send('Page.navigate', { url });
|
||||
await loaded;
|
||||
}
|
||||
|
||||
async eval(expression) {
|
||||
const result = await this.send('Runtime.evaluate', {
|
||||
expression,
|
||||
awaitPromise: true,
|
||||
returnByValue: true,
|
||||
});
|
||||
if (result.exceptionDetails) {
|
||||
throw new Error(result.exceptionDetails.text || 'Runtime.evaluate failed');
|
||||
}
|
||||
return result.result?.value;
|
||||
}
|
||||
|
||||
async waitFor(expression, label, timeout = 30000) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
if (await this.eval(expression)) {
|
||||
report.checks.push({ label, status: 'passed' });
|
||||
return;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
}
|
||||
throw new Error(`Timed out waiting for ${label}`);
|
||||
}
|
||||
|
||||
waitForRequest(substring, label, timeout = 30000) {
|
||||
return this.waitFor(
|
||||
`performance.getEntriesByType('resource').some(entry => entry.name.includes(${JSON.stringify(substring)}))`,
|
||||
label,
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
async close() {
|
||||
this.ws.close();
|
||||
this.chrome.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
|
||||
async function launchChrome() {
|
||||
const userDataDir = mkdtempSync(path.join(tmpdir(), 'gochat-chrome-'));
|
||||
const chrome = spawn(chromePath, [
|
||||
'--headless=new',
|
||||
'--disable-gpu',
|
||||
'--no-first-run',
|
||||
'--no-default-browser-check',
|
||||
'--disable-dev-shm-usage',
|
||||
'--remote-debugging-port=0',
|
||||
`--user-data-dir=${userDataDir}`,
|
||||
'about:blank',
|
||||
], { stdio: ['ignore', 'ignore', 'pipe'] });
|
||||
const portFile = path.join(userDataDir, 'DevToolsActivePort');
|
||||
for (let i = 0; i < 80; i += 1) {
|
||||
try {
|
||||
const [port] = readFileSync(portFile, 'utf8').trim().split('\n');
|
||||
const target = await fetch(`http://127.0.0.1:${port}/json/new?about:blank`, { method: 'PUT' }).then(r => r.json());
|
||||
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
||||
await new Promise((resolve, reject) => {
|
||||
ws.onopen = resolve;
|
||||
ws.onerror = reject;
|
||||
});
|
||||
const page = new CDPPage(ws, chrome);
|
||||
await page.init();
|
||||
return page;
|
||||
} catch {
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
chrome.kill('SIGTERM');
|
||||
throw new Error('Chrome DevTools did not become ready');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const page = await launchChrome();
|
||||
try {
|
||||
await page.navigate(smokePages.login);
|
||||
await page.waitFor('!!document.querySelector("input[name=email_address]")', 'login email input visible');
|
||||
await page.eval(`(() => {
|
||||
const email = document.querySelector('input[name=email_address]');
|
||||
const password = document.querySelector('input[name=password]');
|
||||
email.value = ${JSON.stringify(seed.admin_email)};
|
||||
password.value = ${JSON.stringify(seed.admin_password)};
|
||||
email.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
password.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
document.querySelector('[data-testid=submit_button], button[type=submit]').click();
|
||||
return true;
|
||||
})()`);
|
||||
await page.waitFor('document.cookie.includes("cw_d_session_info")', 'login stores Chatwoot auth cookie');
|
||||
await page.waitFor(
|
||||
`performance.getEntriesByType('resource').some(entry => entry.name.includes('/auth/sign_in'))`,
|
||||
'login calls auth/sign_in'
|
||||
);
|
||||
|
||||
await page.navigate(smokePages.dashboard);
|
||||
await page.waitFor('document.querySelector("#app") && document.querySelector("#app").children.length > 0', 'dashboard app mounted');
|
||||
await page.waitForRequest('/auth/validate_token', 'dashboard validates auth token');
|
||||
await page.waitForRequest(`/api/v1/accounts/${seed.account_id}/conversations`, 'dashboard requests conversations');
|
||||
|
||||
if (enterpriseMode) {
|
||||
for (const enterprisePage of enterprisePages) {
|
||||
await page.navigate(enterprisePage.url);
|
||||
await page.waitFor(
|
||||
'document.querySelector("#app") && document.querySelector("#app").children.length > 0',
|
||||
`${enterprisePage.label} app mounted`
|
||||
);
|
||||
for (const request of enterprisePage.requests) {
|
||||
await page.waitForRequest(request, `${enterprisePage.label} requests ${request}`);
|
||||
}
|
||||
}
|
||||
await page.eval(`fetch(${JSON.stringify(`${apiBaseURL}/api/v1/accounts/${seed.account_id}/captain/copilot_threads`)}, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ message: 'B12 enterprise browser copilot smoke', assistant_id: ${Number(seed.captain_assistant_id)}, conversation_id: ${Number(seed.conversation_id)} })
|
||||
}).then(response => response.ok)`);
|
||||
await page.waitForRequest('/captain/copilot_threads', 'browser context requests Copilot threads');
|
||||
}
|
||||
report.finished_at = new Date().toISOString();
|
||||
report.status = 'passed';
|
||||
} catch (error) {
|
||||
report.finished_at = new Date().toISOString();
|
||||
report.status = 'failed';
|
||||
report.error = error.message;
|
||||
throw error;
|
||||
} finally {
|
||||
writeFileSync(path.join(logDir, 'browser-smoke-report.json'), JSON.stringify(report, null, 2));
|
||||
await page.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -19,13 +19,16 @@ KEEP_ALIVE="true"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: scripts/parity_frontend_smoke.sh [--check|--print|--boot-only|--api-smoke] [--no-seed]
|
||||
Usage: scripts/parity_frontend_smoke.sh [--check|--print|--boot-only|--api-smoke|--browser-smoke|--enterprise-smoke|--enterprise-browser-smoke] [--no-seed]
|
||||
|
||||
Modes:
|
||||
--check Validate local prerequisites and write a readiness report.
|
||||
--print Print the exact boot commands without starting processes.
|
||||
--boot-only Start GoChat and Vite, verify /health and frontend HTTP, then exit.
|
||||
--api-smoke Run seed plus Chatwoot-frontend API assertions against a running GoChat backend.
|
||||
--browser-smoke Run API smoke, then drive the reused Chatwoot frontend in headless Chrome.
|
||||
--enterprise-smoke Run API smoke plus enterprise feature API assertions against GoChat.
|
||||
--enterprise-browser-smoke Run enterprise smoke, then navigate reused Chatwoot enterprise screens in headless Chrome.
|
||||
--no-seed Skip seed during --api-smoke and use GOCHAT_SEED_* values already present in DB.
|
||||
|
||||
Environment:
|
||||
@@ -37,6 +40,7 @@ Environment:
|
||||
GOCHAT_SMOKE_SEARCH_ENGINE Search engine for boot smoke. Default: meilisearch
|
||||
GOCHAT_SMOKE_MEILI_HOST Meilisearch URL. Default: http://127.0.0.1:7700
|
||||
GOCHAT_SMOKE_MEILI_API_KEY Meilisearch API key. Default: gochat_dev
|
||||
GOCHAT_SMOKE_CHROME Chrome binary for browser smoke. Default: /usr/bin/google-chrome
|
||||
USAGE
|
||||
}
|
||||
|
||||
@@ -48,6 +52,9 @@ for arg in "$@"; do
|
||||
--print) MODE="print" ;;
|
||||
--boot-only) KEEP_ALIVE="false" ;;
|
||||
--api-smoke) MODE="api-smoke" ;;
|
||||
--browser-smoke) MODE="browser-smoke" ;;
|
||||
--enterprise-smoke) MODE="enterprise-smoke" ;;
|
||||
--enterprise-browser-smoke) MODE="enterprise-browser-smoke" ;;
|
||||
--no-seed) RUN_SEED="false" ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "unknown argument: $arg" >&2; usage; exit 2 ;;
|
||||
@@ -89,6 +96,24 @@ scripts/parity_frontend_smoke.sh --boot-only
|
||||
scripts/parity_frontend_smoke.sh --api-smoke
|
||||
\`\`\`
|
||||
|
||||
## Browser Smoke Command
|
||||
|
||||
\`\`\`bash
|
||||
scripts/parity_frontend_smoke.sh --browser-smoke
|
||||
\`\`\`
|
||||
|
||||
## Enterprise Smoke Command
|
||||
|
||||
\`\`\`bash
|
||||
scripts/parity_frontend_smoke.sh --enterprise-smoke
|
||||
\`\`\`
|
||||
|
||||
## Enterprise Browser Smoke Command
|
||||
|
||||
\`\`\`bash
|
||||
scripts/parity_frontend_smoke.sh --enterprise-browser-smoke
|
||||
\`\`\`
|
||||
|
||||
## Backend
|
||||
|
||||
- URL: http://$API_HOST:$API_PORT
|
||||
@@ -167,6 +192,9 @@ print_commands() {
|
||||
echo "Backend: ${backend_cmd[*]}"
|
||||
echo "Frontend: (cd $CHATWOOT_DIR && ${frontend_cmd[*]})"
|
||||
echo "API smoke: scripts/parity_frontend_smoke.sh --api-smoke"
|
||||
echo "Browser smoke: scripts/parity_frontend_smoke.sh --browser-smoke"
|
||||
echo "Enterprise smoke: scripts/parity_frontend_smoke.sh --enterprise-smoke"
|
||||
echo "Enterprise browser smoke: scripts/parity_frontend_smoke.sh --enterprise-browser-smoke"
|
||||
echo "Report: $REPORT_PATH"
|
||||
}
|
||||
|
||||
@@ -216,7 +244,7 @@ run_api_smoke() {
|
||||
(cd "$ROOT" && env GOCACHE="${GOCACHE:-/tmp/gochat-gocache}" GOMODCACHE="${GOMODCACHE:-/tmp/gochat-gomodcache}" go run ./cmd/gochat seed) >"$seed_file"
|
||||
else
|
||||
cat >"$seed_file" <<SEED
|
||||
{"admin_email":"${GOCHAT_SEED_ADMIN_EMAIL:-admin@gochat.local}","admin_password":"${GOCHAT_SEED_ADMIN_PASSWORD:-changeme}","account_id":"${GOCHAT_SMOKE_ACCOUNT_ID:-1}","inbox_id":"${GOCHAT_SMOKE_INBOX_ID:-1}","contact_id":"${GOCHAT_SMOKE_CONTACT_ID:-1}","company_id":"${GOCHAT_SMOKE_COMPANY_ID:-1}","conversation_display_id":"${GOCHAT_SMOKE_CONVERSATION_DISPLAY_ID:-1}","conversation_uuid":"${GOCHAT_SMOKE_CONVERSATION_UUID:-}"}
|
||||
{"admin_email":"${GOCHAT_SEED_ADMIN_EMAIL:-admin@gochat.local}","admin_password":"${GOCHAT_SEED_ADMIN_PASSWORD:-changeme}","account_id":"${GOCHAT_SMOKE_ACCOUNT_ID:-1}","inbox_id":"${GOCHAT_SMOKE_INBOX_ID:-1}","contact_id":"${GOCHAT_SMOKE_CONTACT_ID:-1}","company_id":"${GOCHAT_SMOKE_COMPANY_ID:-1}","conversation_id":"${GOCHAT_SMOKE_CONVERSATION_ID:-1}","conversation_display_id":"${GOCHAT_SMOKE_CONVERSATION_DISPLAY_ID:-1}","conversation_uuid":"${GOCHAT_SMOKE_CONVERSATION_UUID:-}","sla_policy_id":"${GOCHAT_SMOKE_SLA_POLICY_ID:-1}","custom_role_id":"${GOCHAT_SMOKE_CUSTOM_ROLE_ID:-1}","capacity_policy_id":"${GOCHAT_SMOKE_CAPACITY_POLICY_ID:-1}","captain_assistant_id":"${GOCHAT_SMOKE_CAPTAIN_ASSISTANT_ID:-1}"}
|
||||
SEED
|
||||
fi
|
||||
cp "$seed_file" "$LOG_DIR/seed.json"
|
||||
@@ -326,6 +354,201 @@ SEED
|
||||
echo "api smoke passed; report written to $REPORT_PATH"
|
||||
}
|
||||
|
||||
run_browser_smoke() {
|
||||
run_api_smoke
|
||||
wait_url "http://$FRONTEND_HOST:$FRONTEND_PORT" "Chatwoot Vite"
|
||||
GOCHAT_ROOT="$ROOT" \
|
||||
CHATWOOT_DIR="$CHATWOOT_DIR" \
|
||||
GOCHAT_SMOKE_LOG_DIR="$LOG_DIR" \
|
||||
GOCHAT_SMOKE_API_HOST="$API_HOST" \
|
||||
GOCHAT_SMOKE_API_PORT="$API_PORT" \
|
||||
GOCHAT_SMOKE_FRONTEND_HOST="$FRONTEND_HOST" \
|
||||
GOCHAT_SMOKE_FRONTEND_PORT="$FRONTEND_PORT" \
|
||||
node "$ROOT/scripts/parity_frontend_browser_smoke.mjs"
|
||||
BOOT_BACKEND_RESULT="Passed /health during browser smoke" \
|
||||
BOOT_FRONTEND_RESULT="Passed Vite readiness during browser smoke" \
|
||||
AUTH_PROFILE_RESULT="Passed browser login plus API smoke" \
|
||||
INBOX_RESULT="Passed API smoke" \
|
||||
CONVERSATION_RESULT="Passed dashboard browser request plus API smoke" \
|
||||
CRM_RESULT="Passed contact/company API smoke" \
|
||||
WIDGET_RESULT="Passed API smoke" \
|
||||
CSAT_RESULT="Passed public show API smoke" \
|
||||
ENTERPRISE_RESULT="Pending B12.3 browser/API smoke" \
|
||||
write_report "Browser smoke passed for reused Chatwoot login and dashboard boot; API smoke passed for core frontend paths." "Command run: \`scripts/parity_frontend_smoke.sh --browser-smoke\`. Browser report: \`$LOG_DIR/browser-smoke-report.json\`. B12.3 must add enterprise screen assertions."
|
||||
echo "browser smoke passed; report written to $REPORT_PATH"
|
||||
}
|
||||
|
||||
run_enterprise_api_smoke() {
|
||||
run_api_smoke
|
||||
|
||||
local seed_file account_id inbox_id conversation_id conversation_display_id conversation_uuid custom_role_id capacity_policy_id captain_assistant_id sla_policy_id
|
||||
seed_file="$LOG_DIR/seed.json"
|
||||
account_id="$(json_value "$seed_file" 'data.account_id')"
|
||||
inbox_id="$(json_value "$seed_file" 'data.inbox_id')"
|
||||
conversation_id="$(json_value "$seed_file" 'data.conversation_id')"
|
||||
conversation_display_id="$(json_value "$seed_file" 'data.conversation_display_id || 1')"
|
||||
conversation_uuid="$(json_value "$seed_file" 'data.conversation_uuid || ""')"
|
||||
custom_role_id="$(json_value "$seed_file" 'data.custom_role_id')"
|
||||
capacity_policy_id="$(json_value "$seed_file" 'data.capacity_policy_id')"
|
||||
captain_assistant_id="$(json_value "$seed_file" 'data.captain_assistant_id')"
|
||||
sla_policy_id="$(json_value "$seed_file" 'data.sla_policy_id')"
|
||||
|
||||
local body request_body created_id csv_file
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/applied_slas?page=1&sla_policy_id=$sla_policy_id"
|
||||
cp "$body" "$LOG_DIR/enterprise_applied_slas.json"
|
||||
json_assert "$body" 'Array.isArray(data.payload) && data.meta && Number(data.meta.current_page) === 1' "SLA report list returns Chatwoot payload/meta"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/applied_slas/metrics?sla_policy_id=$sla_policy_id"
|
||||
cp "$body" "$LOG_DIR/enterprise_applied_sla_metrics.json"
|
||||
json_assert "$body" 'typeof data === "object" && data !== null' "SLA report metrics returns JSON"
|
||||
|
||||
csv_file="$(tmp_file)"
|
||||
authed_curl -o "$csv_file" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/applied_slas/download?sla_policy_id=$sla_policy_id"
|
||||
cp "$csv_file" "$LOG_DIR/enterprise_applied_sla_download.csv"
|
||||
if ! grep -q "Conversation ID,SLA policy breached" "$csv_file"; then
|
||||
echo "assertion failed: SLA download returns Chatwoot CSV headers" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "ok: SLA download returns Chatwoot CSV headers"
|
||||
|
||||
if [[ -n "$conversation_uuid" ]]; then
|
||||
request_body="$(tmp_file)"
|
||||
node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ message: { submitted_values: [{ csat_survey_response: { rating: 5, feedback_message: "B12 enterprise smoke" } }] } }));' "$request_body"
|
||||
body="$(tmp_file)"
|
||||
curl -fsS -H "Content-Type: application/json" -X PATCH --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/public/api/v1/csat_survey/$conversation_uuid"
|
||||
cp "$body" "$LOG_DIR/enterprise_public_csat_submit.json"
|
||||
json_assert "$body" 'data.rating === 5 || (data.csat_survey_response && data.csat_survey_response.rating === 5)' "public CSAT submit creates account report data"
|
||||
fi
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/csat_survey_responses?rating=5"
|
||||
cp "$body" "$LOG_DIR/enterprise_csat_responses.json"
|
||||
json_assert "$body" 'Array.isArray(data)' "CSAT report list returns raw array"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/csat_survey_responses/metrics"
|
||||
cp "$body" "$LOG_DIR/enterprise_csat_metrics.json"
|
||||
json_assert "$body" 'Object.prototype.hasOwnProperty.call(data, "total_count") && Object.prototype.hasOwnProperty.call(data, "ratings_count")' "CSAT metrics returns report counters"
|
||||
|
||||
csv_file="$(tmp_file)"
|
||||
authed_curl -o "$csv_file" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/csat_survey_responses/download"
|
||||
cp "$csv_file" "$LOG_DIR/enterprise_csat_download.csv"
|
||||
if ! grep -q "Conversation ID" "$csv_file"; then
|
||||
echo "assertion failed: CSAT download returns CSV headers" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "ok: CSAT download returns CSV headers"
|
||||
|
||||
request_body="$(tmp_file)"
|
||||
node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ name: "B12 Enterprise Smoke Automation", description: "B12.3 smoke", event_name: "conversation_created", active: true, conditions: [], actions: [] }));' "$request_body"
|
||||
body="$(tmp_file)"
|
||||
authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/automation_rules"
|
||||
cp "$body" "$LOG_DIR/enterprise_automation_create.json"
|
||||
json_assert "$body" 'data.name === "B12 Enterprise Smoke Automation" && data.event_name === "conversation_created"' "automation rule create returns raw Chatwoot rule"
|
||||
created_id="$(json_value "$body" 'data.id')"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/automation_rules"
|
||||
cp "$body" "$LOG_DIR/enterprise_automation_list.json"
|
||||
json_assert "$body" 'data.payload && data.payload.some(rule => Number(rule.id) === Number("'$created_id'"))' "automation rule list returns created rule in payload"
|
||||
|
||||
request_body="$(tmp_file)"
|
||||
node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ name: "B12 Enterprise Smoke Macro", visibility: "global", actions: [] }));' "$request_body"
|
||||
body="$(tmp_file)"
|
||||
authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/macros"
|
||||
cp "$body" "$LOG_DIR/enterprise_macro_create.json"
|
||||
json_assert "$body" 'data.payload && data.payload.name === "B12 Enterprise Smoke Macro" && data.payload.visibility === "global"' "macro create returns Chatwoot payload"
|
||||
created_id="$(json_value "$body" 'data.payload.id')"
|
||||
|
||||
request_body="$(tmp_file)"
|
||||
node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ conversation_ids: [Number(process.argv[2])] }));' "$request_body" "$conversation_display_id"
|
||||
body="$(tmp_file)"
|
||||
authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/macros/$created_id/execute"
|
||||
cp "$body" "$LOG_DIR/enterprise_macro_execute.txt"
|
||||
echo "ok: macro execute returns success"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/custom_roles"
|
||||
cp "$body" "$LOG_DIR/enterprise_custom_roles.json"
|
||||
json_assert "$body" 'Array.isArray(data) && data.some(role => Number(role.id) === Number("'$custom_role_id'") && Array.isArray(role.permissions))' "custom role list returns seeded enterprise role"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/agent_capacity_policies"
|
||||
cp "$body" "$LOG_DIR/enterprise_capacity_policies.json"
|
||||
json_assert "$body" 'Array.isArray(data) && data.some(policy => Number(policy.id) === Number("'$capacity_policy_id'"))' "agent capacity list returns seeded policy"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/agent_capacity_policies/$capacity_policy_id/users"
|
||||
cp "$body" "$LOG_DIR/enterprise_capacity_users.json"
|
||||
json_assert "$body" 'Array.isArray(data)' "agent capacity users returns array"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/audit_logs"
|
||||
cp "$body" "$LOG_DIR/enterprise_audit_logs.json"
|
||||
json_assert "$body" 'data.audit_logs && Array.isArray(data.audit_logs) && data.audit_logs.length >= 1 && data.current_page === 1' "audit logs return enterprise Jbuilder list"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/preferences"
|
||||
cp "$body" "$LOG_DIR/enterprise_captain_preferences.json"
|
||||
json_assert "$body" 'data.providers && data.models && data.features' "Captain preferences returns providers/models/features"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/assistants"
|
||||
cp "$body" "$LOG_DIR/enterprise_captain_assistants.json"
|
||||
json_assert "$body" 'data.payload && data.payload.some(assistant => Number(assistant.id) === Number("'$captain_assistant_id'"))' "Captain assistants list returns seeded assistant"
|
||||
|
||||
request_body="$(tmp_file)"
|
||||
node -e 'const fs = require("fs"); fs.writeFileSync(process.argv[1], JSON.stringify({ message: "B12 enterprise copilot smoke", assistant_id: Number(process.argv[2]), conversation_id: Number(process.argv[3]) }));' "$request_body" "$captain_assistant_id" "$conversation_id"
|
||||
body="$(tmp_file)"
|
||||
authed_curl -X POST --data-binary "@$request_body" -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/copilot_threads"
|
||||
cp "$body" "$LOG_DIR/enterprise_copilot_thread_create.json"
|
||||
json_assert "$body" 'data.id && data.assistant && Number(data.assistant.id) === Number("'$captain_assistant_id'")' "Copilot thread create returns Chatwoot thread payload"
|
||||
created_id="$(json_value "$body" 'data.id')"
|
||||
|
||||
body="$(tmp_file)"
|
||||
authed_curl -o "$body" "http://$API_HOST:$API_PORT/api/v1/accounts/$account_id/captain/copilot_threads/$created_id/copilot_messages"
|
||||
cp "$body" "$LOG_DIR/enterprise_copilot_messages.json"
|
||||
json_assert "$body" 'data.payload && data.payload.length >= 1 && data.payload[0].copilot_thread' "Copilot thread messages return nested payload"
|
||||
|
||||
BOOT_BACKEND_RESULT="Passed /health during enterprise smoke" \
|
||||
AUTH_PROFILE_RESULT="Passed API smoke" \
|
||||
INBOX_RESULT="Passed API smoke" \
|
||||
CONVERSATION_RESULT="Passed API smoke" \
|
||||
CRM_RESULT="Passed contact/company API smoke" \
|
||||
WIDGET_RESULT="Passed API smoke" \
|
||||
CSAT_RESULT="Passed public/account/download enterprise smoke" \
|
||||
ENTERPRISE_RESULT="Passed enterprise API smoke for SLA, CSAT, automation, macros, audit, custom roles, capacity, Captain, and Copilot" \
|
||||
write_report "Enterprise API smoke passed for reused Chatwoot enterprise paths; live enterprise browser navigation remains optional." "Command run: \`scripts/parity_frontend_smoke.sh --enterprise-smoke\`. Logs and payload captures are under \`$LOG_DIR\`. Next B12.3 browser work should load the enterprise screens through the reused Vite app."
|
||||
echo "enterprise smoke passed; report written to $REPORT_PATH"
|
||||
}
|
||||
|
||||
run_enterprise_browser_smoke() {
|
||||
run_enterprise_api_smoke
|
||||
wait_url "http://$FRONTEND_HOST:$FRONTEND_PORT" "Chatwoot Vite"
|
||||
GOCHAT_ROOT="$ROOT" \
|
||||
CHATWOOT_DIR="$CHATWOOT_DIR" \
|
||||
GOCHAT_SMOKE_LOG_DIR="$LOG_DIR" \
|
||||
GOCHAT_SMOKE_API_HOST="$API_HOST" \
|
||||
GOCHAT_SMOKE_API_PORT="$API_PORT" \
|
||||
GOCHAT_SMOKE_FRONTEND_HOST="$FRONTEND_HOST" \
|
||||
GOCHAT_SMOKE_FRONTEND_PORT="$FRONTEND_PORT" \
|
||||
node "$ROOT/scripts/parity_frontend_browser_smoke.mjs" --enterprise
|
||||
BOOT_BACKEND_RESULT="Passed /health during enterprise browser smoke" \
|
||||
BOOT_FRONTEND_RESULT="Passed Vite readiness during enterprise browser smoke" \
|
||||
AUTH_PROFILE_RESULT="Passed browser login plus API smoke" \
|
||||
INBOX_RESULT="Passed API smoke" \
|
||||
CONVERSATION_RESULT="Passed dashboard browser request plus API smoke" \
|
||||
CRM_RESULT="Passed contact/company API smoke" \
|
||||
WIDGET_RESULT="Passed API smoke" \
|
||||
CSAT_RESULT="Passed CSAT API/download and browser route requests" \
|
||||
ENTERPRISE_RESULT="Passed enterprise browser route requests for SLA, CSAT, automation, macros, audit, custom roles, capacity, Captain, and Copilot" \
|
||||
write_report "Enterprise browser smoke passed for reused Chatwoot enterprise route requests; enterprise API smoke passed." "Command run: \`scripts/parity_frontend_smoke.sh --enterprise-browser-smoke\`. Browser report: \`$LOG_DIR/browser-smoke-report.json\`."
|
||||
echo "enterprise browser smoke passed; report written to $REPORT_PATH"
|
||||
}
|
||||
|
||||
if [[ "$MODE" == "print" ]]; then
|
||||
print_commands
|
||||
exit 0
|
||||
@@ -345,6 +568,21 @@ if [[ "$MODE" == "api-smoke" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "browser-smoke" ]]; then
|
||||
run_browser_smoke
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "enterprise-smoke" ]]; then
|
||||
run_enterprise_api_smoke
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "enterprise-browser-smoke" ]]; then
|
||||
run_enterprise_browser_smoke
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
trap 'jobs -pr | xargs -r kill 2>/dev/null || true' EXIT
|
||||
|
||||
|
||||
Reference in New Issue
Block a user