feat: lancer manuellement les imports SFR
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
createEmailImportAccountController,
|
||||||
createImapFlowOptions,
|
createImapFlowOptions,
|
||||||
type EmailImportConfig,
|
type EmailImportConfig,
|
||||||
|
isEmailImportServiceRunning,
|
||||||
|
startConfiguredEmailImportService,
|
||||||
|
stopEmailImportService,
|
||||||
validateEmailImportConfiguration,
|
validateEmailImportConfiguration,
|
||||||
} from "./emailImportService";
|
} from "./emailImportService";
|
||||||
|
|
||||||
@@ -76,3 +80,71 @@ describe("validateEmailImportConfiguration", () => {
|
|||||||
expect(error).toBe("Configuration IMAP incomplète");
|
expect(error).toBe("Configuration IMAP incomplète");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("startConfiguredEmailImportService", () => {
|
||||||
|
it("ne crée aucune planification si le test IMAP préalable échoue", async () => {
|
||||||
|
const userId = 901;
|
||||||
|
stopEmailImportService(userId);
|
||||||
|
|
||||||
|
const result = await startConfiguredEmailImportService(
|
||||||
|
{ ...baseConfig, userId, authMode: "basic" },
|
||||||
|
5,
|
||||||
|
async () => ({
|
||||||
|
success: false,
|
||||||
|
message: "Authentification IMAP refusée.",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
success: false,
|
||||||
|
message: "Authentification IMAP refusée.",
|
||||||
|
});
|
||||||
|
expect(isEmailImportServiceRunning(userId)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("démarre après un test IMAP concluant et exécute le contrôle initial", async () => {
|
||||||
|
const userId = 902;
|
||||||
|
const runEmailCheck = vi.fn(async () => undefined);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await startConfiguredEmailImportService(
|
||||||
|
{ ...baseConfig, userId, authMode: "basic" },
|
||||||
|
5,
|
||||||
|
async () => ({ success: true, message: "Connexion IMAP réussie." }),
|
||||||
|
runEmailCheck,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
success: true,
|
||||||
|
message: "Service de réception e-mail démarré",
|
||||||
|
});
|
||||||
|
expect(isEmailImportServiceRunning(userId)).toBe(true);
|
||||||
|
expect(runEmailCheck).toHaveBeenCalledWith(expect.objectContaining({ userId }));
|
||||||
|
} finally {
|
||||||
|
stopEmailImportService(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("createEmailImportAccountController", () => {
|
||||||
|
it("cible exclusivement le compte sélectionné par un administrateur", async () => {
|
||||||
|
const start = vi.fn(async () => ({ success: true, message: "Service de réception e-mail démarré" }));
|
||||||
|
const stop = vi.fn();
|
||||||
|
const isRunning = vi.fn(() => true);
|
||||||
|
const controller = createEmailImportAccountController({ start, stop, isRunning });
|
||||||
|
|
||||||
|
await expect(controller.startAccount(42)).resolves.toEqual({
|
||||||
|
success: true,
|
||||||
|
message: "Service de réception e-mail démarré",
|
||||||
|
});
|
||||||
|
expect(start).toHaveBeenCalledWith(42);
|
||||||
|
|
||||||
|
expect(controller.stopAccount(42)).toEqual({
|
||||||
|
success: true,
|
||||||
|
message: "Service de réception e-mail arrêté",
|
||||||
|
});
|
||||||
|
expect(stop).toHaveBeenCalledWith(42);
|
||||||
|
expect(controller.accountStatus(42)).toEqual({ isRunning: true });
|
||||||
|
expect(isRunning).toHaveBeenCalledWith(42);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -34,6 +34,17 @@ export interface EmailImportConfig {
|
|||||||
|
|
||||||
export type EmailImportTrigger = "manual" | "automatic";
|
export type EmailImportTrigger = "manual" | "automatic";
|
||||||
|
|
||||||
|
export type EmailImportServiceStartResult = {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EmailConnectionTester = (
|
||||||
|
config: EmailImportConfig,
|
||||||
|
) => Promise<{ success: boolean; message: string }>;
|
||||||
|
|
||||||
|
type EmailCheckRunner = (config: EmailImportConfig) => Promise<void>;
|
||||||
|
|
||||||
type EmailImportSettingsSnapshot = {
|
type EmailImportSettingsSnapshot = {
|
||||||
emailImportEnabled: number;
|
emailImportEnabled: number;
|
||||||
emailImportAddress: string | null;
|
emailImportAddress: string | null;
|
||||||
@@ -566,21 +577,63 @@ export async function testImapConnection(config: EmailImportConfig): Promise<{ s
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start email import service for a user
|
* Vérifie la boîte avant de créer la planification. Une authentification refusée
|
||||||
|
* ne doit jamais produire une boucle de tentatives automatiques sans fin.
|
||||||
*/
|
*/
|
||||||
export async function startEmailImportService(userId: number): Promise<boolean> {
|
export async function startConfiguredEmailImportService(
|
||||||
|
config: EmailImportConfig,
|
||||||
|
frequencyMinutes: number,
|
||||||
|
connectionTester: EmailConnectionTester = testImapConnection,
|
||||||
|
emailCheckRunner: EmailCheckRunner = runEmailCheckExclusive,
|
||||||
|
): Promise<EmailImportServiceStartResult> {
|
||||||
|
const connection = await connectionTester(config);
|
||||||
|
if (!connection.success) {
|
||||||
|
console.error(
|
||||||
|
`[EmailImport] Service non démarré pour user ${config.userId}: ${connection.message}`,
|
||||||
|
);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop existing service if running only after the new configuration has passed validation.
|
||||||
|
stopEmailImportService(config.userId);
|
||||||
|
|
||||||
|
const frequencyMs = Math.max(1, frequencyMinutes || 30) * 60 * 1000;
|
||||||
|
console.log(
|
||||||
|
`[EmailImport] Starting email import service for user ${config.userId} with frequency ${frequencyMinutes || 30} minutes (auth: ${config.authMode || "basic"})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Run immediately on start. The preflight above confirms that the account accepts the credentials.
|
||||||
|
emailCheckRunner(config).catch((error) => {
|
||||||
|
console.error(`[EmailImport] Error checking emails for user ${config.userId}:`, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
emailCheckRunner(config).catch((error) => {
|
||||||
|
console.error(`[EmailImport] Error checking emails for user ${config.userId}:`, error);
|
||||||
|
});
|
||||||
|
}, frequencyMs);
|
||||||
|
|
||||||
|
activeIntervals.set(config.userId, interval);
|
||||||
|
console.log(`[EmailImport] Email import service started for user ${config.userId}`);
|
||||||
|
return { success: true, message: "Service de réception e-mail démarré" };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start email import service for a user.
|
||||||
|
*/
|
||||||
|
export async function startEmailImportService(userId: number): Promise<EmailImportServiceStartResult> {
|
||||||
try {
|
try {
|
||||||
// Get user's import settings
|
// Get user's import settings
|
||||||
const settings = await getImportSettingsByUser(userId);
|
const settings = await getImportSettingsByUser(userId);
|
||||||
|
|
||||||
if (!settings || settings.emailImportEnabled !== 1) {
|
if (!settings || settings.emailImportEnabled !== 1) {
|
||||||
console.log(`[EmailImport] Email import not enabled for user ${userId}`);
|
console.log(`[EmailImport] Email import not enabled for user ${userId}`);
|
||||||
return false;
|
return { success: false, message: "Activez et enregistrez la réception automatique avant de démarrer le service." };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.emailImportAddress || !settings.emailImportHost) {
|
if (!settings.emailImportAddress || !settings.emailImportHost) {
|
||||||
console.log(`[EmailImport] Email import configuration incomplete for user ${userId}`);
|
console.log(`[EmailImport] Email import configuration incomplete for user ${userId}`);
|
||||||
return false;
|
return { success: false, message: "Configuration IMAP incomplète : adresse e-mail ou serveur IMAP manquant." };
|
||||||
}
|
}
|
||||||
|
|
||||||
const authMode = (settings as any).emailImportAuthMode as "basic" | "oauth2" || "basic";
|
const authMode = (settings as any).emailImportAuthMode as "basic" | "oauth2" || "basic";
|
||||||
@@ -588,16 +641,13 @@ export async function startEmailImportService(userId: number): Promise<boolean>
|
|||||||
// Vérification selon le mode d'auth
|
// Vérification selon le mode d'auth
|
||||||
if (authMode === "basic" && !settings.emailImportPassword) {
|
if (authMode === "basic" && !settings.emailImportPassword) {
|
||||||
console.log(`[EmailImport] Email import password missing for user ${userId}`);
|
console.log(`[EmailImport] Email import password missing for user ${userId}`);
|
||||||
return false;
|
return { success: false, message: "Mot de passe IMAP manquant." };
|
||||||
}
|
}
|
||||||
if (authMode === "oauth2" && (!settings.azureTenantId || !settings.azureClientId || !settings.azureClientSecret)) {
|
if (authMode === "oauth2" && (!settings.azureTenantId || !settings.azureClientId || !settings.azureClientSecret)) {
|
||||||
console.log(`[EmailImport] OAuth2 credentials incomplete for user ${userId}`);
|
console.log(`[EmailImport] OAuth2 credentials incomplete for user ${userId}`);
|
||||||
return false;
|
return { success: false, message: "Configuration OAuth2 IMAP incomplète : renseignez les paramètres Microsoft Azure AD." };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop existing service if running
|
|
||||||
stopEmailImportService(userId);
|
|
||||||
|
|
||||||
const config: EmailImportConfig = {
|
const config: EmailImportConfig = {
|
||||||
userId,
|
userId,
|
||||||
emailAddress: settings.emailImportAddress,
|
emailAddress: settings.emailImportAddress,
|
||||||
@@ -611,31 +661,13 @@ export async function startEmailImportService(userId: number): Promise<boolean>
|
|||||||
azureClientSecret: settings.azureClientSecret || undefined,
|
azureClientSecret: settings.azureClientSecret || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const frequencyMs = (settings.emailImportFrequency || 30) * 60 * 1000; // Convert minutes to milliseconds
|
return startConfiguredEmailImportService(
|
||||||
|
config,
|
||||||
console.log(
|
settings.emailImportFrequency || 30,
|
||||||
`[EmailImport] Starting email import service for user ${userId} with frequency ${settings.emailImportFrequency} minutes (auth: ${authMode})`
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Run immediately on start
|
|
||||||
runEmailCheckExclusive(config).catch((error) => {
|
|
||||||
console.error(`[EmailImport] Error checking emails for user ${userId}:`, error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up interval for periodic checks
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
runEmailCheckExclusive(config).catch((error) => {
|
|
||||||
console.error(`[EmailImport] Error checking emails for user ${userId}:`, error);
|
|
||||||
});
|
|
||||||
}, frequencyMs);
|
|
||||||
|
|
||||||
activeIntervals.set(userId, interval);
|
|
||||||
console.log(`[EmailImport] Email import service started for user ${userId}`);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[EmailImport] Error starting email import service for user ${userId}:`, error);
|
console.error(`[EmailImport] Error starting email import service for user ${userId}:`, error);
|
||||||
return false;
|
return { success: false, message: "Erreur technique lors du démarrage de la réception e-mail." };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,6 +690,33 @@ export function isEmailImportServiceRunning(userId: number): boolean {
|
|||||||
return activeIntervals.has(userId);
|
return activeIntervals.has(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmailImportAccountControllerDependencies = {
|
||||||
|
start: (userId: number) => Promise<EmailImportServiceStartResult>;
|
||||||
|
stop: (userId: number) => void;
|
||||||
|
isRunning: (userId: number) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrôles administratifs d'une boîte source. Les paramètres restent rattachés
|
||||||
|
* à leur compte propriétaire ; seul le service planifié est administré ici.
|
||||||
|
*/
|
||||||
|
export function createEmailImportAccountController(
|
||||||
|
dependencies: EmailImportAccountControllerDependencies = {
|
||||||
|
start: startEmailImportService,
|
||||||
|
stop: stopEmailImportService,
|
||||||
|
isRunning: isEmailImportServiceRunning,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
startAccount: (userId: number) => dependencies.start(userId),
|
||||||
|
stopAccount: (userId: number): EmailImportServiceStartResult => {
|
||||||
|
dependencies.stop(userId);
|
||||||
|
return { success: true, message: "Service de réception e-mail arrêté" };
|
||||||
|
},
|
||||||
|
accountStatus: (userId: number) => ({ isRunning: dependencies.isRunning(userId) }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function triggerConfiguredEmailCheck(
|
async function triggerConfiguredEmailCheck(
|
||||||
userId: number,
|
userId: number,
|
||||||
requireAutomaticEnabled: boolean,
|
requireAutomaticEnabled: boolean,
|
||||||
|
|||||||
Reference in New Issue
Block a user