From 5a046110f0924466f7df4d8adebee0cfff1f6ebb Mon Sep 17 00:00:00 2001 From: djuka Date: Wed, 4 Mar 2026 07:50:43 +0000 Subject: [PATCH] Dodao Playwright E2E testove i azurirao CLAUDE.md - 7 E2E test fajlova (54 testa ukupno): - login.spec.ts: prijava, pogresna lozinka, redirect bez sesije - dashboard.spec.ts: statistike, navbar, odjava, root redirect - licenses.spec.ts: tabela, filteri, pretraga - license-crud.spec.ts: forma, kreiranje LT/ARV/ESIR licence - license-detail.spec.ts: informacije, aktivacije, audit, revoke, force release - audit.spec.ts: audit log stranica, kolone, generisanje zapisa - api-client.spec.ts: activate, deactivate, validate, revoke flow, API key auth - CLAUDE.md: dodato pravilo o rigoroznom testiranju Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 79 ++++++++++ package.json | 22 +++ playwright.config.ts | 11 ++ tests/e2e/api-client.spec.ts | 256 +++++++++++++++++++++++++++++++ tests/e2e/audit.spec.ts | 59 +++++++ tests/e2e/dashboard.spec.ts | 47 ++++++ tests/e2e/license-crud.spec.ts | 132 ++++++++++++++++ tests/e2e/license-detail.spec.ts | 117 ++++++++++++++ tests/e2e/licenses.spec.ts | 61 ++++++++ tests/e2e/login.spec.ts | 41 +++++ 10 files changed, 825 insertions(+) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100644 tests/e2e/api-client.spec.ts create mode 100644 tests/e2e/audit.spec.ts create mode 100644 tests/e2e/dashboard.spec.ts create mode 100644 tests/e2e/license-crud.spec.ts create mode 100644 tests/e2e/license-detail.spec.ts create mode 100644 tests/e2e/licenses.spec.ts create mode 100644 tests/e2e/login.spec.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..36b0dcb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,79 @@ +{ + "name": "dal-license-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dal-license-server", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.58.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..408eaca --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "dal-license-server", + "version": "1.0.0", + "description": "Univerzalni licencni server za sve DAL proizvode (ESIR, ARV, Light-Ticket).", + "main": "index.js", + "directories": { + "doc": "docs" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "http://djuka:dzabulan1@localhost:3000/djuka/dal-license-server.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.58.2" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..89475d0 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/e2e', + timeout: 30000, + retries: 0, + use: { + baseURL: 'http://localhost:8090', + headless: true, + }, +}); diff --git a/tests/e2e/api-client.spec.ts b/tests/e2e/api-client.spec.ts new file mode 100644 index 0000000..2f819fd --- /dev/null +++ b/tests/e2e/api-client.spec.ts @@ -0,0 +1,256 @@ +import { test, expect } from '@playwright/test'; + +const BASE = 'http://localhost:8090'; +const API_KEY = 'dev-api-key-minimum-32-characters-long'; + +test.describe('Klijentski API', () => { + let licenseKey: string; + + test.beforeAll(async ({ request }) => { + // Kreiraj licencu za testove + const res = await request.post(`${BASE}/api/v1/admin/licenses`, { + headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }, + data: { + product_id: 3, // LIGHT_TICKET + license_type: 'MONTHLY', + customer_name: 'API Test Firma', + customer_email: 'api@test.rs', + limits: { max_operators: 3 }, + features: ['TICKET_VALIDATION', 'REPORTS'], + grace_days: 30, + }, + }); + expect(res.status()).toBe(201); + const body = await res.json(); + licenseKey = body.license_key; + expect(licenseKey).toMatch(/^LT-/); + }); + + test('aktivacija licence', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/activate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:e2e-test-fingerprint-001', + app_version: '1.0.0', + os: 'linux', + hostname: 'E2E-TEST-PC', + }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.license).toBeDefined(); + expect(body.signature).toBeDefined(); + expect(body.signature).toMatch(/^RSA-SHA256:/); + expect(body.license.product).toBe('LIGHT_TICKET'); + expect(body.license.license_type).toBe('MONTHLY'); + expect(body.license.customer.name).toBe('API Test Firma'); + }); + + test('ponovna aktivacija sa istim fingerprint-om (refresh)', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/activate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:e2e-test-fingerprint-001', + app_version: '1.0.0', + os: 'linux', + hostname: 'E2E-TEST-PC', + }, + }); + expect(res.status()).toBe(200); + }); + + test('aktivacija sa drugog racunara — odbijeno', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/activate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:drugi-racunar-002', + app_version: '1.0.0', + os: 'windows', + hostname: 'DRUGI-PC', + }, + }); + expect(res.status()).toBe(400); + const body = await res.json(); + expect(body.error.code).toBe('ALREADY_ACTIVATED'); + }); + + test('validacija aktivne licence', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/validate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:e2e-test-fingerprint-001', + }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.valid).toBe(true); + expect(body.revoked).toBe(false); + }); + + test('deaktivacija licence', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/deactivate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:e2e-test-fingerprint-001', + }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.can_reactivate).toBe(true); + }); + + test('re-aktivacija sa novog racunara posle deaktivacije', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/activate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:novi-racunar-003', + app_version: '1.0.1', + os: 'windows', + hostname: 'NOVI-PC', + }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.license.machine_fingerprint).toBe('sha256:novi-racunar-003'); + }); + + test('aktivacija sa nepostojecim kljucem', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/activate`, { + data: { + license_key: 'LT-FAKE-KEY1-KEY2-KEY3', + machine_fingerprint: 'sha256:test', + app_version: '1.0.0', + os: 'linux', + hostname: 'TEST', + }, + }); + expect(res.status()).toBe(400); + const body = await res.json(); + expect(body.error.code).toBe('INVALID_KEY'); + }); + + test('validacija nepostojeceg kljuca', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/validate`, { + data: { + license_key: 'LT-NEMA-OVOG-KLUC-AAAA', + machine_fingerprint: 'sha256:test', + }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.valid).toBe(false); + }); +}); + +test.describe('Admin API autentifikacija', () => { + test('bez API kljuca — 401', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/admin/licenses`); + expect(res.status()).toBe(401); + }); + + test('pogresan API kljuc — 401', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/admin/licenses`, { + headers: { 'X-API-Key': 'pogresan-kljuc' }, + }); + expect(res.status()).toBe(401); + }); + + test('ispravan API kljuc — 200', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/admin/licenses`, { + headers: { 'X-API-Key': API_KEY }, + }); + expect(res.status()).toBe(200); + }); + + test('products endpoint', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/admin/products`, { + headers: { 'X-API-Key': API_KEY }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(Array.isArray(body)).toBe(true); + expect(body.length).toBeGreaterThanOrEqual(3); + }); + + test('stats endpoint', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/admin/stats`, { + headers: { 'X-API-Key': API_KEY }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.total_licenses).toBeDefined(); + expect(body.by_product).toBeDefined(); + }); + + test('audit endpoint', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/admin/audit`, { + headers: { 'X-API-Key': API_KEY }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(Array.isArray(body)).toBe(true); + }); + + test('health endpoint', async ({ request }) => { + const res = await request.get(`${BASE}/api/v1/health`); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.status).toBe('ok'); + }); +}); + +test.describe('Admin API — Revoke flow', () => { + let licenseId: number; + let licenseKey: string; + + test.beforeAll(async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/admin/licenses`, { + headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }, + data: { + product_id: 3, + license_type: 'MONTHLY', + customer_name: 'Revoke Flow Test', + grace_days: 30, + }, + }); + const body = await res.json(); + licenseId = body.id; + licenseKey = body.license_key; + }); + + test('revoke licence', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/admin/licenses/${licenseId}/revoke`, { + headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }, + data: { reason: 'E2E test revoke' }, + }); + expect(res.status()).toBe(200); + }); + + test('aktivacija opozvane licence — odbijeno', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/activate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:revoke-test', + app_version: '1.0.0', + os: 'linux', + hostname: 'TEST', + }, + }); + expect(res.status()).toBe(400); + const body = await res.json(); + expect(body.error.code).toBe('KEY_REVOKED'); + }); + + test('validacija opozvane licence', async ({ request }) => { + const res = await request.post(`${BASE}/api/v1/validate`, { + data: { + license_key: licenseKey, + machine_fingerprint: 'sha256:revoke-test', + }, + }); + expect(res.status()).toBe(200); + const body = await res.json(); + expect(body.valid).toBe(false); + expect(body.revoked).toBe(true); + }); +}); diff --git a/tests/e2e/audit.spec.ts b/tests/e2e/audit.spec.ts new file mode 100644 index 0000000..d9595b7 --- /dev/null +++ b/tests/e2e/audit.spec.ts @@ -0,0 +1,59 @@ +import { test, expect, Page } from '@playwright/test'; + +async function login(page: Page) { + await page.goto('/login'); + await page.fill('input[name="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await expect(page).toHaveURL(/\/dashboard/); +} + +test.describe('Audit Log', () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test('prikazuje audit log stranicu', async ({ page }) => { + await page.goto('/audit'); + await expect(page.locator('h1')).toHaveText('Audit Log'); + await expect(page.locator('table')).toBeVisible(); + }); + + test('tabela ima ispravne kolone', async ({ page }) => { + await page.goto('/audit'); + const headers = page.locator('thead th'); + await expect(headers.nth(0)).toHaveText('Vreme'); + await expect(headers.nth(1)).toHaveText('Akcija'); + await expect(headers.nth(2)).toHaveText('Licenca'); + await expect(headers.nth(3)).toHaveText('IP'); + await expect(headers.nth(4)).toHaveText('Detalji'); + }); + + test('kreiranje licence generise audit zapis', async ({ page }) => { + // Kreiraj licencu + await page.goto('/licenses/new'); + const productSelect = page.locator('select[name="product_id"]'); + const options = productSelect.locator('option'); + const count = await options.count(); + for (let i = 0; i < count; i++) { + const text = await options.nth(i).textContent(); + if (text && text.includes('LIGHT_TICKET')) { + await productSelect.selectOption({ index: i }); + break; + } + } + await page.selectOption('select[name="license_type"]', 'MONTHLY'); + await page.fill('input[name="customer_name"]', 'Audit E2E Test'); + await page.click('button:has-text("Kreiraj licencu")'); + await expect(page).toHaveURL(/\/licenses\/\d+/); + + // Proveri audit log + await page.goto('/audit'); + await expect(page.locator('.badge', { hasText: 'CREATE' }).first()).toBeVisible(); + }); + + test('navigacija na audit iz navbara', async ({ page }) => { + await page.click('a[href="/audit"]'); + await expect(page).toHaveURL(/\/audit/); + await expect(page.locator('h1')).toHaveText('Audit Log'); + }); +}); diff --git a/tests/e2e/dashboard.spec.ts b/tests/e2e/dashboard.spec.ts new file mode 100644 index 0000000..3a77928 --- /dev/null +++ b/tests/e2e/dashboard.spec.ts @@ -0,0 +1,47 @@ +import { test, expect, Page } from '@playwright/test'; + +async function login(page: Page) { + await page.goto('/login'); + await page.fill('input[name="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await expect(page).toHaveURL(/\/dashboard/); +} + +test.describe('Dashboard', () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test('prikazuje statistike po proizvodu', async ({ page }) => { + await expect(page.locator('h1')).toHaveText('Dashboard'); + await expect(page.locator('.stats-grid')).toBeVisible(); + // Treba da postoje kartice za proizvode + const statCards = page.locator('.stat-card'); + await expect(statCards).toHaveCount(3); // ESIR, ARV, LIGHT_TICKET + }); + + test('prikazuje tabelu poslednje aktivnosti', async ({ page }) => { + await expect(page.locator('h2', { hasText: 'Poslednja aktivnost' })).toBeVisible(); + await expect(page.locator('table').last()).toBeVisible(); + }); + + test('navbar ima ispravne linkove', async ({ page }) => { + await expect(page.locator('.nav-brand')).toHaveText('DAL License Server'); + await expect(page.locator('a[href="/dashboard"]')).toBeVisible(); + await expect(page.locator('a[href="/licenses"]')).toBeVisible(); + await expect(page.locator('a[href="/audit"]')).toBeVisible(); + }); + + test('odjava funkcionise', async ({ page }) => { + await page.click('button:has-text("Odjava")'); + await expect(page).toHaveURL(/\/login/); + // Posle odjave ne moze na dashboard + await page.goto('/dashboard'); + await expect(page).toHaveURL(/\/login/); + }); + + test('root preusmerava na dashboard', async ({ page }) => { + await page.goto('/'); + await expect(page).toHaveURL(/\/dashboard/); + }); +}); diff --git a/tests/e2e/license-crud.spec.ts b/tests/e2e/license-crud.spec.ts new file mode 100644 index 0000000..18b0b3d --- /dev/null +++ b/tests/e2e/license-crud.spec.ts @@ -0,0 +1,132 @@ +import { test, expect, Page } from '@playwright/test'; + +async function login(page: Page) { + await page.goto('/login'); + await page.fill('input[name="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await expect(page).toHaveURL(/\/dashboard/); +} + +test.describe('Kreiranje licence', () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test('forma za novu licencu prikazuje sva polja', async ({ page }) => { + await page.goto('/licenses/new'); + await expect(page.locator('h1')).toHaveText('Nova licenca'); + await expect(page.locator('select[name="product_id"]')).toBeVisible(); + await expect(page.locator('select[name="license_type"]')).toBeVisible(); + await expect(page.locator('input[name="customer_name"]')).toBeVisible(); + await expect(page.locator('input[name="customer_pib"]')).toBeVisible(); + await expect(page.locator('input[name="customer_email"]')).toBeVisible(); + await expect(page.locator('input[name="limits"]')).toBeVisible(); + await expect(page.locator('input[name="grace_days"]')).toBeVisible(); + await expect(page.locator('textarea[name="notes"]')).toBeVisible(); + }); + + test('dropdown ima sve proizvode', async ({ page }) => { + await page.goto('/licenses/new'); + const options = page.locator('select[name="product_id"] option'); + const count = await options.count(); + expect(count).toBeGreaterThanOrEqual(3); // ESIR, ARV, LIGHT_TICKET + }); + + test('dropdown ima sve tipove licenci', async ({ page }) => { + await page.goto('/licenses/new'); + const options = page.locator('select[name="license_type"] option'); + const texts = await options.allTextContents(); + expect(texts.join(' ')).toContain('Mesecna'); + expect(texts.join(' ')).toContain('Godisnja'); + expect(texts.join(' ')).toContain('Trajna'); + expect(texts.join(' ')).toContain('Trial'); + }); + + test('kreiranje LIGHT_TICKET licence', async ({ page }) => { + await page.goto('/licenses/new'); + + // Izaberi Light-Ticket proizvod + const productSelect = page.locator('select[name="product_id"]'); + const options = productSelect.locator('option'); + const count = await options.count(); + for (let i = 0; i < count; i++) { + const text = await options.nth(i).textContent(); + if (text && text.includes('LIGHT_TICKET')) { + await productSelect.selectOption({ index: i }); + break; + } + } + + await page.selectOption('select[name="license_type"]', 'MONTHLY'); + await page.fill('input[name="customer_name"]', 'E2E Test Firma DOO'); + await page.fill('input[name="customer_pib"]', '999888777'); + await page.fill('input[name="customer_email"]', 'e2e@test.rs'); + await page.fill('input[name="limits"]', '{"max_operators": 5}'); + await page.fill('input[name="grace_days"]', '30'); + await page.fill('textarea[name="notes"]', 'Kreirana iz E2E testa'); + + await page.click('button:has-text("Kreiraj licencu")'); + + // Treba da preusmeriva na detalje licence + await expect(page).toHaveURL(/\/licenses\/\d+/); + await expect(page.locator('h1 code')).toBeVisible(); + // Proverava da je LT prefix + const licenseKey = await page.locator('h1 code').textContent(); + expect(licenseKey).toMatch(/^LT-/); + }); + + test('kreiranje ARV licence', async ({ page }) => { + await page.goto('/licenses/new'); + + const productSelect = page.locator('select[name="product_id"]'); + const options = productSelect.locator('option'); + const count = await options.count(); + for (let i = 0; i < count; i++) { + const text = await options.nth(i).textContent(); + if (text && text.includes('ARV')) { + await productSelect.selectOption({ index: i }); + break; + } + } + + await page.selectOption('select[name="license_type"]', 'ANNUAL'); + await page.fill('input[name="customer_name"]', 'E2E ARV Firma'); + await page.fill('input[name="customer_email"]', 'arv@test.rs'); + + await page.click('button:has-text("Kreiraj licencu")'); + + await expect(page).toHaveURL(/\/licenses\/\d+/); + const licenseKey = await page.locator('h1 code').textContent(); + expect(licenseKey).toMatch(/^ARV-/); + }); + + test('kreiranje ESIR licence', async ({ page }) => { + await page.goto('/licenses/new'); + + const productSelect = page.locator('select[name="product_id"]'); + const options = productSelect.locator('option'); + const count = await options.count(); + for (let i = 0; i < count; i++) { + const text = await options.nth(i).textContent(); + if (text && text.includes('ESIR')) { + await productSelect.selectOption({ index: i }); + break; + } + } + + await page.selectOption('select[name="license_type"]', 'PERPETUAL'); + await page.fill('input[name="customer_name"]', 'E2E ESIR Prodavnica'); + + await page.click('button:has-text("Kreiraj licencu")'); + + await expect(page).toHaveURL(/\/licenses\/\d+/); + const licenseKey = await page.locator('h1 code').textContent(); + expect(licenseKey).toMatch(/^ESIR-/); + }); + + test('otkazi dugme vodi na listu licenci', async ({ page }) => { + await page.goto('/licenses/new'); + await page.click('a:has-text("Otkazi")'); + await expect(page).toHaveURL(/\/licenses$/); + }); +}); diff --git a/tests/e2e/license-detail.spec.ts b/tests/e2e/license-detail.spec.ts new file mode 100644 index 0000000..3f2dd2b --- /dev/null +++ b/tests/e2e/license-detail.spec.ts @@ -0,0 +1,117 @@ +import { test, expect, Page } from '@playwright/test'; + +async function login(page: Page) { + await page.goto('/login'); + await page.fill('input[name="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await expect(page).toHaveURL(/\/dashboard/); +} + +async function createLicense(page: Page, customerName: string): Promise { + await page.goto('/licenses/new'); + const productSelect = page.locator('select[name="product_id"]'); + const options = productSelect.locator('option'); + const count = await options.count(); + for (let i = 0; i < count; i++) { + const text = await options.nth(i).textContent(); + if (text && text.includes('LIGHT_TICKET')) { + await productSelect.selectOption({ index: i }); + break; + } + } + await page.selectOption('select[name="license_type"]', 'MONTHLY'); + await page.fill('input[name="customer_name"]', customerName); + await page.click('button:has-text("Kreiraj licencu")'); + await expect(page).toHaveURL(/\/licenses\/\d+/); + return page.url(); +} + +test.describe('Detalji licence', () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test('prikazuje informacije o licenci', async ({ page }) => { + const url = await createLicense(page, 'Detail Test Firma'); + + await expect(page.locator('h3', { hasText: 'Informacije' })).toBeVisible(); + await expect(page.locator('.detail-table')).toBeVisible(); + + // Proverava osnovne podatke + const detailText = await page.locator('.detail-table').textContent(); + expect(detailText).toContain('LIGHT_TICKET'); + expect(detailText).toContain('MONTHLY'); + expect(detailText).toContain('Detail Test Firma'); + }); + + test('prikazuje tabelu aktivacija', async ({ page }) => { + await createLicense(page, 'Activation Test Firma'); + await expect(page.locator('h2', { hasText: 'Aktivacije' })).toBeVisible(); + // Nova licenca nema aktivacija + await expect(page.locator('td', { hasText: 'Nema aktivacija' })).toBeVisible(); + }); + + test('prikazuje audit log za licencu', async ({ page }) => { + await createLicense(page, 'Audit Test Firma'); + await expect(page.locator('h2', { hasText: 'Audit Log' })).toBeVisible(); + // Kreiranje licence generise CREATE audit entry + await expect(page.locator('.badge', { hasText: 'CREATE' })).toBeVisible(); + }); + + test('prikazuje akcije (revoke, force release)', async ({ page }) => { + await createLicense(page, 'Actions Test Firma'); + await expect(page.locator('h3', { hasText: 'Akcije' })).toBeVisible(); + await expect(page.locator('button:has-text("Opozovi licencu")')).toBeVisible(); + await expect(page.locator('button:has-text("Force Release")')).toBeVisible(); + }); + + test('revoke licence funkcionise', async ({ page }) => { + await createLicense(page, 'Revoke Test Firma'); + + // Prihvatamo confirm dialog + page.on('dialog', dialog => dialog.accept()); + + await page.fill('input[name="reason"]', 'E2E test opozivanje'); + await page.click('button:has-text("Opozovi licencu")'); + + // Posle revoke-a ostajemo na istoj stranici + await expect(page).toHaveURL(/\/licenses\/\d+/); + // Status treba biti Opozvana + await expect(page.locator('.badge.status-revoked')).toBeVisible(); + }); + + test('posle revoke-a dugme za opoziv nestaje', async ({ page }) => { + await createLicense(page, 'Revoke Hide Test'); + + page.on('dialog', dialog => dialog.accept()); + await page.fill('input[name="reason"]', 'Test'); + await page.click('button:has-text("Opozovi licencu")'); + await expect(page).toHaveURL(/\/licenses\/\d+/); + + // Dugme za opoziv ne treba da postoji + await expect(page.locator('button:has-text("Opozovi licencu")')).toHaveCount(0); + // Ali Force Release treba da postoji + await expect(page.locator('button:has-text("Force Release")')).toBeVisible(); + }); + + test('force release funkcionise', async ({ page }) => { + await createLicense(page, 'Release Test Firma'); + + page.on('dialog', dialog => dialog.accept()); + await page.click('button:has-text("Force Release")'); + + await expect(page).toHaveURL(/\/licenses\/\d+/); + }); + + test('klik na licencu iz liste otvara detalje', async ({ page }) => { + await createLicense(page, 'Link Test Firma'); + + await page.goto('/licenses'); + // Klik na prvi link sa kodom licence + const licenseLink = page.locator('a:has(code)').first(); + await licenseLink.click(); + + await expect(page).toHaveURL(/\/licenses\/\d+/); + await expect(page.locator('h1 code')).toBeVisible(); + }); +}); diff --git a/tests/e2e/licenses.spec.ts b/tests/e2e/licenses.spec.ts new file mode 100644 index 0000000..f12575b --- /dev/null +++ b/tests/e2e/licenses.spec.ts @@ -0,0 +1,61 @@ +import { test, expect, Page } from '@playwright/test'; + +async function login(page: Page) { + await page.goto('/login'); + await page.fill('input[name="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await expect(page).toHaveURL(/\/dashboard/); +} + +test.describe('Lista licenci', () => { + test.beforeEach(async ({ page }) => { + await login(page); + }); + + test('prikazuje tabelu licenci', async ({ page }) => { + await page.goto('/licenses'); + await expect(page.locator('h1')).toHaveText('Licence'); + await expect(page.locator('table')).toBeVisible(); + // Tabela ima ispravne kolone + const headers = page.locator('thead th'); + await expect(headers.nth(0)).toHaveText('Kljuc'); + await expect(headers.nth(1)).toHaveText('Proizvod'); + await expect(headers.nth(2)).toHaveText('Firma'); + await expect(headers.nth(3)).toHaveText('Tip'); + }); + + test('ima dugme za novu licencu', async ({ page }) => { + await page.goto('/licenses'); + const btn = page.locator('a[href="/licenses/new"]'); + await expect(btn).toBeVisible(); + await expect(btn).toHaveText('Nova licenca'); + }); + + test('filteri su prikazani', async ({ page }) => { + await page.goto('/licenses'); + await expect(page.locator('select[name="product"]')).toBeVisible(); + await expect(page.locator('select[name="status"]')).toBeVisible(); + await expect(page.locator('input[name="search"]')).toBeVisible(); + }); + + test('filter po proizvodu radi', async ({ page }) => { + await page.goto('/licenses'); + await page.selectOption('select[name="product"]', 'ESIR'); + await page.click('button:has-text("Filtriraj")'); + await expect(page).toHaveURL(/product=ESIR/); + }); + + test('filter po statusu radi', async ({ page }) => { + await page.goto('/licenses'); + await page.selectOption('select[name="status"]', 'active'); + await page.click('button:has-text("Filtriraj")'); + await expect(page).toHaveURL(/status=active/); + }); + + test('pretraga po firmi radi', async ({ page }) => { + await page.goto('/licenses'); + await page.fill('input[name="search"]', 'TestFirma'); + await page.click('button:has-text("Filtriraj")'); + await expect(page).toHaveURL(/search=TestFirma/); + }); +}); diff --git a/tests/e2e/login.spec.ts b/tests/e2e/login.spec.ts new file mode 100644 index 0000000..74c1518 --- /dev/null +++ b/tests/e2e/login.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Login', () => { + test('prikazuje login formu', async ({ page }) => { + await page.goto('/login'); + await expect(page.locator('h1')).toHaveText('DAL License Server'); + await expect(page.locator('input[name="password"]')).toBeVisible(); + await expect(page.locator('button[type="submit"]')).toHaveText('Prijava'); + }); + + test('prijava sa ispravnom lozinkom', async ({ page }) => { + await page.goto('/login'); + await page.fill('input[name="password"]', 'admin123'); + await page.click('button[type="submit"]'); + await expect(page).toHaveURL(/\/dashboard/); + await expect(page.locator('h1')).toHaveText('Dashboard'); + }); + + test('prijava sa pogresnom lozinkom prikazuje gresku', async ({ page }) => { + await page.goto('/login'); + await page.fill('input[name="password"]', 'pogresna_lozinka'); + await page.click('button[type="submit"]'); + await expect(page.locator('.alert-error')).toHaveText('Pogresna lozinka'); + await expect(page).toHaveURL(/\/login/); + }); + + test('pristup dashboard-u bez logina preusmerava na login', async ({ page }) => { + await page.goto('/dashboard'); + await expect(page).toHaveURL(/\/login/); + }); + + test('pristup licencama bez logina preusmerava na login', async ({ page }) => { + await page.goto('/licenses'); + await expect(page).toHaveURL(/\/login/); + }); + + test('pristup auditu bez logina preusmerava na login', async ({ page }) => { + await page.goto('/audit'); + await expect(page).toHaveURL(/\/login/); + }); +});