diff --git a/packages/client-linkedin/__tests__/LinkedInPostScheduler.test.ts b/packages/client-linkedin/__tests__/LinkedInPostScheduler.test.ts new file mode 100644 index 00000000000..2eb36c95429 --- /dev/null +++ b/packages/client-linkedin/__tests__/LinkedInPostScheduler.test.ts @@ -0,0 +1,133 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { LinkedInPostScheduler } from "../src/services/LinkedInPostScheduler"; +import { LinkedInPostPublisher } from "../src/repositories/LinkedinPostPublisher"; +import { LinkedInUserInfoFetcher } from "../src/repositories/LinkedinUserInfoFetcher"; +import { PostContentCreator } from "../src/services/PostContentCreator"; +import { IAgentRuntime } from "@elizaos/core"; +import { PublisherConfig } from "../src/interfaces"; +import axios from "axios"; + +vi.mock("../src/repositories/LinkedinPostPublisher"); +vi.mock("../src/services/PostContentCreator"); +vi.useFakeTimers(); + +describe("LinkedInPostScheduler", () => { + let mockRuntime: IAgentRuntime; + let mockPostPublisher: LinkedInPostPublisher; + let mockPostContentCreator: PostContentCreator; + let mockUserInfoFetcher: LinkedInUserInfoFetcher; + let config: PublisherConfig; + + beforeEach(() => { + mockRuntime = { + cacheManager: { + get: vi.fn(), + set: vi.fn(), + }, + messageManager: { + createMemory: vi.fn(), + }, + agentId: "test-agent-id", + } as unknown as IAgentRuntime; + + mockPostPublisher = new LinkedInPostPublisher(axios.create(), "test-user"); + vi.spyOn(mockPostPublisher, 'publishPost'); + + mockPostContentCreator = new PostContentCreator(mockRuntime); + vi.spyOn(mockPostContentCreator, 'createPostContent'); + + mockUserInfoFetcher = { + getUserInfo: vi.fn().mockResolvedValue({ sub: "test-user" }), + } as unknown as LinkedInUserInfoFetcher; + + config = { + LINKEDIN_POST_INTERVAL_MIN: 60, + LINKEDIN_POST_INTERVAL_MAX: 120, + LINKEDIN_DRY_RUN: false, + }; + }); + + describe("createPostScheduler", () => { + it("should create a new instance of LinkedInPostScheduler", async () => { + const scheduler = await LinkedInPostScheduler.createPostScheduler({ + axiosInstance: axios.create(), + userInfoFetcher: mockUserInfoFetcher, + runtime: mockRuntime, + config, + }); + + expect(scheduler).toBeInstanceOf(LinkedInPostScheduler); + expect(mockUserInfoFetcher.getUserInfo).toHaveBeenCalled(); + }); + }); + + describe("createPostPublicationLoop", () => { + let scheduler: LinkedInPostScheduler; + + beforeEach(() => { + scheduler = new LinkedInPostScheduler( + mockRuntime, + mockPostPublisher, + mockPostContentCreator, + "test-user", + config + ); + }); + + it("should publish post when enough time has passed", async () => { + vi.spyOn(mockRuntime.cacheManager, 'get').mockResolvedValue(null); + vi.spyOn(mockPostContentCreator, 'createPostContent').mockResolvedValue("Test post content"); + vi.spyOn(mockPostPublisher, 'publishPost').mockResolvedValue(); + + await scheduler.createPostPublicationLoop(); + + expect(mockPostContentCreator.createPostContent).toHaveBeenCalledWith("test-user"); + expect(mockPostPublisher.publishPost).toHaveBeenCalledWith({ + postText: "Test post content", + }); + expect(mockRuntime.cacheManager.set).toHaveBeenCalled(); + expect(mockRuntime.messageManager.createMemory).toHaveBeenCalled(); + }); + + it("should not publish post when in dry run mode", async () => { + const dryRunConfig = { ...config, LINKEDIN_DRY_RUN: true }; + scheduler = new LinkedInPostScheduler( + mockRuntime, + mockPostPublisher, + mockPostContentCreator, + "test-user", + dryRunConfig + ); + + vi.spyOn(mockRuntime.cacheManager, 'get').mockResolvedValue(null); + vi.spyOn(mockPostContentCreator, 'createPostContent').mockResolvedValue("Test post content"); + + await scheduler.createPostPublicationLoop(); + + expect(mockPostContentCreator.createPostContent).toHaveBeenCalled(); + expect(mockPostPublisher.publishPost).not.toHaveBeenCalled(); + }); + + it("should not publish post when not enough time has passed", async () => { + const currentTime = Date.now(); + vi.spyOn(mockRuntime.cacheManager, 'get').mockResolvedValue({ + timestamp: currentTime, + }); + + await scheduler.createPostPublicationLoop(); + + expect(mockPostContentCreator.createPostContent).not.toHaveBeenCalled(); + expect(mockPostPublisher.publishPost).not.toHaveBeenCalled(); + }); + + it("should schedule next execution", async () => { + const setTimeoutSpy = vi.spyOn(global, 'setTimeout'); + vi.spyOn(mockRuntime.cacheManager, 'get').mockResolvedValue(null); + vi.spyOn(mockPostContentCreator, 'createPostContent').mockResolvedValue("Test post content"); + + await scheduler.createPostPublicationLoop(); + + expect(setTimeoutSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/client-linkedin/__tests__/LinkedinFileUploader.test.ts b/packages/client-linkedin/__tests__/LinkedinFileUploader.test.ts new file mode 100644 index 00000000000..28b04c3698d --- /dev/null +++ b/packages/client-linkedin/__tests__/LinkedinFileUploader.test.ts @@ -0,0 +1,107 @@ +import { describe, it, expect, vi } from 'vitest'; +import { LinkedInFileUploader } from '../src/repositories/LinkedinFileUploader'; +import { AxiosInstance, AxiosError } from 'axios'; +import { API_VERSION, API_VERSION_HEADER } from '../src/interfaces'; + +describe('LinkedInFileUploader', () => { + const mockAxios = { + post: vi.fn(), + put: vi.fn(), + } as unknown as AxiosInstance; + + const userId = 'test-user-id'; + const uploader = new LinkedInFileUploader(mockAxios, userId); + + it('should upload asset successfully', async () => { + const mockUploadUrl = 'https://example.com/upload'; + const mockImageId = 'image123'; + const mockBlob = new Blob(['test'], { type: 'image/jpeg' }); + + vi.mocked(mockAxios.post).mockResolvedValueOnce({ + data: { + value: { + uploadUrl: mockUploadUrl, + image: mockImageId, + uploadUrlExpiresAt: Date.now() + 3600000, + }, + }, + }); + + vi.mocked(mockAxios.put).mockResolvedValueOnce({}); + + const result = await uploader.uploadAsset(mockBlob); + + expect(mockAxios.post).toHaveBeenCalledWith( + '/rest/images', + { + initializeUploadRequest: { + owner: `urn:li:person:${userId}`, + }, + }, + { + headers: { + [API_VERSION_HEADER]: [API_VERSION], + }, + params: { + action: 'initializeUpload', + }, + } + ); + + expect(mockAxios.put).toHaveBeenCalledWith( + mockUploadUrl, + mockBlob, + { + headers: { + 'Content-Type': 'application/octet-stream', + }, + } + ); + + expect(result).toBe(mockImageId); + }); + + it('should handle create upload url error', async () => { + const error = new AxiosError( + 'Network Error', + 'ERR_NETWORK', + undefined, + undefined, + { + status: 500, + data: { message: 'Server Error' }, + } as any + ); + + vi.mocked(mockAxios.post).mockRejectedValueOnce(error); + + const mockBlob = new Blob(['test'], { type: 'image/jpeg' }); + + await expect(uploader.uploadAsset(mockBlob)) + .rejects + .toThrow('Failed create media upload url'); + }); + + it('should handle upload media error', async () => { + const mockUploadUrl = 'https://example.com/upload'; + const mockImageId = 'image123'; + const mockBlob = new Blob(['test'], { type: 'image/jpeg' }); + + vi.mocked(mockAxios.post).mockResolvedValueOnce({ + data: { + value: { + uploadUrl: mockUploadUrl, + image: mockImageId, + uploadUrlExpiresAt: Date.now() + 3600000, + }, + }, + }); + + const error = new Error('Upload failed'); + vi.mocked(mockAxios.put).mockRejectedValueOnce(error); + + await expect(uploader.uploadAsset(mockBlob)) + .rejects + .toThrow('Failed to upload media: Error: Upload failed'); + }); +}); diff --git a/packages/client-linkedin/__tests__/LinkedinPostPublisher.test.ts b/packages/client-linkedin/__tests__/LinkedinPostPublisher.test.ts new file mode 100644 index 00000000000..ae5fd0d140d --- /dev/null +++ b/packages/client-linkedin/__tests__/LinkedinPostPublisher.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, vi } from 'vitest'; +import { LinkedInPostPublisher } from '../src/repositories/LinkedinPostPublisher'; +import { AxiosInstance, AxiosError } from 'axios'; +import { API_VERSION, API_VERSION_HEADER } from '../src/interfaces'; + +describe('LinkedInPostPublisher', () => { + const mockAxios = { + post: vi.fn(), + } as unknown as AxiosInstance; + + const userId = 'test-user-id'; + const publisher = new LinkedInPostPublisher(mockAxios, userId); + + it('should publish text-only post successfully', async () => { + const postText = 'Test post content'; + + await publisher.publishPost({ postText }); + + expect(mockAxios.post).toHaveBeenCalledWith( + '/rest/posts', + { + author: `urn:li:person:${userId}`, + commentary: postText, + visibility: 'PUBLIC', + distribution: { + feedDistribution: 'MAIN_FEED', + targetEntities: [], + thirdPartyDistributionChannels: [], + }, + lifecycleState: 'PUBLISHED', + isReshareDisabledByAuthor: false, + }, + { + headers: { + [API_VERSION_HEADER]: [API_VERSION], + }, + } + ); + }); + + it('should publish post with media successfully', async () => { + const postText = 'Test post with media'; + const media = { id: 'media-id', title: 'media-title' }; + + await publisher.publishPost({ postText, media }); + + expect(mockAxios.post).toHaveBeenCalledWith( + '/rest/posts', + { + author: `urn:li:person:${userId}`, + commentary: postText, + visibility: 'PUBLIC', + distribution: { + feedDistribution: 'MAIN_FEED', + targetEntities: [], + thirdPartyDistributionChannels: [], + }, + lifecycleState: 'PUBLISHED', + isReshareDisabledByAuthor: false, + content: { media }, + }, + { + headers: { + [API_VERSION_HEADER]: [API_VERSION], + }, + } + ); + }); + + it('should handle axios error', async () => { + const error = new AxiosError( + 'Network Error', + 'ERR_NETWORK', + undefined, + undefined, + { + status: 500, + data: { message: 'Internal Server Error' }, + } as any + ); + + vi.mocked(mockAxios.post).mockRejectedValueOnce(error); + + await expect(publisher.publishPost({ postText: 'Test post' })) + .rejects + .toThrow('Failed to publish LinkedIn post'); + }); + + it('should handle non-axios error', async () => { + const error = new Error('Unexpected error'); + vi.mocked(mockAxios.post).mockRejectedValueOnce(error); + + await expect(publisher.publishPost({ postText: 'Test post' })) + .rejects + .toThrow('Failed to publish LinkedIn post: Error: Unexpected error'); + }); +}); diff --git a/packages/client-linkedin/__tests__/LinkedinUserInfoFetcher.test.ts b/packages/client-linkedin/__tests__/LinkedinUserInfoFetcher.test.ts new file mode 100644 index 00000000000..80c9ec752d6 --- /dev/null +++ b/packages/client-linkedin/__tests__/LinkedinUserInfoFetcher.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect, vi } from 'vitest'; +import { LinkedInUserInfoFetcher } from '../src/repositories/LinkedinUserInfoFetcher'; +import { AxiosInstance, AxiosError } from 'axios'; +import { UserInfo } from '../src/interfaces'; + +describe('LinkedInUserInfoFetcher', () => { + const mockAxios = { + get: vi.fn(), + } as unknown as AxiosInstance; + + const fetcher = new LinkedInUserInfoFetcher(mockAxios); + + it('should fetch user info successfully', async () => { + const mockUserInfo: UserInfo = { + sub: 'user123', + email: 'test@example.com', + email_verified: true, + name: 'Test User', + locale: { country: 'US', language: 'en_US' }, + given_name: 'Test', + family_name: 'User', + picture: 'https://example.com/picture.jpg' + }; + + vi.mocked(mockAxios.get).mockResolvedValueOnce({ + data: mockUserInfo + }); + + const result = await fetcher.getUserInfo(); + + expect(mockAxios.get).toHaveBeenCalledWith('/v2/userinfo'); + expect(result).toEqual(mockUserInfo); + }); + + it('should handle axios error', async () => { + const error = new AxiosError( + 'Network Error', + 'ERR_NETWORK', + undefined, + undefined, + { + status: 401, + data: { message: 'Unauthorized' }, + } as any + ); + + vi.mocked(mockAxios.get).mockRejectedValueOnce(error); + + await expect(fetcher.getUserInfo()) + .rejects + .toThrow('Failed to fetch user info'); + }); + + it('should handle non-axios error', async () => { + const error = new Error('Unexpected error'); + vi.mocked(mockAxios.get).mockRejectedValueOnce(error); + + await expect(fetcher.getUserInfo()) + .rejects + .toThrow('Failed to fetch user info: Error: Unexpected error'); + }); +}); diff --git a/packages/client-linkedin/__tests__/PostContentCreator.test.ts b/packages/client-linkedin/__tests__/PostContentCreator.test.ts new file mode 100644 index 00000000000..8713bddfb43 --- /dev/null +++ b/packages/client-linkedin/__tests__/PostContentCreator.test.ts @@ -0,0 +1,99 @@ +import { describe, it, expect, vi } from 'vitest'; +import { PostContentCreator } from '../src/services/PostContentCreator'; +import { IAgentRuntime, ModelClass } from '@elizaos/core'; + +vi.mock('@elizaos/core', async () => { + const actual = await vi.importActual('@elizaos/core'); + return { + ...actual as object, + generateText: vi.fn(), + }; +}); + +describe('PostContentCreator', () => { + const mockRuntime = { + agentId: 'test-agent', + character: { + topics: ['AI', 'Machine Learning', 'Web Development'], + }, + composeState: vi.fn(), + messageManager: { + createMemory: vi.fn(), + }, + } as unknown as IAgentRuntime; + + const creator = new PostContentCreator(mockRuntime); + + it('should create post content successfully', async () => { + const userId = 'test-user'; + const mockGeneratedText = 'This is a generated post about #AI and #MachineLearning'; + const mockState = { state: 'data' }; + // @ts-ignore + vi.mocked(mockRuntime.composeState).mockResolvedValueOnce(mockState); + + const { generateText } = await import('@elizaos/core'); + vi.mocked(generateText).mockResolvedValueOnce(mockGeneratedText); + + const result = await creator.createPostContent(userId); + + expect(mockRuntime.composeState).toHaveBeenCalledWith({ + userId: mockRuntime.agentId, + roomId: expect.any(String), + agentId: mockRuntime.agentId, + content: { + text: 'AI, Machine Learning, Web Development', + action: 'LINKEDIN_POST', + }, + }); + + expect(generateText).toHaveBeenCalledWith(expect.objectContaining({ + runtime: mockRuntime, + modelClass: ModelClass.SMALL + })); + + expect(result).toBe('This is a generated post about #AI and #MachineLearning'); + }); + + it('should escape special characters in content', () => { + const testContent = 'Test (content) with [brackets] and {braces}'; + const escaped = creator.escapeSpecialCharacters(testContent); + + expect(escaped).toBe('Test \\(content\\) with \\[brackets\\] and \\{braces\\}'); + }); + + it('should remove markdown formatting', () => { + const markdownContent = '**Bold** and *italic* with `code`'; + const plainText = creator.removeMd(markdownContent); + + expect(plainText).toBe('Bold and italic with code'); + }); + + it('should handle empty topics array', async () => { + const emptyTopicsRuntime = { + ...mockRuntime, + character: { + topics: [], + }, + } as unknown as IAgentRuntime; + + const creatorWithEmptyTopics = new PostContentCreator(emptyTopicsRuntime); + const userId = 'test-user'; + // @ts-ignore + vi.mocked(emptyTopicsRuntime.composeState).mockResolvedValueOnce({}); + + const { generateText } = await import('@elizaos/core'); + vi.mocked(generateText).mockResolvedValueOnce('Generated content'); + + await creatorWithEmptyTopics.createPostContent(userId); + + expect(emptyTopicsRuntime.composeState).toHaveBeenCalledWith({ + userId: mockRuntime.agentId, + roomId: expect.any(String), + agentId: mockRuntime.agentId, + content: { + text: '', + action: 'LINKEDIN_POST', + }, + }); + }); +}); diff --git a/packages/client-linkedin/__tests__/get-random-integer.test.ts b/packages/client-linkedin/__tests__/get-random-integer.test.ts new file mode 100644 index 00000000000..2a2ee377749 --- /dev/null +++ b/packages/client-linkedin/__tests__/get-random-integer.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest'; +import { getRandomInteger } from '../src/helpers/get-random-integer'; + +describe('getRandomInteger', () => { + it('should return a number between min and max inclusive', () => { + const min = 1; + const max = 10; + const result = getRandomInteger(min, max); + + expect(result).toBeGreaterThanOrEqual(min); + expect(result).toBeLessThanOrEqual(max); + expect(Number.isInteger(result)).toBe(true); + }); + + it('should work with same min and max values', () => { + const result = getRandomInteger(5, 5); + expect(result).toBe(5); + }); + + it('should throw error when min is greater than max', () => { + expect(() => getRandomInteger(10, 1)).toThrow('Min value cannot be greater than max value'); + }); + + it('should throw error for NaN values', () => { + expect(() => getRandomInteger(NaN, 10)).toThrow('Invalid range: min and max must be valid numbers'); + expect(() => getRandomInteger(1, NaN)).toThrow('Invalid range: min and max must be valid numbers'); + expect(() => getRandomInteger(NaN, NaN)).toThrow('Invalid range: min and max must be valid numbers'); + }); +}); diff --git a/packages/client-linkedin/__tests__/prepare-axios-error-message.test.ts b/packages/client-linkedin/__tests__/prepare-axios-error-message.test.ts new file mode 100644 index 00000000000..bdd5eaf73fd --- /dev/null +++ b/packages/client-linkedin/__tests__/prepare-axios-error-message.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { AxiosError } from 'axios'; +import { prepareAxiosErrorMessage } from '../src/helpers/prepare-axios-error-message'; + +describe('prepareAxiosErrorMessage', () => { + it('should format error with all fields', () => { + const mockError = new AxiosError( + 'Network Error', + 'ERR_NETWORK', + undefined, + undefined, + { + status: 404, + data: { error: 'Not Found' }, + } as any + ); + + const result = prepareAxiosErrorMessage(mockError); + const parsed = JSON.parse(result); + + expect(parsed).toEqual({ + message: 'Network Error', + status: 404, + data: { error: 'Not Found' }, + code: 'ERR_NETWORK' + }); + }); + + it('should handle error without response data', () => { + const mockError = new AxiosError( + 'Timeout Error', + 'ECONNABORTED' + ); + + const result = prepareAxiosErrorMessage(mockError); + const parsed = JSON.parse(result); + + expect(parsed).toEqual({ + message: 'Timeout Error', + status: undefined, + data: undefined, + code: 'ECONNABORTED' + }); + }); +}); diff --git a/packages/client-linkedin/__tests__/validate-config.test.ts b/packages/client-linkedin/__tests__/validate-config.test.ts new file mode 100644 index 00000000000..6b7f2467c46 --- /dev/null +++ b/packages/client-linkedin/__tests__/validate-config.test.ts @@ -0,0 +1,131 @@ +import { describe, it, expect, vi } from 'vitest'; +import { validateConfig, configSchema } from '../src/helpers/validate-config'; +import { IAgentRuntime } from '@elizaos/core'; + +describe('validateConfig', () => { + const mockRuntime = { + getSetting: vi.fn(), + } as unknown as IAgentRuntime; + + it('should validate correct config', () => { + vi.mocked(mockRuntime.getSetting) + .mockImplementation((key: string) => { + const settings = { + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: '60', + LINKEDIN_POST_INTERVAL_MAX: '120', + LINKEDIN_API_URL: 'https://api.linkedin.com', + LINKEDIN_DRY_RUN: 'false' + }; + return settings[key]; + }); + + const config = validateConfig(mockRuntime); + + expect(config).toEqual({ + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: 60, + LINKEDIN_POST_INTERVAL_MAX: 120, + LINKEDIN_API_URL: 'https://api.linkedin.com', + LINKEDIN_DRY_RUN: false + }); + }); + + it('should throw error when access token is missing', () => { + vi.mocked(mockRuntime.getSetting) + .mockImplementation((key: string) => { + const settings = { + LINKEDIN_POST_INTERVAL_MIN: '60', + LINKEDIN_POST_INTERVAL_MAX: '120' + }; + return settings[key]; + }); + + expect(() => validateConfig(mockRuntime)) + .toThrow('Invalid environment variables'); + }); + + it('should throw error when min interval is greater than max', () => { + vi.mocked(mockRuntime.getSetting) + .mockImplementation((key: string) => { + const settings = { + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: '150', + LINKEDIN_POST_INTERVAL_MAX: '120' + }; + return settings[key]; + }); + + expect(() => validateConfig(mockRuntime)) + .toThrow('Min value cannot be greater than max value'); + }); + + it('should use default values when optional settings are missing', () => { + vi.mocked(mockRuntime.getSetting) + .mockImplementation((key: string) => { + const settings = { + LINKEDIN_ACCESS_TOKEN: 'test-token' + }; + return settings[key]; + }); + + const config = validateConfig(mockRuntime); + + expect(config).toEqual({ + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: 60, + LINKEDIN_POST_INTERVAL_MAX: 120, + LINKEDIN_API_URL: 'https://api.linkedin.com', + LINKEDIN_DRY_RUN: false + }); + }); + + it('should handle non-ZodError errors', () => { + vi.mocked(mockRuntime.getSetting).mockImplementation(() => { + throw new Error('Unexpected runtime error'); + }); + + expect(() => validateConfig(mockRuntime)) + .toThrow('Invalid environment variables.'); + }); + + describe('parseNumber validation', () => { + it('should parse valid string numbers', () => { + const result = configSchema.parse({ + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: '42', + LINKEDIN_POST_INTERVAL_MAX: '120' + }); + + expect(result.LINKEDIN_POST_INTERVAL_MIN).toBe(42); + }); + + it('should parse actual numbers', () => { + const result = configSchema.parse({ + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: 42, + LINKEDIN_POST_INTERVAL_MAX: 120 + }); + + expect(result.LINKEDIN_POST_INTERVAL_MIN).toBe(42); + }); + + it('should throw error for invalid number strings', () => { + expect(() => configSchema.parse({ + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: 'not-a-number', + LINKEDIN_POST_INTERVAL_MAX: '120' + })).toThrow('Invalid number: not-a-number'); + }); + + it('should handle decimal strings', () => { + const result = configSchema.parse({ + LINKEDIN_ACCESS_TOKEN: 'test-token', + LINKEDIN_POST_INTERVAL_MIN: '42.5', + LINKEDIN_POST_INTERVAL_MAX: '120' + }); + + expect(result.LINKEDIN_POST_INTERVAL_MIN).toBe(42.5); + }); + }); +}); diff --git a/packages/client-linkedin/src/helpers/validate-config.ts b/packages/client-linkedin/src/helpers/validate-config.ts index 64cbfde2360..6aa1bbd4638 100644 --- a/packages/client-linkedin/src/helpers/validate-config.ts +++ b/packages/client-linkedin/src/helpers/validate-config.ts @@ -46,11 +46,15 @@ export const configSchema = z LINKEDIN_API_URL: z .union([z.string(), z.null(), z.undefined()]) .transform((val) => val ?? DEFAULT_LINKEDIN_API_URL), - LINKEDIN_DRY_RUN: z.union([ - z.boolean(), - z.null(), - z.undefined(), - ]).transform((val) => val ?? DEFAULT_LINKEDIN_DRY_RUN), + LINKEDIN_DRY_RUN: z + .union([z.string(), z.null(), z.undefined()]) + .transform((val) => { + if (val === null || val === undefined) { + return DEFAULT_LINKEDIN_DRY_RUN; + } + + return val === "true"; + }), }) .superRefine((data, ctx) => { if (data.LINKEDIN_POST_INTERVAL_MIN > data.LINKEDIN_POST_INTERVAL_MAX) { @@ -63,23 +67,25 @@ export const configSchema = z }); export const validateConfig = (runtime: IAgentRuntime) => { - const LINKEDIN_ACCESS_TOKEN = runtime.getSetting("LINKEDIN_ACCESS_TOKEN"); - const LINKEDIN_POST_INTERVAL_MIN = runtime.getSetting( - "LINKEDIN_POST_INTERVAL_MIN" - ); - const LINKEDIN_POST_INTERVAL_MAX = runtime.getSetting( - "LINKEDIN_POST_INTERVAL_MAX" - ); - const LINKEDIN_API_URL = runtime.getSetting("LINKEDIN_API_URL"); - const LINKEDIN_DRY_RUN = runtime.getSetting("LINKEDIN_DRY_RUN"); - try { + const LINKEDIN_ACCESS_TOKEN = runtime.getSetting( + "LINKEDIN_ACCESS_TOKEN" + ); + const LINKEDIN_POST_INTERVAL_MIN = runtime.getSetting( + "LINKEDIN_POST_INTERVAL_MIN" + ); + const LINKEDIN_POST_INTERVAL_MAX = runtime.getSetting( + "LINKEDIN_POST_INTERVAL_MAX" + ); + const LINKEDIN_API_URL = runtime.getSetting("LINKEDIN_API_URL"); + const LINKEDIN_DRY_RUN = runtime.getSetting("LINKEDIN_DRY_RUN"); + const envs = configSchema.parse({ LINKEDIN_ACCESS_TOKEN, LINKEDIN_POST_INTERVAL_MIN, LINKEDIN_POST_INTERVAL_MAX, LINKEDIN_API_URL, - LINKEDIN_DRY_RUN + LINKEDIN_DRY_RUN, }); return envs;