Skip to Content

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 .txt file prepared for upload
  • AZURE_IMAGE_API_KEY set in apps/api/.env (Azure Cognitive Services key for GPT Image 1.5)
  • AZURE_IMAGE_ENDPOINT set in apps/api/.env (full Azure deployment URL incl. api-version)
  • DO_SPACES_ENDPOINT set in apps/api/.env (e.g. https://sgp1.digitaloceanspaces.com) — required by the storage package; missing it causes the designer to fail all slides silently
  • ANTHROPIC_API_KEY set in apps/api/.env (for social-post-writer Claude call)

Actors

ActorCredentialsPortalRole
Tenant user (Rohan Nair)rohan@brandvault.in / Password123Dashboard (3000)Company owner
Tenant user (Sarah Mitchell)sarah@technovate.io / Password123Dashboard (3000)Technovate owner (used for validated run Apr 2026)
DM reviewerreviewer@leadmetrics.ai / reviewerDM Portal (3002)Content reviewer
Super adminsuperadmin@leadmetrics.ai / adminManage (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_reviewclient_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

  1. Navigate to http://localhost:3000/register
  2. Fill in contact details:
    • Country: India
    • First Name: Rohan, Last Name: Nair
    • Email: rohan@brandvault.in
    • Mobile: 9876543210
    • Password: Password123
  3. Click Continue →
  4. Fill in company details:
    • Legal Name: BrandVault Digital Pvt Ltd
    • Brand Name: BrandVault
  5. Click Continue →
  6. Select State: Karnataka, click Continue →
  7. Select plan: Basic (₹30,000/month)
  8. Fill billing address (12, Koramangala 5th Block / Bangalore / 560095)
  9. 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

  1. Enter email: rohan@brandvault.in and password: Password123
  2. 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.

  1. Navigate to http://localhost:3000/client-info?tab=brand_assets
  2. 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
  3. 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
  4. Typography:
    • Primary Font: e.g. Inter
    • Secondary Font: e.g. Playfair Display
  5. Visual Style — click one of the style pills: minimal / professional / bold / playful / elegant
  6. 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.

  7. 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 designNotes field 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

  1. Log in to manage portal as superadmin@leadmetrics.ai / admin
  2. Navigate to the new BrandVault tenant’s detail page
  3. Confirm: status is onboarding, Knowledge Base shows 4 datasets
  4. Click the Knowledge Base tab
  5. Click Upload on the client_docs dataset row
  6. Select a business profile .txt file (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:

AgentAdapterWhat it does
client-researchercodex_local (gpt-5.4)Researches the business via web search and uploaded documents
competitor-researchergemini_local (auto)Identifies and researches competitors
context-file-writerclaude_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-writer

Expected: “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)

  1. Review the AI-generated context profile
  2. Click Approve Context

Expected:

  • Strategy Writer agent immediately enqueued
  • Navigating to /strategy shows “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)

  1. Click the Pending Review status button
  2. Select Approved from the dropdown

Expected:

  • Deliverable Planner agent immediately enqueued
  • Navigating to /strategy/deliverable-plan shows “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_post as a type)
  • Monthly Deliverables section with quantity per type (e.g. 8 social posts/month)
  • Planning Notes

Key check: Confirm social_post deliverable 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)

  1. 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, linkedin
  • bodyText — main post copy
  • hashtags — array of relevant hashtags
  • engagementHook — attention-grabbing opening line
  • characterCount — validated against platform limits
  • platformFormat — e.g. reel, carousel, post
  • altText — accessibility description for the poster image
  • contentType — e.g. educational, promotional

Activity statusawaiting_approval
Notification sent to DM reviewers


Step 14 — DM Reviews Social Post Copy

Portal: DM Portal → /social
Actor: Reviewer (reviewer@leadmetrics.ai / reviewer)

  1. Log in to DM portal at http://localhost:3002
  2. Select BrandVault from the tenant sidebar switcher
  3. Navigate to Social in the sidebar
  4. Confirm the post appears with status badge Needs DM Review (amber)
  5. 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

  1. Review the copy carefully
  2. 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, use browser_evaluate to invoke it directly: document.querySelector('button[...approve...]').click() or call the API route directly via fetch('/api/dm/social/{id}/dm-approve', { method: 'POST' }).

Expected:

  • SocialPost.statusdm_approved
  • SocialPost.dmApprovedBy set to reviewer’s name
  • SocialPost.dmApprovedAt set to current timestamp
  • social-post-designer job enqueued on agent__social-post-designer queue

