feat: add cashbox export subscription endpoints
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Param,
|
Param,
|
||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
|
Put,
|
||||||
Query,
|
Query,
|
||||||
Request,
|
Request,
|
||||||
Res,
|
Res,
|
||||||
@@ -12,7 +14,9 @@ import { AuthGuard } from '@nestjs/passport';
|
|||||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||||
import type { Response } from 'express';
|
import type { Response } from 'express';
|
||||||
import { CashboxExportQueryDto } from './dto/cashbox-export-query.dto';
|
import { CashboxExportQueryDto } from './dto/cashbox-export-query.dto';
|
||||||
|
import { UpsertCashboxExportSubscriptionDTO } from './dto/upsert-cashbox-export-subscription.dto';
|
||||||
import { CashboxExportService } from './cashbox-export.service';
|
import { CashboxExportService } from './cashbox-export.service';
|
||||||
|
import { CashboxExportSubscriptionService } from './cashbox-export-subscription.service';
|
||||||
|
|
||||||
type AuthenticatedRequest = { user: { id: number } };
|
type AuthenticatedRequest = { user: { id: number } };
|
||||||
|
|
||||||
@@ -20,7 +24,10 @@ type AuthenticatedRequest = { user: { id: number } };
|
|||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
@Controller({ path: 'cashbox-export', version: '1' })
|
@Controller({ path: 'cashbox-export', version: '1' })
|
||||||
export class CashboxExportController {
|
export class CashboxExportController {
|
||||||
constructor(private readonly service: CashboxExportService) {}
|
constructor(
|
||||||
|
private readonly service: CashboxExportService,
|
||||||
|
private readonly subscriptionService: CashboxExportSubscriptionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get(':teamId')
|
@Get(':teamId')
|
||||||
async exportCashbox(
|
async exportCashbox(
|
||||||
@@ -42,4 +49,21 @@ export class CashboxExportController {
|
|||||||
});
|
});
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get(':teamId/subscription')
|
||||||
|
getSubscription(
|
||||||
|
@Request() request: AuthenticatedRequest,
|
||||||
|
@Param('teamId', ParseIntPipe) teamId: number,
|
||||||
|
) {
|
||||||
|
return this.subscriptionService.getSubscription(teamId, request.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put(':teamId/subscription')
|
||||||
|
upsertSubscription(
|
||||||
|
@Request() request: AuthenticatedRequest,
|
||||||
|
@Param('teamId', ParseIntPipe) teamId: number,
|
||||||
|
@Body() dto: UpsertCashboxExportSubscriptionDTO,
|
||||||
|
) {
|
||||||
|
return this.subscriptionService.upsertSubscription(teamId, request.user.id, dto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,29 @@ import {
|
|||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import * as request from 'supertest';
|
import * as request from 'supertest';
|
||||||
|
import { RecurringTransactionIntervalEnum } from '../recurring-transactions/recurring-transaction-interval.enum';
|
||||||
import validationOptions from '../utils/validation-options';
|
import validationOptions from '../utils/validation-options';
|
||||||
import { CashboxExportController } from './cashbox-export.controller';
|
import { CashboxExportController } from './cashbox-export.controller';
|
||||||
import { CashboxExportService } from './cashbox-export.service';
|
import { CashboxExportService } from './cashbox-export.service';
|
||||||
|
import { CashboxExportSubscriptionService } from './cashbox-export-subscription.service';
|
||||||
|
|
||||||
describe('cashbox export HTTP boundary', () => {
|
describe('cashbox export HTTP boundary', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
const service = {
|
const service = {
|
||||||
exportForUser: jest.fn(),
|
exportForUser: jest.fn(),
|
||||||
};
|
};
|
||||||
|
const subscriptionService = {
|
||||||
|
getSubscription: jest.fn(),
|
||||||
|
upsertSubscription: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const module = await Test.createTestingModule({
|
const module = await Test.createTestingModule({
|
||||||
controllers: [CashboxExportController],
|
controllers: [CashboxExportController],
|
||||||
providers: [{ provide: CashboxExportService, useValue: service }],
|
providers: [
|
||||||
|
{ provide: CashboxExportService, useValue: service },
|
||||||
|
{ provide: CashboxExportSubscriptionService, useValue: subscriptionService },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
.overrideGuard(AuthGuard('jwt'))
|
.overrideGuard(AuthGuard('jwt'))
|
||||||
.useValue({
|
.useValue({
|
||||||
@@ -95,4 +104,52 @@ describe('cashbox export HTTP boundary', () => {
|
|||||||
expect(response.headers['content-type']).toBe('application/pdf');
|
expect(response.headers['content-type']).toBe('application/pdf');
|
||||||
expect(Buffer.from(response.body).equals(pdfBuffer)).toBe(true);
|
expect(Buffer.from(response.body).equals(pdfBuffer)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reads the current subscription', async () => {
|
||||||
|
const subscription = {
|
||||||
|
recipients: ['vorstand@example.com'],
|
||||||
|
interval: RecurringTransactionIntervalEnum.monthly,
|
||||||
|
active: true,
|
||||||
|
nextRunDate: '2026-09-01T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
subscriptionService.getSubscription.mockResolvedValue(subscription);
|
||||||
|
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.get('/api/v1/cashbox-export/5/subscription')
|
||||||
|
.set('Authorization', 'Bearer user')
|
||||||
|
.expect(200)
|
||||||
|
.expect(subscription);
|
||||||
|
expect(subscriptionService.getSubscription).toHaveBeenCalledWith(5, 42);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects an invalid recipient email on upsert', async () => {
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.put('/api/v1/cashbox-export/5/subscription')
|
||||||
|
.set('Authorization', 'Bearer user')
|
||||||
|
.send({ recipients: ['not-an-email'], interval: 'monthly', active: true })
|
||||||
|
.expect(422);
|
||||||
|
expect(subscriptionService.upsertSubscription).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts a valid subscription upsert', async () => {
|
||||||
|
const subscription = {
|
||||||
|
recipients: ['vorstand@example.com'],
|
||||||
|
interval: RecurringTransactionIntervalEnum.monthly,
|
||||||
|
active: true,
|
||||||
|
nextRunDate: '2026-09-01T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
subscriptionService.upsertSubscription.mockResolvedValue(subscription);
|
||||||
|
|
||||||
|
await request(app.getHttpServer())
|
||||||
|
.put('/api/v1/cashbox-export/5/subscription')
|
||||||
|
.set('Authorization', 'Bearer user')
|
||||||
|
.send({ recipients: ['vorstand@example.com'], interval: 'monthly', active: true })
|
||||||
|
.expect(200)
|
||||||
|
.expect(subscription);
|
||||||
|
expect(subscriptionService.upsertSubscription).toHaveBeenCalledWith(5, 42, {
|
||||||
|
recipients: ['vorstand@example.com'],
|
||||||
|
interval: 'monthly',
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user