import { beforeEach, describe, expect, it, vi } from "vitest"; // Mock environment variables BEFORE importing module process.env.N8N_API_BASE_URL = "https://n8n.test/api/v1"; process.env.N8N_API_KEY = "test-api-key"; process.env.N8N_GOOGLE_CREDENTIAL_ID = "cred-123"; import { createGoogleCredential, deleteCredential, updateCredentialTokens, upsertGoogleCredential, } from "../n8n-api"; // Mock global fetch global.fetch = vi.fn(); describe("n8n-api", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("deleteCredential", () => { it("deve deletar credencial com sucesso", async () => { (global.fetch as any).mockResolvedValueOnce({ ok: true, }); const result = await deleteCredential("cred-123"); expect(result).toBe(true); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining("/credentials/cred-123"), expect.objectContaining({ method: "DELETE", headers: expect.objectContaining({ "X-N8N-API-KEY": expect.any(String), }), }), ); }); it("deve retornar false quando credencial não existe", async () => { (global.fetch as any).mockResolvedValueOnce({ ok: false, statusText: "Not Found", }); const result = await deleteCredential("invalid-id"); expect(result).toBe(false); }); }); describe("upsertGoogleCredential", () => { it("deve deletar credencial existente e criar nova (DELETE + POST)", async () => { const mockResponse = { id: "new-cred-456", name: "refugio", type: "googleCalendarOAuth2Api", }; // DELETE sucede (global.fetch as any).mockResolvedValueOnce({ ok: true, }); // POST sucede (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await upsertGoogleCredential( "refugio", "client-id", "client-secret", "https://www.googleapis.com/auth/calendar.events", "access-token", "refresh-token", 3599, ); expect(result.id).toBe("new-cred-456"); expect(global.fetch).toHaveBeenCalledTimes(2); // Primeiro DELETE expect(global.fetch).toHaveBeenNthCalledWith( 1, expect.stringContaining("/credentials/cred-123"), expect.objectContaining({ method: "DELETE" }), ); // Depois POST expect(global.fetch).toHaveBeenNthCalledWith( 2, expect.stringContaining("/credentials"), expect.objectContaining({ method: "POST" }), ); }); it("deve criar credencial via POST quando não existe N8N_GOOGLE_CREDENTIAL_ID", async () => { // Temporariamente remove credential ID const originalId = process.env.N8N_GOOGLE_CREDENTIAL_ID; delete process.env.N8N_GOOGLE_CREDENTIAL_ID; const mockResponse = { id: "new-cred-789", name: "refugio", type: "googleCalendarOAuth2Api", }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await upsertGoogleCredential( "refugio", "client-id", "client-secret", "https://www.googleapis.com/auth/calendar.events", ); expect(result.id).toBe("new-cred-789"); expect(global.fetch).toHaveBeenCalledTimes(1); // Apenas POST expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining("/credentials"), expect.objectContaining({ method: "POST" }), ); // Restaura credential ID process.env.N8N_GOOGLE_CREDENTIAL_ID = originalId; }); }); describe("createGoogleCredential", () => { it("deve criar credencial com dados corretos", async () => { const mockResponse = { id: "new-cred-123", name: "refugio", type: "googleOAuth2Api", }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await createGoogleCredential( "refugio", "client-id", "client-secret", "https://www.googleapis.com/auth/calendar.events", ); expect(result.id).toBe("new-cred-123"); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining("/credentials"), expect.objectContaining({ method: "POST", headers: expect.objectContaining({ "X-N8N-API-KEY": expect.any(String), "Content-Type": "application/json", }), body: expect.stringContaining('"name":"refugio"'), }), ); }); }); describe("updateCredentialTokens", () => { it("deve atualizar tokens com sucesso", async () => { const mockResponse = { id: "cred-123", name: "refugio", }; (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await updateCredentialTokens( "cred-123", "access-token", "refresh-token", 3599, ); expect(result.id).toBe("cred-123"); expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining("/credentials/cred-123"), expect.objectContaining({ method: "PUT", body: expect.stringContaining('"access_token":"access-token"'), }), ); }); it("deve lançar erro quando credencial não existe (404)", async () => { const mockError = { message: "Credential not found" }; (global.fetch as any).mockResolvedValueOnce({ ok: false, json: async () => mockError, }); await expect( updateCredentialTokens("invalid-id", "token", "refresh", 3599), ).rejects.toThrow("Failed to update credential"); }); }); describe("createGoogleCredential - error scenarios", () => { it("deve lançar erro quando schema está inválido (400)", async () => { const mockError = { message: "Validation error", errors: ["Missing required field: clientId"], }; (global.fetch as any).mockResolvedValueOnce({ ok: false, json: async () => mockError, }); await expect( createGoogleCredential("test", "", "", "scopes"), ).rejects.toThrow("Failed to create credential"); }); it("deve lançar erro quando API key é inválida (401)", async () => { const mockError = { message: "Unauthorized" }; (global.fetch as any).mockResolvedValueOnce({ ok: false, status: 401, json: async () => mockError, }); await expect( createGoogleCredential("test", "id", "secret", "scopes"), ).rejects.toThrow("Failed to create credential"); }); }); describe("upsertGoogleCredential - error scenarios", () => { it("deve lançar erro quando POST falha após DELETE", async () => { const mockError = { message: "Internal server error" }; // DELETE sucede (global.fetch as any).mockResolvedValueOnce({ ok: true, }); // POST falha (global.fetch as any).mockResolvedValueOnce({ ok: false, json: async () => mockError, }); await expect( upsertGoogleCredential( "test", "id", "secret", "scopes", "token", "refresh", 3599, ), ).rejects.toThrow("Failed to create credential"); }); it("deve continuar tentando criar mesmo se DELETE falhar", async () => { const mockResponse = { id: "new-cred-999", name: "refugio", type: "googleCalendarOAuth2Api", }; // DELETE falha (credencial não existe) (global.fetch as any).mockResolvedValueOnce({ ok: false, statusText: "Not Found", }); // POST sucede (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockResponse, }); const result = await upsertGoogleCredential( "refugio", "client-id", "client-secret", "https://www.googleapis.com/auth/calendar.events", ); expect(result.id).toBe("new-cred-999"); expect(global.fetch).toHaveBeenCalledTimes(2); }); }); });