Dashboard-Automatizase/lib/__tests__/n8n-api.test.ts
Luis 1391fe6216 feat: enhance Google Calendar integration and update Dockerfile for environment variables
- Updated Dockerfile to include hardcoded environment variables for Next.js build.
- Enhanced Google Calendar API integration by extracting user email from id_token and adding scopes for OpenID and email access.
- Modified credential management to delete existing credentials before creating new ones in n8n.
- Updated dashboard to display connected Google Calendar email and credential details.

Story: 4.2 - Melhorar integração com Google Calendar e atualizar Dockerfile

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2025-10-12 21:16:21 -03:00

304 lines
8.2 KiB
TypeScript

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);
});
});
});