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:
parent
dc0114e4b7
commit
5a046110f0
79
package-lock.json
generated
Normal file
79
package-lock.json
generated
Normal 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
22
package.json
Normal 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
11
playwright.config.ts
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
256
tests/e2e/api-client.spec.ts
Normal file
256
tests/e2e/api-client.spec.ts
Normal 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
59
tests/e2e/audit.spec.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
47
tests/e2e/dashboard.spec.ts
Normal file
47
tests/e2e/dashboard.spec.ts
Normal 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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
132
tests/e2e/license-crud.spec.ts
Normal file
132
tests/e2e/license-crud.spec.ts
Normal 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$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
117
tests/e2e/license-detail.spec.ts
Normal file
117
tests/e2e/license-detail.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
61
tests/e2e/licenses.spec.ts
Normal file
61
tests/e2e/licenses.spec.ts
Normal 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
41
tests/e2e/login.spec.ts
Normal 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/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user