Skip to main content
Glama

mcp-youtube

by kirbah
nicheAnalyzer.service.test.ts9 kB
import { NicheAnalyzerService } from "../nicheAnalyzer.service"; import { CacheService } from "../cache.service"; import { YoutubeService } from "../../services/youtube.service"; import { NicheRepository } from "../analysis/niche.repository"; // Import NicheRepository import { executeInitialCandidateSearch } from "../analysis/phase1-candidate-search"; import { executeChannelPreFiltering } from "../analysis/phase2-channel-filtering"; import { executeDeepConsistencyAnalysis } from "../analysis/phase3-deep-analysis"; import { formatAndRankAnalysisResults } from "../analysis/phase4-ranking-formatting"; import { FindConsistentOutlierChannelsOptions, NicheAnalysisOutput, } from "../../types/analyzer.types"; // Mock dependencies jest.mock("../cache.service"); jest.mock("../../services/youtube.service"); jest.mock("../analysis/niche.repository"); // Mock NicheRepository jest.mock("../analysis/phase1-candidate-search"); jest.mock("../analysis/phase2-channel-filtering"); jest.mock("../analysis/phase3-deep-analysis"); jest.mock("../analysis/phase4-ranking-formatting"); describe("NicheAnalyzerService", () => { let nicheAnalyzerService: NicheAnalyzerService; let mockCacheService: jest.Mocked<CacheService>; let mockVideoManagement: jest.Mocked<YoutubeService>; let mockNicheRepository: jest.Mocked<NicheRepository>; // Declare mockNicheRepository // Mocked phase functions const mockExecuteInitialCandidateSearch = executeInitialCandidateSearch as jest.Mock; const mockExecuteChannelPreFiltering = executeChannelPreFiltering as jest.Mock; const mockExecuteDeepConsistencyAnalysis = executeDeepConsistencyAnalysis as jest.Mock; const mockFormatAndRankAnalysisResults = formatAndRankAnalysisResults as jest.Mock; beforeEach(() => { // Reset mocks before each test mockCacheService = new CacheService(null as any); mockVideoManagement = new YoutubeService(mockCacheService); // Provide mock implementations for VideoManagement methods mockVideoManagement.resetApiCreditsUsed = jest.fn(); mockVideoManagement.getApiCreditsUsed = jest.fn().mockReturnValue(0); mockVideoManagement.searchVideos = jest.fn(); mockVideoManagement.batchFetchChannelStatistics = jest.fn(); mockVideoManagement.fetchChannelRecentTopVideos = jest.fn(); mockVideoManagement.getVideo = jest.fn(); mockVideoManagement.getTranscript = jest.fn(); mockVideoManagement.getChannelStatistics = jest.fn(); mockVideoManagement.getChannelTopVideos = jest.fn(); mockVideoManagement.getTrendingVideos = jest.fn(); mockVideoManagement.getVideoCategories = jest.fn(); mockNicheRepository = new NicheRepository(null as any); // Initialize mockNicheRepository nicheAnalyzerService = new NicheAnalyzerService( mockVideoManagement, mockNicheRepository ); // Reset phase function mocks mockExecuteInitialCandidateSearch.mockReset(); mockExecuteChannelPreFiltering.mockReset(); mockExecuteDeepConsistencyAnalysis.mockReset(); mockFormatAndRankAnalysisResults.mockReset(); }); it("should execute a successful end-to-end analysis run, calling phases in order and passing data", async () => { const options: FindConsistentOutlierChannelsOptions = { query: "test query", channelAge: "NEW", consistencyLevel: "MODERATE", outlierMagnitude: "STANDARD", maxResults: 10, }; const phase1Output = ["channel1", "channel2"]; const phase2Output = ["channel1"]; // channel2 filtered out const phase3Output = { results: [{ _id: "channel1" /* other channel data */ } as any], quotaExceeded: false, }; mockExecuteInitialCandidateSearch.mockResolvedValue(phase1Output); mockExecuteChannelPreFiltering.mockResolvedValue(phase2Output); mockExecuteDeepConsistencyAnalysis.mockResolvedValue(phase3Output); // IMPORTANT: The mockReturnValue for mockFormatAndRankAnalysisResults will be set inline where it's used // to ensure the summary object is correctly formed with up-to-date data. // mockFormatAndRankAnalysisResults.mockReturnValue(phase4Output); // This line is removed mockVideoManagement.getApiCreditsUsed.mockReturnValue(100); // Ensure this is mocked for summary // Define the expected phase4Output structure here, based on phase3Output and other mocks const expectedPhase4Output: NicheAnalysisOutput = { status: "COMPLETED_SUCCESSFULLY", summary: { candidatesFound: phase1Output.length, candidatesAnalyzed: phase3Output.results.length, apiCreditsUsed: 100, // This will be asserted against mockVideoManagement.getApiCreditsUsed }, results: [{ channelId: "channel1" /* other result data */ } as any], // Assuming this structure }; mockFormatAndRankAnalysisResults.mockReturnValue(expectedPhase4Output); const result = await nicheAnalyzerService.findConsistentOutlierChannels(options); // Check order of calls and data passing expect(mockExecuteInitialCandidateSearch).toHaveBeenCalledWith( options, mockVideoManagement ); expect(mockExecuteChannelPreFiltering).toHaveBeenCalledWith( phase1Output, options, mockVideoManagement, mockNicheRepository ); expect(mockExecuteDeepConsistencyAnalysis).toHaveBeenCalledWith( phase2Output, options, mockVideoManagement, mockNicheRepository ); expect(mockFormatAndRankAnalysisResults).toHaveBeenCalledWith( phase3Output.results, options, phase3Output.quotaExceeded ); // Check final output expect(result).toEqual(expectedPhase4Output); // Compare with the locally defined expected output expect(result.summary.candidatesFound).toBe(phase1Output.length); expect(result.summary.candidatesAnalyzed).toBe(phase3Output.results.length); expect(result.summary.apiCreditsUsed).toBe(100); }); it("should correctly track and report API credit usage", async () => { const options: FindConsistentOutlierChannelsOptions = { query: "api test", channelAge: "ESTABLISHED", consistencyLevel: "HIGH", outlierMagnitude: "STRONG", maxResults: 5, }; const expectedApiCredits = 123; // Mock phase functions to allow execution flow mockExecuteInitialCandidateSearch.mockResolvedValue([]); mockExecuteChannelPreFiltering.mockResolvedValue([]); mockExecuteDeepConsistencyAnalysis.mockResolvedValue({ results: [], quotaExceeded: false, }); mockFormatAndRankAnalysisResults.mockImplementation(() => { // Return a basic structure for phase 4, the important part is the summary update later return { status: "COMPLETED_SUCCESSFULLY", summary: { candidatesFound: 0, candidatesAnalyzed: 0, apiCreditsUsed: 0, }, // Placeholder, will be overridden results: [], }; }); mockVideoManagement.getApiCreditsUsed.mockReturnValue(expectedApiCredits); const result = await nicheAnalyzerService.findConsistentOutlierChannels(options); expect(mockVideoManagement.resetApiCreditsUsed).toHaveBeenCalledTimes(1); // Ensure reset is called before other VideoManagement potential calls within phases (though phases are mocked here) // A more robust way if phases weren't fully mocked would be to check order of calls. // For this test, checking times is sufficient given the mocks. expect(mockVideoManagement.getApiCreditsUsed).toHaveBeenCalledTimes(1); expect(result.summary.apiCreditsUsed).toBe(expectedApiCredits); }); it("should stop and re-throw an error descriptively if a middle phase fails", async () => { const options: FindConsistentOutlierChannelsOptions = { query: "error test", channelAge: "NEW", consistencyLevel: "MODERATE", outlierMagnitude: "STANDARD", maxResults: 10, }; const originalErrorMessage = "Phase 2 exploded!"; const expectedErrorMessage = `Failed to find consistent outlier channels: ${originalErrorMessage}`; mockExecuteInitialCandidateSearch.mockResolvedValue([ "channel1", "channel2", ]); mockExecuteChannelPreFiltering.mockRejectedValue( new Error(originalErrorMessage) ); await expect( nicheAnalyzerService.findConsistentOutlierChannels(options) ).rejects.toThrow(expectedErrorMessage); // Ensure Phase 1 was called expect(mockExecuteInitialCandidateSearch).toHaveBeenCalledWith( options, mockVideoManagement ); // Ensure Phase 2 (the failing one) was called expect(mockExecuteChannelPreFiltering).toHaveBeenCalledWith( ["channel1", "channel2"], options, mockVideoManagement, mockNicheRepository ); // Ensure subsequent phases were NOT called expect(mockExecuteDeepConsistencyAnalysis).not.toHaveBeenCalled(); expect(mockFormatAndRankAnalysisResults).not.toHaveBeenCalled(); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kirbah/mcp-youtube'

If you have feedback or need assistance with the MCP directory API, please join our Discord server