fix(agrarmonitor): Lieferanten-Detailseite ohne AJAX-Header laden
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s
Im Einzeltest fehlte auf /kunden/detail/<id> das Feld #lieferantennummer, obwohl die Liste kurz vorher mit derselben Session geladen wurde. Die Detailseite wird jetzt wie eine normale Browser-Navigation ohne X-Requested-With geladen (wie im Connector bei den Eingangsrechnungen). Fehlt das Feld trotzdem, nennt der Fehler HTTP-Status, Ziel-URL, Seitentitel und Feldnamen – ohne Feldwerte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3d4125b29a
commit
549948913a
4 files changed
+82
-6
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
baueFormData,
|
||||
beschreibeSeite,
|
||||
bestimmeKandidaten,
|
||||
hoechsteLieferantennummer,
|
||||
lieferantennummerAusDetail,
|
||||
@@ -194,3 +195,17 @@ describe('lieferantennummerAusDetail', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('beschreibeSeite', () => {
|
||||
it('nennt Titel und Feldnamen, aber keine Werte', () => {
|
||||
const html =
|
||||
'<html><head><title>Anmeldung - AGRARMONITOR</title></head><body>' +
|
||||
'<form><input name="email" value="geheim@example.de"><input name="iban" value="DE001234"></form></body></html>';
|
||||
const text = beschreibeSeite(html);
|
||||
expect(text).toContain('Titel „Anmeldung - AGRARMONITOR“');
|
||||
expect(text).toContain('#form_detail fehlt');
|
||||
expect(text).toContain('Felder: email, iban');
|
||||
expect(text).not.toContain('geheim@example.de');
|
||||
expect(text).not.toContain('DE001234');
|
||||
});
|
||||
});
|
||||
@@ -90,6 +90,31 @@ export function lieferantennummerAusDetail(html: string): string {
|
||||
return (input.attr('value') ?? '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Kurzbeschreibung einer unerwarteten Seite für das Log: Titel, Formular und
|
||||
* Feldnamen – bewusst ohne Feldwerte (IBAN o. Ä.).
|
||||
*/
|
||||
export function beschreibeSeite(html: string): string {
|
||||
const $ = cheerio.load(html);
|
||||
const titel = $('title').first().text().trim() || '–';
|
||||
const form = $('form#form_detail');
|
||||
const namen = Array.from(
|
||||
new Set(
|
||||
$('input[name], select[name], textarea[name]')
|
||||
.toArray()
|
||||
.map((el) => $(el).attr('name')!),
|
||||
),
|
||||
);
|
||||
const feldListe =
|
||||
namen.length > 15
|
||||
? `${namen.slice(0, 15).join(', ')}, …`
|
||||
: namen.join(', ');
|
||||
return (
|
||||
`Titel „${titel}“, #form_detail ${form.length ? 'vorhanden' : 'fehlt'}, ` +
|
||||
`${html.length} Zeichen, Felder: ${feldListe || 'keine'}`
|
||||
);
|
||||
}
|
||||
|
||||
function istDisabled(el: Cheerio<Element>): boolean {
|
||||
return el.attr('disabled') !== undefined;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ function fakeHttp(opts: {
|
||||
nummern: Record<number, string>;
|
||||
dubletten?: number[];
|
||||
speichernIgnorieren?: number[];
|
||||
detailHtml?: string;
|
||||
}) {
|
||||
const aufrufe: string[] = [];
|
||||
const updates: UpdateBody[] = [];
|
||||
@@ -62,7 +63,8 @@ function fakeHttp(opts: {
|
||||
const m = /\/kunden\/detail\/(\d+)/.exec(url);
|
||||
if (m) {
|
||||
return Promise.resolve({
|
||||
data: detail(opts.nummern[Number(m[1])] ?? ''),
|
||||
status: 200,
|
||||
data: opts.detailHtml ?? detail(opts.nummern[Number(m[1])] ?? ''),
|
||||
});
|
||||
}
|
||||
return Promise.resolve({ data: '' });
|
||||
@@ -148,4 +150,23 @@ describe('LieferantennummernService.vergeben', () => {
|
||||
expect(lauf.eintraege[0].hinweis).toContain('15550706');
|
||||
expect(aufrufe[aufrufe.length - 1]).toContain('itemsperpage=25');
|
||||
});
|
||||
|
||||
it('lädt die Detailseite ohne AJAX-Header und meldet eine unerwartete Seite verständlich', async () => {
|
||||
const { http, updates } = fakeHttp({
|
||||
nummern: {},
|
||||
detailHtml:
|
||||
'<html><head><title>Anmeldung - AGRARMONITOR</title></head><body></body></html>',
|
||||
});
|
||||
const lauf = neuerLauf(1);
|
||||
await service().vergeben(http, [kandidat(1, 'A')], 15550706, lauf);
|
||||
|
||||
const detailAufruf = (http.get as jest.Mock).mock.calls.find(
|
||||
([url]) => url === '/kunden/detail/1',
|
||||
) as [string, { headers?: Record<string, string> }];
|
||||
expect(detailAufruf[1].headers?.['X-Requested-With']).toBeUndefined();
|
||||
expect(updates).toHaveLength(0);
|
||||
expect(lauf).toMatchObject({ fehler: 1, abgebrochen: true });
|
||||
expect(lauf.eintraege[0].hinweis).toContain('HTTP 200');
|
||||
expect(lauf.eintraege[0].hinweis).toContain('Anmeldung - AGRARMONITOR');
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import { fehlerText } from './agrarmonitor-polling.service';
|
||||
import {
|
||||
anzeigeName,
|
||||
baueFormData,
|
||||
beschreibeSeite,
|
||||
bestimmeKandidaten,
|
||||
hoechsteLieferantennummer,
|
||||
lieferantennummerAusDetail,
|
||||
@@ -318,15 +319,29 @@ export class LieferantennummernService {
|
||||
return { maximum, kandidaten: bestimmeKandidaten(lieferanten) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die Detailseite wie eine normale Browser-Navigation (ohne AJAX-Header)
|
||||
* und prüft, dass das Formular mit #lieferantennummer enthalten ist.
|
||||
*/
|
||||
private async ladeDetail(
|
||||
http: AxiosInstance,
|
||||
kundeId: number,
|
||||
): Promise<string> {
|
||||
const response = await http.get<string>(`/kunden/detail/${kundeId}`, {
|
||||
headers: AJAX_HEADERS,
|
||||
responseType: 'text',
|
||||
});
|
||||
return response.data;
|
||||
const url = `/kunden/detail/${kundeId}`;
|
||||
const response = await http.get<string>(url, { responseType: 'text' });
|
||||
const html = String(response.data ?? '');
|
||||
try {
|
||||
lieferantennummerAusDetail(html);
|
||||
} catch {
|
||||
const request = response.request as
|
||||
| { res?: { responseUrl?: string } }
|
||||
| undefined;
|
||||
const zielUrl = request?.res?.responseUrl ?? url;
|
||||
throw new Error(
|
||||
`Detailseite ohne Feld #lieferantennummer (HTTP ${response.status}, ${zielUrl}): ${beschreibeSeite(html)}`,
|
||||
);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
/** Dublettencheck wie in der Weboberfläche; true, wenn Agrarmonitor ähnliche Kontakte meldet. */
|
||||
|
||||
Reference in new issue
Block a user