112 lines
6.9 KiB
JavaScript
112 lines
6.9 KiB
JavaScript
// Integrationstest: die Erweiterung (lib/core.js, background.js) gegen einen ECHTEN DeskHub-Server.
|
|
// Start: DESKHUB_URL=http://127.0.0.1:8181 DESKHUB_EMAIL=chef@test.de DESKHUB_PASSWORD=… node --test test/integration.test.mjs
|
|
// Ohne DESKHUB_URL werden die Tests übersprungen.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
const URL_ = process.env.DESKHUB_URL, EMAIL = process.env.DESKHUB_EMAIL, PASSWORD = process.env.DESKHUB_PASSWORD;
|
|
const skip = !URL_ ? 'DESKHUB_URL nicht gesetzt' : false;
|
|
|
|
const store = {}; const tabs = []; let granted = new Set(); const notes = []; let badge = '';
|
|
const noop = { addListener() {} };
|
|
globalThis.chrome = {
|
|
storage: { local: {
|
|
async get(keys) { const o = {}; for (const k of [].concat(keys ?? Object.keys(store))) if (k in store) o[k] = structuredClone(store[k]); return o; },
|
|
async set(o) { for (const [k, v] of Object.entries(o)) store[k] = structuredClone(v); },
|
|
async remove(keys) { for (const k of [].concat(keys)) delete store[k]; },
|
|
} },
|
|
permissions: { async contains({ origins }) { return origins.every((o) => granted.has(o)); }, async request({ origins }) { origins.forEach((o) => granted.add(o)); return true; }, async remove({ origins }) { origins.forEach((o) => granted.delete(o)); return true; } },
|
|
tabs: { async create(o) { tabs.push(o); return o; } }, windows: { async create() {} },
|
|
action: { async setBadgeText({ text }) { badge = text; }, async setBadgeBackgroundColor() {}, async openPopup() {} },
|
|
notifications: { create(id, o) { notes.push({ id, ...o }); }, clear() {}, onClicked: noop },
|
|
alarms: { async get() { return null; }, create() {}, onAlarm: noop },
|
|
contextMenus: { removeAll(cb) { cb?.(); }, create() {}, onClicked: noop },
|
|
runtime: { onInstalled: noop, onStartup: noop, onMessage: noop, getURL: (p) => p, async sendMessage() { return {}; } },
|
|
};
|
|
|
|
const core = await import('../lib/core.js');
|
|
const bg = await import('../background.js');
|
|
let session; let created;
|
|
|
|
test('Anmeldung mit falschem Passwort wird abgelehnt (deutsche Meldung vom Server)', { skip }, async () => {
|
|
await assert.rejects(core.login(URL_, EMAIL, 'falsch-falsch', ''), (e) => e.status === 401 && e.code === 'INVALID_CREDENTIALS' && /falsch/i.test(e.message));
|
|
assert.equal(await core.getSession(), null);
|
|
});
|
|
|
|
test('Anmeldung mit richtigem Passwort', { skip }, async () => {
|
|
const r = await core.login(URL_, EMAIL, PASSWORD, '');
|
|
session = r.session;
|
|
assert.equal(session.staff.email, EMAIL);
|
|
assert.ok(session.token.length >= 40);
|
|
assert.ok([...granted].includes(new URL(URL_).origin + '/*'), 'Berechtigung nur für diesen Server');
|
|
assert.equal((await core.getSession()).token, session.token);
|
|
});
|
|
|
|
test('Stammdaten und Übersicht', { skip }, async () => {
|
|
const meta = (await core.apiCall('metadata.php')).data;
|
|
assert.ok(meta.departments.length >= 1 && meta.priorities.length >= 1);
|
|
const dash = (await core.apiCall('dashboard.php')).data;
|
|
for (const k of ['myOpen', 'overdue', 'unassigned']) assert.equal(typeof dash[k], 'number', k);
|
|
assert.ok(Array.isArray(dash.recent));
|
|
});
|
|
|
|
test('Suche findet das vorhandene Ticket', { skip }, async () => {
|
|
const { data } = await core.apiCall('tickets.php', { query: { q: 'Drucker', status: 'open,pending,answered,on_hold,resolved,closed', perPage: 20 } });
|
|
assert.ok(data.total >= 1);
|
|
assert.ok(data.items.some((t) => t.number === 'T-TEST-0001' && t.subject.includes('Drucker')));
|
|
});
|
|
|
|
test('Baseline und Aktivität: ein neues, nicht zugewiesenes Ticket wird als neu gezählt', { skip }, async () => {
|
|
const first = await bg.poll(); // legt nur den Bezugspunkt fest
|
|
assert.equal(first.fresh, 0);
|
|
assert.ok((await chrome.storage.local.get('lastSeen')).lastSeen);
|
|
await new Promise((r) => setTimeout(r, 1100)); // Serverzeit hat Sekundenauflösung
|
|
const meta = (await core.apiCall('metadata.php')).data;
|
|
const msg = 'Zeile 1\nZeile 2 <script>alert(1)</script> & "Anführungszeichen"';
|
|
const { data, status } = await core.apiCall('ticket_create.php', { method: 'POST', body: {
|
|
requesterName: 'Max Mustermann', requesterEmail: '', subject: 'Test aus der Erweiterung', message: core.plainToHtml(msg),
|
|
departmentId: meta.departments[0].id, priorityId: meta.priorities[0].id, notifyCustomer: false } });
|
|
assert.equal(status, 201);
|
|
created = data.ticket;
|
|
assert.match(created.number, /\S+/); assert.equal(created.subject, 'Test aus der Erweiterung');
|
|
const second = await bg.poll();
|
|
assert.ok(second.fresh >= 1, 'neues Ticket wird gezählt: ' + JSON.stringify(second));
|
|
assert.equal(badge, String(second.total));
|
|
assert.ok(notes.some((n) => n.id === 'deskhub-new' && /nicht zugewiesen/.test(n.message)));
|
|
});
|
|
|
|
test('Pflichtfelder werden vom Server mit deutscher Meldung abgelehnt', { skip }, async () => {
|
|
const meta = (await core.apiCall('metadata.php')).data;
|
|
await assert.rejects(core.apiCall('ticket_create.php', { method: 'POST', body: { requesterName: '', subject: 'x', message: 'y', departmentId: meta.departments[0].id, priorityId: meta.priorities[0].id } }), (e) => e.status === 422 && e.code === 'REQUESTER_NAME_REQUIRED');
|
|
await assert.rejects(core.apiCall('ticket_create.php', { method: 'POST', body: { requesterName: 'A', subject: '', message: 'y', departmentId: meta.departments[0].id, priorityId: meta.priorities[0].id } }), (e) => e.code === 'SUBJECT_REQUIRED');
|
|
});
|
|
|
|
test('Text mit Sonderzeichen: kommt als Klartext unverändert zurück (gespeichert wird maskiertes HTML)', { skip }, async () => {
|
|
const ticket = (await core.apiCall('ticket.php', { query: { number: created.number } })).data.ticket;
|
|
// Der Server speichert „<script>“ und liefert den Text für Apps als Klartext aus. Die Erweiterung zeigt Nachrichten nie als HTML an.
|
|
assert.equal(ticket.threads[0].message, 'Zeile 1\nZeile 2 <script>alert(1)</script> & "Anführungszeichen"');
|
|
});
|
|
|
|
test('Ticket im Browser öffnen: einmalige Anmelde-Adresse des eigenen Servers', { skip }, async () => {
|
|
tabs.length = 0;
|
|
await core.openWeb(core.ticketPath(created.number));
|
|
const u = new URL(tabs[0].url);
|
|
assert.equal(u.origin, new URL(URL_).origin);
|
|
assert.ok(u.pathname.endsWith('/staff/app_login.php'));
|
|
});
|
|
|
|
test('Eine Seite, die nicht freigegeben ist, wird nicht geöffnet', { skip }, async () => {
|
|
tabs.length = 0;
|
|
await assert.rejects(core.apiCall('web_pages.php', { method: 'POST', body: { path: '/staff/../config/config.php' } }), (e) => e.status === 403 && e.code === 'PAGE_NOT_ALLOWED');
|
|
assert.equal(tabs.length, 0);
|
|
});
|
|
|
|
test('Abmelden sperrt das Token auf dem Server', { skip }, async () => {
|
|
const token = (await core.getSession()).token;
|
|
await core.logout();
|
|
assert.equal(await core.getSession(), null);
|
|
assert.equal(granted.size, 0, 'Berechtigung wurde zurückgegeben');
|
|
const res = await fetch(`${URL_}/api/staff/v1/me.php`, { headers: { Authorization: `Bearer ${token}`, 'X-DeskHub-Authorization': `Bearer ${token}` } });
|
|
assert.equal(res.status, 401, 'das alte Token gilt nicht mehr');
|
|
});
|