diff --git a/src/main/services/erp/erp-auth.ts b/src/main/services/erp/erp-auth.ts new file mode 100644 index 0000000..2ded586 --- /dev/null +++ b/src/main/services/erp/erp-auth.ts @@ -0,0 +1,93 @@ +import { chromium, type Browser, type BrowserContext, type Page } from 'playwright'; +import { ERP_LOCATORS } from './locators'; +import type { ErpConfig, ErpSession } from '../../types/erp.types'; + +/** + * ERP Authentication Service + * Manages login session and browser lifecycle + */ +export class ErpAuthService { + private config: ErpConfig; + private session: ErpSession | null = null; + + constructor(config: ErpConfig) { + this.config = config; + } + + /** + * Login to ERP system and establish session + */ + async login(): Promise { + if (this.session?.isLoggedIn) { + return this.session; + } + + // Launch browser + const browser = await chromium.launch({ + headless: false, // Set to true for production + slowMo: 100, // Slow down for debugging + }); + + const context = await browser.newContext({ + acceptDownloads: true, + viewport: { width: 1920, height: 1080 }, + }); + + const page = await context.newPage(); + + // Navigate to login page + await page.goto(this.config.url); + + // Wait for login form + await page.waitForSelector(ERP_LOCATORS.login.usernameInput); + + // Fill credentials + await page.fill(ERP_LOCATORS.login.usernameInput, this.config.username); + await page.fill(ERP_LOCATORS.login.passwordInput, this.config.password); + + // Submit login + await page.click(ERP_LOCATORS.login.submitButton); + + // Wait for main page to load + await page.waitForURL(`${this.config.url}/**`); + await page.waitForSelector(ERP_LOCATORS.main.mainIframe, { timeout: 10000 }); + + // Create session + this.session = { + browser, + context, + page, + isLoggedIn: true, + }; + + return this.session; + } + + /** + * Close browser and cleanup session + */ + async close(): Promise { + if (this.session) { + await this.session.context.close(); + await this.session.browser.close(); + this.session = null; + } + } + + /** + * Get current session (must be logged in first) + */ + getSession(): ErpSession { + if (!this.session?.isLoggedIn) { + throw new Error('Not logged in. Call login() first.'); + } + return this.session; + } + + /** + * Check if session is active + */ + isActive(): boolean { + return this.session?.isLoggedIn ?? false; + } +} diff --git a/tests/integration/erp-auth.test.ts b/tests/integration/erp-auth.test.ts new file mode 100644 index 0000000..ae454e6 --- /dev/null +++ b/tests/integration/erp-auth.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { ErpAuthService } from '../../src/main/services/erp/erp-auth'; +import type { ErpConfig } from '../../src/main/types/erp.types'; + +describe('ERP Authentication Service (Integration)', () => { + let authService: ErpAuthService; + const config: ErpConfig = { + url: process.env.ERP_URL || '', + username: process.env.ERP_USERNAME || '', + password: process.env.ERP_PASSWORD || '', + }; + + beforeAll(() => { + if (!config.url || !config.username || !config.password) { + throw new Error('Missing ERP credentials in environment variables'); + } + authService = new ErpAuthService(config); + }); + + it('should login successfully', async () => { + const session = await authService.login(); + + expect(session).toBeDefined(); + expect(session.browser).toBeDefined(); + expect(session.context).toBeDefined(); + expect(session.page).toBeDefined(); + expect(session.isLoggedIn).toBe(true); + }, 30000); + + it('should navigate to main page after login', async () => { + const session = await authService.login(); + + const url = session.page.url(); + expect(url).toContain(config.url); + }, 30000); + + afterAll(async () => { + await authService?.close(); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts index 39aeacb..72646bb 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,8 +1,9 @@ import { beforeAll, afterAll } from 'vitest'; import dotenv from 'dotenv'; +import path from 'path'; -// Load environment variables -dotenv.config({ path: '.env' }); +// Load environment variables from project root +dotenv.config({ path: path.resolve(process.cwd(), '.env') }); beforeAll(async () => { // Global test setup diff --git a/tests/unit/erp-auth.unit.test.ts b/tests/unit/erp-auth.unit.test.ts new file mode 100644 index 0000000..cb99d9c --- /dev/null +++ b/tests/unit/erp-auth.unit.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { ErpAuthService } from '../../src/main/services/erp/erp-auth'; +import type { ErpConfig } from '../../src/main/types/erp.types'; + +describe('ERP Authentication Service (Unit)', () => { + describe('Session Management', () => { + it('should create service instance with config', () => { + const config: ErpConfig = { + url: 'https://test.example.com', + username: 'testuser', + password: 'testpass', + }; + + const service = new ErpAuthService(config); + + expect(service).toBeDefined(); + expect(service.isActive()).toBe(false); + }); + + it('should throw error when getting session before login', () => { + const config: ErpConfig = { + url: 'https://test.example.com', + username: 'testuser', + password: 'testpass', + }; + + const service = new ErpAuthService(config); + + expect(() => service.getSession()).toThrow('Not logged in. Call login() first.'); + }); + + it('should report inactive status before login', () => { + const config: ErpConfig = { + url: 'https://test.example.com', + username: 'testuser', + password: 'testpass', + }; + + const service = new ErpAuthService(config); + + expect(service.isActive()).toBe(false); + }); + }); + + describe('Close Method', () => { + it('should handle close when no session exists', async () => { + const config: ErpConfig = { + url: 'https://test.example.com', + username: 'testuser', + password: 'testpass', + }; + + const service = new ErpAuthService(config); + + // Should not throw when closing without session + await expect(service.close()).resolves.toBeUndefined(); + }); + }); +});