Social Posts — End-to-End Test Scenario
Overview
End-to-end test covering the full social post pipeline — from tenant registration through AI-generated copy, DM review, poster design (GPT Image 1.5 via Azure), and final client approval.
Tested via: MCP Playwright browser (real browser, not CLI specs)
Portals: Dashboard (port 3000), DM Portal (port 3002), Manage (port 3001), API (port 3003)
Prerequisites
- All Docker containers healthy (
docker ps) - All dev servers running (see startup procedure memory)
- Queue worker running (
pnpm --filter api worker) - A business profile
.txtfile prepared for upload AZURE_IMAGE_API_KEYset inapps/api/.env(Azure Cognitive Services key for GPT Image 1.5)AZURE_IMAGE_ENDPOINTset inapps/api/.env(full Azure deployment URL incl. api-version)DO_SPACES_ENDPOINTset inapps/api/.env(e.g.https://sgp1.digitaloceanspaces.com) — required by the storage package; missing it causes the designer to fail all slides silentlyANTHROPIC_API_KEYset inapps/api/.env(for social-post-writer Claude call)
Actors
| Actor | Credentials | Portal | Role |
|---|---|---|---|
| Tenant user (Rohan Nair) | rohan@brandvault.in / Password123 | Dashboard (3000) | Company owner |
| Tenant user (Sarah Mitchell) | sarah@technovate.io / Password123 | Dashboard (3000) | Technovate owner (used for validated run Apr 2026) |
| DM reviewer | reviewer@leadmetrics.ai / reviewer | DM Portal (3002) | Content reviewer |
| Super admin | superadmin@leadmetrics.ai / admin | Manage (3001) | Platform admin |
Social Post Status Machine
A social post moves through the following states during the pipeline:
social-post-writer agent runs
↓
[dm_review] ← DM reviewer sees it in DM portal
↓ DM approves
[dm_approved] ← social-post-designer queued
↓ designer starts
[design_pending] ← GPT Image 1.5 generating poster(s)
↓ upload complete
[client_review] ← client sees "Needs Your Approval" in Dashboard
↓ client approves
[client_approved] ← social-publisher worker enqueued (delay if scheduledAt set)
↓ worker picks up
[publishing] ← API call in progress
↓
[published] ← platformPostId + platformPostUrl stored ✅
OR
[publish_failed] ← publishError stored; BullMQ retries up to 3×GBP posts (platform: "google_business_profile") skip the design phase — they go dm_review → client_review directly.
If the client requests changes: status resets to dm_review, clientRejectionNote stored, version incremented on regeneration.
Step 1 — Register New Tenant
Portal: Dashboard → /register
- Navigate to
http://localhost:3000/register - Fill in contact details:
- Country: India
- First Name: Rohan, Last Name: Nair
- Email: rohan@brandvault.in
- Mobile: 9876543210
- Password: Password123
- Click Continue →
- Fill in company details:
- Legal Name: BrandVault Digital Pvt Ltd
- Brand Name: BrandVault
- Click Continue →
- Select State: Karnataka, click Continue →
- Select plan: Basic (₹30,000/month)
- Fill billing address (12, Koramangala 5th Block / Bangalore / 560095)
- Click Complete Sign-Up & Get Started →
Expected: Redirected to /login?registered=1 with “Account created!” message.
Step 2 — Log In as Tenant
Portal: Dashboard → /login
- Enter email: rohan@brandvault.in and password: Password123
- Click Sign in
Expected: Redirected to /dashboard. Onboarding wizard shown — “AI agents are working on your account…”
Step 3 — Fill Brand Assets
Portal: Dashboard → /client-info → Brand Assets tab
Actor: Tenant user (Rohan Nair)
Brand assets are injected directly into the social post designer’s DALL-E 3 prompt. Filling these in before the context agents run ensures every generated poster uses the correct colours, fonts, and visual style.
- Navigate to
http://localhost:3000/client-info?tab=brand_assets - Logos — upload three logo variants (click each dashed upload area):
- Primary Logo — full-colour version (PNG or SVG recommended)
- White / Inverted — white version for dark backgrounds
- Square / Icon — square crop or icon mark
- Brand Colours — set each colour using the colour picker or hex input:
- Primary: e.g.
#7C3AED - Secondary: e.g.
#1E1B4B - Accent: e.g.
#F59E0B - Background: e.g.
#FFFFFF
- Primary: e.g.
- Typography:
- Primary Font: e.g.
Inter - Secondary Font: e.g.
Playfair Display
- Primary Font: e.g.
- Visual Style — click one of the style pills:
minimal/professional/bold/playful/elegant - Design Notes — add freeform guidance for the AI poster generator, e.g.:
Use clean white backgrounds. Avoid drop shadows. Always include the logo in the bottom-right corner. Never use red.
- Click Save Changes
Expected: ”✓ Saved” confirmation on the button. Brand asset data stored and will be available to the social-post-designer agent when it generates posters.
Why this matters: The
designNotesfield is injected verbatim into the GPT Image 1.5 prompt. Without brand assets, the designer agent generates a generic poster with no brand identity. Filling these in before activities run produces on-brand output from the first attempt.
Step 4 — Upload Business Profile to Knowledge Base
Portal: Manage → /tenants/{tenantId}#knowledge
- Log in to manage portal as
superadmin@leadmetrics.ai / admin - Navigate to the new BrandVault tenant’s detail page
- Confirm: status is onboarding, Knowledge Base shows 4 datasets
- Click the Knowledge Base tab
- Click Upload on the
client_docsdataset row - Select a business profile
.txtfile (e.g.brandvault-profile.txt)
Expected: Navigate to /tenants/{id}/datasets/{datasetId}:
- File listed with status indexed
- Chunk count > 0
Note: The inline “Files” expand in the Knowledge Base tab uses a non-admin endpoint that returns 403 for super admin tokens. Always verify uploads via the dataset detail page directly.
Step 5 — Context Agents Run (Automated)
Portal: Dashboard → /client-info → Client Context tab
Worker: Queue worker processes three agents in sequence
After upload, the queue worker automatically enqueues and runs three context agents:
| Agent | Adapter | What it does |
|---|---|---|
client-researcher | codex_local (gpt-5.4) | Researches the business via web search and uploaded documents |
competitor-researcher | gemini_local (auto) | Identifies and researches competitors |
context-file-writer | claude_local (claude-sonnet-4-6) | Synthesises findings into a structured client context profile |
Wait for: The “AI agents are researching your business” banner to disappear.
Check worker log (/tmp/worker.log) to confirm each job completes:
[setup-worker] Job completed queueName: agent__client-researcher
[setup-worker] Job completed queueName: agent__competitor-researcher
[setup-worker] Job completed queueName: agent__context-file-writerExpected: “Context ready for review” banner on /client-info showing:
- Company Overview
- Products & Services
- Target Audience
- Brand Voice & Tone
- Competitors table
- Geographic Focus
- Key Messages
- What NOT to Do
Step 6 — Approve Client Context (HITL)
Portal: Dashboard → /client-info → Client Context tab
Actor: Tenant user (Rohan Nair)
- Review the AI-generated context profile
- Click Approve Context
Expected:
- Strategy Writer agent immediately enqueued
- Navigating to
/strategyshows “Writing your 6-month marketing strategy…” agent status
Step 7 — Strategy Writer Runs (Automated)
Portal: Dashboard → /strategy
Worker: agent__strategy-writer queue
Wait for: “Pending Review” status badge to appear.
Expected: Full 6-month strategy document with sections:
- Executive Summary
- Situation Analysis (SWOT)
- Target Audience Personas
- Marketing Goals & KPIs
- Channel Strategy (including Social Media)
- Content Strategy
- Monthly Execution Roadmap (Month 1–6)
- Budget & Resource Allocation
Step 8 — Approve Strategy (HITL)
Portal: Dashboard → /strategy
Actor: Tenant user (Rohan Nair)
- Click the Pending Review status button
- Select Approved from the dropdown
Expected:
- Deliverable Planner agent immediately enqueued
- Navigating to
/strategy/deliverable-planshows “Building your deliverable plan and goals…” agent status
Step 9 — Deliverable Planner Runs (Automated)
Portal: Dashboard → /strategy/deliverable-plan
Worker: agent__deliverable-planner queue
Wait for: “Approve Plan” button to appear.
Expected: Deliverable plan rendered showing:
- Goals list with linked deliverable types (should include
social_postas a type) - Monthly Deliverables section with quantity per type (e.g. 8 social posts/month)
- Planning Notes
Key check: Confirm
social_postdeliverable type is included in the plan. If the strategy did not include social media, the social post deliverable type will not appear.
Step 10 — Approve Deliverable Plan (HITL)
Portal: Dashboard → /strategy/deliverable-plan
Actor: Tenant user (Rohan Nair)
- Click Approve Plan
Expected: Redirected to /deliverables showing the approved deliverable types for the current month, including social posts.
Step 11 — Activity Planner Runs (Automated)
Worker: agent__activity-planner queue
After deliverable plan approval, the activity planner agent runs and creates individual Activity rows for each deliverable unit in the current period. This includes:
- One activity per blog post to be written
- One activity per social post to be written (one per platform per post)
Confirm: Check /deliverables — each deliverable type shows activities queued.
Step 12 — Blog Writer Agent Runs (Automated)
Worker: agent__blog-writer queue
The blog writer agent is enqueued for each blog post activity. It generates the blog draft and creates a BlogPost row with status dm_review.
Note: Blog posts must go through DM review → client approval before social post activities are unblocked in some pipeline configurations. In the default pipeline, blog and social post activities run independently in parallel.
Step 13 — Social Post Writer Agent Runs (Automated)
Worker: agent__social-post-writer queue
The social post writer agent picks up each social post activity. It calls Claude to generate platform-native copy and creates a SocialPost row with status dm_review.
Generated fields per post:
platform— e.g.instagram,linkedinbodyText— main post copyhashtags— array of relevant hashtagsengagementHook— attention-grabbing opening linecharacterCount— validated against platform limitsplatformFormat— e.g.reel,carousel,postaltText— accessibility description for the poster imagecontentType— e.g.educational,promotional
Activity status → awaiting_approval
Notification sent to DM reviewers
Step 14 — DM Reviews Social Post Copy
Portal: DM Portal → /social
Actor: Reviewer (reviewer@leadmetrics.ai / reviewer)
- Log in to DM portal at
http://localhost:3002 - Select BrandVault from the tenant sidebar switcher
- Navigate to Social in the sidebar
- Confirm the post appears with status badge Needs DM Review (amber)
- Click the post row to open the detail view (
/social/{id})
Expected detail view shows:
- Platform label and icon
- Status badge: “Needs DM Review”
- Engagement hook
- Full body text in platform-native preview container
- Hashtags
- Character count bar (green if within limit)
- Alt text
- “No image yet” placeholder (poster not generated yet)
- Approve and Reject action buttons
Step 15 — DM Approves Social Post Copy
Portal: DM Portal → /social/{id}
Actor: Reviewer
- Review the copy carefully
- Click Approve Copy button in the sticky footer
Playwright note: The sticky footer position can prevent a normal
click()from registering. If the button click produces no network request, usebrowser_evaluateto invoke it directly:document.querySelector('button[...approve...]').click()or call the API route directly viafetch('/api/dm/social/{id}/dm-approve', { method: 'POST' }).
Expected:
SocialPost.status→dm_approvedSocialPost.dmApprovedByset to reviewer’s nameSocialPost.dmApprovedAtset to current timestampsocial-post-designerjob enqueued onagent__social-post-designerqueue
Alternative — Reject flow:
- Click Reject
- Enter feedback note (e.g. “Make the hook more punchy, use a question format”)
- Click Send Feedback
- Expected:
SocialPost.status→dm_review,dmRejectionNotesaved,social-post-writerre-queued withwakeReason: "rejection"— worker re-generates with feedback, increments version number
Step 16 — Social Post Designer Runs (Automated)
Worker: agent__social-post-designer queue
Provider: GPT Image 1.5 via Azure OpenAI
After DM approval, the social post designer agent:
- Sets
SocialPost.status→design_pending - Reads approved copy, brand assets (colours, logo, designNotes), and platform dimensions
- Builds an image prompt and calls Azure GPT Image 1.5 for each slide
- Overlays the tenant logo (if
BrandAssets.logoUrlis set) - Uploads each slide to DigitalOcean Spaces, creates a
Mediarow linked to the post - Updates
SocialPost.status→client_review - Syncs
Deliverable.status→needs_approval - Sends an in-app notification to the tenant user
Expected in worker log:
[social-post-designer-worker] Social post designer job started
[social-post-designer-worker] Slide uploaded slideIndex: 0 mediaId: ...
[social-post-designer-worker] Social post designer completed — client_review
[social-post-designer-worker] Social post designer job completedFailure mode: If all slides fail (e.g. missing
DO_SPACES_ENDPOINT), the worker resetsSocialPost.statusback todm_reviewand throws. The BullMQ job lands in the failed queue. To retry:queue.clean(0, 100, "failed"), remove the stale job by ID, then re-enqueue.
Step 17 — Client Reviews Social Post
Portal: Dashboard → /social
Actor: Tenant user (Rohan Nair)
- Log in to Dashboard at
http://localhost:3000 - Navigate to Social Posts in the sidebar
- Confirm the post appears with status Needs Your Approval (violet badge)
- A count badge in the header shows “N need your approval”
- Click the post row to open
/social/{id}
Expected detail view shows:
- “This post needs your approval” banner (violet)
- Poster Image panel (left): generated DALL-E 3 image
- Post Copy panel (right): platform-native preview with body text, hashtags, engagement hook
- Character count bar
- Accessibility alt text
- Approve Post button (green) and Request Changes button
Step 18 — Client Approves Social Post
Portal: Dashboard → /social/{id}
Actor: Tenant user (Rohan Nair)
- Review both the poster image and the post copy
- Click Approve Post
Expected:
- “Post approved! Redirecting…” confirmation message (emerald)
SocialPost.status→client_approvedSocialPost.clientApprovedByset to tenant user’s nameSocialPost.clientApprovedAtset to current timestamp- Linked
Deliverable.status→approved - Post body auto-ingested into
published_contentKB dataset (fire-and-forget) social-publisherjob enqueued onagent__social-publisherqueue- Redirected to
/sociallist
Step 18b — Social Publisher Worker Runs (Automated)
Worker: agent__social-publisher (in apps/servers/agents)
Queue: agent__social-publisher
Dedup key: social-publisher__{socialPostId}
After client approval, the publisher worker:
- Sets
SocialPost.status→publishing - Resolves the connected channel credentials for the post’s platform
- Calls the platform API:
- Facebook →
FacebookService.createPagePost()using page token fromsubChannelInfo.tokenInfo.accessToken - Instagram → decrypts
tokenInfo; callscreateCarouselPost()(multi-image) orcreateImagePost()(single) - LinkedIn → decrypts
tokenInfo; strips URN prefix fromsubChannelInfo.id; callsLinkedInService.createPost() - GBP → decrypts
tokenInfo; callsGoogleBusinessProfileService.createPost()atsubChannelInfo.id(locationName)
- Facebook →
- Sets
SocialPost.status→published, storesplatformPostId,platformPostUrl,publishedAt
Expected in worker log:
[social-publisher-worker] Publishing social post {id} to facebook
[social-publisher-worker] Successfully published {id} → platformPostId: {pageId}_{postId}Expected on Dashboard /social/{id} after refresh:
- Status badge changes to Published (emerald)
- “Published to Facebook on 26 Apr” message
- “View live post on Facebook” link (opens the actual post in a new tab)
Expected on DM Portal /social/{id}:
- Green panel: “Published to [Platform] on [date]” with live post link
Step 18c — Verify Publish Failure Path (Optional)
To test the failure path:
- In the DB, corrupt a connected channel’s
tokenInfofor the relevant platform:UPDATE connected_channel SET "tokenInfo" = 'invalid' WHERE id = '{channelId}'; - Approve a social post for that platform
- The publisher worker will fail; after 3 retry attempts (60s backoff each):
SocialPost.status→publish_failedSocialPost.publishErrorset to the error messageSocialPost.publishAttempts= 3
Expected on Dashboard /social/{id}:
- Status badge: Publish Failed (red)
- Red panel: “Publishing failed after 3 attempts. The team has been notified.”
- Error detail box showing the raw error message
Expected on DM Portal /social/{id}:
- Red panel showing error message and attempt count
Restore: Reset the token from the working backup or reconnect the channel.
Step 19 — Client Requests Changes (Alternative Path)
Portal: Dashboard → /social/{id}
Actor: Tenant user (Rohan Nair)
If the client is not happy with the poster or copy:
- Click Request Changes
- Enter feedback in the modal (e.g. “The poster colours don’t match our brand — use blue and white instead”)
- Click Send Feedback
Expected:
SocialPost.status→dm_review(reset for DM to re-review)SocialPost.clientRejectionNotesaved with feedbackSocialPost.versionincremented (e.g. v1 → v2)social-post-writerre-queued withwakeReason: "rejection"andreviewerFeedback- Worker regenerates copy using the feedback note
- After re-generation, post goes back through the full DM → designer → client cycle
On the re-review detail page:
A “Your previous feedback (this post was revised)” amber banner shows the client’s original note.
Step 20 — Verify Activity Log
Portal: Dashboard → /activities → Activity Log tab
Actor: Tenant user (Rohan Nair)
Expected entries (most recent first, from Activity Log tab):
| Event | Actor |
|---|---|
| Social post copy generated (awaiting DM review) | social-post-writer |
| Blog post generated (awaiting DM review) | blog-writer |
| Activity pipeline created — N activities planned | activity-planner |
| Deliverable plan created — N goals, N deliverable types | deliverable-planner |
| Strategy status changed to Approved | Tenant user |
| Marketing strategy v1 generated | strategy-writer |
| Client context file generated | context-file-writer |
Known gap: Client approval and DM approval actions are not written to the AuditLog —
clientApproveSocialPostand the/dm/v1/social/:id/dm-approveendpoint do not callwriteAuditLog. These events will not appear in the Activity Log tab. Verify approval state directly via the API or DB instead.
Step 21 — Verify Social Post in DM Portal
Portal: DM Portal → /social
Actor: Reviewer
- Navigate to Social with BrandVault selected
- Find the approved post in the list
Expected:
- Status badge: Client Approved (emerald)
- Poster thumbnail visible in the image column
dmApprovedAtandclientApprovedAttimestamps populated on the detail view
Sample Test Data
Technovate Solutions (validated run — April 2026)
- Tenant ID:
cmnnz8dl20000w1kk0uo36ayd - User: Sarah Mitchell / sarah@technovate.io / Password123
- Location: Mumbai, Maharashtra
- Validated post: Instagram “KYC. AML. Fraud detection.” (
SocialPost.id: JctESxprYzd5I3G5rTst-) - Final status:
client_approved— clientApprovedBy: Sarah Mitchell, clientApprovedAt: 2026-04-07T08:42:43Z
BrandVault Digital (template — fill in after run)
- Tenant ID: (set after registration —
cmnobc1qz0006w1hsda86n8zmfor Apr 2026 test run) - User: Rohan Nair / rohan2@brandvault.in / Password123
- Location: Bangalore, Karnataka
- Profile file:
brandvault-profile.txt(at repo root) - Expected deliverable types: blog_post, social_post (Instagram + LinkedIn + Facebook)
- Social post platforms: instagram, linkedin, facebook
- Note: BrandVault’s social posts were not tested end-to-end in the Apr 2026 run (tenant registered after connectedChannels fix; posts queued but not yet reviewed)
Known Issues / Gotchas
| Issue | Detail |
|---|---|
DO_SPACES_ENDPOINT missing causes designer to silently fail | The @leadmetrics/storage package requires DO_SPACES_ENDPOINT (e.g. https://sgp1.digitaloceanspaces.com) in addition to KEY/SECRET/BUCKET. If missing, all slides throw, the worker resets the post back to dm_review, and the BullMQ job lands in the failed queue. Add to apps/api/.env and restart the worker. |
social-post-designer requires Azure Image env vars | GPT Image 1.5 call will fail if AZURE_IMAGE_API_KEY or AZURE_IMAGE_ENDPOINT are missing. Check apps/api/.env before testing. |
| Social posts included on all plans | connectedChannels is now queried from the real DB (ConnectedChannel table). Blogs are always included. The deliverable planner only generates social post activities for platforms the tenant has actually connected. |
| Publisher not called if channel not connected | The publisher worker looks up the tenant’s ConnectedChannel by platform type. If no connected channel exists for that platform, publishing fails with “No connected channel found”. Connect the channel first. |
| LinkedIn URN strip required | ConnectedChannel.subChannelInfo.id stores the full URN "urn:li:organization:123456". The publisher strips the numeric ID with .split(":").pop(). If subChannelInfo.id is stored differently, the LinkedIn API call will fail. |
| Facebook page token is in subChannelInfo, not tokenInfo | For Facebook, the page access token is stored in subChannelInfo.tokenInfo.accessToken (plain JSON, not encrypted). The user token in tokenInfo is encrypted but is NOT used for publishing — only the page token is. |
| Instagram requires at least one media item | Instagram publishing fails if SocialPost.medias is empty. The designer must have run successfully before publishing. |
| tsx watch restarts worker if files are edited | Editing any watched source file while an agent job is running will stall the job. Do not edit source files while agents are running. |
| BullMQ stale failed jobs block re-enqueue | If a job is in the failed state, adding a new job with the same jobId is a no-op. Fix: await queue.clean(0, 100, "failed"); const old = await queue.getJob(id); await old?.remove(); then re-enqueue. |
| Playwright sticky footer click may not register | The “Approve Copy” button in the DM portal detail view sits in a sticky bottom bar. Playwright’s click() may not trigger the React handler. Use browser_evaluate to call fetch('/api/dm/social/{id}/dm-approve', { method: 'POST' }) directly. |
| Client and DM approval not logged in Activity Log | clientApproveSocialPost and POST /dm/v1/social/:id/dm-approve do not call writeAuditLog. These events won’t appear in the Activity Log tab — verify via API or DB. |
client_context status stuck at generating | If competitor-researcher stalled, context-file-writer was never queued. Use the retrigger endpoint: POST /admin/v1/tenants/:tenantId/retrigger-setup. |
SocialPost.status does not advance to client_review until designer completes | After DM approval the post shows dm_approved → design_pending until GPT Image 1.5 generates and uploads the image. The client approval action is only available at client_review. |
| Client approval also updates the linked Deliverable | clientApproveSocialPost server action must call updateMany on the linked Deliverable row. If a Deliverable stays in pending after client approval, check actions.ts. |
| Manage portal session expiry | JWT session expires mid-flow. Re-login with superadmin@leadmetrics.ai / admin. |
Social posts not visible in Dashboard if no DeliverablePeriod | The /social page queries by tenantId; no period required. But the link in the sidebar may be hidden if the plan is not approved yet. |
Full Pipeline Summary
Register tenant
↓
Upload business profile (Manage portal)
↓
client-researcher runs (codex_local / gpt-5.4)
↓
competitor-researcher runs (gemini_local / auto)
↓
context-file-writer runs (claude_local)
↓
Client Context status → context_ready
↓
[HITL] Tenant approves context (Dashboard /client-info)
↓
strategy-writer runs (claude_local)
↓
[HITL] Tenant approves strategy (Dashboard /strategy)
↓
deliverable-planner runs (claude_local)
↓
[HITL] Tenant approves deliverable plan (Dashboard /strategy/deliverable-plan)
↓
activity-planner runs — creates Activity rows for blog + social posts
↓
blog-writer runs → BlogPost (dm_review) → [DM reviews] → [Client approves]
↓
social-post-writer runs → SocialPost (dm_review)
↓
[HITL-DM] Reviewer approves copy (DM Portal /social/{id})
↓
social-post-designer runs → GPT Image 1.5 poster → SocialPost (design_pending → client_review)
↓
[HITL-Client] Tenant approves post + poster (Dashboard /social/{id})
↓
SocialPost (client_approved) · Deliverable (approved)
↓
social-publisher-worker runs → calls Facebook/Instagram/LinkedIn/GBP API
↓
SocialPost (published) · platformPostUrl set ✅
OR
SocialPost (publish_failed) · publishError set ❌ (retried 3× automatically)