Skip to main content
Glama
by wei
hn-api.test.ts7.36 kB
/** * Integration Tests for HackerNews API Client * * These tests make real API calls to the HackerNews Algolia API. * They verify that the client correctly handles API responses and errors. */ import { describe, expect, it } from "vitest"; import { HNAPIClient } from "../../src/services/hn-api.js"; // Use a longer timeout for integration tests with real API const TEST_TIMEOUT = 10000; describe( "HNAPIClient - Real API Integration", () => { const client = new HNAPIClient(); describe("search", () => { it("should return search results for a valid query", async () => { const result = await client.search({ query: "JavaScript", page: 0, hitsPerPage: 10, }); expect(result.hits).toBeInstanceOf(Array); expect(result.nbHits).toBeGreaterThan(0); expect(result.page).toBe(0); expect(result.hitsPerPage).toBe(10); expect(result.query).toBe("JavaScript"); }); it("should filter by tags", async () => { const result = await client.search({ query: "Python", tags: ["story"], hitsPerPage: 5, }); expect(result.hits).toBeInstanceOf(Array); // All results should have 'story' tag for (const hit of result.hits) { expect(hit._tags).toContain("story"); } }); it("should apply numeric filters", async () => { const result = await client.search({ query: "AI", numericFilters: ["points>=100"], hitsPerPage: 10, }); expect(result.hits).toBeInstanceOf(Array); // All results should have at least 100 points for (const hit of result.hits) { if (hit.points !== null) { expect(hit.points).toBeGreaterThanOrEqual(100); } } }); it("should handle pagination", async () => { const page0 = await client.search({ query: "JavaScript", page: 0, hitsPerPage: 5, }); const page1 = await client.search({ query: "JavaScript", page: 1, hitsPerPage: 5, }); expect(page0.hits).toHaveLength(5); expect(page1.hits).toHaveLength(5); // Results should be different expect(page0.hits[0]?.objectID).not.toBe(page1.hits[0]?.objectID); }); it("should handle empty query results", async () => { const result = await client.search({ query: "veryrandomstringthatwontmatch12345xyz", hitsPerPage: 10, }); expect(result.hits).toBeInstanceOf(Array); // May have zero hits expect(result.nbHits).toBeGreaterThanOrEqual(0); }); }); describe("searchByDate", () => { it("should return results sorted by date", async () => { const result = await client.searchByDate({ query: "", tags: ["story"], hitsPerPage: 10, }); expect(result.hits).toBeInstanceOf(Array); expect(result.hits.length).toBeGreaterThan(0); // Check that results are sorted by date (newest first) for (let i = 1; i < result.hits.length; i++) { const prevDate = result.hits[i - 1]?.created_at_i ?? 0; const currDate = result.hits[i]?.created_at_i ?? 0; expect(prevDate).toBeGreaterThanOrEqual(currDate); } }); it("should work with empty query", async () => { const result = await client.searchByDate({ query: "", hitsPerPage: 5, }); expect(result.hits).toBeInstanceOf(Array); expect(result.hits.length).toBeGreaterThan(0); }); it("should filter by tags", async () => { const result = await client.searchByDate({ query: "", tags: ["comment"], hitsPerPage: 5, }); expect(result.hits).toBeInstanceOf(Array); for (const hit of result.hits) { expect(hit._tags).toContain("comment"); } }); }); describe("getItem", () => { it("should fetch a known story with its metadata", async () => { // Using a well-known HN post (the first one) const result = await client.getItem("1"); expect(result).toBeDefined(); expect(result.id).toBe("1"); expect(result.type).toBeDefined(); expect(result.author).toBeDefined(); }); it( "should include nested children for items with comments", async () => { // Using a story that likely has comments (recent popular story) // Note: This test may need adjustment based on what's available const searchResult = await client.search({ query: "", tags: ["story", "front_page"], numericFilters: ["num_comments>10"], hitsPerPage: 1, }); if (searchResult.hits.length > 0) { const itemId = searchResult.hits[0]?.objectID; if (itemId) { const result = await client.getItem(itemId); expect(result.id).toBe(itemId); expect(result.children).toBeInstanceOf(Array); // Should have at least some comments expect(result.children.length).toBeGreaterThan(0); } } }, TEST_TIMEOUT ); it("should throw error for non-existent item", async () => { // Using a very high number that likely doesn't exist await expect(client.getItem("999999999999")).rejects.toThrow(); }); }); describe("getUser", () => { it("should fetch a known user profile", async () => { // Paul Graham is a well-known HN user const result = await client.getUser("pg"); expect(result.username).toBe("pg"); expect(result.karma).toBeGreaterThan(0); // about field is optional if (result.about) { expect(typeof result.about).toBe("string"); } }); it("should include optional about field", async () => { const result = await client.getUser("pg"); // about can be null or string if (result.about !== null) { expect(typeof result.about).toBe("string"); } }); it("should throw error for non-existent user", async () => { await expect(client.getUser("thisuserdefinitelydoesnotexist12345xyz")).rejects.toThrow(); }); }); describe("Error Handling", () => { it("should handle network timeout", async () => { // Create client with very short timeout const shortTimeoutClient = new HNAPIClient(undefined, 1); await expect( shortTimeoutClient.search({ query: "test", }) ).rejects.toThrow(/timeout/i); }); it("should handle invalid API responses gracefully", async () => { // Create client with invalid base URL const invalidClient = new HNAPIClient("https://invalid.example.com"); await expect( invalidClient.search({ query: "test", }) ).rejects.toThrow(); }); }); describe("Query String Building", () => { it("should handle multiple tags", async () => { const result = await client.search({ query: "", tags: ["story", "show_hn"], hitsPerPage: 5, }); expect(result.hits).toBeInstanceOf(Array); }); it("should handle multiple numeric filters", async () => { const result = await client.search({ query: "AI", numericFilters: ["points>=50", "num_comments>=10"], hitsPerPage: 5, }); expect(result.hits).toBeInstanceOf(Array); }); it("should handle all parameters combined", async () => { const result = await client.search({ query: "JavaScript", tags: ["story"], numericFilters: ["points>=100"], page: 0, hitsPerPage: 5, }); expect(result.hits).toBeInstanceOf(Array); expect(result.page).toBe(0); expect(result.hitsPerPage).toBe(5); }); }); }, { timeout: TEST_TIMEOUT } );

Latest Blog Posts

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/wei/hn-mcp-server'

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