Skip to content

Commit

Permalink
processing mock submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
elliotBraem committed Jan 31, 2025
1 parent a86c07f commit ff0ecf1
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 245 deletions.
129 changes: 48 additions & 81 deletions backend/src/__tests__/mocks/twitter-service.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tweet } from "agent-twitter-client";
import { SearchMode, Tweet } from "agent-twitter-client";
import { TwitterService } from "../../services/twitter/client";
import { logger } from "../../utils/logger";

Expand All @@ -8,9 +8,9 @@ export class MockTwitterService extends TwitterService {
private tweetIdCounter: bigint = BigInt(Date.now());

constructor() {
// Pass empty config since we're mocking
// Pass config with the bot's username so mentions are found
super({
username: "mock_user",
username: "curatedotfun",
password: "mock_pass",
email: "[email protected]",
});
Expand All @@ -21,6 +21,47 @@ export class MockTwitterService extends TwitterService {
logout: async () => {},
getCookies: async () => [],
setCookies: async () => {},
fetchSearchTweets: async (query: string, count: number, mode: SearchMode) => {
// Filter tweets that match the query (mentions @curatedotfun)
const matchingTweets = this.mockTweets.filter(tweet =>
tweet.text?.includes("@curatedotfun")
);

// Sort by ID descending (newest first) to match Twitter search behavior
const sortedTweets = [...matchingTweets].sort((a, b) => {
const aId = BigInt(a.id);
const bId = BigInt(b.id);
return bId > aId ? 1 : bId < aId ? -1 : 0;
});

return {
tweets: sortedTweets.slice(0, count),
};
},
likeTweet: async (tweetId: string) => {
logger.info(`Mock: Liked tweet ${tweetId}`);
return true;
},
sendTweet: async (message: string, replyToId?: string) => {
const newTweet = this.addMockTweet({
text: message,
username: "curatedotfun",
inReplyToStatusId: replyToId,
});
return {
json: async () => ({
data: {
create_tweet: {
tweet_results: {
result: {
rest_id: newTweet.id,
},
},
},
},
}),
} as Response;
},
};
}

Expand All @@ -31,7 +72,7 @@ export class MockTwitterService extends TwitterService {

public addMockTweet(tweet: Partial<Tweet> & { inReplyToStatusId?: string }) {
const fullTweet: Tweet = {
id: this.getNextTweetId(),
id: tweet.id || this.getNextTweetId(),
text: tweet.text || "",
username: tweet.username || "test_user",
userId: tweet.userId || `mock-user-id-${tweet.username || "test_user"}`,
Expand Down Expand Up @@ -69,86 +110,12 @@ export class MockTwitterService extends TwitterService {
async getUserIdByScreenName(screenName: string): Promise<string> {
return this.mockUserIds.get(screenName) || `mock-user-id-${screenName}`;
}

async fetchAllNewMentions(): Promise<Tweet[]> {
// Get the last tweet ID we processed
const lastCheckedId = this.getLastCheckedTweetId();

// If we have tweets and no last checked ID, set it to the newest tweet
if (this.mockTweets.length > 0 && !lastCheckedId) {
const newestTweet = this.mockTweets[this.mockTweets.length - 1];
await this.setLastCheckedTweetId(newestTweet.id);
return [newestTweet];
}

// Filter tweets newer than last checked ID
const newTweets = this.mockTweets.filter(tweet => {
if (!lastCheckedId) return true;
return BigInt(tweet.id) > BigInt(lastCheckedId);
});

// Update last checked ID if we found new tweets
if (newTweets.length > 0) {
const newestTweet = newTweets[newTweets.length - 1];
await this.setLastCheckedTweetId(newestTweet.id);
}

return newTweets;
// Let the parent TwitterService handle the processing logic
return super.fetchAllNewMentions();
}

async getTweet(tweetId: string): Promise<Tweet | null> {
return this.mockTweets.find((t) => t.id === tweetId) || null;
}

async replyToTweet(tweetId: string, message: string): Promise<string | null> {
const replyTweet = await this.addMockTweet({
text: message,
username: "curatedotfun",
inReplyToStatusId: tweetId,
});
logger.info(`Mock: Replied to tweet ${tweetId} with "${message}"`);
return replyTweet.id;
}

async likeTweet(tweetId: string): Promise<void> {
logger.info(`Mock: Liked tweet ${tweetId}`);
}

// Helper methods for test scenarios
async simulateSubmission(contentUrl: string) {
// First create the content tweet
const contentTweet = await this.addMockTweet({
text: "Original content",
username: "content_creator",
});

// Then create the curator's submission as a reply
return this.addMockTweet({
text: `@curatedotfun !submit ${contentUrl}`,
username: "curator",
userId: "mock-user-id-curator",
timeParsed: new Date(),
inReplyToStatusId: contentTweet.id,
});
}

async simulateApprove(submissionTweetId: string, projectId: string) {
return this.addMockTweet({
text: `@curatedotfun !approve ${projectId}`,
username: "moderator",
userId: "mock-user-id-moderator",
timeParsed: new Date(),
inReplyToStatusId: submissionTweetId,
});
}

async simulateReject(submissionTweetId: string, projectId: string, reason: string) {
return this.addMockTweet({
text: `@curatedotfun !reject ${projectId} ${reason}`,
username: "moderator",
userId: "mock-user-id-moderator",
timeParsed: new Date(),
inReplyToStatusId: submissionTweetId,
});
}
}
50 changes: 19 additions & 31 deletions backend/src/routes/test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { Tweet } from "agent-twitter-client";
import { Elysia } from "elysia";
import { MockTwitterService } from "../__tests__/mocks/twitter-service.mock";
import { Tweet } from "agent-twitter-client";

// Create a single mock instance to maintain state
const mockTwitterService = new MockTwitterService();

// Helper to create a tweet object
const createTweet = (text: string, username: string): Tweet => ({
id: Date.now().toString(),
text,
username,
userId: `mock-user-id-${username}`,
timeParsed: new Date(),
hashtags: [],
mentions: [],
photos: [],
urls: [],
videos: [],
thread: [],
});
const createTweet = (id: string, text: string, username: string, inReplyToStatusId?: string, hashtags?: string[]): Tweet => {
return {
id,
text,
username,
userId: `mock-user-id-${username}`,
timeParsed: new Date(),
hashtags: hashtags ?? [],
mentions: [],
photos: [],
urls: [],
videos: [],
thread: [],
inReplyToStatusId,
};
};

export const testRoutes = new Elysia({ prefix: "/api/test" })
.guard({
Expand All @@ -29,30 +32,15 @@ export const testRoutes = new Elysia({ prefix: "/api/test" })
}
},
})
.get("/tweets", () => {
return mockTwitterService.fetchAllNewMentions();
})
.post("/tweets", async ({ body }) => {
const { text, username } = body as { text: string; username: string };
const tweet = createTweet(text, username);
const { id, text, username, inReplyToStatusId, hashtags } = body as { id: string; text: string; username: string; inReplyToStatusId?: string; hashtags?: string[] };
const tweet = createTweet(id, text, username, inReplyToStatusId, hashtags);
mockTwitterService.addMockTweet(tweet);
return tweet;
})
.post("/reset", () => {
mockTwitterService.clearMockTweets();
return { success: true };
})
.post("/scenario/approve", async ({ body }) => {
const { projectId } = body as { projectId: string };
const tweet = createTweet(`@curatedotfun approve ${projectId}`, "curator");
mockTwitterService.addMockTweet(tweet);
return { success: true, tweet };
})
.post("/scenario/reject", async ({ body }) => {
const { projectId, reason } = body as { projectId: string; reason: string };
const tweet = createTweet(`@curatedotfun reject ${projectId} ${reason}`, "curator");
mockTwitterService.addMockTweet(tweet);
return { success: true, tweet };
});

// Export for use in tests and for replacing the real service
Expand Down
10 changes: 6 additions & 4 deletions curate.config.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"feeds": [
{
"id": "test-feed",
"id": "test",
"name": "Test Feed",
"description": "Main feed for testing basic functionality",
"moderation": {
Expand All @@ -30,15 +30,17 @@
{
"plugin": "@curatedotfun/supabase",
"config": {

"supabaseUrl": "{SUPABASE_URL}",
"supabaseKey": "{SUPABASE_SECRET_KEY}",
"tableName": "test"
}
}
]
}
}
},
{
"id": "multi-approver",
"id": "multi",
"name": "Multi-Approver Test",
"description": "Testing multiple approver scenarios",
"moderation": {
Expand All @@ -53,7 +55,7 @@
}
},
{
"id": "edge-cases",
"id": "edge",
"name": "Edge Cases",
"description": "Testing edge cases and error scenarios",
"moderation": {
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ const Header = () => {
>
How It Works
</button>
{process.env.NODE_ENV === "development" && (
<Link
to="/test"
className="text-gray-600 hover:text-black transition-colors"
>
Test Panel
</Link>
)}
</div>
<nav className="flex space-x-4 mx-4">
<a
Expand Down
Loading

0 comments on commit ff0ecf1

Please sign in to comment.