feat: add Xquik social posting service#18
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
Reviewer's GuideIntroduces a new Xquik-based social posting service wired into the existing social-posting module, including request/response mapping to the AgentOS social platform result model and focused tests using stubbed fetch calls. Sequence diagram for XquikSocialPostingService.publishPost flowsequenceDiagram
participant Caller
participant XquikSocialPostingService
participant SocialAbstractService
participant XquikAPI
Caller->>XquikSocialPostingService: publishPost(post, options)
XquikSocialPostingService->>XquikSocialPostingService: publish(input, options)
XquikSocialPostingService->>XquikSocialPostingService: createRequestBody(input)
XquikSocialPostingService->>SocialAbstractService: fetchJson(url, requestInit, options)
SocialAbstractService->>XquikAPI: POST /api/v1/x/tweets
XquikAPI-->>SocialAbstractService: XquikCreateTweetResponse
SocialAbstractService-->>XquikSocialPostingService: XquikCreateTweetResponse
alt [response.success]
XquikSocialPostingService-->>Caller: SocialPostPlatformResult(status=success, postId, url)
else [pending_confirmation]
XquikSocialPostingService-->>Caller: SocialPostPlatformResult(status=pending)
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR adds Xquik social publishing with typed inputs, response normalization, content fallbacks, barrel exports, and tests. It also changes ChangesXquik Social Posting
Publishing Aggregation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant XquikSocialPostingService
participant XquikAPI
Caller->>XquikSocialPostingService: publishPost(post, options)
XquikSocialPostingService->>XquikSocialPostingService: select adapted content and create request body
XquikSocialPostingService->>XquikAPI: POST /api/v1/x/tweets with x-api-key
XquikAPI-->>XquikSocialPostingService: success, pending, or error response
XquikSocialPostingService-->>Caller: platform result
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoAdd Xquik social posting service with success/pending result mapping
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/social-posting/XquikSocialPostingService.spec.ts (1)
44-79: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd coverage for a genuine (non-pending-confirmation) error response.
Current tests only exercise the
successand exactpending_confirmationshapes. Sincepublish()currently maps any other response to"pending"(see comment onXquikSocialPostingService.tsLines 82-95), a test asserting that a real error response yieldsstatus: "error"would have caught that gap.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/social-posting/XquikSocialPostingService.spec.ts` around lines 44 - 79, Add a test in XquikSocialPostingService.spec.ts that covers a non-pending-confirmation error response from XquikSocialPostingService.publish, using the existing fetchMock/service setup. Verify that when the API returns a real error shape (not the pending_confirmation case), publish() maps it to a platform result with status "error" instead of "pending".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/io/channels/social-posting/XquikSocialPostingService.ts`:
- Around line 82-95: The fallback in XquikSocialPostingService should not treat
every non-success response as pending, because that hides real failures and
drops pending metadata. Update the response handling in the posting method to
explicitly discriminate between the documented success and pending_confirmation
shapes, return pending only for a genuine pending_confirmation response while
preserving its writeActionId, and map any other unexpected or failed payload to
SocialPostPlatformResult.status = "error" with the available error details so
SocialPostManager can surface it correctly.
---
Nitpick comments:
In `@tests/social-posting/XquikSocialPostingService.spec.ts`:
- Around line 44-79: Add a test in XquikSocialPostingService.spec.ts that covers
a non-pending-confirmation error response from
XquikSocialPostingService.publish, using the existing fetchMock/service setup.
Verify that when the API returns a real error shape (not the
pending_confirmation case), publish() maps it to a platform result with status
"error" instead of "pending".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 03be8d6c-e142-4240-a829-7ff04f3a8781
📒 Files selected for processing (3)
src/io/channels/social-posting/XquikSocialPostingService.tssrc/io/channels/social-posting/index.tstests/social-posting/XquikSocialPostingService.spec.ts
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- In
publish, any non-successresponse is treated aspending; consider explicitly checking for thex_write_unconfirmed/pending_confirmationshape and surfacing unexpected response formats as errors to avoid silently misclassifying failures. - The returned
urlis alwayshttps://x.com/...even whenplatformand/orbaseUrlare overridden; if these configurations are meant to support alternative environments or platforms, consider deriving the URL from config to keep the result consistent with the target platform.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `publish`, any non-`success` response is treated as `pending`; consider explicitly checking for the `x_write_unconfirmed`/`pending_confirmation` shape and surfacing unexpected response formats as errors to avoid silently misclassifying failures.
- The returned `url` is always `https://x.com/...` even when `platform` and/or `baseUrl` are overridden; if these configurations are meant to support alternative environments or platforms, consider deriving the URL from config to keep the result consistent with the target platform.
## Individual Comments
### Comment 1
<location path="tests/social-posting/XquikSocialPostingService.spec.ts" line_range="6" />
<code_context>
+import { XquikSocialPostingService } from "../../src/io/channels/social-posting/XquikSocialPostingService.js";
+import type { SocialPost } from "../../src/io/channels/social-posting/SocialPostManager.js";
+
+describe("XquikSocialPostingService", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for all optional Xquik request fields (replyToTweetId, attachmentUrl, communityId, isNoteTweet, mediaUrls, account override) to lock in the camel→snake mapping behavior.
Right now the tests only cover `text` and the `mediaUrls`-when-empty behavior. Please add one or two focused tests that build an `XquikPublishInput` with these optional properties set and assert that the resulting `XquikCreateTweetBody` matches the expected shape. That will lock in the mapping behavior and guard against regressions if the request-building logic changes.
Suggested implementation:
```typescript
describe("XquikSocialPostingService", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("maps optional Xquik publish input fields to snake_case request body", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true, tweetId: "12345" }), {
headers: { "content-type": "application/json" },
status: 200,
}),
);
vi.stubGlobal("fetch", fetchMock);
const service = new XquikSocialPostingService({
apiBaseUrl: "https://xquik.example.com",
apiKey: "test-api-key",
});
const post: SocialPost = {
platform: "x",
text: "Optional fields test",
mediaUrls: ["https://example.com/image.png"],
// Optional Xquik-specific fields we want to lock in
replyToTweetId: "9876543210",
attachmentUrl: "https://example.com/attachment.pdf",
communityId: "community-123",
isNoteTweet: true,
accountOverride: "override-account-id",
} as SocialPost;
await service.publish(post);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [, requestInit] = fetchMock.mock.calls[0];
expect(requestInit).toBeDefined();
const body = JSON.parse((requestInit as RequestInit).body as string);
expect(body).toEqual({
text: post.text,
media_urls: post.mediaUrls,
reply_to_tweet_id: post.replyToTweetId,
attachment_url: post.attachmentUrl,
community_id: post.communityId,
is_note_tweet: post.isNoteTweet,
account_override: post.accountOverride,
});
});
it("publishes a tweet through the Xquik create tweet endpoint", async () => {
```
You may need to adjust:
1. The construction of `XquikSocialPostingService` (its constructor options) to match the actual implementation.
2. The `SocialPost` shape and property names (`replyToTweetId`, `attachmentUrl`, `communityId`, `isNoteTweet`, `accountOverride`) if your `SocialPost`/`XquikPublishInput` type uses different names.
3. The expected snake_case keys (`reply_to_tweet_id`, `attachment_url`, `community_id`, `is_note_tweet`, `account_override`) if the actual `XquikCreateTweetBody` uses slightly different field names.
4. The method name `publish` if the service exposes a different method for posting (e.g. `publishToX`, `publishTweet`, etc.).
</issue_to_address>
### Comment 2
<location path="tests/social-posting/XquikSocialPostingService.spec.ts" line_range="81" />
<code_context>
+ );
+ });
+
+ it("publishes the adapted platform content from a social post", async () => {
+ const fetchMock = vi.fn().mockResolvedValue(
+ new Response(JSON.stringify({ success: true, tweetId: "67890" }), {
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for the fallback order of `publishPost` content (platform adaptation → twitter adaptation → baseContent).
This only covers the happy path where `adaptations.x` exists. To validate the documented fallback logic, please add tests for: (1) `adaptations.x` missing but `adaptations.twitter` present, and (2) both missing so `baseContent` is used. Each should assert the exact body passed to `fetch`, as in the current test.
Suggested implementation:
```typescript
it("publishes the adapted platform content from a social post", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true, tweetId: "67890" }), {
headers: { "content-type": "application/json" },
status: 200,
}),
);
vi.stubGlobal("fetch", fetchMock);
const service = new XquikSocialPostingService({
account: "@agent",
apiKey: "test-key",
platform: "x",
});
const socialPost = {
baseContent: "Base content",
adaptations: {
x: "X adaptation",
twitter: "Twitter adaptation",
},
media: ["https://example.com/image.png"],
};
await service.publishPost(socialPost);
const init = fetchMock.mock.calls[0]?.[1] as RequestInit;
expect(init.body).toBe(
JSON.stringify({
account: "@agent",
media: ["https://example.com/image.png"],
content: "X adaptation",
}),
);
});
it("falls back to twitter adaptation when platform-specific adaptation is missing", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true, tweetId: "67890" }), {
headers: { "content-type": "application/json" },
status: 200,
}),
);
vi.stubGlobal("fetch", fetchMock);
const service = new XquikSocialPostingService({
account: "@agent",
apiKey: "test-key",
platform: "x",
});
const socialPost = {
baseContent: "Base content",
adaptations: {
twitter: "Twitter adaptation",
},
media: ["https://example.com/image.png"],
};
await service.publishPost(socialPost);
const init = fetchMock.mock.calls[0]?.[1] as RequestInit;
expect(init.body).toBe(
JSON.stringify({
account: "@agent",
media: ["https://example.com/image.png"],
content: "Twitter adaptation",
}),
);
});
it("falls back to baseContent when no adaptations are present", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true, tweetId: "67890" }), {
headers: { "content-type": "application/json" },
status: 200,
}),
);
vi.stubGlobal("fetch", fetchMock);
const service = new XquikSocialPostingService({
account: "@agent",
apiKey: "test-key",
platform: "x",
});
const socialPost = {
baseContent: "Base content",
media: ["https://example.com/image.png"],
};
await service.publishPost(socialPost);
const init = fetchMock.mock.calls[0]?.[1] as RequestInit;
expect(init.body).toBe(
JSON.stringify({
account: "@agent",
media: ["https://example.com/image.png"],
content: "Base content",
}),
);
});
```
- Align the `socialPost` shape with the actual type used in this file (e.g., if there is a `SocialPost` type or helper factory, use it instead of a raw object).
- If the existing happy-path test already creates a `socialPost` fixture differently (e.g., via a factory or with additional fields), reuse that pattern to avoid duplication and keep the tests consistent.
- If `publishPost` requires additional options/parameters in this file, ensure those are passed in these new tests in the same way as in the existing happy-path test.
</issue_to_address>
### Comment 3
<location path="tests/social-posting/XquikSocialPostingService.spec.ts" line_range="24-33" />
<code_context>
+ account: "@agent",
+ apiKey: "test-key",
+ });
+ const result = await service.publish({ text: "hello" });
+
+ expect(result).toMatchObject({
</code_context>
<issue_to_address>
**suggestion (testing):** Assert that `publishedAt` is set on successful platform results to fully validate the mapping contract.
Since this test is validating the Xquik → AgentOS mapping, it should also cover the `publishedAt` field that the implementation sets. Please add an assertion that `result.publishedAt` is defined (and ideally a valid ISO timestamp) so this part of the mapping can’t be accidentally removed or changed without test failures.
```suggestion
const result = await service.publish({ text: "hello" });
expect(result).toMatchObject({
platform: "twitter",
postId: "12345",
status: "success",
url: "https://x.com/i/web/status/12345",
});
expect(result.publishedAt).toBeDefined();
expect(typeof result.publishedAt).toBe("string");
expect(Number.isNaN(Date.parse(result.publishedAt as string))).toBe(false);
const call = fetchMock.mock.calls[0];
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
tests/social-posting/XquikSocialPostingService.spec.ts (2)
41-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBody assertions are coupled to exact key insertion order.
Comparing
init.bodyagainstJSON.stringify({...})string literals passes only because the literal's key order in each test happens to matchcreateRequestBody's field-insertion order. Any harmless reordering of fields increateRequestBody(e.g. movingtextaftermedia) would break these tests despite producing an equivalent JSON object.♻️ Suggested pattern: parse then compare structurally
- expect(init.body).toBe( - JSON.stringify({ account: "`@agent`", text: "hello" }), - ); + expect(JSON.parse(init.body as string)).toEqual({ + account: "`@agent`", + text: "hello", + });Also applies to: 75-80, 108-118, 171-173, 216-221
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/social-posting/XquikSocialPostingService.spec.ts` around lines 41 - 43, The body checks in XquikSocialPostingService.spec.ts are relying on exact JSON string order instead of the actual payload shape. Update the assertions around createRequestBody/init.body to parse the body and compare the resulting object structurally, so the tests stay stable if field insertion order changes. Apply the same approach in all affected XquikSocialPostingService.spec.ts cases using init.body and JSON.stringify literals.
154-166: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated
SocialPostfixture construction across tests.The post literals in these two tests differ by only a few fields (
adaptations,baseContent,id,seedId). Consider extracting a small factory (e.g.buildTestPost(overrides)) to reduce repetition.Also applies to: 190-209
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/social-posting/XquikSocialPostingService.spec.ts` around lines 154 - 166, The SocialPost fixture is duplicated across multiple tests with only small field differences, so extract a shared factory in XquikSocialPostingService.spec.ts such as buildTestPost(overrides) and use it in the affected test cases. Keep the default SocialPost shape in one place and pass per-test overrides for fields like adaptations, baseContent, id, and seedId to reduce repetition and make future changes easier.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/social-posting/XquikSocialPostingService.spec.ts`:
- Around line 41-43: The body checks in XquikSocialPostingService.spec.ts are
relying on exact JSON string order instead of the actual payload shape. Update
the assertions around createRequestBody/init.body to parse the body and compare
the resulting object structurally, so the tests stay stable if field insertion
order changes. Apply the same approach in all affected
XquikSocialPostingService.spec.ts cases using init.body and JSON.stringify
literals.
- Around line 154-166: The SocialPost fixture is duplicated across multiple
tests with only small field differences, so extract a shared factory in
XquikSocialPostingService.spec.ts such as buildTestPost(overrides) and use it in
the affected test cases. Keep the default SocialPost shape in one place and pass
per-test overrides for fields like adaptations, baseContent, id, and seedId to
reduce repetition and make future changes easier.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 525b7592-8644-4b1a-b60d-845353115d96
📒 Files selected for processing (2)
src/io/channels/social-posting/XquikSocialPostingService.tstests/social-posting/XquikSocialPostingService.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/io/channels/social-posting/XquikSocialPostingService.ts
|
Review repairs are public at
I kept the successful post URL on All 4 review threads are resolved. Build, typecheck, 39 focused tests, formatting, and lint with 0 errors pass. @coderabbitai review |
|
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/io/channels/social-posting/XquikSocialPostingService.ts (1)
143-147: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFall back when an adaptation is empty.
Using
??accepts"";createRequestBody()then omits that text, potentially publishing an account-only/media-only request instead of using the available fallback. Use truthy fallback and cover empty adaptations in the fallback test.Proposed fix
text: - post.adaptations[this.platform] ?? - post.adaptations.twitter ?? + post.adaptations[this.platform] || + post.adaptations.twitter || post.baseContent,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/io/channels/social-posting/XquikSocialPostingService.ts` around lines 143 - 147, Update the input construction in XquikSocialPostingService to use truthy fallback semantics for post.adaptations[this.platform], post.adaptations.twitter, and post.baseContent, so empty adaptations fall through to the next available content. Extend the existing fallback test to cover empty adaptation values and verify the resulting request uses the non-empty fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/io/channels/social-posting/XquikSocialPostingService.ts`:
- Around line 143-147: Update the input construction in
XquikSocialPostingService to use truthy fallback semantics for
post.adaptations[this.platform], post.adaptations.twitter, and post.baseContent,
so empty adaptations fall through to the next available content. Extend the
existing fallback test to cover empty adaptation values and verify the resulting
request uses the non-empty fallback.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: dd5b3cc1-6c3b-4555-97de-3391ad26265d
📒 Files selected for processing (4)
src/io/channels/social-posting/SocialPostManager.tssrc/io/channels/social-posting/XquikSocialPostingService.tssrc/io/channels/social-posting/index.tstests/social-posting/XquikSocialPostingService.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/io/channels/social-posting/index.ts
Signed-off-by: kriptoburak <kriptoburak@users.noreply.github.com>
Signed-off-by: kriptoburak <kriptoburak@users.noreply.github.com>
b65e2fe to
94ee029
Compare
Signed-off-by: kriptoburak <kriptoburak@users.noreply.github.com>
|
Addressed the latest CodeRabbit outside-diff finding in Empty platform adaptations now fall through to the Twitter adaptation, and empty platform plus Twitter adaptations fall through to base content. The fallback test covers both cases with structural request-body assertions. Validation passed again: 39 focused tests, typecheck, changed-file lint with 0 errors, formatting, and diff checks. @coderabbitai review |
|
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! ✅ Action performedReview finished.
|
Signed-off-by: kriptoburak <kriptoburak@users.noreply.github.com>
|
Published b06a6e1 to close the remaining non-JSON response evidence gap. The regression test proves a successful text/plain response maps to an explicit error result instead of triggering the historical in-operator crash. Reverified all optional-field mappings, publishedAt, strict success/pending/error discrimination, writeActionId preservation, platform/Twitter/base and empty-value fallbacks, plus the independent SocialPostManager concurrency cleanup. Build and typecheck pass; the 2 changed suites pass 40/40 tests. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary
Promise.allSettledresult fromSocialPostManager.publishNowwhile preserving concurrent error handling.Why
AgentOS already provides a provider-neutral social-posting abstraction. This adds an authenticated X publishing implementation without changing the default behavior. The separate manager cleanup removes dead result allocation and clears its existing lint warning.
Checklist
Validation
pnpm run buildpnpm run typecheckpnpm exec vitest run tests/social-posting/XquikSocialPostingService.spec.ts tests/social-posting/SocialPostManager.spec.tswith 40 tests passingpnpm exec prettier --checkfor the Xquik service and test filesgit diff --checkThe full-suite command also ran 7,113 tests. Its 29 unrelated failures are present in untouched modules and missing optional workspace dependencies; both changed suites pass independently.
Related
No linked issue.
Not affiliated with X Corp.
Summary by CodeRabbit