feat(deploy): TrueNAS Custom-App + Auto-Deploy, plus aufgelaufene Arbeit
Deployment: - custom-app.compose.yaml: self-contained Compose fuer TrueNAS "Custom App" (absolute Host-Bind-Pfade, postgres:18, pull_policy always, Port 8090) - scripts/truenas-deploy.sh: Host-Skript create/redeploy via midclt (App bleibt unter Apps sichtbar) inkl. Image-Pull + Health-Check - ci.yml Deploy-Job: laeuft auf ubuntu-latest-Runner, kopiert Deploy-Dateien per SSH auf den NAS-Host und triggert truenas-deploy.sh (statt runs-on goldeye) - compose.yaml/.env.example: postgres:18 (Locale-Match zur Quell-DB), Port 8090 - .gitignore: .agents/, tools/rag/, deploy/truenas/.env (Secrets/Scratch) Aufgelaufene Feature-Arbeit (verified/Freeze, Migrationen, Import-Triage): - GerbilOverride/VerifiedGerbil-Endpoints + GerbilSnapshotService + Tests - EF-Migrationen (ShowInChronicle, Stillborn, BirthOrder, ManualFlag, DSGVO) - Frontend VerifizierteTierePage + verified-API + e2e-Spec - diverse Import-/Triage-Skripte und -Tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -414,6 +414,44 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
return json(route, 200, result)
|
||||
}
|
||||
|
||||
// GEPRÜFTE TIERE: „vollständig korrekt"-Markierung / Schutz (/verified-gerbils).
|
||||
if (path === '/verified-gerbils' && method === 'GET') {
|
||||
return json(route, 200, [...db.verified].reverse())
|
||||
}
|
||||
const verMatch = path.match(/^\/verified-gerbils\/([^/]+)$/)
|
||||
if (verMatch) {
|
||||
const gid = decodeURIComponent(verMatch[1])
|
||||
const vIdx = db.verified.findIndex((v) => v.gerbilId === gid)
|
||||
if (method === 'GET') {
|
||||
return vIdx >= 0 ? json(route, 200, db.verified[vIdx]) : json(route, 404, { title: 'Not Found' })
|
||||
}
|
||||
if (method === 'POST') {
|
||||
const g = db.gerbils.find((x) => x.id === gid)
|
||||
if (!g) return json(route, 404, { title: 'Not Found' })
|
||||
const body = (request.postDataJSON() ?? {}) as { note?: string | null }
|
||||
const row = {
|
||||
gerbilId: gid,
|
||||
entityName: g.name ?? null,
|
||||
isVerified: true,
|
||||
status: 'unchanged',
|
||||
verifiedAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
note: body.note ?? null,
|
||||
protectedFields: ['name', 'gender', 'dateOfBirth', 'genotype'],
|
||||
importDiff: [],
|
||||
}
|
||||
if (vIdx >= 0) db.verified[vIdx] = row
|
||||
else db.verified.push(row)
|
||||
return json(route, 200, row)
|
||||
}
|
||||
if (method === 'DELETE') {
|
||||
if (vIdx < 0) return json(route, 404, { title: 'Not Found' })
|
||||
db.verified.splice(vIdx, 1)
|
||||
return json(route, 204)
|
||||
}
|
||||
return json(route, 405)
|
||||
}
|
||||
|
||||
// FEEDBACK: "Fehler melden" — POST persistiert, GET listet (neueste zuerst).
|
||||
if (path === '/feedback') {
|
||||
if (method === 'POST') {
|
||||
|
||||
@@ -80,6 +80,8 @@ export interface MockDb {
|
||||
namesConfigured: boolean
|
||||
// FEEDBACK: "Fehler melden" — gesammelte Berichte (POST /feedback)
|
||||
feedback: Record<string, unknown>[]
|
||||
// GEPRÜFTE TIERE: „vollständig korrekt"-Markierungen / Schutz (/verified-gerbils)
|
||||
verified: Record<string, unknown>[]
|
||||
// RPRO3: RennmausPro-III-Import — feste Auswertung + Import-Ergebnis für die UI-Specs.
|
||||
rpro3: {
|
||||
analyze: Record<string, unknown>
|
||||
@@ -222,10 +224,10 @@ export function seedDb(): MockDb {
|
||||
]
|
||||
|
||||
const litters: Litter[] = [
|
||||
{ id: 'w-zwillinge', name: 'Wurf Z', date: '2020-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'opa-w', motherId: 'oma-u' },
|
||||
{ id: 'w-inzucht', name: 'Wurf I', date: '2021-06-01', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'zwilling-bock', motherId: 'zwilling-maus' },
|
||||
{ id: 'w-zwillinge', name: 'Wurf Z', date: '2020-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'opa-w', motherId: 'oma-u', showInChronicle: true },
|
||||
{ id: 'w-inzucht', name: 'Wurf I', date: '2021-06-01', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'zwilling-bock', motherId: 'zwilling-maus', showInChronicle: true },
|
||||
{
|
||||
id: 'w-kruemel', name: 'Wurf K', date: '2025-03-12', totalBorn: 5, expectedGoHomeDate: '2025-04-16', notes: null, fatherId: 'fridolin', motherId: 'luna', deathsWithin8Weeks: 1,
|
||||
id: 'w-kruemel', name: 'Wurf K', date: '2025-03-12', totalBorn: 5, expectedGoHomeDate: '2025-04-16', notes: null, fatherId: 'fridolin', motherId: 'luna', deathsWithin8Weeks: 1, stillborn: 1, showInChronicle: true,
|
||||
// NACHVERFOLGUNG: Datenherkunft des Wurfs (vom Import erzeugter JSON-String).
|
||||
provenance: JSON.stringify({
|
||||
sourceFiles: ['Wurfchronik Teil 1_page_0009.md', 'Wurfchronik Teil 1_page_0028.md'],
|
||||
@@ -239,13 +241,16 @@ export function seedDb(): MockDb {
|
||||
],
|
||||
}),
|
||||
},
|
||||
{ id: 'w-fridolin', name: 'Wurf F', date: '2023-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'balu', motherId: 'maja' },
|
||||
{ id: 'w-luna', name: 'Wurf L', date: '2023-08-15', totalBorn: 6, expectedGoHomeDate: null, notes: null, fatherId: 'karlsson', motherId: 'smilla' },
|
||||
{ id: 'w-balu', name: 'Wurf B', date: '2021-04-20', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'greta' },
|
||||
{ id: 'w-fridolin', name: 'Wurf F', date: '2023-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'balu', motherId: 'maja', showInChronicle: true },
|
||||
{ id: 'w-luna', name: 'Wurf L', date: '2023-08-15', totalBorn: 6, expectedGoHomeDate: null, notes: null, fatherId: 'karlsson', motherId: 'smilla', showInChronicle: true },
|
||||
{ id: 'w-balu', name: 'Wurf B', date: '2021-04-20', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'greta', showInChronicle: true },
|
||||
// Anton ist auch Karlssons Vater -> gemeinsamer Vorfahre (Inzucht-Demo)
|
||||
{ id: 'w-karlsson', name: 'Wurf C', date: '2022-02-02', totalBorn: 5, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'frieda' },
|
||||
{ id: 'w-anton', name: 'Wurf A', date: '2019-07-07', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'emil', motherId: 'hilde' },
|
||||
{ id: 'w-emil', name: 'Wurf E', date: '2017-03-03', totalBorn: 2, expectedGoHomeDate: null, notes: null, fatherId: 'max', motherId: null },
|
||||
{ id: 'w-karlsson', name: 'Wurf C', date: '2022-02-02', totalBorn: 5, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'frieda', showInChronicle: true },
|
||||
{ id: 'w-anton', name: 'Wurf A', date: '2019-07-07', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'emil', motherId: 'hilde', showInChronicle: true },
|
||||
{ id: 'w-emil', name: 'Wurf E', date: '2017-03-03', totalBorn: 2, expectedGoHomeDate: null, notes: null, fatherId: 'max', motherId: null, showInChronicle: true },
|
||||
// WURFCHRONIK-FILTER: dieser Wurf ist NICHT in der Wurfchronik (showInChronicle: false)
|
||||
// -> taucht in der Wurf-Liste nicht auf, aber auf Fridolins Tier-Detailseite (parentLitters) weiterhin.
|
||||
{ id: 'w-versteckt', name: 'Wurf V', date: '2024-02-02', totalBorn: 2, expectedGoHomeDate: null, notes: null, fatherId: 'fridolin', motherId: 'luna', showInChronicle: false },
|
||||
]
|
||||
|
||||
const enclosures: Enclosure[] = [
|
||||
@@ -426,6 +431,8 @@ export function seedDb(): MockDb {
|
||||
],
|
||||
saleAdConfigured: true,
|
||||
namesConfigured: true,
|
||||
// GEPRÜFTE TIERE: startet leer; die Verify-Specs markieren über POST.
|
||||
verified: [],
|
||||
// FEEDBACK-TICKETS: ein offenes + ein gelöstes Ticket (GET liefert neueste zuerst →
|
||||
// db.feedback wird im Mock umgekehrt; das offene steht hier zuletzt = erscheint oben).
|
||||
feedback: [
|
||||
|
||||
36
gerbil-manager-web/e2e/verified.spec.ts
Normal file
36
gerbil-manager-web/e2e/verified.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* GEPRÜFTE TIERE: „vollständig korrekt"-Markierung auf der Rennmausakte (Badge + Toggle)
|
||||
* und die Übersichtsseite unter Hilfe. Mock-gebunden → skipUnlessMock.
|
||||
*/
|
||||
import { de, expect, gotoSection, acceptNextDialog, skipUnlessMock, test } from './fixtures'
|
||||
|
||||
const vt = de.verified
|
||||
|
||||
test.describe('Geprüfte Tiere', () => {
|
||||
test('Tier als vollständig korrekt markieren und Markierung entfernen', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await gotoSection(page, de.nav.gerbils)
|
||||
|
||||
// Erste Rennmaus aus der Liste öffnen.
|
||||
await page.locator('.gerbil-card').first().click()
|
||||
|
||||
// Prüfstatus-Karte ist sichtbar und erklärt sich selbst.
|
||||
await expect(page.getByRole('heading', { name: vt.sectionTitle })).toBeVisible()
|
||||
|
||||
// Markieren → Bestätigungs-Badge erscheint.
|
||||
await page.getByRole('button', { name: vt.markAction }).click()
|
||||
await expect(page.getByText(vt.verifiedBadge).first()).toBeVisible()
|
||||
|
||||
// Markierung entfernen (Klartext-Bestätigung akzeptieren) → Badge verschwindet.
|
||||
acceptNextDialog(page)
|
||||
await page.getByRole('button', { name: vt.unmarkAction }).click()
|
||||
await expect(page.getByText(vt.verifiedBadge)).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('Übersichtsseite unter Hilfe erreichbar', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/hilfe/verifizierte-tiere')
|
||||
await expect(page.getByRole('heading', { name: vt.pageTitle })).toBeVisible()
|
||||
await expect(page.getByText(vt.pageIntro1)).toBeVisible()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user