Alternative — Reject flow:

  1. Click Reject
  2. Enter feedback note (e.g. “Make the hook more punchy, use a question format”)
  3. Click Send Feedback
  4. Expected: SocialPost.statusdm_review, dmRejectionNote saved, social-post-writer re-queued with wakeReason: "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:

  1. Sets SocialPost.statusdesign_pending
  2. Reads approved copy, brand assets (colours, logo, designNotes), and platform dimensions
  3. Builds an image prompt and calls Azure GPT Image 1.5 for each slide
  4. Overlays the tenant logo (if BrandAssets.logoUrl is set)
  5. Uploads each slide to DigitalOcean Spaces, creates a Media row linked to the post
  6. Updates SocialPost.statusclient_review
  7. Syncs Deliverable.statusneeds_approval
  8. 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 completed

Failure mode: If all slides fail (e.g. missing DO_SPACES_ENDPOINT), the worker resets SocialPost.status back to dm_review and 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)

  1. Log in to Dashboard at http://localhost:3000
  2. Navigate to Social Posts in the sidebar
  3. Confirm the post appears with status Needs Your Approval (violet badge)
  4. A count badge in the header shows “N need your approval”
  5. 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)

  1. Review both the poster image and the post copy
  2. Click Approve Post

Expected:

  • “Post approved! Redirecting…” confirmation message (emerald)
  • SocialPost.statusclient_approved
  • SocialPost.clientApprovedBy set to tenant user’s name
  • SocialPost.clientApprovedAt set to current timestamp
  • Linked Deliverable.statusapproved
  • Post body auto-ingested into published_content KB dataset (fire-and-forget)
  • social-publisher job enqueued on agent__social-publisher queue
  • Redirected to /social list

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:

  1. Sets SocialPost.statuspublishing
  2. Resolves the connected channel credentials for the post’s platform
  3. Calls the platform API:
    • FacebookFacebookService.createPagePost() using page token from subChannelInfo.tokenInfo.accessToken
    • Instagram → decrypts tokenInfo; calls createCarouselPost() (multi-image) or createImagePost() (single)
    • LinkedIn → decrypts tokenInfo; strips URN prefix from subChannelInfo.id; calls LinkedInService.createPost()
    • GBP → decrypts tokenInfo; calls GoogleBusinessProfileService.createPost() at subChannelInfo.id (locationName)
  4. Sets SocialPost.statuspublished, stores platformPostId, 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:

  1. In the DB, corrupt a connected channel’s tokenInfo for the relevant platform:
    UPDATE connected_channel SET "tokenInfo" = 'invalid' WHERE id = '{channelId}';
  2. Approve a social post for that platform
  3. The publisher worker will fail; after 3 retry attempts (60s backoff each):
    • SocialPost.statuspublish_failed
    • SocialPost.publishError set to the error message
    • SocialPost.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:

  1. Click Request Changes
  2. Enter feedback in the modal (e.g. “The poster colours don’t match our brand — use blue and white instead”)
  3. Click Send Feedback

Expected:

  • SocialPost.statusdm_review (reset for DM to re-review)
  • SocialPost.clientRejectionNote saved with feedback
  • SocialPost.version incremented (e.g. v1 → v2)
  • social-post-writer re-queued with wakeReason: "rejection" and reviewerFeedback
  • 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):

EventActor
Social post copy generated (awaiting DM review)social-post-writer
Blog post generated (awaiting DM review)blog-writer
Activity pipeline created — N activities plannedactivity-planner
Deliverable plan created — N goals, N deliverable typesdeliverable-planner
Strategy status changed to ApprovedTenant user
Marketing strategy v1 generatedstrategy-writer
Client context file generatedcontext-file-writer

Known gap: Client approval and DM approval actions are not written to the AuditLog — clientApproveSocialPost and the /dm/v1/social/:id/dm-approve endpoint do not call writeAuditLog. 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

  1. Navigate to Social with BrandVault selected
  2. Find the approved post in the list

Expected:

  • Status badge: Client Approved (emerald)
  • Poster thumbnail visible in the image column
  • dmApprovedAt and clientApprovedAt timestamps 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 — cmnobc1qz0006w1hsda86n8zm for 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

IssueDetail
DO_SPACES_ENDPOINT missing causes designer to silently failThe @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 varsGPT 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 plansconnectedChannels 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 connectedThe 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 requiredConnectedChannel.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 tokenInfoFor 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 itemInstagram publishing fails if SocialPost.medias is empty. The designer must have run successfully before publishing.
tsx watch restarts worker if files are editedEditing 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-enqueueIf 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 registerThe “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 LogclientApproveSocialPost 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 generatingIf 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 completesAfter DM approval the post shows dm_approveddesign_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 DeliverableclientApproveSocialPost 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 expiryJWT session expires mid-flow. Re-login with superadmin@leadmetrics.ai / admin.
Social posts not visible in Dashboard if no DeliverablePeriodThe /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)

© 2026 Leadmetrics — Internal use only