forked from elliotBraem/efizzybot
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
local mock testing and loading module
- Loading branch information
1 parent
b51fc59
commit a86c07f
Showing
12 changed files
with
570 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,52 @@ | ||
import { Tweet } from "agent-twitter-client"; | ||
import { TwitterService } from "../../services/twitter/client"; | ||
import { logger } from "../../utils/logger"; | ||
|
||
export class MockTwitterService { | ||
export class MockTwitterService extends TwitterService { | ||
private mockTweets: Tweet[] = []; | ||
private mockUserIds: Map<string, string> = new Map(); | ||
private lastCheckedTweetId: string | null = null; | ||
private tweetIdCounter: bigint = BigInt(Date.now()); | ||
|
||
public addMockTweet(tweet: Tweet) { | ||
this.mockTweets.push(tweet); | ||
constructor() { | ||
// Pass empty config since we're mocking | ||
super({ | ||
username: "mock_user", | ||
password: "mock_pass", | ||
email: "[email protected]", | ||
}); | ||
// Override the client with a basic mock | ||
(this as any).client = { | ||
isLoggedIn: async () => true, | ||
login: async () => {}, | ||
logout: async () => {}, | ||
getCookies: async () => [], | ||
setCookies: async () => {}, | ||
}; | ||
} | ||
|
||
private getNextTweetId(): string { | ||
this.tweetIdCounter = this.tweetIdCounter + BigInt(1); | ||
return this.tweetIdCounter.toString(); | ||
} | ||
|
||
public addMockTweet(tweet: Partial<Tweet> & { inReplyToStatusId?: string }) { | ||
const fullTweet: Tweet = { | ||
id: this.getNextTweetId(), | ||
text: tweet.text || "", | ||
username: tweet.username || "test_user", | ||
userId: tweet.userId || `mock-user-id-${tweet.username || "test_user"}`, | ||
timeParsed: tweet.timeParsed || new Date(), | ||
hashtags: tweet.hashtags || [], | ||
mentions: tweet.mentions || [], | ||
photos: tweet.photos || [], | ||
urls: tweet.urls || [], | ||
videos: tweet.videos || [], | ||
thread: [], | ||
inReplyToStatusId: tweet.inReplyToStatusId, | ||
}; | ||
this.mockTweets.push(fullTweet); | ||
logger.info(`Mock: Added tweet "${fullTweet.text}" from @${fullTweet.username}${tweet.inReplyToStatusId ? ` as reply to ${tweet.inReplyToStatusId}` : ''}`); | ||
return fullTweet; | ||
} | ||
|
||
public addMockUserId(username: string, userId: string) { | ||
|
@@ -15,41 +55,100 @@ export class MockTwitterService { | |
|
||
public clearMockTweets() { | ||
this.mockTweets = []; | ||
logger.info("Mock: Cleared all tweets"); | ||
} | ||
|
||
async initialize(): Promise<void> { | ||
// No-op for mock | ||
logger.info("Mock Twitter service initialized"); | ||
} | ||
|
||
async stop(): Promise<void> { | ||
// No-op for mock | ||
logger.info("Mock Twitter service stopped"); | ||
} | ||
|
||
async getUserIdByScreenName(screenName: string): Promise<string> { | ||
return this.mockUserIds.get(screenName) || `mock-user-id-${screenName}`; | ||
} | ||
|
||
async fetchAllNewMentions(): Promise<Tweet[]> { | ||
return this.mockTweets; | ||
// 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; | ||
} | ||
|
||
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> { | ||
return `mock-reply-${Date.now()}`; | ||
const replyTweet = await this.addMockTweet({ | ||
text: message, | ||
username: "curatedotfun", | ||
inReplyToStatusId: tweetId, | ||
}); | ||
logger.info(`Mock: Replied to tweet ${tweetId} with "${message}"`); | ||
return replyTweet.id; | ||
} | ||
|
||
async setLastCheckedTweetId(tweetId: string): Promise<void> { | ||
this.lastCheckedTweetId = tweetId; | ||
async likeTweet(tweetId: string): Promise<void> { | ||
logger.info(`Mock: Liked tweet ${tweetId}`); | ||
} | ||
|
||
async likeTweet(tweetId: string): Promise<void> { | ||
return; | ||
// 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, | ||
}); | ||
} | ||
|
||
getLastCheckedTweetId(): string | null { | ||
return this.lastCheckedTweetId; | ||
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, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
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: [], | ||
}); | ||
|
||
export const testRoutes = new Elysia({ prefix: "/api/test" }) | ||
.guard({ | ||
beforeHandle: ({ request }) => { | ||
// Only allow in development and test environments | ||
if (process.env.NODE_ENV === "production") { | ||
return new Response("Not found", { status: 404 }); | ||
} | ||
}, | ||
}) | ||
.get("/tweets", () => { | ||
return mockTwitterService.fetchAllNewMentions(); | ||
}) | ||
.post("/tweets", async ({ body }) => { | ||
const { text, username } = body as { text: string; username: string }; | ||
const tweet = createTweet(text, username); | ||
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 | ||
export { mockTwitterService }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.