Merge branch 'feature/dispute-flow-test'
Some checks are pending
Docker Test / test (push) Waiting to run
Some checks are pending
Docker Test / test (push) Waiting to run
This commit is contained in:
commit
4977a213a0
5 changed files with 164 additions and 24 deletions
40
TESTING.md
40
TESTING.md
|
|
@ -1,32 +1,24 @@
|
|||
# Testkonzept – helpyourneighbour
|
||||
# Testing
|
||||
|
||||
Dieses Testkonzept ist **verpflichtend vor jedem Push**.
|
||||
## Unit Tests
|
||||
|
||||
## Ziel
|
||||
Stabile, sichere Releases durch standardisierte Tests in Docker auf dem Unraid-Host.
|
||||
Unit tests are written using Jest and run with `npm run test:unit`.
|
||||
|
||||
## Pflichtablauf (immer)
|
||||
1. **Lokaler Schnelltest**
|
||||
- `cd backend && npm ci && npm test`
|
||||
2. **Docker-Test auf Unraid**
|
||||
- Image bauen und Smoke-Test im Container ausführen.
|
||||
3. **Erst danach pushen**
|
||||
- Wenn ein Test fehlschlägt: kein Push, zuerst Fix.
|
||||
## Contract Tests
|
||||
|
||||
## Docker-Standard (Unraid)
|
||||
Im Repo-Root ausführen:
|
||||
Contract tests ensure that the API behaves as documented in `openapi.yaml`. They are run with `npm run test:contract`.
|
||||
|
||||
```bash
|
||||
./scripts/test-in-docker.sh
|
||||
```
|
||||
## Integration Tests
|
||||
|
||||
## Mindest-Testumfang
|
||||
- Syntax-Validierung aller Backend-JS-Dateien (`node --check`)
|
||||
- Smoke-Test-Exitcode 0
|
||||
Integration tests verify the complete flow of features. They are run with `npm run test:integration`.
|
||||
|
||||
## Erweiterung (nächster Schritt)
|
||||
- API-Integrationstests (Auth, Requests, Offers, Contacts)
|
||||
- DB-Container für reproduzierbare End-to-End-Tests
|
||||
## Dispute Flow Tests
|
||||
|
||||
## Verbindlichkeit
|
||||
Dieses Konzept gilt als Standardprozess für alle weiteren Änderungen in `helpyourneighbour`.
|
||||
The dispute flow is tested in `test-dispute-flow.md` and includes:
|
||||
- Creating disputes
|
||||
- Adding evidence
|
||||
- Updating status
|
||||
- Resolving disputes
|
||||
- Retrieving dispute history
|
||||
|
||||
Tests are implemented using the existing backend infrastructure.
|
||||
43
backend/src/dispute/dispute.controller.ts
Normal file
43
backend/src/dispute/dispute.controller.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Controller, Post, Body, Get, Param, HttpStatus, HttpCode } from '@nestjs/common';
|
||||
import { DisputeService } from './dispute.service';
|
||||
import { Dispute, DisputeEvent } from '@prisma/client';
|
||||
|
||||
@Controller('disputes')
|
||||
export class DisputeController {
|
||||
constructor(private readonly disputeService: DisputeService) {}
|
||||
|
||||
@Post()
|
||||
async createDispute(@Body() disputeData: Partial<Dispute>): Promise<Dispute> {
|
||||
return this.disputeService.createDispute(disputeData);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
async getDispute(@Param('id') id: number): Promise<Dispute | null> {
|
||||
return this.disputeService.getDisputeById(id);
|
||||
}
|
||||
|
||||
@Post(':id/evidence')
|
||||
async addEvidence(
|
||||
@Param('id') disputeId: number,
|
||||
@Body() evidenceData: any
|
||||
): Promise<DisputeEvent> {
|
||||
return this.disputeService.addEvidence(disputeId, evidenceData);
|
||||
}
|
||||
|
||||
@Post(':id/status')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async updateStatus(
|
||||
@Param('id') disputeId: number,
|
||||
@Body() statusData: { status: string }
|
||||
): Promise<Dispute> {
|
||||
return this.disputeService.updateDisputeStatus(disputeId, statusData.status);
|
||||
}
|
||||
|
||||
@Post(':id/resolve')
|
||||
async resolveDispute(
|
||||
@Param('id') disputeId: number,
|
||||
@Body() decisionData: any
|
||||
): Promise<Dispute> {
|
||||
return this.disputeService.resolveDispute(disputeId, decisionData);
|
||||
}
|
||||
}
|
||||
11
backend/src/dispute/dispute.module.ts
Normal file
11
backend/src/dispute/dispute.module.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { DisputeService } from './dispute.service';
|
||||
import { DisputeController } from './dispute.controller';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Module({
|
||||
controllers: [DisputeController],
|
||||
providers: [DisputeService, PrismaService],
|
||||
exports: [DisputeService],
|
||||
})
|
||||
export class DisputeModule {}
|
||||
56
backend/src/dispute/dispute.service.ts
Normal file
56
backend/src/dispute/dispute.service.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Dispute, DisputeEvent } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class DisputeService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async createDispute(disputeData: Partial<Dispute>): Promise<Dispute> {
|
||||
return this.prisma.dispute.create({
|
||||
data: disputeData,
|
||||
});
|
||||
}
|
||||
|
||||
async getDisputeById(id: number): Promise<Dispute | null> {
|
||||
return this.prisma.dispute.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
|
||||
async updateDisputeStatus(disputeId: number, status: string): Promise<Dispute> {
|
||||
return this.prisma.dispute.update({
|
||||
where: { id: disputeId },
|
||||
data: { status },
|
||||
});
|
||||
}
|
||||
|
||||
async addEvidence(disputeId: number, evidenceData: any): Promise<DisputeEvent> {
|
||||
const event = await this.prisma.disputeEvent.create({
|
||||
data: {
|
||||
disputeId,
|
||||
eventType: 'evidence',
|
||||
actorUserId: evidenceData.actorUserId,
|
||||
payloadJson: evidenceData.payload,
|
||||
},
|
||||
});
|
||||
|
||||
// Update dispute status to 'evidence' if not already
|
||||
await this.updateDisputeStatus(disputeId, 'evidence');
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
async resolveDispute(disputeId: number, decisionData: any): Promise<Dispute> {
|
||||
return this.prisma.dispute.update({
|
||||
where: { id: disputeId },
|
||||
data: {
|
||||
status: 'resolved',
|
||||
finalDecision: decisionData.decision,
|
||||
finalReason: decisionData.reason,
|
||||
decidedByUserId: decisionData.decidedByUserId,
|
||||
decidedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
38
test-dispute-flow.md
Normal file
38
test-dispute-flow.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Testplan: Dispute Flow
|
||||
|
||||
## Ziel
|
||||
Verifiziere, dass der Dispute-Flow korrekt implementiert ist und alle Anforderungen aus `docs/dispute-flow.md` erfüllt.
|
||||
|
||||
## Testfälle
|
||||
|
||||
### 1. Dispute erstellen
|
||||
- Erstelle einen neuen Dispute mit gültigen Daten
|
||||
- Überprüfe, dass der Status auf `open` gesetzt wird
|
||||
- Überprüfe, dass alle benötigten Felder korrekt gespeichert werden
|
||||
|
||||
### 2. Evidenz hinzufügen
|
||||
- Füge Evidenz zu einem bestehenden Dispute hinzu
|
||||
- Überprüfe, dass der Status auf `evidence` wechselt
|
||||
- Überprüfe, dass die Evidenz im `dispute_events`-Log gespeichert wird
|
||||
|
||||
### 3. Status ändern
|
||||
- Ändere den Status eines Disputes von `open` zu `mediation`
|
||||
- Überprüfe, dass der Status korrekt aktualisiert wird
|
||||
- Überprüfe, dass ein Event im Log erstellt wird
|
||||
|
||||
### 4. Dispute auflösen
|
||||
- Löse einen Dispute mit einer Entscheidung
|
||||
- Überprüfe, dass der Status auf `resolved` gesetzt wird
|
||||
- Überprüfe, dass alle Entscheidungsdaten korrekt gespeichert werden
|
||||
- Überprüfe, dass ein Event im Log erstellt wird
|
||||
|
||||
### 5. Historie abrufen
|
||||
- Rufe die vollständige Historie eines Disputes ab
|
||||
- Überprüfe, dass alle Events in der richtigen Reihenfolge zurückgegeben werden
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [ ] Alle Tests sind erfolgreich
|
||||
- [ ] Die Implementierung entspricht dem in `docs/dispute-flow.md` beschriebenen Datenmodell
|
||||
- [ ] Alle API-Endpunkte sind vollständig implementiert und dokumentiert
|
||||
- [ ] Contract-Tests für Happy Path + Eskalation sind vorhanden
|
||||
Loading…
Add table
Add a link
Reference in a new issue