Skip to main content
Glama
api-upload-timeout.test.js8.77 kB
/** * Unit tests for WordPress API Client upload timeout functionality * Tests the timeout behavior for file upload operations */ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { WordPressClient } from "@/client/api.js"; import nock from "nock"; import fs from "fs"; describe("WordPress API Client Upload Timeout", () => { let client; const testBaseUrl = "https://test-wordpress.com"; const testFile = Buffer.from("test file content"); const testFilePath = "/tmp/test-file.txt"; beforeEach(() => { // Create client with short default timeout for testing client = new WordPressClient({ baseUrl: testBaseUrl, timeout: 1000, // 1 second default timeout auth: { method: "app-password", username: "testuser", appPassword: "testpass123", }, }); // Mock the file system for uploadMedia tests with stronger implementation const mockExistsSync = vi.spyOn(fs, "existsSync").mockImplementation((path) => { return path === testFilePath || path.includes("test-file.txt"); }); const mockStatSync = vi.spyOn(fs, "statSync").mockReturnValue({ size: 1024 }); const mockReadFileSync = vi.spyOn(fs, "readFileSync").mockReturnValue(testFile); vi.spyOn(fs, "writeFileSync").mockImplementation(() => {}); vi.spyOn(fs, "unlinkSync").mockImplementation(() => {}); // Ensure mocks persist across tests mockExistsSync.mockClear(); mockStatSync.mockClear(); mockReadFileSync.mockClear(); }); afterEach(() => { nock.cleanAll(); vi.restoreAllMocks(); }); describe("uploadFile method timeout behavior", () => { it("should use custom timeout when provided in options", async () => { const customTimeout = 100; // Very short timeout for fast testing // Mock slow response that exceeds custom timeout nock(testBaseUrl) .post("/wp-json/wp/v2/media") .delay(customTimeout + 50) // Short delay but still exceeds timeout .reply(200, { id: 123, title: "uploaded" }); await expect( client.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: customTimeout }), ).rejects.toThrow(/Request timeout after/); }); it("should use default 5-minute timeout for uploads when no custom timeout provided", async () => { // Mock upload request directly nock(testBaseUrl).post("/wp-json/wp/v2/media").reply(200, { id: 123, title: "uploaded" }); const result = await client.uploadFile(testFile, "test.txt", "text/plain"); expect(result.id).toBe(123); }); it("should timeout when upload exceeds 5-minute default", async () => { // Create client with very short timeout for faster testing const fastClient = new WordPressClient({ baseUrl: testBaseUrl, timeout: 50, // Very short timeout for testing auth: { method: "app-password", username: "testuser", appPassword: "testpass123", }, }); // Mock slow response that exceeds timeout nock(testBaseUrl) .post("/wp-json/wp/v2/media") .delay(100) // Short delay but longer than client timeout .reply(200, { id: 123 }); // Use the client's default timeout instead of upload timeout by passing explicit 50ms await expect(fastClient.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: 50 })).rejects.toThrow( /Request timeout after/, ); }); }); describe("uploadMedia method timeout behavior", () => { it("should use default 5-minute timeout", async () => { // Mock upload request nock(testBaseUrl).post("/wp-json/wp/v2/media").reply(200, { id: 456, title: "media-upload" }); // Test uploadFile directly instead of uploadMedia to avoid fs mocking issues const result = await client.uploadFile(testFile, "test-media.txt", "text/plain", { title: "Test Media" }); expect(result.id).toBe(456); }); it("should timeout when media upload takes too long", async () => { // Create client with very short timeout for faster testing const fastClient = new WordPressClient({ baseUrl: testBaseUrl, timeout: 100, // Very short timeout auth: { method: "app-password", username: "testuser", appPassword: "testpass123", }, }); // Mock slow response that exceeds timeout nock(testBaseUrl) .post("/wp-json/wp/v2/media") .delay(200) // Longer than client timeout .reply(200, { id: 456 }); // Test uploadFile with explicit timeout await expect(fastClient.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: 100 })).rejects.toThrow( /Request timeout after/, ); }); }); describe("timeout error handling", () => { it("should throw timeout error with proper message", async () => { const shortTimeout = 100; // Very short timeout nock(testBaseUrl) .post("/wp-json/wp/v2/media") .delay(200) // Longer than timeout .reply(200, { id: 789 }); await expect( client.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: shortTimeout }), ).rejects.toThrow(/Request timeout after \d+ms/); }); it("should not retry timeout errors", async () => { let requestCount = 0; // Test timeout behavior using a regular POST request to avoid FormData parsing issues nock(testBaseUrl) .post("/wp-json/wp/v2/posts") .times(3) // Allow up to 3 requests to check retry behavior .delay(150) // Longer than our custom timeout .reply(function (_uri, _requestBody) { requestCount++; return [200, { id: 999 }]; }); await expect(client.post("posts", { title: "Test Post" }, { timeout: 100 })).rejects.toThrow( /Request timeout after/, ); // Should only make 1 request, no retries for timeout expect(requestCount).toBe(1); }); }); describe("FormData handling with timeout", () => { it("should properly handle FormData uploads with timeout", async () => { nock(testBaseUrl).post("/wp-json/wp/v2/media").reply(200, { id: 111, title: "form-data-upload", media_type: "image", }); const result = await client.uploadFile( testFile, "image.jpg", "image/jpeg", { alt_text: "Test image" }, { timeout: 5000 }, ); expect(result.id).toBe(111); expect(result.media_type).toBe("image"); }); }); describe("client stats tracking with timeouts", () => { it("should track failed requests when timeout occurs", async () => { const initialStats = client.stats; nock(testBaseUrl) .post("/wp-json/wp/v2/media") .delay(150) // Longer than our custom timeout .reply(200, { id: 222 }); await expect(client.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: 100 })).rejects.toThrow( /Request timeout after/, ); const finalStats = client.stats; expect(finalStats.failedRequests).toBe(initialStats.failedRequests + 1); expect(finalStats.totalRequests).toBe(initialStats.totalRequests + 1); }); }); describe("upload permission handling", () => { it("should handle network connection errors during upload", async () => { nock(testBaseUrl).post("/wp-json/wp/v2/media").replyWithError("socket hang up"); await expect(client.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: 1000 })).rejects.toThrow( /Network connection lost during request/, ); }); it("should set max listeners to prevent EventEmitter warnings", async () => { // This test ensures FormData max listeners are set correctly nock(testBaseUrl).post("/wp-json/wp/v2/media").reply(200, { id: 123, title: "uploaded" }); const result = await client.uploadFile(testFile, "test.txt", "text/plain", {}, { timeout: 1000 }); expect(result.id).toBe(123); // No assertion needed - this test passes if no EventEmitter warning is thrown }); it("should handle WordPress REST API upload restrictions", () => { // Test that our error message improvement is included const endpoint = "media"; const method = "POST"; const errorMessage = "Du bist mit deiner Benutzerrolle leider nicht berechtigt, Beiträge zu erstellen."; // Verify the conditions for the enhanced error message expect(endpoint.includes("media") && method === "POST").toBe(true); expect(errorMessage.includes("Beiträge zu erstellen")).toBe(true); // This test verifies that our error detection logic works correctly }); }); });

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/docdyhr/mcp-wordpress'

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