13 Commits

Author SHA1 Message Date
eebfaf67f6 test: Vorgang-Detail Seite: Share (mail-to) Link enthält URL zum Vorgang 2025-12-03 13:28:52 +01:00
8762836b46 test: Vorgang-Detail Seite: Share (Teilen) Button deaktiviert bei leerer Tatort-Liste 2025-12-02 12:12:54 +01:00
7c6ff2e250 test: Anmeldung via Token/PIN - falsche PIN und Fehlermeldung 2025-12-02 11:04:04 +01:00
a50c5243a5 test: NameItemEditor component - Focussing of Input element after click 2025-12-02 10:39:40 +01:00
1158c88d43 fix SonarQube issues: mainly unused imports
All checks were successful
InnoHub Processor/tatort/pipeline/head This commit looks good
2025-11-24 08:41:46 +01:00
e6add823a5 update packages 2025-11-24 08:36:42 +01:00
b1c246113c Merge pull request 'f112_vorgang_operationen' (#41) from f112_vorgang_operationen into development
Some checks failed
InnoHub Processor/tatort/pipeline/head There was a failure building this commit
Reviewed-on: #41
2025-11-21 13:12:25 +01:00
5c76e77766 add tests for Vorgang operation: change name and pin 2025-11-21 12:00:14 +01:00
3aee87aaed clarify test case description 2025-11-21 09:39:13 +01:00
97aaf2cd12 fix ´onDelete´ function type 2025-11-21 09:33:41 +01:00
9d35079058 change function parameter name to make it more descriptive: newName -> newValue 2025-11-21 09:26:08 +01:00
73cb398aa0 position PIN code on the same line as the label 2025-11-20 13:05:40 +01:00
365fb0f2c7 allow vorgangPIN to be changed on Vorgang page
includes:
- UI and backend logic
- adjustment to `NameItemEditor` to disallow deletion
2025-11-20 12:54:53 +01:00
14 changed files with 548 additions and 740 deletions

1099
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@
export let currentName: string;
export let vorgangToken: string | null;
export let onSave: (n: string, o: string, t?: string) => unknown = () => {};
export let onDelete: (n: string) => unknown = () => {};
export let onDelete: ((n: string) => unknown) | null = () => {};
let localName = currentName;
let isEditing = false;
@@ -98,13 +98,15 @@
>
<Edit class="w-4 h-4" />
</button>
<button
data-testid="delete-button"
onclick={handleDeleteClick}
class="text-gray-500 hover:text-red-600 transition"
>
<Trash class="w-4 h-4" />
</button>
{#if onDelete}
<button
data-testid="delete-button"
onclick={handleDeleteClick}
class="text-gray-500 hover:text-red-600 transition"
>
<Trash class="w-4 h-4" />
</button>
{/if}
</div>
{/if}

View File

@@ -236,19 +236,21 @@ export const vorgangPINValidation = function (vorgangToken: string, vorgangPIN:
};
/**
* Rename Vorgang
* Change VorgangName or VorgangPIN
* @param vorgangToken
* @param newName
* @param newValue
* @returns {int} number of affected lines
*/
export const renameVorgangByToken = function (vorgangToken: string, newName: string) {
const renameSQLStmt = 'UPDATE cases set name = ? WHERE token = ?';
export const updateVorgangAttrByToken = function (vorgangToken: string,
newValue: string,
column: string) {
const renameSQLStmt = `UPDATE cases set ${column} = ? WHERE token = ?`;
const statement = db.prepare(renameSQLStmt);
let info;
try {
info = statement.run(newName, vorgangToken);
info = statement.run(newValue, vorgangToken);
} catch (err) {
console.log(`error: ${err}`)
return 0;

View File

@@ -1,8 +1,6 @@
import { type ServerLoadEvent } from '@sveltejs/kit';
import type { PageServerLoad } from '../anmeldung/$types';
import { ROUTE_NAMES } from '..';
export const load: PageServerLoad = (event: ServerLoadEvent) => {
if (event.locals.user) {
return {

View File

@@ -1,10 +1,8 @@
import { Readable } from 'stream';
import { BUCKET, client } from '$lib/minio';
import { fail, error } from '@sveltejs/kit';
import { v4 as uuidv4 } from 'uuid';
import { db } from '$lib/server/dbService';
import { getVorgangByName, vorgangNameExists } from '$lib/server/vorgangService';
import { getVorgangByName } from '$lib/server/vorgangService';
const isRequiredFieldValid = (value: unknown) => {
if (value == null) return false;

View File

@@ -190,6 +190,34 @@
}
}
async function savePIN(newVorgangPIN: string, oldVorgangPIN: string) {
open = true;
inProgress = true;
isError = false;
try {
const res = await fetch(API_ROUTES.VORGANG(vorgangToken), {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ vorgangToken, oldVorgangPIN, newVorgangPIN,
changePIN: true})
});
if (!res.ok) {
throw new Error('Fehler beim Speichern');
}
await invalidateAll();
crimesList = data.crimesList;
open = false;
} catch (err) {
console.error('⚠️ Netzwerkfehler beim Speichern', err);
isError = true;
} finally {
inProgress = false;
}
}
async function handleDelete(tatort: string) {
open = true;
inProgress = true;
@@ -251,7 +279,15 @@ Mit freundlichen Grüßen,
<h1 class="text-xl">{vorgangName}</h1>
{#if admin}
Zugangs-PIN: {vorgangPIN}
<div class="flex items-center gap-2">
Zugangs-PIN:
<NameItemEditor
list={[]}
currentName={vorgangPIN}
onSave={savePIN}
onDelete={null}
/>
</div>
<a class="pt-2 pb-6" href={constructMailToLink()}
><Button disabled={isEmptyList}>Share Link</Button></a
>

View File

@@ -4,7 +4,7 @@ import {
deleteVorgangByToken,
getCrimesListByToken,
vorgangNameExists,
renameVorgangByToken
updateVorgangAttrByToken
} from '$lib/server/vorgangService';
export async function DELETE({ params }) {
@@ -59,14 +59,26 @@ export async function GET({ params }) {
}
}
// rename vorgang
// change Vorgang properties
export async function PUT({ request }) {
const data = await request.json();
const vorgangToken = data['vorgangToken'];
const newVorgangName = data['newName'];
const changePIN = data['changePIN'];
let attrChanged;
let newValue;
const res = renameVorgangByToken(vorgangToken, newVorgangName);
if (changePIN) {
attrChanged = 'pin';
newValue = data['newVorgangPIN']
} else {
attrChanged = 'name';
newValue = data['newName']
}
const res = updateVorgangAttrByToken(vorgangToken, newValue, attrChanged);
if (!res) {
return json({ msg: 'Fehler beim Umbenennen' }, { status: 400 });

View File

@@ -1,4 +1,3 @@
import { json } from '@sveltejs/kit';
import { deleteUser } from '$lib/server/userService';
export async function DELETE({ params }) {

View File

@@ -18,7 +18,13 @@ describe('NameItemEditor - Funktionalität', () => {
onDelete
};
test.todo('FocusIn nach Klick auf edit');
test('Focus Input nach Klick auf edit', async () => {
render(NameItemEditor, { props: baseProps });
await fireEvent.click(screen.getByTestId('edit-button'));
const input = screen.getByTestId('test-input');
expect(document.activeElement).toBe(input);
});
it('zeigt initial Edit/Delete Buttons und aktuellen Namen', () => {
render(NameItemEditor, { props: baseProps });
@@ -87,7 +93,7 @@ describe('NameItemEditor - Funktionalität', () => {
expect(onSave).not.toHaveBeenCalled();
});
it('ruft onSave korrekt auf bei gültigem Namen: Tatort/Crime (ohne Vorgang)', async () => {
it('ruft onSave korrekt auf bei gültigem Namen: Tatort/Crime', async () => {
render(NameItemEditor, { props: baseProps });
await fireEvent.click(screen.getByTestId('edit-button'));
@@ -117,7 +123,7 @@ describe('NameItemEditor - Funktionalität', () => {
expect(screen.getByTestId('edit-button')).toBeInTheDocument();
});
it('triggert Save bei Enter-Taste: Tatort/Crime (ohne Vorgang)', async () => {
it('triggert Save bei Enter-Taste: Tatort/Crime', async () => {
render(NameItemEditor, { props: baseProps });
await fireEvent.click(screen.getByTestId('edit-button'));

View File

@@ -41,7 +41,7 @@ export const baseData = {
vorgang: testVorgangsList[0],
vorgangList: testVorgangsList,
crimesList: testCrimesList,
url: `https://example.com/list/${testVorgangsList[0].vorgangToken}`,
url: new URL(`https://example.com/list/${testVorgangsList[0].vorgangToken}`),
crimeNames: ['modell-A', 'Fall-A']
};

View File

@@ -77,7 +77,39 @@ describe('Vorgang Anzeige via Token', () => {
// Cookie wird nicht gesetzt
expect(cookiesSet).not.toHaveBeenCalled();
});
it.todo('Überprüfe was passiert, wenn Eingabe falsch, bzw. nicht im System passend gefunden');
it('Falsche PIN', async () => {
// Mock formData
const vorgObj = baseData.vorgang;
const formData = new FormData();
formData.set('vorgang-token', vorgObj.vorgangToken);
formData.set('vorgang-pin', vorgObj.vorgangPIN);
const mockRequest = {
formData: vi.fn().mockResolvedValue(formData)
};
// PIN-Validierung nicht erfolgreich
vi.mocked(vorgangPINValidation).mockReturnValueOnce(false);
const cookiesSet = vi.fn();
const event = {
request: mockRequest,
cookies: {
set: cookiesSet
}
};
const result = await actions.default(event);
expect(result.status).toBe(400);
expect(result.data.message).toMatch(/Falsch/i);
});
// Nicht vorhandener Vorgang-Token nicht notwendig, da PIN-Check
// entsprechend fehlerhaft
it.skip('Nicht vorhandener Vorgang-Token', () => {});
});
describe('Teste Guard', () => {

View File

@@ -26,9 +26,29 @@ async function clickPlusButton() {
}
describe('Seite: Vorgangsansicht', () => {
test.todo('Share Link disabled wenn Liste leer');
test('Share Link disabled wenn Liste leer', () => {
const testData = { ...baseData, crimesList: [] };
render(TatortListPage, { props: { data: testData } });
const button = screen.getByRole('button', { name: /share link/i });
expect(button).toBeInTheDocument()
expect(button).toBeDisabled();
});
describe('Szenario: Admin + Liste gefüllt - Funktionalität', () => {
test.todo('Share Link Link generierung richtig');
test('Share Link Link generierung richtig', () => {
const testData = { ...baseData};
render(TatortListPage, { props: { data: testData } });
const link = screen.getByRole('link', { name: /share link/i });
expect(link).toBeInTheDocument()
// const vorgangTokenFirstUUIDGroup = testData.vorgangList[0].vorgangToken.split('-')[0]
const vorgangURL = testData.url.toString()
const vorgangURLEncoded = encodeURIComponent(vorgangURL)
expect(link).toHaveAttribute('href', expect.stringContaining(vorgangURLEncoded));
});
it('führt PUT-Request aus und aktualisiert UI nach onSave', async () => {
const data = structuredClone(baseData);

View File

@@ -100,4 +100,16 @@ describe('Seite: Vorgangsansicht', () => {
expect(linkElement).toHaveAttribute('href', expectedURL);
});
});
describe('PIN Anzeige & Button', () => {
it('Teste korrekte Anzeige von PIN Komponente', () => {
const testData = { ...baseData};
render(TatortListPage, { props: { data: testData } });
const vorgObj = baseData.vorgangList[0]
// PIN is being displayed within ´NameItemEditor´
let label = screen.queryByText(vorgObj.vorgangPIN);
expect(label).toBeInTheDocument();
});
});
});

View File

@@ -171,4 +171,14 @@ describe('Hinzufügen Buton', () => {
const result = await actions.default(event);
expect(result).toEqual({ token: testVorgangToken });
});
});
describe('Vorgang-Operationen', () => {
it('Teste korrekte Anzeige von Vorgang-Input Komponente', () => {
const testData = { ...baseData};
const { getAllByTestId } = render(VorgangListPage, { props: { data: testData } });
let buttons = getAllByTestId('edit-button')
expect(buttons.length).toBeGreaterThan(1);
});
});