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 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-03-04 07:50:43 +00:00
parent dc0114e4b7
commit 5a046110f0
10 changed files with 825 additions and 0 deletions

79
package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

22
package.json Normal file
View File

@ -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"
}
}

11
playwright.config.ts Normal file
View File

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

View File

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

59
tests/e2e/audit.spec.ts Normal file
View File

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

View File

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

View File

@ -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$/);
});
});

View File

@ -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<string> {
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();
});
});

View File

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

41
tests/e2e/login.spec.ts Normal file
View File

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