update
This commit is contained in:
15
package.json
15
package.json
@@ -10,28 +10,21 @@
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --verbose --logfile)",
|
||||
"test:basic": "(tstest test/test.ts --verbose)",
|
||||
"test:payments": "(tstest test/test.payments.simple.ts --verbose)",
|
||||
"test:webhooks": "(tstest test/test.webhooks.ts --verbose)",
|
||||
"test:session": "(tstest test/test.session.ts --verbose)",
|
||||
"test:errors": "(tstest test/test.errors.ts --verbose)",
|
||||
"test:advanced": "(tstest test/test.advanced.ts --verbose)",
|
||||
"test:oauth": "(tstest test/test.oauth.ts --verbose)",
|
||||
"test": "(tstest test/ --verbose --logfile --timeout 60)",
|
||||
"build": "(tsbuild --web)"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.6.4",
|
||||
"@git.zone/tsrun": "^1.3.3",
|
||||
"@git.zone/tstest": "^2.3.1",
|
||||
"@git.zone/tstest": "^2.3.2",
|
||||
"@push.rocks/qenv": "^6.1.0",
|
||||
"@push.rocks/tapbundle": "^6.0.3",
|
||||
"@types/node": "^24.0.14"
|
||||
"@types/node": "^22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/smartcrypto": "^2.0.4",
|
||||
"@push.rocks/smartfile": "^11.2.5",
|
||||
"@push.rocks/smartpath": "^5.0.18",
|
||||
"@push.rocks/smartpath": "^6.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smarttime": "^4.0.54"
|
||||
},
|
||||
|
798
pnpm-lock.yaml
generated
798
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
245
test/test.statements.ts
Normal file
245
test/test.statements.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from '../ts/bunq.plugins.js';
|
||||
import * as bunq from '../ts/index.js';
|
||||
|
||||
let testBunqAccount: bunq.BunqAccount;
|
||||
let sandboxApiKey: string;
|
||||
let primaryAccount: bunq.BunqMonetaryAccount;
|
||||
|
||||
tap.test('should setup statement test environment', async () => {
|
||||
// Create sandbox user
|
||||
const tempAccount = new bunq.BunqAccount({
|
||||
apiKey: '',
|
||||
deviceName: 'bunq-statement-test',
|
||||
environment: 'SANDBOX',
|
||||
});
|
||||
|
||||
sandboxApiKey = await tempAccount.createSandboxUser();
|
||||
|
||||
// Initialize bunq account
|
||||
testBunqAccount = new bunq.BunqAccount({
|
||||
apiKey: sandboxApiKey,
|
||||
deviceName: 'bunq-statement-test',
|
||||
environment: 'SANDBOX',
|
||||
});
|
||||
|
||||
await testBunqAccount.init();
|
||||
|
||||
// Get primary account
|
||||
const { accounts } = await testBunqAccount.getAccounts();
|
||||
primaryAccount = accounts[0];
|
||||
|
||||
console.log('Statement test environment setup complete');
|
||||
console.log(`Using account: ${primaryAccount.description}`);
|
||||
});
|
||||
|
||||
tap.test('should create export builder with specific date range', async () => {
|
||||
const fromDate = new Date('2024-01-01');
|
||||
const toDate = new Date('2024-01-31');
|
||||
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
from: fromDate,
|
||||
to: toDate,
|
||||
includeTransactionAttachments: true
|
||||
});
|
||||
|
||||
expect(exportBuilder).toBeInstanceOf(bunq.ExportBuilder);
|
||||
|
||||
// The export builder should be properly configured
|
||||
const privateOptions = (exportBuilder as any).options;
|
||||
expect(privateOptions.dateStart).toEqual('01-01-2024');
|
||||
expect(privateOptions.dateEnd).toEqual('31-01-2024');
|
||||
expect(privateOptions.includeAttachment).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('should create export builder with monthly index from 0', async () => {
|
||||
// Test with 0-indexed month (0 = current month, 1 = last month, etc.)
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
monthlyIndexedFrom0: 2, // Two months ago
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
|
||||
expect(exportBuilder).toBeInstanceOf(bunq.ExportBuilder);
|
||||
|
||||
// The export builder should have dates for two months ago
|
||||
const privateOptions = (exportBuilder as any).options;
|
||||
const now = new Date();
|
||||
const twoMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 2, 1);
|
||||
const expectedStart = `01-${String(twoMonthsAgo.getMonth() + 1).padStart(2, '0')}-${twoMonthsAgo.getFullYear()}`;
|
||||
|
||||
expect(privateOptions.dateStart).toEqual(expectedStart);
|
||||
expect(privateOptions.includeAttachment).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('should create export builder with monthly index from 1', async () => {
|
||||
// Test with 1-indexed month (1 = last month, 2 = two months ago, etc.)
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
monthlyIndexedFrom1: 1, // Last month
|
||||
includeTransactionAttachments: true
|
||||
});
|
||||
|
||||
expect(exportBuilder).toBeInstanceOf(bunq.ExportBuilder);
|
||||
|
||||
// The export builder should have dates for last month
|
||||
const privateOptions = (exportBuilder as any).options;
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const expectedStart = `01-${String(lastMonth.getMonth() + 1).padStart(2, '0')}-${lastMonth.getFullYear()}`;
|
||||
|
||||
expect(privateOptions.dateStart).toEqual(expectedStart);
|
||||
expect(privateOptions.includeAttachment).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('should default to last month when no date options provided', async () => {
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
|
||||
expect(exportBuilder).toBeInstanceOf(bunq.ExportBuilder);
|
||||
|
||||
// Should default to last month
|
||||
const privateOptions = (exportBuilder as any).options;
|
||||
const now = new Date();
|
||||
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const expectedStart = `01-${String(lastMonth.getMonth() + 1).padStart(2, '0')}-${lastMonth.getFullYear()}`;
|
||||
|
||||
expect(privateOptions.dateStart).toEqual(expectedStart);
|
||||
});
|
||||
|
||||
tap.test('should create and download PDF statement', async () => {
|
||||
try {
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
monthlyIndexedFrom1: 1,
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
|
||||
// Configure as PDF
|
||||
exportBuilder.asPdf();
|
||||
|
||||
// Create the export
|
||||
const bunqExport = await exportBuilder.create();
|
||||
expect(bunqExport).toBeInstanceOf(bunq.BunqExport);
|
||||
expect(bunqExport.id).toBeTypeofNumber();
|
||||
|
||||
// Wait for completion with longer timeout for PDF
|
||||
await bunqExport.waitForCompletion(120000);
|
||||
|
||||
// Download to test directory
|
||||
const testFilePath = '.nogit/teststatements/test-statement.pdf';
|
||||
await bunqExport.saveToFile(testFilePath);
|
||||
|
||||
// Verify file exists
|
||||
const fileExists = await plugins.smartfile.fs.fileExists(testFilePath);
|
||||
expect(fileExists).toBeTrue();
|
||||
|
||||
console.log(`Statement downloaded to: ${testFilePath}`);
|
||||
} catch (error) {
|
||||
if (error.message && error.message.includes('timed out')) {
|
||||
console.log('PDF export timed out - sandbox may not support PDF exports');
|
||||
// Try CSV instead
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
monthlyIndexedFrom1: 1,
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
const csvExport = await exportBuilder.asCsv().create();
|
||||
console.log('Created CSV export instead with ID:', csvExport.id);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should create CSV statement with custom date range', async () => {
|
||||
// Use last month's date range to ensure it's in the past
|
||||
const now = new Date();
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
from: startOfMonth,
|
||||
to: endOfMonth,
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
|
||||
// Configure as CSV
|
||||
const csvExport = await exportBuilder.asCsv().create();
|
||||
await csvExport.waitForCompletion(30000);
|
||||
|
||||
const testFilePath = '.nogit/teststatements/test-statement.csv';
|
||||
await csvExport.saveToFile(testFilePath);
|
||||
|
||||
// Verify file exists
|
||||
const fileExists = await plugins.smartfile.fs.fileExists(testFilePath);
|
||||
expect(fileExists).toBeTrue();
|
||||
|
||||
console.log(`CSV statement downloaded to: ${testFilePath}`);
|
||||
});
|
||||
|
||||
tap.test('should create MT940 statement', async () => {
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
monthlyIndexedFrom0: 1, // Last month
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
|
||||
// Configure as MT940
|
||||
const mt940Export = await exportBuilder.asMt940().create();
|
||||
await mt940Export.waitForCompletion(30000);
|
||||
|
||||
const testFilePath = '.nogit/teststatements/test-statement.txt';
|
||||
await mt940Export.saveToFile(testFilePath);
|
||||
|
||||
// Verify file exists
|
||||
const fileExists = await plugins.smartfile.fs.fileExists(testFilePath);
|
||||
expect(fileExists).toBeTrue();
|
||||
|
||||
console.log(`MT940 statement downloaded to: ${testFilePath}`);
|
||||
});
|
||||
|
||||
tap.test('should handle edge cases for date calculations', async () => {
|
||||
// Mock the getAccountStatement method to test with a specific date
|
||||
const originalMethod = primaryAccount.getAccountStatement;
|
||||
|
||||
// Override the method temporarily for this test
|
||||
primaryAccount.getAccountStatement = function(optionsArg) {
|
||||
const exportBuilder = new bunq.ExportBuilder(this.bunqAccountRef, this);
|
||||
|
||||
// Simulate January 2024 as "now"
|
||||
const mockNow = new Date(2024, 0, 15); // January 15, 2024
|
||||
const targetDate = new Date(mockNow.getFullYear(), mockNow.getMonth() - 1, 1);
|
||||
const startDate = new Date(targetDate.getFullYear(), targetDate.getMonth(), 1);
|
||||
const endDate = new Date(targetDate.getFullYear(), targetDate.getMonth() + 1, 0);
|
||||
|
||||
const formatDate = (date: Date): string => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${day}-${month}-${year}`;
|
||||
};
|
||||
|
||||
exportBuilder.dateRange(formatDate(startDate), formatDate(endDate));
|
||||
exportBuilder.includeAttachments(optionsArg.includeTransactionAttachments);
|
||||
|
||||
return exportBuilder;
|
||||
};
|
||||
|
||||
try {
|
||||
const exportBuilder = primaryAccount.getAccountStatement({
|
||||
monthlyIndexedFrom1: 1, // Last month (December 2023)
|
||||
includeTransactionAttachments: false
|
||||
});
|
||||
|
||||
const privateOptions = (exportBuilder as any).options;
|
||||
expect(privateOptions.dateStart).toEqual('01-12-2023');
|
||||
expect(privateOptions.dateEnd).toEqual('31-12-2023');
|
||||
} finally {
|
||||
// Restore original method
|
||||
primaryAccount.getAccountStatement = originalMethod;
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should cleanup test environment', async () => {
|
||||
await testBunqAccount.stop();
|
||||
console.log('Test environment cleaned up');
|
||||
});
|
||||
|
||||
export default tap.start();
|
@@ -251,8 +251,16 @@ export class ExportBuilder {
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - days);
|
||||
|
||||
this.options.dateStart = startDate.toISOString().split('T')[0];
|
||||
this.options.dateEnd = endDate.toISOString().split('T')[0];
|
||||
// Format as DD-MM-YYYY for bunq API
|
||||
const formatDate = (date: Date): string => {
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
return `${day}-${month}-${year}`;
|
||||
};
|
||||
|
||||
this.options.dateStart = formatDate(startDate);
|
||||
this.options.dateEnd = formatDate(endDate);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -264,8 +272,16 @@ export class ExportBuilder {
|
||||
const startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const endDate = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
|
||||
this.options.dateStart = startDate.toISOString().split('T')[0];
|
||||
this.options.dateEnd = endDate.toISOString().split('T')[0];
|
||||
// Format as DD-MM-YYYY for bunq API
|
||||
const formatDate = (date: Date): string => {
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const year = date.getFullYear();
|
||||
return `${day}-${month}-${year}`;
|
||||
};
|
||||
|
||||
this.options.dateStart = formatDate(startDate);
|
||||
this.options.dateEnd = formatDate(endDate);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ import * as plugins from './bunq.plugins.js';
|
||||
import { BunqAccount } from './bunq.classes.account.js';
|
||||
import { BunqTransaction } from './bunq.classes.transaction.js';
|
||||
import { BunqPayment } from './bunq.classes.payment.js';
|
||||
import { ExportBuilder } from './bunq.classes.export.js';
|
||||
import type { IBunqPaginationOptions, IBunqMonetaryAccountBank } from './bunq.interfaces.js';
|
||||
|
||||
export type TAccountType = 'bank' | 'joint' | 'savings' | 'external' | 'light' | 'card' | 'external_savings' | 'savings_external';
|
||||
@@ -251,4 +252,60 @@ export class BunqMonetaryAccount {
|
||||
reason_description: reason
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account statement with flexible date options
|
||||
* @param optionsArg - Options for statement generation
|
||||
* @returns ExportBuilder instance for creating the statement
|
||||
*/
|
||||
public getAccountStatement(optionsArg: {
|
||||
from?: Date;
|
||||
to?: Date;
|
||||
monthlyIndexedFrom0?: number;
|
||||
monthlyIndexedFrom1?: number;
|
||||
includeTransactionAttachments: boolean;
|
||||
}): ExportBuilder {
|
||||
const exportBuilder = new ExportBuilder(this.bunqAccountRef, this);
|
||||
|
||||
// Determine date range based on provided options
|
||||
let startDate: Date;
|
||||
let endDate: Date;
|
||||
|
||||
if (optionsArg.from && optionsArg.to) {
|
||||
// Use provided date range
|
||||
startDate = optionsArg.from;
|
||||
endDate = optionsArg.to;
|
||||
} else if (optionsArg.monthlyIndexedFrom0 !== undefined) {
|
||||
// Calculate date range for 0-indexed month
|
||||
const now = new Date();
|
||||
const targetDate = new Date(now.getFullYear(), now.getMonth() - optionsArg.monthlyIndexedFrom0, 1);
|
||||
startDate = new Date(targetDate.getFullYear(), targetDate.getMonth(), 1);
|
||||
endDate = new Date(targetDate.getFullYear(), targetDate.getMonth() + 1, 0);
|
||||
} else if (optionsArg.monthlyIndexedFrom1 !== undefined) {
|
||||
// Calculate date range for 1-indexed month (1 = last month, 2 = two months ago, etc.)
|
||||
const now = new Date();
|
||||
const targetDate = new Date(now.getFullYear(), now.getMonth() - optionsArg.monthlyIndexedFrom1, 1);
|
||||
startDate = new Date(targetDate.getFullYear(), targetDate.getMonth(), 1);
|
||||
endDate = new Date(targetDate.getFullYear(), targetDate.getMonth() + 1, 0);
|
||||
} else {
|
||||
// Default to last month if no date options provided
|
||||
const now = new Date();
|
||||
startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
endDate = new Date(now.getFullYear(), now.getMonth(), 0);
|
||||
}
|
||||
|
||||
// Format dates as DD-MM-YYYY (bunq API format)
|
||||
const formatDate = (date: Date): string => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${day}-${month}-${year}`;
|
||||
};
|
||||
|
||||
// Configure the export builder
|
||||
exportBuilder.dateRange(formatDate(startDate), formatDate(endDate));
|
||||
exportBuilder.includeAttachments(optionsArg.includeTransactionAttachments);
|
||||
|
||||
return exportBuilder;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user